diff --git a/CMakeLists.txt b/CMakeLists.txt
index 23ccb14..e165dd3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -26,6 +26,7 @@ option(BASISU_EXAMPLES "build examples" TRUE)
option(BASISU_WASM_THREADING "Enable WASI threading support" OFF)
option(BASISU_BUILD_PYTHON "Build native Python module via pybind11" OFF)
option(BASISU_DISABLE_ANDROID_ASTC_DECOMP "Disable Android ASTC decompressor" OFF)
+option(BASISU_SUPPORT_ASTCENC "Enable astcenc support in encoder" FALSE)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin)
@@ -97,6 +98,7 @@ message("Initial BASISU_OPENCL=${BASISU_OPENCL}")
message("Initial BASISU_SAN=${BASISU_SAN}")
message("Initial BASISU_EXAMPLES=${BASISU_EXAMPLES}")
message("Initial BASISU_DISABLE_ANDROID_ASTC_DECOMP=${BASISU_DISABLE_ANDROID_ASTC_DECOMP}")
+message("Initial BASISU_SUPPORT_ASTCENC=${BASISU_SUPPORT_ASTCENC}")
if ((NOT MSVC) AND BASISU_OPENCL)
# With MSVC builds we use the Khronos lib/include files in the project's "OpenCL" directory, to completely avoid requiring fiddly to install vendor SDK's.
@@ -237,9 +239,13 @@ set(ENCODER_LIB_SRC_LIST
encoder/basisu_resample_filters.cpp
encoder/basisu_ssim.cpp
encoder/basisu_uastc_enc.cpp
+ encoder/basisu_bc7e_scalar.cpp
+ encoder/basisu_dds_export.cpp
encoder/basisu_bc7enc.cpp
encoder/jpgd.cpp
encoder/basisu_kernels_sse.cpp
+ encoder/basisu_bc15_spmd.cpp
+ encoder/basisu_bc15_spmd_sse.cpp
encoder/basisu_opencl.cpp
encoder/pvpngreader.cpp
encoder/basisu_uastc_hdr_4x4_enc.cpp
@@ -247,6 +253,8 @@ set(ENCODER_LIB_SRC_LIST
encoder/basisu_astc_hdr_common.cpp
encoder/basisu_astc_ldr_common.cpp
encoder/basisu_astc_ldr_encode.cpp
+ encoder/basisu_astc_ldr_fencode.cpp
+ encoder/basisu_xbc7_encode.cpp
encoder/basisu_tinyexr.cpp
transcoder/basisu_transcoder.cpp
encoder/basisu_astc_hdr_6x6_enc.h
@@ -270,9 +278,13 @@ set(ENCODER_LIB_SRC_LIST
encoder/basisu_resampler.h
encoder/basisu_ssim.h
encoder/basisu_uastc_enc.h
+ encoder/basisu_bc7e_scalar.h
+ encoder/basisu_dds_export.h
encoder/basisu_uastc_hdr_4x4_enc.h
encoder/basisu_astc_ldr_common.h
encoder/basisu_astc_ldr_encode.h
+ encoder/basisu_astc_ldr_fencode.h
+ encoder/basisu_xbc7_encode.h
encoder/cppspmd_flow.h
encoder/cppspmd_math_declares.h
encoder/cppspmd_math.h
@@ -301,6 +313,33 @@ if (NOT BASISU_DISABLE_ANDROID_ASTC_DECOMP)
)
endif()
+if (BASISU_SUPPORT_ASTCENC)
+ list(APPEND ENCODER_LIB_SRC_LIST
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_averages_and_directions.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_block_sizes.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_color_quantize.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_color_unquantize.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_compress_symbolic.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_compute_variance.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_decompress_symbolic.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_diagnostic_trace.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_entry.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_find_best_partitioning.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_ideal_endpoints_and_weights.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_image.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_integer_sequence.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_mathlib.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_mathlib_softfloat.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_partition_tables.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_percentile_tables.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_pick_best_endpoint_format.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_quantization.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_symbolic_physical.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_weight_align.cpp
+ encoder/3rdparty/astc-encoder-main/Source/astcenc_weight_quant_xfer_tables.cpp
+ )
+endif()
+
if (BASISU_ZSTD)
set(ENCODER_LIB_SRC_LIST ${ENCODER_LIB_SRC_LIST} zstd/zstd.c)
endif()
@@ -308,8 +347,14 @@ endif()
# Create the static library
add_library(basisu_encoder STATIC ${ENCODER_LIB_SRC_LIST})
+if (BASISU_SUPPORT_ASTCENC)
+ target_compile_definitions(basisu_encoder PRIVATE BASISU_SUPPORT_ASTCENC=1)
+else()
+ target_compile_definitions(basisu_encoder PRIVATE BASISU_SUPPORT_ASTCENC=0)
+endif()
+
# Create the basisu executable and link against the static library
-add_executable(basisu basisu_tool.cpp)
+add_executable(basisu basisu_tool.cpp basisu_text_image.cpp basisu_tool_test_codecs.inl)
target_link_libraries(basisu PRIVATE basisu_encoder)
# Create the new example executable and link against the static library
@@ -355,7 +400,7 @@ if (BASISU_BUILD_WASM)
endif()
- # 256 MB initial, 3.5 GB max � safe defaults for BasisU
+ # 256 MB initial, 3.5 GB max - safe defaults for BasisU
target_link_options(basisu PRIVATE
-Wl,--initial-memory=268435456
-Wl,--max-memory=3758096384
@@ -387,19 +432,24 @@ endif()
#endif()
if (BASISU_ZSTD)
+ # Note: the static encoder library MUST be compiled with the same value, otherwise its TUs
+ # (transcoder, basisu_comp, basisu_xbc7_encode, basisu_astc_ldr_encode) default to ZSTD=1 and
+ # emit ZSTD_* calls that won't link when zstd.c is excluded.
+ target_compile_definitions(basisu_encoder PRIVATE BASISD_SUPPORT_KTX2_ZSTD=1)
target_compile_definitions(basisu PRIVATE BASISD_SUPPORT_KTX2_ZSTD=1)
if(BASISU_EXAMPLES)
target_compile_definitions(example PRIVATE BASISD_SUPPORT_KTX2_ZSTD=1)
target_compile_definitions(example_capi PRIVATE BASISD_SUPPORT_KTX2_ZSTD=1)
target_compile_definitions(example_transcoding PRIVATE BASISD_SUPPORT_KTX2_ZSTD=1)
- endif()
+ endif()
else()
+ target_compile_definitions(basisu_encoder PRIVATE BASISD_SUPPORT_KTX2_ZSTD=0)
target_compile_definitions(basisu PRIVATE BASISD_SUPPORT_KTX2_ZSTD=0)
if(BASISU_EXAMPLES)
target_compile_definitions(example PRIVATE BASISD_SUPPORT_KTX2_ZSTD=0)
target_compile_definitions(example_capi PRIVATE BASISD_SUPPORT_KTX2_ZSTD=0)
target_compile_definitions(example_transcoding PRIVATE BASISD_SUPPORT_KTX2_ZSTD=0)
- endif()
+ endif()
endif()
if (NOT MSVC)
diff --git a/basisu.vcxproj b/basisu.vcxproj
index 1e52b46..f2d6f02 100644
--- a/basisu.vcxproj
+++ b/basisu.vcxproj
@@ -146,7 +146,7 @@
OpenCL
true
true
- _MBCS;%(PreprocessorDefinitions);BASISU_SUPPORT_SSE=1;BASISU_SUPPORT_OPENCL=1;
+ _MBCS;%(PreprocessorDefinitions);BASISU_SUPPORT_SSE=1;BASISU_SUPPORT_OPENCL=1;_HAS_EXCEPTIONS=0
AdvancedVectorExtensions
stdcpp17
Level4
@@ -155,7 +155,7 @@
Console
OpenCL\lib
opencl64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
- false
+ true
@@ -165,7 +165,7 @@
OpenCL
true
true
- _MBCS;%(PreprocessorDefinitions);BASISU_SUPPORT_SSE=0;BASISU_SUPPORT_OPENCL=1;
+ _MBCS;%(PreprocessorDefinitions);BASISU_SUPPORT_SSE=0;BASISU_SUPPORT_OPENCL=1;_HAS_EXCEPTIONS=0
Level4
stdcpp17
@@ -257,6 +257,7 @@
+
@@ -268,6 +269,12 @@
+
+
+
+
+
+
diff --git a/basisu.vcxproj.filters b/basisu.vcxproj.filters
index 5281d48..1b472b4 100644
--- a/basisu.vcxproj.filters
+++ b/basisu.vcxproj.filters
@@ -2,11 +2,18 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/basisu_tool.cpp b/basisu_tool.cpp
index 67941e6..941c98f 100644
--- a/basisu_tool.cpp
+++ b/basisu_tool.cpp
@@ -22,6 +22,8 @@
#include "transcoder/basisu.h"
#include "transcoder/basisu_transcoder_internal.h"
#include "encoder/basisu_enc.h"
+#include "encoder/basisu_xbc7_encode.h"
+#include "transcoder/basisu_xbc7_decoder.h"
#include "encoder/basisu_etc.h"
#include "encoder/basisu_gpu_texture.h"
#include "encoder/basisu_frontend.h"
@@ -38,9 +40,14 @@
#include
#include
+#include
+#include
#include "encoder/basisu_resampler.h"
#include "encoder/basisu_resampler_filters.h"
+#include "basisu_text_image.h"
+#include "encoder/basisu_dds_export.h"
+#include "encoder/pvpngreader.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
@@ -57,7 +64,7 @@
using namespace basisu;
using namespace buminiz;
-#define BASISU_TOOL_VERSION "2.10.0"
+#define BASISU_TOOL_VERSION "2.50.0"
#if defined(DEBUG)
#pragma message("DEBUG defined")
@@ -87,7 +94,9 @@ enum tool_mode
cInfo,
cUnpack,
cCompare,
+ cCompareHVS,
cHDRCompare,
+ cImageDumpStats,
cVersion,
cBench,
cCompSize,
@@ -96,10 +105,24 @@ enum tool_mode
cTestHDR_6x6,
cTestHDR_6x6i,
cTestXUASTCLDR,
+ cTestCodecs,
+ cTestCodecsGen,
cCLBench,
cSplitImage,
cCombineImages,
- cTonemapImage
+ cExtractChannel,
+ cExtractSwizzle,
+ cExtractRegion,
+ cTextToPng,
+ cPngToText,
+ cTonemapImage,
+ cDDS,
+ cExportDDS,
+ cExportKTX,
+ cTinyDDSInfo,
+ cKTXInfo,
+ cBenchmarkSingle,
+ cBenchmarkSweep
};
static void print_usage()
@@ -182,6 +205,66 @@ static bool load_listing_file(const std::string &f, basisu::vector
return true;
}
+// Command line option matching helper. Recognizes an option by its canonical
+// name (given with a single leading dash, e.g. "-etc1s"), accepting both the
+// "-name" and Unix-style "--name" forms, case-insensitively.
+static inline bool opt_match(const char *pArg, const char *pName)
+{
+ if (strcasecmp(pArg, pName) == 0)
+ return true;
+ // Accept the "--name" variant when the canonical name is "-name".
+ if ((pArg[0] == '-') && (pArg[1] == '-') && (pName[0] == '-') && (strcasecmp(pArg + 1, pName) == 0))
+ return true;
+ return false;
+}
+
+// Same, but matches against any of several alias names, e.g.
+// opt_match(pArg, { "-uastc", "-uastc_ldr", "-uastc_ldr_4x4" }).
+static inline bool opt_match(const char *pArg, std::initializer_list names)
+{
+ for (const char *pName : names)
+ if (opt_match(pArg, pName))
+ return true;
+ return false;
+}
+
+// Optional sRGB<->linear transform applied by the -extract_* modes.
+enum extract_color_xform
+{
+ cExtractXformNone = 0,
+ cExtractXformToLinear, // treat input as sRGB, output linear light
+ cExtractXformToSRGB // treat input as linear light, output sRGB
+};
+
+// True if the argument is a non-empty run of decimal digits (an unsigned integer). Used to decide whether an
+// optional region rectangle follows a command, so that a positional filename beginning with a digit (e.g.
+// "5texture.png") is NOT mistaken for a region coordinate.
+static bool arg_is_unsigned_int(const char* pArg)
+{
+ if ((!pArg) || (!pArg[0]))
+ return false;
+ for (const char* p = pArg; *p; p++)
+ if ((*p < '0') || (*p > '9'))
+ return false;
+ return true;
+}
+
+// Maps a single channel token to a source channel index in [0,3] (R/G/B/A), or -1 if it is not a valid channel.
+static int channel_token_to_index(char c)
+{
+ switch (c)
+ {
+ case 'r': case 'R': case '0': return 0;
+ case 'g': case 'G': case '1': return 1;
+ case 'b': case 'B': case '2': return 2;
+ case 'a': case 'A': case '3': return 3;
+ default: return -1;
+ }
+}
+
+// Parses a -benchmark_single format token into a basis_tex_format (definition is down by codec_benchmark()).
+static bool parse_benchmark_tex_format(const std::string& s, basist::basis_tex_format& out_fmt);
+
class command_line_params
{
BASISU_NO_EQUALS_OR_COPY_CONSTRUCT(command_line_params);
@@ -195,7 +278,7 @@ class command_line_params
BASISU_NOTE_UNUSED(num_remaining_args);
BASISU_NOTE_UNUSED(arg_count);
- if (strcasecmp(pArg, "-wasi_threads") == 0)
+ if (opt_match(pArg, "-wasi_threads"))
{
REMAINING_ARGS_CHECK(1);
int num_threads = atoi(arg_v[arg_index + 1]);
@@ -208,57 +291,52 @@ class command_line_params
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-higher_quality_transcoding") == 0)
+ else if (opt_match(pArg, "-higher_quality_transcoding"))
{
m_higher_quality_transcoding = true;
return true;
}
- else if (strcasecmp(pArg, "-no_fast_xuastc_ldr_bc7_transcoding") == 0)
+ else if (opt_match(pArg, "-no_fast_xuastc_ldr_bc7_transcoding"))
{
m_xuastc_ldr_disable_bc7_transcoding = true;
return true;
}
- else if (strcasecmp(pArg, "-fast_xuastc_ldr_bc7_transcoding") == 0)
+ else if (opt_match(pArg, "-fast_xuastc_ldr_bc7_transcoding"))
{
m_xuastc_ldr_disable_bc7_transcoding = false;
return true;
}
- else if (strcasecmp(pArg, "-no_etc1s_chroma_filtering") == 0)
+ else if (opt_match(pArg, "-no_etc1s_chroma_filtering"))
{
m_no_etc1s_transcoding_chroma_filtering = true;
return true;
}
- else if (strcasecmp(pArg, "-force_deblocking") == 0)
+ else if (opt_match(pArg, "-transcode_force_deblocking"))
{
- m_force_deblocking = true;
+ m_transcode_force_deblocking = true;
return true;
}
- else if ((strcasecmp(pArg, "-disable_deblocking") == 0) || (strcasecmp(pArg, "-no_deblocking") == 0))
+ else if (opt_match(pArg, {"-transcode_disable_deblocking", "-transcode_no_deblocking"}))
{
- m_disable_deblocking = true;
+ m_transcode_disable_deblocking = true;
return true;
}
- else if (strcasecmp(pArg, "-stronger_deblocking") == 0)
- {
- m_stronger_deblocking = true;
- return true;
- }
-
+
return false;
}
bool check_for_xuastc_options(const char** arg_v, const char* pArg, int arg_index, const int num_remaining_args, int& arg_count)
{
// New unified -quality level which works across all codecs
- if (strcasecmp(pArg, "-quality") == 0)
+ if (opt_match(pArg, "-quality"))
{
REMAINING_ARGS_CHECK(1);
- m_quality_level = clamp(atoi(arg_v[arg_index + 1]), 0, 100);
+ m_quality_level = clamp(atoi(arg_v[arg_index + 1]), 1, 100);
arg_count++;
return true;
}
// New unified -effort level, which works across all codecs
- else if (strcasecmp(pArg, "-effort") == 0)
+ else if (opt_match(pArg, "-effort"))
{
REMAINING_ARGS_CHECK(1);
m_effort_level = clamp(atoi(arg_v[arg_index + 1]), 0, 10);
@@ -266,258 +344,410 @@ class command_line_params
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-xuastc_blurring") == 0) // experimental, not recommended, very slow
+ else if ((opt_match(pArg, "-xuastc_debug_block")))
+ {
+ REMAINING_ARGS_CHECK(2);
+ m_comp_params.m_xuastc_ldr_debug_block_x = clamp(atoi(arg_v[arg_index + 1]), 0, 4096);
+ m_comp_params.m_xuastc_ldr_debug_block_y = clamp(atoi(arg_v[arg_index + 2]), 0, 4096);
+
+ arg_count += 2;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_blurring", "-xuastc_ldr_blurring"})) // experimental, not recommended, very slow
{
m_comp_params.m_xuastc_ldr_blurring = true;
return true;
}
- else if (strcasecmp(pArg, "-weights") == 0)
+ else if (opt_match(pArg, {"-xuastc_no_blurring", "-xuastc_ldr_no_blurring"})) // experimental, not recommended, very slow
+ {
+ m_comp_params.m_xuastc_ldr_blurring = false;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_use_auto_comp", "-xuastc_ldr_use_auto_comp"}))
+ {
+ m_comp_params.m_xuastc_ldr_astc_comp_selection = (int)xuastc_ldr_astc_comp_selection::cAuto;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_use_basisu", "-xuastc_ldr_use_basisu"}))
+ {
+ m_comp_params.m_xuastc_ldr_astc_comp_selection = (int)xuastc_ldr_astc_comp_selection::cBasisU;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_use_astcenc", "-xuastc_ldr_use_astcenc"})) // experimental/development, must be enabled at compilation time by setting BASISU_SUPPORT_ASTCENC=1
+ {
+ m_comp_params.m_xuastc_ldr_astc_comp_selection = (int)xuastc_ldr_astc_comp_selection::cASTCENC;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_use_astcf", "-xuastc_ldr_use_astcf"}))
+ {
+ m_comp_params.m_xuastc_ldr_astc_comp_selection = (int)xuastc_ldr_astc_comp_selection::cASTCF;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_merge_astcenc", "-xuastc_ldr_merge_astcenc"})) // experimental/development, must be enabled at compilation time by setting BASISU_SUPPORT_ASTCENC=1
+ {
+ m_comp_params.m_xuastc_ldr_astc_comp_selection = (int)xuastc_ldr_astc_comp_selection::cBasisU_and_ASTCENC;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_merge_astcf", "-xuastc_ldr_merge_astcf"})) // experimental/development, must be enabled at compilation time by setting BASISU_SUPPORT_ASTCENC=1
+ {
+ m_comp_params.m_xuastc_ldr_astc_comp_selection = (int)xuastc_ldr_astc_comp_selection::cBasisU_and_ASTCF;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_merge_all", "-xuastc_ldr_merge_all"})) // experimental/development, must be enabled at compilation time by setting BASISU_SUPPORT_ASTCENC=1
+ {
+ m_comp_params.m_xuastc_ldr_astc_comp_selection = (int)xuastc_ldr_astc_comp_selection::cUseAll;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_no_deblocking", "-xuastc_ldr_no_deblocking"}))
+ {
+ m_comp_params.m_xuastc_ldr_deblocking_mode = (int)xuastc_ldr_deblocking_mode::cDisabled;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_deblocking_largest", "-xuastc_ldr_deblocking_largest"}))
+ {
+ m_comp_params.m_xuastc_ldr_deblocking_mode = (int)xuastc_ldr_deblocking_mode::cUseSCDAndFilteringOnlyLargestBlocks; // codec default
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_deblocking_all", "-xuastc_ldr_deblocking_all"}))
+ {
+ m_comp_params.m_xuastc_ldr_deblocking_mode = (int)xuastc_ldr_deblocking_mode::cUseSCDAndFilteringAllBlockSizes;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_deblocking_scd_no_filtering", "-xuastc_ldr_deblocking_scd_no_filtering"}))
+ {
+ m_comp_params.m_xuastc_ldr_deblocking_mode = (int)xuastc_ldr_deblocking_mode::cUseSCDNoFiltering;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_deblocking_no_scd_filtering_largest", "-xuastc_ldr_deblocking_no_scd_filtering_largest"}))
+ {
+ m_comp_params.m_xuastc_ldr_deblocking_mode = (int)xuastc_ldr_deblocking_mode::cNoSCDButEnableFilteringOnLargestBlocks;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_deblocking_no_scd_filtering_all", "-xuastc_ldr_deblocking_no_scd_filtering_all"}))
+ {
+ m_comp_params.m_xuastc_ldr_deblocking_mode = (int)xuastc_ldr_deblocking_mode::cNoSCDButEnableFilteringOnAllBlocks;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_deblocking_num_passes", "-xuastc_ldr_deblocking_num_passes"}))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_comp_params.m_xuastc_ldr_num_deblocking_passes = clamp(atoi(arg_v[arg_index + 1]), 2, 256);
+ arg_count++;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_sharpen", "-xuastc_ldr_sharpen"}))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_comp_params.m_xuastc_ldr_sharpen_amount = (float)atof(arg_v[arg_index + 1]);
+ arg_count++;
+
+ // setting to 0 disables
+ if (m_comp_params.m_xuastc_ldr_sharpen_amount <= 0.0f)
+ m_comp_params.m_xuastc_ldr_sharpen_mode = (int)xuastc_ldr_sharpen_mode::cDisabled;
+ else
+ m_comp_params.m_xuastc_ldr_sharpen_mode = (int)xuastc_ldr_sharpen_mode::cAllBlockSizes; // if they've specified -xuastc_sharpen, they want it no matter what block size
+
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_no_sharpen", "-xuastc_ldr_no_sharpen"}))
+ {
+ m_comp_params.m_xuastc_ldr_sharpen_mode = (int)xuastc_ldr_sharpen_mode::cDisabled;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_weights", "-xuastc_ldr_weights", "-weights"}))
{
REMAINING_ARGS_CHECK(4);
- m_comp_params.m_xuastc_ldr_channel_weights[0] = (uint32_t)clamp((float)atof(arg_v[arg_index + 1]), 0.0f, 1024.0f);
- m_comp_params.m_xuastc_ldr_channel_weights[1] = (uint32_t)clamp((float)atof(arg_v[arg_index + 2]), 0.0f, 1024.0f);
- m_comp_params.m_xuastc_ldr_channel_weights[2] = (uint32_t)clamp((float)atof(arg_v[arg_index + 3]), 0.0f, 1024.0f);
- m_comp_params.m_xuastc_ldr_channel_weights[3] = (uint32_t)clamp((float)atof(arg_v[arg_index + 4]), 0.0f, 1024.0f);
+ m_comp_params.m_xuastc_ldr_channel_weights[0] = (uint32_t)clamp((float)atof(arg_v[arg_index + 1]), 1.0f, 1024.0f);
+ m_comp_params.m_xuastc_ldr_channel_weights[1] = (uint32_t)clamp((float)atof(arg_v[arg_index + 2]), 1.0f, 1024.0f);
+ m_comp_params.m_xuastc_ldr_channel_weights[2] = (uint32_t)clamp((float)atof(arg_v[arg_index + 3]), 1.0f, 1024.0f);
+ m_comp_params.m_xuastc_ldr_channel_weights[3] = (uint32_t)clamp((float)atof(arg_v[arg_index + 4]), 1.0f, 1024.0f);
arg_count += 4;
return true;
}
- else if (strcasecmp(pArg, "-ls_min_psnr") == 0)
+ else if (opt_match(pArg, "-ls_min_psnr"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_ls_min_psnr = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-ls_min_alpha_psnr") == 0)
+ else if (opt_match(pArg, "-ls_min_alpha_psnr"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_ls_min_alpha_psnr = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-ls_thresh_psnr") == 0)
+ else if (opt_match(pArg, "-ls_thresh_psnr"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_ls_thresh_psnr = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-ls_thresh_alpha_psnr") == 0)
+ else if (opt_match(pArg, "-ls_thresh_alpha_psnr"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_ls_thresh_alpha_psnr = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-ls_thresh_edge_psnr") == 0)
+ else if (opt_match(pArg, "-ls_thresh_edge_psnr"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_ls_thresh_edge_psnr = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-ls_thresh_edge_alpha_psnr") == 0)
+ else if (opt_match(pArg, "-ls_thresh_edge_alpha_psnr"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_ls_thresh_edge_alpha_psnr = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-xuastc_arith") == 0)
+ else if (opt_match(pArg, {"-xuastc_arith", "-xuastc_ldr_arith"}))
{
m_comp_params.m_xuastc_ldr_syntax = (int)basist::astc_ldr_t::xuastc_ldr_syntax::cFullArith;
return true;
}
- else if (strcasecmp(pArg, "-xuastc_zstd") == 0)
+ else if (opt_match(pArg, {"-xuastc_zstd", "-xuastc_ldr_zstd"}))
{
m_comp_params.m_xuastc_ldr_syntax = (int)basist::astc_ldr_t::xuastc_ldr_syntax::cFullZStd;
return true;
}
- else if (strcasecmp(pArg, "-xuastc_hybrid") == 0)
+ else if (opt_match(pArg, {"-xuastc_hybrid", "-xuastc_ldr_hybrid"}))
{
m_comp_params.m_xuastc_ldr_syntax = (int)basist::astc_ldr_t::xuastc_ldr_syntax::cHybridArithZStd;
return true;
}
- else if (strcasecmp(pArg, "-xy") == 0)
+ else if (opt_match(pArg, {"-xuastc_heavy_subset_usage", "-xuastc_ldr_heavy_subset_usage"}))
+ {
+ m_comp_params.m_xuastc_ldr_heavy_subset_usage = true;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xuastc_no_heavy_subset_usage", "-xuastc_ldr_no_heavy_subset_usage"}))
+ {
+ m_comp_params.m_xuastc_ldr_heavy_subset_usage = false;
+ return true;
+ }
+ else if (opt_match(pArg, "-xy"))
{
m_comp_params.m_xuastc_ldr_use_lossy_supercompression = true;
return true;
}
- else if (strcasecmp(pArg, "-xyd") == 0)
+ else if (opt_match(pArg, "-xyd"))
{
m_comp_params.m_xuastc_ldr_use_lossy_supercompression = false;
return true;
}
- else if (strcasecmp(pArg, "-xs") == 0)
+ else if (opt_match(pArg, "-xs"))
{
m_comp_params.m_xuastc_ldr_force_disable_subsets = true;
return true;
}
- else if (strcasecmp(pArg, "-xsu") == 0)
+ else if (opt_match(pArg, "-xsu"))
{
m_comp_params.m_xuastc_ldr_force_disable_subsets = false;
return true;
}
- else if (strcasecmp(pArg, "-xp") == 0)
+ else if (opt_match(pArg, "-xp"))
{
m_comp_params.m_xuastc_ldr_force_disable_rgb_dual_plane = true;
return true;
}
- else if (strcasecmp(pArg, "-xpu") == 0)
+ else if (opt_match(pArg, "-xpu"))
{
m_comp_params.m_xuastc_ldr_force_disable_rgb_dual_plane = false;
return true;
}
- else if (strcasecmp(pArg, "-ts") == 0)
+ else if (opt_match(pArg, "-ts"))
{
m_comp_params.m_perceptual = true;
m_comp_params.m_ktx2_and_basis_srgb_transfer_function = true;
return true;
}
- else if (strcasecmp(pArg, "-tl") == 0)
+ else if (opt_match(pArg, "-tl"))
{
m_comp_params.m_perceptual = false;
m_comp_params.m_ktx2_and_basis_srgb_transfer_function = false;
return true;
}
// Supercompressed XUASTC LDR 4x4-12x12
- else if ((strcasecmp(pArg, "-ldr_4x4i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_4x4") == 0))
+ else if (opt_match(pArg, {"-ldr_4x4i", "-xuastc_ldr_4x4"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_4x4);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_5x4i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_5x4") == 0))
+ else if (opt_match(pArg, {"-ldr_5x4i", "-xuastc_ldr_5x4"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_5x4);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_5x5i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_5x5") == 0))
+ else if (opt_match(pArg, {"-ldr_5x5i", "-xuastc_ldr_5x5"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_5x5);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_6x5i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_6x5") == 0))
+ else if (opt_match(pArg, {"-ldr_6x5i", "-xuastc_ldr_6x5"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_6x5);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_6x6i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_6x6") == 0))
+ else if (opt_match(pArg, {"-ldr_6x6i", "-xuastc_ldr_6x6"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_6x6);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_8x5i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_8x5") == 0))
+ else if (opt_match(pArg, {"-ldr_8x5i", "-xuastc_ldr_8x5"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_8x5);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_8x6i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_8x6") == 0))
+ else if (opt_match(pArg, {"-ldr_8x6i", "-xuastc_ldr_8x6"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_8x6);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_10x5i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_10x5") == 0))
+ else if (opt_match(pArg, {"-ldr_10x5i", "-xuastc_ldr_10x5"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_10x5);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_10x6i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_10x6") == 0))
+ else if (opt_match(pArg, {"-ldr_10x6i", "-xuastc_ldr_10x6"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_10x6);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_8x8i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_8x8") == 0))
+ else if (opt_match(pArg, {"-ldr_8x8i", "-xuastc_ldr_8x8"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_8x8);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_10x8i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_10x8") == 0))
+ else if (opt_match(pArg, {"-ldr_10x8i", "-xuastc_ldr_10x8"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_10x8);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_10x10i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_10x10") == 0))
+ else if (opt_match(pArg, {"-ldr_10x10i", "-xuastc_ldr_10x10"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_10x10);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_12x10i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_12x10") == 0))
+ else if (opt_match(pArg, {"-ldr_12x10i", "-xuastc_ldr_12x10"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_12x10);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_12x12i") == 0) || (strcasecmp(pArg, "-xuastc_ldr_12x12") == 0))
+ else if (opt_match(pArg, {"-ldr_12x12i", "-xuastc_ldr_12x12"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cXUASTC_LDR_12x12);
return true;
}
+ else if (opt_match(pArg, "-xubc7"))
+ {
+ m_comp_params.set_format_mode(basist::basis_tex_format::cXUBC7);
+ return true;
+ }
+ else if (opt_match(pArg, {"-xubc7_rdo", "-xubc7_rdo_level"}))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_comp_params.m_xubc7_rdo_level = clamp(atoi(arg_v[arg_index + 1]), 0, 100);
+ arg_count++;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xubc7_stripes", "-xubc7_num_stripes"}))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_comp_params.m_xubc7_num_stripes = clamp(atoi(arg_v[arg_index + 1]), 1, 16);
+ arg_count++;
+ return true;
+ }
+ else if (opt_match(pArg, "-xubc7_bc7f"))
+ {
+ m_comp_params.m_xubc7_encoder = (int)basisu::xbc7::bc7_encoder_type::cBC7F;
+ return true;
+ }
+ else if (opt_match(pArg, {"-xubc7_bc7e_scalar", "-xubc7_bc7e"}))
+ {
+ m_comp_params.m_xubc7_encoder = (int)basisu::xbc7::bc7_encoder_type::cBC7E_Scalar;
+ return true;
+ }
+ else if (opt_match(pArg, "-xubc7_bc7e_scalar_level"))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_comp_params.m_xubc7_encoder = (int)basisu::xbc7::bc7_encoder_type::cBC7E_Scalar;
+ m_comp_params.m_xubc7_bc7e_scalar_level = clamp(atoi(arg_v[arg_index + 1]), (int)basisu::xbc7::BC7E_SCALAR_MIN_LEVEL, (int)basisu::xbc7::BC7E_SCALAR_MAX_LEVEL);
+ arg_count++;
+ return true;
+ }
// Plain ASTC LDR 4x4-12x12
- else if ((strcasecmp(pArg, "-ldr_4x4") == 0) || (strcasecmp(pArg, "-astc_ldr_4x4") == 0))
+ else if (opt_match(pArg, {"-ldr_4x4", "-astc_ldr_4x4"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_4x4);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_5x4") == 0) || (strcasecmp(pArg, "-astc_ldr_5x4") == 0))
+ else if (opt_match(pArg, {"-ldr_5x4", "-astc_ldr_5x4"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_5x4);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_5x5") == 0) || (strcasecmp(pArg, "-astc_ldr_5x5") == 0))
+ else if (opt_match(pArg, {"-ldr_5x5", "-astc_ldr_5x5"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_5x5);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_6x5") == 0) || (strcasecmp(pArg, "-astc_ldr_6x5") == 0))
+ else if (opt_match(pArg, {"-ldr_6x5", "-astc_ldr_6x5"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_6x5);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_6x6") == 0) || (strcasecmp(pArg, "-astc_ldr_6x6") == 0))
+ else if (opt_match(pArg, {"-ldr_6x6", "-astc_ldr_6x6"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_6x6);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_8x5") == 0) || (strcasecmp(pArg, "-astc_ldr_8x5") == 0))
+ else if (opt_match(pArg, {"-ldr_8x5", "-astc_ldr_8x5"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_8x5);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_8x6") == 0) || (strcasecmp(pArg, "-astc_ldr_8x6") == 0))
+ else if (opt_match(pArg, {"-ldr_8x6", "-astc_ldr_8x6"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_8x6);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_10x5") == 0) || (strcasecmp(pArg, "-astc_ldr_10x5") == 0))
+ else if (opt_match(pArg, {"-ldr_10x5", "-astc_ldr_10x5"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_10x5);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_10x6") == 0) || (strcasecmp(pArg, "-astc_ldr_10x6") == 0))
+ else if (opt_match(pArg, {"-ldr_10x6", "-astc_ldr_10x6"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_10x6);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_8x8") == 0) || (strcasecmp(pArg, "-astc_ldr_8x8") == 0))
+ else if (opt_match(pArg, {"-ldr_8x8", "-astc_ldr_8x8"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_8x8);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_10x8") == 0) || (strcasecmp(pArg, "-astc_ldr_10x8") == 0))
+ else if (opt_match(pArg, {"-ldr_10x8", "-astc_ldr_10x8"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_10x8);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_10x10") == 0) || (strcasecmp(pArg, "-astc_ldr_10x10") == 0))
+ else if (opt_match(pArg, {"-ldr_10x10", "-astc_ldr_10x10"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_10x10);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_12x10") == 0) || (strcasecmp(pArg, "-astc_ldr_12x10") == 0))
+ else if (opt_match(pArg, {"-ldr_12x10", "-astc_ldr_12x10"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_12x10);
return true;
}
- else if ((strcasecmp(pArg, "-ldr_12x12") == 0) || (strcasecmp(pArg, "-astc_ldr_12x12") == 0))
+ else if (opt_match(pArg, {"-ldr_12x12", "-astc_ldr_12x12"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_LDR_12x12);
return true;
@@ -526,43 +756,89 @@ class command_line_params
return false;
}
+ // -dds (basic DX10 DDS writer) options. Kept in its own function so the big option chain in parse() stays
+ // under the compiler's block-nesting limit. See basisu_dds_export.h.
+ bool check_for_dds_options(const char** arg_v, const char* pArg, int arg_index, const int num_remaining_args, int& arg_count)
+ {
+ if (opt_match(pArg, "-dds"))
+ {
+ m_mode = cDDS;
+ return true;
+ }
+ else if (opt_match(pArg, "-dds_format"))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_dds_format = arg_v[arg_index + 1];
+ arg_count++;
+ return true;
+ }
+ else if (opt_match(pArg, "-dds_bc7f"))
+ {
+ m_dds_bc7_encoder = cDDSBC7Encoder_BC7F;
+ return true;
+ }
+ else if (opt_match(pArg, {"-dds_bc7e_scalar", "-dds_bc7e"}))
+ {
+ m_dds_bc7_encoder = cDDSBC7Encoder_BC7E_Scalar;
+ return true;
+ }
+ else if (opt_match(pArg, "-dds_bc7f_level"))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_dds_bc7_encoder = cDDSBC7Encoder_BC7F;
+ m_dds_bc7f_level = clamp(atoi(arg_v[arg_index + 1]), 0, 2);
+ arg_count++;
+ return true;
+ }
+ else if (opt_match(pArg, "-dds_bc7e_scalar_level"))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_dds_bc7_encoder = cDDSBC7Encoder_BC7E_Scalar;
+ m_dds_bc7e_scalar_level = clamp(atoi(arg_v[arg_index + 1]), 0, 6);
+ arg_count++;
+ return true;
+ }
+
+ return false;
+ }
+
bool check_for_hdr_options(const char** arg_v, const char* pArg, int arg_index, const int num_remaining_args, int& arg_count)
{
- if ((strcasecmp(pArg, "-hdr") == 0) || (strcasecmp(pArg, "-hdr_4x4") == 0) || (strcasecmp(pArg, "-uastc_hdr_4x4") == 0))
+ if (opt_match(pArg, {"-hdr", "-hdr_4x4", "-uastc_hdr_4x4"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cUASTC_HDR_4x4);
return true;
}
- else if (strcasecmp(pArg, "-rec_2020") == 0)
+ else if (opt_match(pArg, "-rec_2020"))
{
m_comp_params.m_astc_hdr_6x6_options.m_rec2020_bt2100_color_gamut = true;
return true;
}
- else if (strcasecmp(pArg, "-hdr_6x6i_16_compatibility") == 0)
+ else if (opt_match(pArg, "-hdr_6x6i_16_compatibility"))
{
// UASTC HDR 6x6i: Write v1.60 compatible files vs. 2.0.
m_comp_params.m_astc_hdr_6x6_options.m_write_basisu_1_6_compatible_files = true;
return true;
}
- else if (strcasecmp(pArg, "-hdr_6x6i_20_compatibility") == 0)
+ else if (opt_match(pArg, "-hdr_6x6i_20_compatibility"))
{
// UASTC HDR 6x6i: Write v2.00 compatible files vs. 2.0.
m_comp_params.m_astc_hdr_6x6_options.m_write_basisu_1_6_compatible_files = false;
return true;
}
- else if ((strcasecmp(pArg, "-hdr_6x6") == 0) || (strcasecmp(pArg, "-astc_hdr_6x6") == 0))
+ else if (opt_match(pArg, {"-hdr_6x6", "-astc_hdr_6x6"}))
{
// max quality (if -lambda=0) or RDO UASTC HDR 6x6
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_HDR_6x6);
return true;
}
- else if ((strcasecmp(pArg, "-hdr_6x6i") == 0) || (strcasecmp(pArg, "-uastc_hdr_6x6") == 0) || (strcasecmp(pArg, "-uastc_hdr_6x6i") == 0))
+ else if (opt_match(pArg, {"-hdr_6x6i", "-uastc_hdr_6x6", "-uastc_hdr_6x6i"}))
{
// intermediate format UASTC HDR 6x6
m_comp_params.set_format_mode(basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE);
return true;
}
- else if (strcasecmp(pArg, "-lambda") == 0)
+ else if (opt_match(pArg, "-lambda"))
{
REMAINING_ARGS_CHECK(1);
@@ -584,7 +860,7 @@ class command_line_params
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-hdr_6x6_jnd") == 0)
+ else if (opt_match(pArg, "-hdr_6x6_jnd"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_astc_hdr_6x6_options.m_jnd_optimization = true;
@@ -592,7 +868,7 @@ class command_line_params
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-hdr_6x6_level") == 0)
+ else if (opt_match(pArg, "-hdr_6x6_level"))
{
REMAINING_ARGS_CHECK(1);
const int level = atoi(arg_v[arg_index + 1]);
@@ -604,7 +880,7 @@ class command_line_params
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-hdr_6x6i_level") == 0)
+ else if (opt_match(pArg, "-hdr_6x6i_level"))
{
REMAINING_ARGS_CHECK(1);
const int level = atoi(arg_v[arg_index + 1]);
@@ -616,17 +892,17 @@ class command_line_params
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-hdr_6x6_extra_pats") == 0)
+ else if (opt_match(pArg, "-hdr_6x6_extra_pats"))
{
m_comp_params.m_astc_hdr_6x6_options.m_extra_patterns_flag = true;
return true;
}
- else if (strcasecmp(pArg, "-hdr_6x6_brute_force_pats") == 0)
+ else if (opt_match(pArg, "-hdr_6x6_brute_force_pats"))
{
m_comp_params.m_astc_hdr_6x6_options.m_brute_force_partition_matching = true;
return true;
}
- else if ((strcasecmp(pArg, "-hdr_6x6_comp_levels") == 0) || (strcasecmp(pArg, "-hdr_6x6i_comp_levels") == 0))
+ else if (opt_match(pArg, {"-hdr_6x6_comp_levels", "-hdr_6x6i_comp_levels"}))
{
REMAINING_ARGS_CHECK(2);
@@ -637,7 +913,7 @@ class command_line_params
m_comp_params.m_astc_hdr_6x6_options.m_master_comp_level = minimum(lo_level, hi_level);
m_comp_params.m_astc_hdr_6x6_options.m_highest_comp_level = maximum(lo_level, hi_level);
- if (strcasecmp(pArg, "-hdr_6x6_comp_levels") == 0)
+ if (opt_match(pArg, "-hdr_6x6_comp_levels"))
m_comp_params.set_format_mode(basist::basis_tex_format::cASTC_HDR_6x6);
else
m_comp_params.set_format_mode(basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE);
@@ -647,54 +923,56 @@ class command_line_params
arg_count += 2;
return true;
}
- else if (strcasecmp(pArg, "-hdr_6x6_no_gaussian") == 0)
+ else if (opt_match(pArg, "-hdr_6x6_no_gaussian"))
{
m_comp_params.m_astc_hdr_6x6_options.m_gaussian1_fallback = false;
m_comp_params.m_astc_hdr_6x6_options.m_gaussian2_fallback = false;
return true;
}
- else if (strcasecmp(pArg, "-hdr_6x6_gaussian1") == 0)
+ else if (opt_match(pArg, "-hdr_6x6_gaussian1"))
{
+ REMAINING_ARGS_CHECK(1);
m_comp_params.m_astc_hdr_6x6_options.m_gaussian1_strength = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-hdr_6x6_gaussian2") == 0)
+ else if (opt_match(pArg, "-hdr_6x6_gaussian2"))
{
+ REMAINING_ARGS_CHECK(1);
m_comp_params.m_astc_hdr_6x6_options.m_gaussian2_strength = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if ((strcasecmp(pArg, "-hdr_ldr_no_srgb_to_linear") == 0) || (strcasecmp(pArg, "-hdr_ldr_upconversion_no_srgb_to_linear") == 0))
+ else if (opt_match(pArg, {"-hdr_ldr_no_srgb_to_linear", "-hdr_ldr_upconversion_no_srgb_to_linear"}))
{
m_comp_params.m_ldr_hdr_upconversion_srgb_to_linear = false;
return true;
}
- else if (strcasecmp(pArg, "-hdr_ldr_upconversion_black_bias") == 0)
+ else if (opt_match(pArg, "-hdr_ldr_upconversion_black_bias"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_ldr_hdr_upconversion_black_bias = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-hdr_ldr_upconversion_nit_multiplier") == 0)
+ else if (opt_match(pArg, "-hdr_ldr_upconversion_nit_multiplier"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_ldr_hdr_upconversion_nit_multiplier = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-hdr_uber_mode") == 0)
+ else if (opt_match(pArg, "-hdr_uber_mode"))
{
m_comp_params.m_uastc_hdr_4x4_options.m_allow_uber_mode = true;
return true;
}
- else if (strcasecmp(pArg, "-hdr_ultra_quant") == 0)
+ else if (opt_match(pArg, "-hdr_ultra_quant"))
{
m_comp_params.m_uastc_hdr_4x4_options.m_ultra_quant = true;
return true;
}
- else if (strcasecmp(pArg, "-hdr_favor_astc") == 0)
+ else if (opt_match(pArg, "-hdr_favor_astc"))
{
m_comp_params.m_hdr_favor_astc = true;
return true;
@@ -706,17 +984,18 @@ class command_line_params
// ETC1S or UASTC LDR 4x4 specific options
bool check_for_etc1s_or_uastc_options(const char** arg_v, const char* pArg, int arg_index, const int num_remaining_args, int& arg_count)
{
- if (strcasecmp(pArg, "-etc1s") == 0)
+ if (opt_match(pArg, {"-etc1s", "-ldr"}))
{
+ // -ldr selects the LDR/SDR default codec (ETC1S), symmetric with -hdr.
m_comp_params.set_format_mode(basist::basis_tex_format::cETC1S);
return true;
}
- else if ((strcasecmp(pArg, "-uastc") == 0) || (strcasecmp(pArg, "-uastc_ldr") == 0) || (strcasecmp(pArg, "-uastc_ldr_4x4") == 0))
+ else if (opt_match(pArg, {"-uastc", "-uastc_ldr", "-uastc_ldr_4x4"}))
{
m_comp_params.set_format_mode(basist::basis_tex_format::cUASTC_LDR_4x4);
return true;
}
- else if (strcasecmp(pArg, "-uastc_level") == 0)
+ else if (opt_match(pArg, "-uastc_level"))
{
REMAINING_ARGS_CHECK(1);
@@ -737,7 +1016,7 @@ class command_line_params
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-uastc_rdo_l") == 0)
+ else if (opt_match(pArg, "-uastc_rdo_l"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_rdo_uastc_ldr_4x4_quality_scalar = (float)atof(arg_v[arg_index + 1]);
@@ -748,43 +1027,43 @@ class command_line_params
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-uastc_rdo_d") == 0)
+ else if (opt_match(pArg, "-uastc_rdo_d"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_rdo_uastc_ldr_4x4_dict_size = atoi(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-uastc_rdo_b") == 0)
+ else if (opt_match(pArg, "-uastc_rdo_b"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_rdo_uastc_ldr_4x4_max_smooth_block_error_scale = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-uastc_rdo_s") == 0)
+ else if (opt_match(pArg, "-uastc_rdo_s"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_rdo_uastc_ldr_4x4_smooth_block_max_std_dev = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-uastc_rdo_f") == 0)
+ else if (opt_match(pArg, "-uastc_rdo_f"))
{
m_comp_params.m_rdo_uastc_ldr_4x4_favor_simpler_modes_in_rdo_mode = false;
return true;
}
- else if (strcasecmp(pArg, "-uastc_rdo_m") == 0)
+ else if (opt_match(pArg, "-uastc_rdo_m"))
{
m_comp_params.m_rdo_uastc_ldr_4x4_multithreading = false;
return true;
}
- else if (strcasecmp(pArg, "-validate_etc1s") == 0)
+ else if (opt_match(pArg, "-validate_etc1s"))
{
m_comp_params.m_validate_etc1s = true;
return true;
}
- else if (strcasecmp(pArg, "-comp_level") == 0)
+ else if (opt_match(pArg, "-comp_level"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_etc1s_compression_level = atoi(arg_v[arg_index + 1]);
@@ -794,7 +1073,7 @@ class command_line_params
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-max_endpoints") == 0)
+ else if (opt_match(pArg, "-max_endpoints"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_etc1s_max_endpoint_clusters = clamp(atoi(arg_v[arg_index + 1]), 1, BASISU_MAX_ENDPOINT_CLUSTERS);
@@ -804,7 +1083,7 @@ class command_line_params
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-max_selectors") == 0)
+ else if (opt_match(pArg, "-max_selectors"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_etc1s_max_selector_clusters = clamp(atoi(arg_v[arg_index + 1]), 1, BASISU_MAX_SELECTOR_CLUSTERS);
@@ -815,30 +1094,30 @@ class command_line_params
return true;
}
#if 0
- else if (strcasecmp(pArg, "-gen_global_codebooks") == 0)
+ else if (opt_match(pArg, "-gen_global_codebooks"))
{
// TODO
}
#endif
- else if (strcasecmp(pArg, "-use_global_codebooks") == 0)
+ else if (opt_match(pArg, "-use_global_codebooks"))
{
REMAINING_ARGS_CHECK(1);
m_etc1s_use_global_codebooks_file = arg_v[arg_index + 1];
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-etc1_only") == 0)
+ else if (opt_match(pArg, "-etc1_only"))
{
m_etc1_only = true;
m_unpack_format_only = (int)basist::transcoder_texture_format::cTFETC1_RGB;
return true;
}
- else if (strcasecmp(pArg, "-disable_hierarchical_endpoint_codebooks") == 0)
+ else if (opt_match(pArg, "-disable_hierarchical_endpoint_codebooks"))
{
m_comp_params.m_disable_hierarchical_endpoint_codebooks = true;
return true;
}
- else if (strcasecmp(pArg, "-q") == 0) // old-style -q, prefer -quality instead
+ else if (opt_match(pArg, "-q")) // old-style ETC1S -q X option, prefer -quality instead
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_quality_level = clamp(atoi(arg_v[arg_index + 1]), BASISU_QUALITY_MIN, BASISU_QUALITY_MAX);
@@ -848,24 +1127,24 @@ class command_line_params
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-no_selector_rdo") == 0)
+ else if (opt_match(pArg, "-no_selector_rdo"))
{
m_comp_params.m_no_selector_rdo = true;
return true;
}
- else if (strcasecmp(pArg, "-selector_rdo_thresh") == 0)
+ else if (opt_match(pArg, "-selector_rdo_thresh"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_selector_rdo_thresh = (float)atof(arg_v[arg_index + 1]);
arg_count++;
return true;
}
- else if (strcasecmp(pArg, "-no_endpoint_rdo") == 0)
+ else if (opt_match(pArg, "-no_endpoint_rdo"))
{
m_comp_params.m_no_endpoint_rdo = true;
return true;
}
- else if (strcasecmp(pArg, "-endpoint_rdo_thresh") == 0)
+ else if (opt_match(pArg, "-endpoint_rdo_thresh"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_endpoint_rdo_thresh = (float)atof(arg_v[arg_index + 1]);
@@ -879,6 +1158,22 @@ class command_line_params
public:
command_line_params() :
m_mode(cDefault),
+ m_benchmark_fmt(basist::basis_tex_format::cXUASTC_LDR_6x6),
+ m_benchmark_effort(0),
+ m_benchmark_quality(100),
+ m_benchmark_path("../test_files/kodim"),
+ m_benchmark_first(1),
+ m_benchmark_last(24),
+ m_benchmark_path_set(false),
+ m_benchmark_range_set(false),
+ m_benchmark_linear(false),
+ m_benchmark_xubc7_encoder(0),
+ m_benchmark_xuastc_profile(0),
+ m_benchmark_sweep_block_set(false),
+ m_benchmark_sweep_block_lo(0), m_benchmark_sweep_block_hi(0),
+ m_benchmark_sweep_effort_lo(3), m_benchmark_sweep_effort_hi(3),
+ m_benchmark_sweep_quality_lo(10), m_benchmark_sweep_quality_hi(10),
+ m_benchmark_sweep_csv("benchmark_sweep.csv"),
m_ktx2_mode(true),
m_ktx2_zstandard(true),
m_ktx2_zstandard_level(6),
@@ -897,14 +1192,27 @@ public:
m_fuzz_testing(false),
m_compare_ssim(false),
m_compare_plot(false),
+ m_dump_image_stats(false),
+ m_dump_pixel_x(-1),
+ m_dump_pixel_y(-1),
+ m_dump_block_x(-1),
+ m_dump_block_y(-1),
+ m_stats_region_x(-1),
+ m_stats_region_y(-1),
+ m_stats_region_w(-1),
+ m_stats_region_h(-1),
+ m_extract_channel(-1),
+ m_extract_region_x(-1),
+ m_extract_region_y(-1),
+ m_extract_region_w(-1),
+ m_extract_region_h(-1),
m_parallel_compression(false),
m_tonemap_dither_flag(false),
m_xuastc_ldr_disable_bc7_transcoding(false),
m_no_etc1s_transcoding_chroma_filtering(false),
m_higher_quality_transcoding(false),
- m_force_deblocking(false),
- m_disable_deblocking(false),
- m_stronger_deblocking(false),
+ m_transcode_force_deblocking(false),
+ m_transcode_disable_deblocking(false),
m_effort_level(-1),
m_quality_level(-1),
m_used_old_style_codec_config_param(false)
@@ -930,7 +1238,7 @@ public:
const int num_remaining_args = arg_c - (arg_index + 1);
int arg_count = 1;
- if ((strcasecmp(pArg, "-help") == 0) || (strcasecmp(pArg, "--help") == 0))
+ if (opt_match(pArg, {"-help", "--help"}))
{
print_usage();
exit(EXIT_SUCCESS);
@@ -948,308 +1256,676 @@ public:
else if (check_for_general_options(arg_v, pArg, arg_index, num_remaining_args, arg_count))
{
}
- else if (strcasecmp(pArg, "-ktx2") == 0)
+ else if (check_for_dds_options(arg_v, pArg, arg_index, num_remaining_args, arg_count))
+ {
+ }
+ else if (opt_match(pArg, "-ktx2"))
{
m_ktx2_mode = true;
}
- else if (strcasecmp(pArg, "-basis") == 0)
+ else if (opt_match(pArg, "-basis"))
{
m_ktx2_mode = false;
}
- else if (strcasecmp(pArg, "-ktx2_no_zstandard") == 0)
+ else if (opt_match(pArg, "-ktx2_no_zstandard"))
{
m_ktx2_zstandard = false;
}
- else if (strcasecmp(pArg, "-ktx2_zstandard_level") == 0)
+ else if (opt_match(pArg, "-ktx2_zstandard_level"))
{
REMAINING_ARGS_CHECK(1);
m_ktx2_zstandard_level = atoi(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-ktx2_animdata_duration") == 0)
+ else if (opt_match(pArg, "-ktx2_animdata_duration"))
{
REMAINING_ARGS_CHECK(1);
m_ktx2_animdata_duration = atoi(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-ktx2_animdata_timescale") == 0)
+ else if (opt_match(pArg, "-ktx2_animdata_timescale"))
{
REMAINING_ARGS_CHECK(1);
m_ktx2_animdata_timescale = atoi(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-ktx2_animdata_loopcount") == 0)
+ else if (opt_match(pArg, "-ktx2_animdata_loopcount"))
{
REMAINING_ARGS_CHECK(1);
m_ktx2_animdata_loopcount = atoi(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-compress") == 0)
+ else if (opt_match(pArg, "-compress"))
m_mode = cCompress;
- else if (strcasecmp(pArg, "-compare") == 0)
+ else if (opt_match(pArg, "-compare"))
m_mode = cCompare;
- else if ((strcasecmp(pArg, "-hdr_compare") == 0) || (strcasecmp(pArg, "-compare_hdr") == 0))
+ else if (opt_match(pArg, "-compare_hvs"))
+ m_mode = cCompareHVS;
+ else if (opt_match(pArg, {"-hdr_compare", "-compare_hdr"}))
m_mode = cHDRCompare;
- else if (strcasecmp(pArg, "-split") == 0)
+ else if (opt_match(pArg, "-dump_image_stats"))
+ {
+ m_mode = cImageDumpStats;
+ m_dump_image_stats = true;
+
+ // Optional region: -dump_image_stats . If the next argument looks like a
+ // number then all four must follow; otherwise the stats cover the whole image.
+ if ((num_remaining_args >= 1) && arg_is_unsigned_int(arg_v[arg_index + 1]))
+ {
+ REMAINING_ARGS_CHECK(4);
+ m_stats_region_x = atoi(arg_v[arg_index + 1]);
+ m_stats_region_y = atoi(arg_v[arg_index + 2]);
+ m_stats_region_w = atoi(arg_v[arg_index + 3]);
+ m_stats_region_h = atoi(arg_v[arg_index + 4]);
+ arg_count += 4;
+ if ((m_stats_region_x < 0) || (m_stats_region_y < 0) || (m_stats_region_w <= 0) || (m_stats_region_h <= 0))
+ {
+ error_printf("-dump_image_stats region requires non-negative x,y and positive w,h: -dump_image_stats \n");
+ return false;
+ }
+ }
+ }
+ else if (opt_match(pArg, "-dump_pixel"))
+ {
+ REMAINING_ARGS_CHECK(2);
+ m_mode = cImageDumpStats;
+ m_dump_pixel_x = atoi(arg_v[arg_index + 1]);
+ m_dump_pixel_y = atoi(arg_v[arg_index + 2]);
+ arg_count += 2;
+ if ((m_dump_pixel_x < 0) || (m_dump_pixel_y < 0))
+ {
+ error_printf("-dump_pixel requires two non-negative integer coordinates: -dump_pixel \n");
+ return false;
+ }
+ }
+ else if (opt_match(pArg, "-dump_block"))
+ {
+ REMAINING_ARGS_CHECK(2);
+ m_mode = cImageDumpStats;
+ m_dump_block_x = atoi(arg_v[arg_index + 1]);
+ m_dump_block_y = atoi(arg_v[arg_index + 2]);
+ arg_count += 2;
+ if ((m_dump_block_x < 0) || (m_dump_block_y < 0))
+ {
+ error_printf("-dump_block requires two non-negative integer block coordinates: -dump_block \n");
+ return false;
+ }
+ }
+ else if (opt_match(pArg, "-split"))
m_mode = cSplitImage;
- else if (strcasecmp(pArg, "-combine") == 0)
+ else if (opt_match(pArg, "-combine"))
m_mode = cCombineImages;
- else if (strcasecmp(pArg, "-tonemap") == 0)
+ else if (opt_match(pArg, "-extract_channel"))
+ {
+ REMAINING_ARGS_CHECK(2);
+ m_mode = cExtractChannel;
+
+ // Channel: r/g/b/a (case-insensitive) or 0..3. Always assign (don't leave a stale value from a prior -extract_channel).
+ const char* pCh = arg_v[arg_index + 1];
+ m_extract_channel = ((pCh[0] != '\0') && (pCh[1] == '\0')) ? channel_token_to_index(pCh[0]) : -1;
+ if (m_extract_channel < 0)
+ {
+ error_printf("-extract_channel requires a channel (r/g/b/a or 0..3): -extract_channel [x y w h]\n");
+ return false;
+ }
+
+ m_extract_filename = arg_v[arg_index + 2];
+ arg_count += 2;
+
+ // Optional region: -extract_channel .
+ if ((num_remaining_args >= 3) && arg_is_unsigned_int(arg_v[arg_index + 3]))
+ {
+ REMAINING_ARGS_CHECK(6);
+ m_extract_region_x = atoi(arg_v[arg_index + 3]);
+ m_extract_region_y = atoi(arg_v[arg_index + 4]);
+ m_extract_region_w = atoi(arg_v[arg_index + 5]);
+ m_extract_region_h = atoi(arg_v[arg_index + 6]);
+ arg_count += 4;
+ if ((m_extract_region_x < 0) || (m_extract_region_y < 0) || (m_extract_region_w <= 0) || (m_extract_region_h <= 0))
+ {
+ error_printf("-extract_channel region requires non-negative x,y and positive w,h: -extract_channel \n");
+ return false;
+ }
+ }
+ }
+ else if (opt_match(pArg, "-extract_swizzle"))
+ {
+ REMAINING_ARGS_CHECK(2);
+ m_mode = cExtractSwizzle;
+
+ // Swizzle: comma-separated source channels for output R,G,B,A, e.g. "0,1,2,3" (identity) or "r,g,b,a" or "2,1,0".
+ // Each token selects a source channel in [0,3]; 1..4 tokens. Any output channel not listed passes through unchanged.
+ const char* p = arg_v[arg_index + 1];
+ int count = 0;
+ bool swizzle_ok = true;
+ while (*p)
+ {
+ const int idx = channel_token_to_index(*p);
+ if ((idx < 0) || (count >= 4))
+ {
+ swizzle_ok = false;
+ break;
+ }
+ m_extract_swizzle[count++] = idx;
+ p++;
+ if (*p == ',')
+ {
+ p++;
+ if (*p == '\0') { swizzle_ok = false; break; } // trailing comma
+ }
+ else if (*p != '\0')
+ {
+ swizzle_ok = false; // token longer than a single channel char
+ break;
+ }
+ }
+ if (!swizzle_ok || (count == 0))
+ {
+ error_printf("-extract_swizzle requires 1 to 4 comma-separated source channels (each r/g/b/a or 0..3), e.g. \"0,1,2,3\"\n");
+ return false;
+ }
+ m_extract_swizzle_count = count;
+
+ m_extract_filename = arg_v[arg_index + 2];
+ arg_count += 2;
+
+ // Optional region: -extract_swizzle .
+ if ((num_remaining_args >= 3) && arg_is_unsigned_int(arg_v[arg_index + 3]))
+ {
+ REMAINING_ARGS_CHECK(6);
+ m_extract_region_x = atoi(arg_v[arg_index + 3]);
+ m_extract_region_y = atoi(arg_v[arg_index + 4]);
+ m_extract_region_w = atoi(arg_v[arg_index + 5]);
+ m_extract_region_h = atoi(arg_v[arg_index + 6]);
+ arg_count += 4;
+ if ((m_extract_region_x < 0) || (m_extract_region_y < 0) || (m_extract_region_w <= 0) || (m_extract_region_h <= 0))
+ {
+ error_printf("-extract_swizzle region requires non-negative x,y and positive w,h: -extract_swizzle \n");
+ return false;
+ }
+ }
+ }
+ else if (opt_match(pArg, "-extract_region"))
+ {
+ REMAINING_ARGS_CHECK(5);
+ m_mode = cExtractRegion;
+ m_extract_filename = arg_v[arg_index + 1];
+ m_extract_region_x = atoi(arg_v[arg_index + 2]);
+ m_extract_region_y = atoi(arg_v[arg_index + 3]);
+ m_extract_region_w = atoi(arg_v[arg_index + 4]);
+ m_extract_region_h = atoi(arg_v[arg_index + 5]);
+ arg_count += 5;
+ if ((m_extract_region_x < 0) || (m_extract_region_y < 0) || (m_extract_region_w <= 0) || (m_extract_region_h <= 0))
+ {
+ error_printf("-extract_region requires non-negative x,y and positive w,h: -extract_region \n");
+ return false;
+ }
+ }
+ // Optional color transforms applied by the -extract_* modes (modifiers; order vs. the -extract_* option does not matter).
+ else if (opt_match(pArg, "-extract_to_linear"))
+ m_extract_color_xform = cExtractXformToLinear;
+ else if (opt_match(pArg, "-extract_to_srgb"))
+ m_extract_color_xform = cExtractXformToSRGB;
+ else if (opt_match(pArg, "-extract_xform_alpha"))
+ m_extract_xform_alpha = true;
+ else if (opt_match(pArg, "-text_to_png"))
+ {
+ REMAINING_ARGS_CHECK(2);
+ m_mode = cTextToPng;
+ m_text_image_in = arg_v[arg_index + 1];
+ m_text_image_out = arg_v[arg_index + 2];
+ arg_count += 2;
+ }
+ else if (opt_match(pArg, "-png_to_text"))
+ {
+ REMAINING_ARGS_CHECK(2);
+ m_mode = cPngToText;
+ m_text_image_in = arg_v[arg_index + 1];
+ m_text_image_out = arg_v[arg_index + 2];
+ arg_count += 2;
+ }
+ else if (opt_match(pArg, "-tonemap"))
m_mode = cTonemapImage;
- else if (strcasecmp(pArg, "-unpack") == 0)
+ else if (opt_match(pArg, "-export_dds"))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_mode = cExportDDS;
+ m_export_dds_format = arg_v[arg_index + 1];
+ arg_count++;
+ }
+ else if (opt_match(pArg, "-export_ktx"))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_mode = cExportKTX;
+ m_export_ktx_format = arg_v[arg_index + 1];
+ arg_count++;
+ }
+ // -benchmark_single : developer codec_benchmark() single run (not in help text yet).
+ // FORMAT is any codec name with underscores, e.g. XUASTC_LDR_4x4..12x12, XUBC7, ETC1S, ASTC_LDR_6x6, UASTC_LDR_4x4.
+ else if (opt_match(pArg, "-benchmark_single"))
+ {
+ REMAINING_ARGS_CHECK(3);
+ m_mode = cBenchmarkSingle;
+ if (!parse_benchmark_tex_format(arg_v[arg_index + 1], m_benchmark_fmt))
+ {
+ error_printf("-benchmark_single: unrecognized format \"%s\"\n", arg_v[arg_index + 1]);
+ return false;
+ }
+ // Clamp to the same ranges as -effort [0,10] and -quality [1,100] (matches lines above); also keeps the cast non-negative.
+ m_benchmark_effort = (uint32_t)clamp(atoi(arg_v[arg_index + 2]), 0, 10);
+ m_benchmark_quality = (uint32_t)clamp(atoi(arg_v[arg_index + 3]), 1, 100);
+ arg_count += 3;
+ }
+ // Optional overrides for -benchmark_single's base path and file index range (defaults: "../test_files/kodim", 1..24).
+ else if (opt_match(pArg, "-benchmark_path"))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_benchmark_path = arg_v[arg_index + 1];
+ m_benchmark_path_set = true;
+ arg_count++;
+ }
+ else if (opt_match(pArg, "-benchmark_range"))
+ {
+ REMAINING_ARGS_CHECK(2);
+ m_benchmark_first = (uint32_t)clamp(atoi(arg_v[arg_index + 1]), 0, 1000000);
+ m_benchmark_last = (uint32_t)clamp(atoi(arg_v[arg_index + 2]), 0, 1000000);
+ if (m_benchmark_last < m_benchmark_first) // keep first<=last so codec_benchmark's (last-first+1) can't underflow
+ std::swap(m_benchmark_first, m_benchmark_last);
+ m_benchmark_range_set = true;
+ arg_count += 2;
+ }
+ else if (opt_match(pArg, "-benchmark_linear"))
+ m_benchmark_linear = true;
+ // XUBC7-only BC7 base encoder: 0 = bc7f (default), 1-7 = bc7e_scalar level 0-6 (ignored for non-XUBC7 formats).
+ else if (opt_match(pArg, "-benchmark_xubc7_encoder"))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_benchmark_xubc7_encoder = (uint32_t)clamp(atoi(arg_v[arg_index + 1]), 0, (int)basisu::cFlagXUBC7BaseEncoderMask);
+ arg_count++;
+ }
+ // XUASTC LDR entropy syntax/profile: 0 = full arith (default), 1 = hybrid, 2 = full zstd (ignored for non-XUASTC formats).
+ else if (opt_match(pArg, "-benchmark_xuastc_profile"))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_benchmark_xuastc_profile = (uint32_t)clamp(atoi(arg_v[arg_index + 1]), 0, (int)basist::astc_ldr_t::xuastc_ldr_syntax::cTotal - 1);
+ arg_count++;
+ }
+ // -benchmark_sweep : sweep block size/effort/quality for one codec, dumping a CSV (see benchmark_sweep_mode()).
+ else if (opt_match(pArg, "-benchmark_sweep"))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_mode = cBenchmarkSweep;
+ if (!parse_benchmark_tex_format(arg_v[arg_index + 1], m_benchmark_fmt))
+ {
+ error_printf("-benchmark_sweep: unrecognized format \"%s\"\n", arg_v[arg_index + 1]);
+ return false;
+ }
+ arg_count++;
+ }
+ // -benchmark_sweep axis ranges (inclusive low/high). Block index [0,5] (4x4..12x12 squares); effort [0,10]; quality [0,10] (x10 -> [1,100]).
+ else if (opt_match(pArg, "-benchmark_sweep_block"))
+ {
+ REMAINING_ARGS_CHECK(2);
+ m_benchmark_sweep_block_set = true;
+ m_benchmark_sweep_block_lo = (uint32_t)clamp(atoi(arg_v[arg_index + 1]), 0, 5);
+ m_benchmark_sweep_block_hi = (uint32_t)clamp(atoi(arg_v[arg_index + 2]), 0, 5);
+ arg_count += 2;
+ }
+ else if (opt_match(pArg, "-benchmark_sweep_effort"))
+ {
+ REMAINING_ARGS_CHECK(2);
+ m_benchmark_sweep_effort_lo = (uint32_t)clamp(atoi(arg_v[arg_index + 1]), 0, 10);
+ m_benchmark_sweep_effort_hi = (uint32_t)clamp(atoi(arg_v[arg_index + 2]), 0, 10);
+ arg_count += 2;
+ }
+ else if (opt_match(pArg, "-benchmark_sweep_quality"))
+ {
+ REMAINING_ARGS_CHECK(2);
+ m_benchmark_sweep_quality_lo = (uint32_t)clamp(atoi(arg_v[arg_index + 1]), 0, 10);
+ m_benchmark_sweep_quality_hi = (uint32_t)clamp(atoi(arg_v[arg_index + 2]), 0, 10);
+ arg_count += 2;
+ }
+ else if (opt_match(pArg, "-benchmark_sweep_csv"))
+ {
+ REMAINING_ARGS_CHECK(1);
+ m_benchmark_sweep_csv = arg_v[arg_index + 1];
+ arg_count++;
+ }
+ else if (opt_match(pArg, "-tinydds_info")) // opt_match also accepts the "--tinydds_info" form
+ m_mode = cTinyDDSInfo;
+ else if (opt_match(pArg, "-ktx_info"))
+ m_mode = cKTXInfo;
+ else if (opt_match(pArg, "-unpack"))
m_mode = cUnpack;
- else if (strcasecmp(pArg, "-validate") == 0)
+ else if (opt_match(pArg, "-validate"))
m_mode = cValidate;
- else if (strcasecmp(pArg, "-info") == 0)
+ else if (opt_match(pArg, "-info"))
m_mode = cInfo;
- else if ((strcasecmp(pArg, "-version") == 0) || (strcasecmp(pArg, "--version") == 0))
+ else if (opt_match(pArg, {"-version", "--version"}))
m_mode = cVersion;
- else if (strcasecmp(pArg, "-compare_ssim") == 0)
+ else if (opt_match(pArg, "-compare_ssim"))
m_compare_ssim = true;
- else if (strcasecmp(pArg, "-compare_plot") == 0)
+ else if (opt_match(pArg, "-compare_plot"))
m_compare_plot = true;
- else if (strcasecmp(pArg, "-bench") == 0)
+ else if (opt_match(pArg, "-bench"))
m_mode = cBench;
- else if (strcasecmp(pArg, "-comp_size") == 0)
+ else if (opt_match(pArg, "-comp_size"))
m_mode = cCompSize;
- else if ((strcasecmp(pArg, "-test") == 0) || (strcasecmp(pArg, "-test_ldr") == 0))
+ else if (opt_match(pArg, {"-test", "-test_ldr"}))
m_mode = cTestLDR;
- else if ((strcasecmp(pArg, "-test_xuastc") == 0) || (strcasecmp(pArg, "-test_xuastc_ldr") == 0))
+ else if (opt_match(pArg, {"-test_xuastc", "-test_xuastc_ldr"}))
m_mode = cTestXUASTCLDR;
- else if (strcasecmp(pArg, "-test_hdr_4x4") == 0)
+ else if (opt_match(pArg, {"-test_xuastc_dump", "-test_xuastc_ldr_dump"}))
+ {
+ // Run the XUASTC LDR test across all block sizes and print the expected-value
+ // tables (for copy/paste into the code) instead of validating.
+ m_mode = cTestXUASTCLDR;
+ m_xuastc_test_dump = true;
+ }
+ else if (opt_match(pArg, "-test_hdr_4x4"))
m_mode = cTestHDR_4x4;
- else if (strcasecmp(pArg, "-test_hdr_6x6") == 0)
+ else if (opt_match(pArg, "-test_hdr_6x6"))
m_mode = cTestHDR_6x6;
- else if (strcasecmp(pArg, "-test_hdr_6x6i") == 0)
+ else if (opt_match(pArg, "-test_hdr_6x6i"))
m_mode = cTestHDR_6x6i;
- else if (strcasecmp(pArg, "-clbench") == 0)
+ else if (opt_match(pArg, "-test_codecs"))
+ {
+ // Sweep all LDR+HDR codecs vs the golden .inl table. Optional trailing codec
+ // filter (not beginning with '-'), e.g. "-test_codecs XUASTC".
+ m_mode = cTestCodecs;
+ if ((num_remaining_args >= 1) && (arg_v[arg_index + 1][0] != '-'))
+ {
+ m_codec_filter = std::string(arg_v[arg_index + 1]);
+ arg_count++;
+ }
+ }
+ else if (opt_match(pArg, "-test_codecs_gen"))
+ {
+ // (Re)generate the golden .inl table. Optional trailing output filename, then an
+ // optional codec filter, both not beginning with '-'.
+ m_mode = cTestCodecsGen;
+ if ((num_remaining_args >= 1) && (arg_v[arg_index + 1][0] != '-'))
+ {
+ m_test_codecs_gen_filename = std::string(arg_v[arg_index + 1]);
+ arg_count++;
+
+ if ((num_remaining_args >= 2) && (arg_v[arg_index + 2][0] != '-'))
+ {
+ m_codec_filter = std::string(arg_v[arg_index + 2]);
+ arg_count++;
+ }
+ }
+ }
+ else if (opt_match(pArg, "-clbench"))
m_mode = cCLBench;
- else if (strcasecmp(pArg, "-test_dir") == 0)
+ else if (opt_match(pArg, "-test_dir"))
{
REMAINING_ARGS_CHECK(1);
m_test_file_dir = std::string(arg_v[arg_index + 1]);
+ m_test_dir_explicit = true;
arg_count++;
}
- else if (strcasecmp(pArg, "-no_sse") == 0)
+ else if (opt_match(pArg, "-no_sse"))
{
#if BASISU_SUPPORT_SSE
g_cpu_supports_sse41 = false;
#endif
}
- else if ((strcasecmp(pArg, "-no_status_output") == 0) || (strcasecmp(pArg, "-quiet") == 0))
+ else if (opt_match(pArg, {"-no_status_output", "-quiet"}))
{
m_comp_params.m_status_output = false;
}
- else if (strcasecmp(pArg, "-file") == 0)
+ else if (opt_match(pArg, "-file"))
{
REMAINING_ARGS_CHECK(1);
m_input_filenames.push_back(std::string(arg_v[arg_index + 1]));
arg_count++;
}
- else if (strcasecmp(pArg, "-alpha_file") == 0)
+ else if (opt_match(pArg, "-alpha_file"))
{
REMAINING_ARGS_CHECK(1);
m_input_alpha_filenames.push_back(std::string(arg_v[arg_index + 1]));
arg_count++;
}
- else if (strcasecmp(pArg, "-multifile_printf") == 0)
+ else if (opt_match(pArg, "-multifile_printf"))
{
REMAINING_ARGS_CHECK(1);
m_multifile_printf = std::string(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-multifile_first") == 0)
+ else if (opt_match(pArg, "-multifile_first"))
{
REMAINING_ARGS_CHECK(1);
m_multifile_first = atoi(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-multifile_num") == 0)
+ else if (opt_match(pArg, "-multifile_num"))
{
REMAINING_ARGS_CHECK(1);
m_multifile_num = atoi(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-resample") == 0)
+ else if (opt_match(pArg, "-resample"))
{
REMAINING_ARGS_CHECK(2);
m_comp_params.m_resample_width = atoi(arg_v[arg_index + 1]);
m_comp_params.m_resample_height = atoi(arg_v[arg_index + 2]);
arg_count += 2;
}
- else if (strcasecmp(pArg, "-resample_factor") == 0)
+ else if (opt_match(pArg, "-resample_factor"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_resample_factor = (float)atof(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-linear") == 0)
- {
- m_comp_params.m_perceptual = false;
- m_comp_params.m_ktx2_and_basis_srgb_transfer_function = false;
- }
- else if (strcasecmp(pArg, "-srgb") == 0)
- {
- m_comp_params.m_perceptual = true;
- m_comp_params.m_ktx2_and_basis_srgb_transfer_function = true;
- }
- else if (strcasecmp(pArg, "-output_file") == 0)
+ else if (opt_match(pArg, { "-output_file", "-out_file" }))
{
REMAINING_ARGS_CHECK(1);
m_output_filename = arg_v[arg_index + 1];
arg_count++;
}
- else if (strcasecmp(pArg, "-output_path") == 0)
+ else if (opt_match(pArg, { "-output_path", "-out_path" } ))
{
REMAINING_ARGS_CHECK(1);
m_output_path = arg_v[arg_index + 1];
arg_count++;
}
- else if ((strcasecmp(pArg, "-debug") == 0) || (strcasecmp(pArg, "-verbose") == 0))
+ else if (opt_match(pArg, "-debug"))
{
m_comp_params.m_debug = true;
enable_debug_printf(true);
}
- else if (strcasecmp(pArg, "-validate_output") == 0)
+ else if (opt_match(pArg, "-verbose"))
+ {
+ // -verbose is shorthand for -debug -stats
+ m_comp_params.m_debug = true;
+ enable_debug_printf(true);
+ m_comp_params.m_compute_stats = true;
+ m_comp_params.m_psnr_hvs_m_stats = true;
+ }
+ else if (opt_match(pArg, "-validate_output"))
{
m_comp_params.m_validate_output_data = true;
}
- else if (strcasecmp(pArg, "-debug_images") == 0)
+ else if (opt_match(pArg, "-debug_images"))
m_comp_params.m_debug_images = true;
- else if (strcasecmp(pArg, "-stats") == 0)
- m_comp_params.m_compute_stats = true;
- else if (strcasecmp(pArg, "-y_flip") == 0)
- m_comp_params.m_y_flip = true;
- else if (strcasecmp(pArg, "-normal_map") == 0)
+ else if (opt_match(pArg, {"-stats", "-image_stats"}))
{
- m_comp_params.m_perceptual = false;
- m_comp_params.m_ktx2_and_basis_srgb_transfer_function = false;
- m_comp_params.m_mip_srgb = false;
+ m_comp_params.m_compute_stats = true;
+ m_comp_params.m_psnr_hvs_m_stats = true;
+ }
+ else if (opt_match(pArg, "-no_stats"))
+ {
+ // Turn stats back off (e.g. to override -verbose or -csv_file, which enable them).
+ m_comp_params.m_compute_stats = false;
+ m_comp_params.m_psnr_hvs_m_stats = false;
+ }
+ else if (opt_match(pArg, "-y_flip"))
+ m_comp_params.m_y_flip = true;
+ else if (opt_match(pArg, {"-normal_map", "-texture"}))
+ {
+ // Normal map/plain texture preset - disable all fancy things, linear weights, no sharpening largest blocks, no deblocking awareness.
+ m_comp_params.set_srgb_options(false);
+
m_comp_params.m_no_selector_rdo = true;
m_comp_params.m_no_endpoint_rdo = true;
+
+ m_comp_params.set_xuastc_ldr_srgb_channel_weights(false);
+
+ m_comp_params.m_xuastc_ldr_sharpen_mode = (int)xuastc_ldr_sharpen_mode::cDisabled;
+ m_comp_params.m_xuastc_ldr_deblocking_mode = (int)xuastc_ldr_deblocking_mode::cDisabled;
+
+ // TODO: Once we support some sort of angular metric, enable them for -normal_map.
}
- else if (strcasecmp(pArg, "-no_alpha") == 0)
+ else if (opt_match(pArg, "-photo"))
+ {
+ // Photo preset (the codec's defaults, opposite of -normal_map/-texture)
+ m_comp_params.set_srgb_options(true);
+
+ // -normal_map sets these to true, this preset sets them to false
+ m_comp_params.m_no_selector_rdo = false;
+ m_comp_params.m_no_endpoint_rdo = false;
+
+ m_comp_params.set_xuastc_ldr_srgb_channel_weights(true);
+
+ m_comp_params.m_xuastc_ldr_sharpen_mode = (int)xuastc_ldr_sharpen_mode::cDisabled;
+ m_comp_params.m_xuastc_ldr_deblocking_mode = (int)xuastc_ldr_deblocking_mode::cUseSCDAndFilteringOnlyLargestBlocks;
+ }
+ else if (opt_match(pArg, "-linear"))
+ {
+ // linear preset (opposite of -srgb)
+ m_comp_params.set_srgb_options(false);
+
+ m_comp_params.set_xuastc_ldr_srgb_channel_weights(false);
+ }
+ else if (opt_match(pArg, "-srgb"))
+ {
+ // sRGB preset (opposite of -linear)
+ m_comp_params.set_srgb_options(true);
+
+ m_comp_params.set_xuastc_ldr_srgb_channel_weights(true);
+ }
+ else if (opt_match(pArg, "-no_alpha"))
m_comp_params.m_check_for_alpha = false;
- else if (strcasecmp(pArg, "-force_alpha") == 0)
+ else if (opt_match(pArg, "-force_alpha"))
m_comp_params.m_force_alpha = true;
- else if ((strcasecmp(pArg, "-separate_rg_to_color_alpha") == 0) ||
- (strcasecmp(pArg, "-seperate_rg_to_color_alpha") == 0)) // was mispelled for a while - whoops!
+ else if (opt_match(pArg, {"-separate_rg_to_color_alpha", "-seperate_rg_to_color_alpha"})) // was mispelled for a while - whoops!
{
m_comp_params.m_swizzle[0] = 0;
m_comp_params.m_swizzle[1] = 0;
m_comp_params.m_swizzle[2] = 0;
m_comp_params.m_swizzle[3] = 1;
}
- else if (strcasecmp(pArg, "-swizzle") == 0)
+ else if (opt_match(pArg, "-swizzle"))
{
REMAINING_ARGS_CHECK(1);
const char *swizzle = arg_v[arg_index + 1];
if (strlen(swizzle) != 4)
{
- error_printf("Swizzle requires exactly 4 characters\n");
+ error_printf("Swizzle requires exactly 4 characters (each one of [rgba] or [0123])\n");
return false;
}
for (int i=0; i<4; ++i)
{
- if (swizzle[i] == 'r')
+ if ((tolower(swizzle[i]) == 'r') || (swizzle[i] == '0'))
m_comp_params.m_swizzle[i] = 0;
- else if (swizzle[i] == 'g')
+ else if ((tolower(swizzle[i]) == 'g') || (swizzle[i] == '1'))
m_comp_params.m_swizzle[i] = 1;
- else if (swizzle[i] == 'b')
+ else if ((tolower(swizzle[i]) == 'b') || (swizzle[i] == '2'))
m_comp_params.m_swizzle[i] = 2;
- else if (swizzle[i] == 'a')
+ else if ((tolower(swizzle[i]) == 'a') || (swizzle[i] == '3'))
m_comp_params.m_swizzle[i] = 3;
else
{
- error_printf("Swizzle must be one of [rgba]");
+ error_printf("Swizzle must be one of [rgba] or [0123]");
return false;
}
}
arg_count++;
}
- else if (strcasecmp(pArg, "-renorm") == 0)
+ else if (opt_match(pArg, "-renorm"))
m_comp_params.m_renormalize = true;
- else if ((strcasecmp(pArg, "-no_multithreading") == 0) || (strcasecmp(pArg, "-no_threading") == 0))
+ else if (opt_match(pArg, {"-no_multithreading", "-no_threading", "-no_threads"}))
{
m_comp_params.m_multithreading = false;
}
- else if (strcasecmp(pArg, "-parallel") == 0)
+ else if (opt_match(pArg, "-parallel"))
{
m_parallel_compression = true;
}
- else if (strcasecmp(pArg, "-max_threads") == 0)
+ else if (opt_match(pArg, "-max_threads"))
{
REMAINING_ARGS_CHECK(1);
m_max_threads = maximum(1, atoi(arg_v[arg_index + 1]));
arg_count++;
}
- else if (strcasecmp(pArg, "-mipmap") == 0)
+ else if (opt_match(pArg, "-mipmap"))
m_comp_params.m_mip_gen = true;
- else if (strcasecmp(pArg, "-no_ktx") == 0)
+ else if (opt_match(pArg, {"-no_ktx", "-no_dds"}))
m_no_ktx = true;
- else if (strcasecmp(pArg, "-ktx_only") == 0)
+ else if (opt_match(pArg, {"-ktx_only", "-dds_only"}))
m_ktx_only = true;
- else if (strcasecmp(pArg, "-write_out") == 0)
+ else if (opt_match(pArg, "-write_out"))
m_write_out = true;
- else if (strcasecmp(pArg, "-tonemap_dither") == 0)
+ else if (opt_match(pArg, "-tonemap_dither"))
m_tonemap_dither_flag = true;
- else if (strcasecmp(pArg, "-format_only") == 0)
+ else if (opt_match(pArg, "-format_only"))
{
REMAINING_ARGS_CHECK(1);
m_unpack_format_only = atoi(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-opencl") == 0)
+ else if (opt_match(pArg, "-opencl"))
{
m_comp_params.m_use_opencl = true;
}
- else if (strcasecmp(pArg, "-opencl_serialize") == 0)
+ else if (opt_match(pArg, "-opencl_serialize"))
{
}
- else if (strcasecmp(pArg, "-mip_scale") == 0)
+ else if (opt_match(pArg, "-mip_scale"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_mip_scale = (float)atof(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-mip_filter") == 0)
+ else if (opt_match(pArg, "-mip_filter"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_mip_filter = arg_v[arg_index + 1];
// TODO: Check filter
arg_count++;
}
- else if (strcasecmp(pArg, "-mip_renorm") == 0)
+ else if (opt_match(pArg, "-mip_renorm"))
m_comp_params.m_mip_renormalize = true;
- else if (strcasecmp(pArg, "-mip_clamp") == 0)
+ else if (opt_match(pArg, "-mip_clamp"))
m_comp_params.m_mip_wrapping = false;
- else if (strcasecmp(pArg, "-mip_fast") == 0)
+ else if (opt_match(pArg, "-mip_fast"))
m_comp_params.m_mip_fast = true;
- else if (strcasecmp(pArg, "-mip_slow") == 0)
+ else if (opt_match(pArg, "-mip_slow"))
m_comp_params.m_mip_fast = false;
- else if (strcasecmp(pArg, "-mip_smallest") == 0)
+ else if (opt_match(pArg, "-mip_smallest"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_mip_smallest_dimension = atoi(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-mip_srgb") == 0)
+ else if (opt_match(pArg, "-mip_srgb"))
m_comp_params.m_mip_srgb = true;
- else if (strcasecmp(pArg, "-mip_linear") == 0)
+ else if (opt_match(pArg, "-mip_linear"))
m_comp_params.m_mip_srgb = false;
- else if (strcasecmp(pArg, "-userdata0") == 0)
+ else if (opt_match(pArg, "-userdata0"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_userdata0 = atoi(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-userdata1") == 0)
+ else if (opt_match(pArg, "-userdata1"))
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_userdata1 = atoi(arg_v[arg_index + 1]);
arg_count++;
}
- else if (strcasecmp(pArg, "-framerate") == 0)
+ else if (opt_match(pArg, "-framerate"))
{
REMAINING_ARGS_CHECK(1);
double fps = atof(arg_v[arg_index + 1]);
@@ -1260,12 +1936,12 @@ public:
m_comp_params.m_us_per_frame = clamp(static_cast(us_per_frame + .5f), 0, basist::cBASISMaxUSPerFrame);
arg_count++;
}
- else if (strcasecmp(pArg, "-cubemap") == 0)
+ else if (opt_match(pArg, "-cubemap"))
{
m_comp_params.m_tex_type = basist::cBASISTexTypeCubemapArray;
m_individual = false;
}
- else if (strcasecmp(pArg, "-tex_type") == 0)
+ else if (opt_match(pArg, "-tex_type"))
{
REMAINING_ARGS_CHECK(1);
const char* pType = arg_v[arg_index + 1];
@@ -1299,17 +1975,18 @@ public:
}
arg_count++;
}
- else if (strcasecmp(pArg, "-individual") == 0)
+ else if (opt_match(pArg, "-individual"))
m_individual = true;
- else if ((strcasecmp(pArg, "-tex_array") == 0) || (strcasecmp(pArg, "-texarray") == 0))
+ else if (opt_match(pArg, {"-tex_array", "-texarray"}))
m_individual = false;
- else if (strcasecmp(pArg, "-fuzz_testing") == 0)
+ else if (opt_match(pArg, "-fuzz_testing"))
m_fuzz_testing = true;
- else if (strcasecmp(pArg, "-csv_file") == 0)
+ else if (opt_match(pArg, "-csv_file"))
{
REMAINING_ARGS_CHECK(1);
m_csv_file = arg_v[arg_index + 1];
m_comp_params.m_compute_stats = true;
+ m_comp_params.m_psnr_hvs_m_stats = true;
arg_count++;
}
@@ -1328,31 +2005,47 @@ public:
arg_index += arg_count;
assert(arg_index <= arg_c);
}
+
+ const bool lossy_supercompression_changed = m_comp_params.m_xuastc_ldr_use_lossy_supercompression.was_changed();
+ const bool lossy_supercompression_value = m_comp_params.m_xuastc_ldr_use_lossy_supercompression;
- if (m_comp_params.m_quality_level != -1) // old-style -q X option
+ if (m_comp_params.m_quality_level != -1) // they set the old-style "-q X" option, which is the legacy ETC1S-specific quality level which ranges from [1,255]
{
m_comp_params.m_etc1s_max_endpoint_clusters = 0;
m_comp_params.m_etc1s_max_selector_clusters = 0;
-
- // -q also controls XUASTC LDR weight grid DCT quality level
- m_comp_params.m_xuastc_ldr_use_dct = true;
+
+ if (basis_tex_format_is_xuastc_ldr(m_comp_params.get_format_mode()))
+ {
+ printf("WARNING: Using \"-q [1,255]\" is not recommended for XUASTC LDR, instead use \"-quality [1,100]\"\n");
- // Automatically enable lossy XUASTC supercompression if DCT is enabled.
- if (!m_comp_params.m_xuastc_ldr_use_lossy_supercompression.was_changed())
- m_comp_params.m_xuastc_ldr_use_lossy_supercompression = true;
+ // -q also controls XUASTC LDR/XUBC7 weight grid DCT quality level
+ if (m_comp_params.m_quality_level < 100)
+ m_comp_params.m_xuastc_ldr_use_dct = true;
+
+ // Automatically enable lossy XUASTC supercompression if DCT is enabled.
+ if ((!lossy_supercompression_changed) && (m_comp_params.m_quality_level < 100))
+ m_comp_params.m_xuastc_ldr_use_lossy_supercompression = true;
+ }
+ else if (basis_tex_format_is_xubc7(m_comp_params.get_format_mode()))
+ {
+ printf("WARNING: Using \"-q [1,255]\" is not recommended for XUBC7, instead use \"-quality [1,100]\"\n");
+ }
}
else if ((!m_comp_params.m_etc1s_max_endpoint_clusters) || (!m_comp_params.m_etc1s_max_selector_clusters))
{
m_comp_params.m_etc1s_max_endpoint_clusters = 0;
m_comp_params.m_etc1s_max_selector_clusters = 0;
- m_comp_params.m_quality_level = 128;
+ // Don't slam to 128 unless it's ETC1S
+ if (m_comp_params.get_format_mode() == basist::basis_tex_format::cETC1S)
+ m_comp_params.m_quality_level = 128;
}
// Ensure mip_srgb is set to match the perceptual flag if the user didn't explicitly set it.
if (!m_comp_params.m_mip_srgb.was_changed())
{
// They didn't specify what colorspace to do mipmap filtering in, so choose sRGB if they've specified that the texture is sRGB.
+ // 4/26/2026: m_mip_srgb now defaults to true, unsure if this is needed now.
if (m_comp_params.m_perceptual)
m_comp_params.m_mip_srgb = true;
else
@@ -1369,10 +2062,7 @@ public:
{
fmt_printf("WARNING: Mixing old and new-style (-effort and/or -quality) codec configuration parameters.\nNew-style parameters may overwrite your old-style codec configuration settings.\nPrefer using -effort X and -quality X.\n");
}
-
- const bool lossy_supercompression_changed = m_comp_params.m_xuastc_ldr_use_lossy_supercompression.was_changed();
- const bool lossy_supercompression_value = m_comp_params.m_xuastc_ldr_use_lossy_supercompression;
-
+
// Set the new-style effort/quality level, but importantly don't override any settings already changed if they haven't explictly specified -effort or -quality.
m_comp_params.set_format_mode_and_quality_effort(m_comp_params.get_format_mode(), m_quality_level, m_effort_level, false);
@@ -1420,7 +2110,31 @@ public:
basis_compressor_params m_comp_params;
tool_mode m_mode;
-
+
+ // -benchmark_single parameters (cBenchmarkSingle).
+ basist::basis_tex_format m_benchmark_fmt;
+ uint32_t m_benchmark_effort;
+ uint32_t m_benchmark_quality;
+ // Optional overrides for the codec_benchmark() base path and file index range: -benchmark_path, -benchmark_range.
+ // The _set flags record whether the user explicitly overrode them (so HDR can fall back to its own default set).
+ std::string m_benchmark_path;
+ uint32_t m_benchmark_first;
+ uint32_t m_benchmark_last;
+ bool m_benchmark_path_set;
+ bool m_benchmark_range_set;
+ // -benchmark_linear: use linear (not sRGB/perceptual) metrics for the benchmark. Default is sRGB.
+ bool m_benchmark_linear;
+ // -benchmark_xubc7_encoder [0,7]: XUBC7-only BC7 base encoder selection. 0 = bc7f (default), 1-7 = bc7e_scalar level 0-6.
+ uint32_t m_benchmark_xubc7_encoder;
+ // -benchmark_xuastc_profile [0,2]: XUASTC LDR entropy syntax/profile. 0 = full arith (default), 1 = hybrid, 2 = full zstd.
+ uint32_t m_benchmark_xuastc_profile;
+ // -benchmark_sweep axis ranges (cBenchmarkSweep). Block index [0,5] (4x4..12x12 squares), effort [0,10], quality [0,10] (x10 -> [1,100]).
+ bool m_benchmark_sweep_block_set;
+ uint32_t m_benchmark_sweep_block_lo, m_benchmark_sweep_block_hi;
+ uint32_t m_benchmark_sweep_effort_lo, m_benchmark_sweep_effort_hi;
+ uint32_t m_benchmark_sweep_quality_lo, m_benchmark_sweep_quality_hi;
+ std::string m_benchmark_sweep_csv;
+
bool m_ktx2_mode;
bool m_ktx2_zstandard;
int m_ktx2_zstandard_level;
@@ -1434,6 +2148,20 @@ public:
std::string m_output_filename;
std::string m_output_path;
+ // Target texture format string for -export_dds (e.g. "BC1", "BC7", "RGBA32").
+ std::string m_export_dds_format;
+
+ // Target output format string for -dds (e.g. "bc7", "a8r8g8b8"); see basisu_dds_export.h.
+ std::string m_dds_format;
+
+ // -dds BC7 packer options (defaults mean "not specified" -> use the dds_export_params default). See basisu_dds_export.h.
+ dds_bc7_encoder m_dds_bc7_encoder = cDDSBC7Encoder_Default; // which BC7 packer
+ int m_dds_bc7f_level = -1; // 0 = analytical, 1 = partially analytical, 2 = non analytical (bc7f); -1 = unset
+ int m_dds_bc7e_scalar_level = -1; // 0..6 (bc7e_scalar); -1 = unset
+
+ // Target texture format string for -export_ktx (e.g. "BC7", "ETC2", "ASTC_6x6").
+ std::string m_export_ktx_format;
+
int m_unpack_format_only;
std::string m_multifile_printf;
@@ -1445,7 +2173,17 @@ public:
std::string m_etc1s_use_global_codebooks_file;
std::string m_test_file_dir;
-
+ bool m_test_dir_explicit = false; // true if -test_dir was explicitly given (so -test_codecs can default to test_files)
+
+ // When true, -test_xuastc runs the compressor across all block sizes and prints the
+ // expected-value tables (to copy/paste into the code) instead of validating.
+ bool m_xuastc_test_dump = false;
+
+ // -test_codecs: optional codec name filter (token-boundary, case-insensitive), e.g. "ETC1S", "XUASTC", "ASTC", "HDR".
+ std::string m_codec_filter;
+ // -test_codecs_gen: output .inl path (defaults to "basisu_tool_test_codecs.inl").
+ std::string m_test_codecs_gen_filename;
+
uint32_t m_max_threads;
bool m_individual;
@@ -1456,17 +2194,30 @@ public:
bool m_fuzz_testing;
bool m_compare_ssim;
bool m_compare_plot;
+
+ // -dump_image_stats / -dump_pixel / -dump_block (diagnostic image inspection; see image_stats_mode())
+ bool m_dump_image_stats;
+ int m_dump_pixel_x, m_dump_pixel_y; // -1 == not requested
+ int m_dump_block_x, m_dump_block_y; // -1 == not requested (4x4 texel block coords)
+ int m_stats_region_x, m_stats_region_y, m_stats_region_w, m_stats_region_h; // -1 == stats cover the whole image; otherwise restrict to this rectangle
+ int m_extract_channel; // -extract_channel: 0=R,1=G,2=B,3=A; -1 == not requested
+ int m_extract_swizzle[4] = { 0, 1, 2, 3 }; // -extract_swizzle: source channel index feeding each output R/G/B/A (identity by default)
+ int m_extract_swizzle_count = -1; // number of swizzle tokens given (1..4); -1 == not requested. Output channels >= count pass through unchanged
+ std::string m_extract_filename; // output PNG for -extract_channel / -extract_swizzle / -extract_region
+ int m_extract_region_x, m_extract_region_y, m_extract_region_w, m_extract_region_h; // region rectangle; w<=0 means "whole image" (optional for -extract_channel/-extract_swizzle, required for -extract_region)
+ int m_extract_color_xform = cExtractXformNone; // optional sRGB<->linear transform applied to the extracted output
+ bool m_extract_xform_alpha = false; // default: transform RGB only (A passes through); true == also transform the alpha channel
+ std::string m_text_image_in, m_text_image_out; // -text_to_png / -png_to_text: input and output filenames (see basisu_text_image.h)
bool m_parallel_compression;
bool m_tonemap_dither_flag;
bool m_xuastc_ldr_disable_bc7_transcoding;
bool m_no_etc1s_transcoding_chroma_filtering;
bool m_higher_quality_transcoding;
- bool m_force_deblocking;
- bool m_disable_deblocking;
- bool m_stronger_deblocking;
-
+ bool m_transcode_force_deblocking;
+ bool m_transcode_disable_deblocking;
+
int m_effort_level;
- int m_quality_level;
+ int m_quality_level; // new-style -quality X
bool m_used_old_style_codec_config_param; // true if the user has specified low-level or old-style codec configuration parameters
};
@@ -1564,12 +2315,11 @@ static uint32_t get_transcode_flags_from_options(const command_line_params& opts
{
uint32_t transcode_flags = opts.m_higher_quality_transcoding ? basist::cDecodeFlagsHighQuality : 0;
- if (opts.m_disable_deblocking)
+ if (opts.m_transcode_disable_deblocking)
transcode_flags |= basist::cDecodeFlagsNoDeblockFiltering;
- else if (opts.m_force_deblocking)
+ else if (opts.m_transcode_force_deblocking)
transcode_flags |= basist::cDecodeFlagsForceDeblockFiltering;
- if (opts.m_stronger_deblocking)
- transcode_flags |= basist::cDecodeFlagsStrongerDeblockFiltering;
+
if (opts.m_no_etc1s_transcoding_chroma_filtering)
transcode_flags |= basist::cDecodeFlagsNoETC1SChromaFiltering;
if (opts.m_xuastc_ldr_disable_bc7_transcoding)
@@ -1633,7 +2383,7 @@ static bool compress_mode(command_line_params &opts)
{
// Create KTXanimData key value entry
// TODO: Move this to basisu_comp.h
- basist::ktx2_transcoder::key_value kv;
+ basist::key_value kv;
const char* pAD = "KTXanimData";
kv.m_key.resize(strlen(pAD) + 1);
@@ -1647,7 +2397,7 @@ static bool compress_mode(command_line_params &opts)
kv.m_value.resize(sizeof(ad));
memcpy(kv.m_value.data(), &ad, sizeof(ad));
- params.m_ktx2_key_values.push_back(kv);
+ params.m_key_values.push_back(kv);
}
// TODO- expose this to command line.
@@ -2005,10 +2755,12 @@ static bool unpack_and_validate_ktx2_file(
printf("Texture Array Size (layers): %u\n", dec.get_layers());
printf("Total Faces: %u (%s)\n", dec.get_faces(), (dec.get_faces() == 6) ? "CUBEMAP" : "2D");
printf("Is Texture Video: %u\n", dec.is_video());
-
+
if (dec.is_hdr())
fmt_printf("LDR to HDR upconversion nit multiplier: {}\n", dec.get_ldr_hdr_upconversion_nit_multiplier());
-
+
+ printf("Deblocking filter ID: %u\n", dec.get_deblocking_filter_index());
+
const bool is_etc1s = (dec.get_basis_tex_format() == basist::basis_tex_format::cETC1S);
bool is_hdr = false;
@@ -2093,8 +2845,15 @@ static bool unpack_and_validate_ktx2_file(
pFmt_str = fmt_str_temp.c_str();
break;
}
+ case basist::basis_tex_format::cXUBC7:
+ {
+ pFmt_str = "XUBC7";
+ break;
+ }
default:
{
+ fmt_error_printf("Unknown/invalid format!\n");
+
assert(0);
return false;
}
@@ -2109,6 +2868,7 @@ static bool unpack_and_validate_ktx2_file(
case basist::KTX2_SS_DEFLATE: printf("DEFLATE\n"); break;
case basist::KTX2_SS_UASTC_HDR_6x6I: printf("UASTC_HDR_6x6I\n"); break;
case basist::KTX2_SS_XUASTC_LDR: printf("XUASTC_LDR\n"); break;
+ case basist::KTX2_SS_XUBC7: printf("XUBC7\n"); break;
default:
error_printf("Invalid/unknown/unsupported\n");
return false;
@@ -2160,6 +2920,7 @@ static bool unpack_and_validate_ktx2_file(
bool srgb_transfer_func_astc_writing = actual_ktx2_srgb_transfer_func;
const bool is_uastc_ldr_4x4 = (dec.get_basis_tex_format() == basist::basis_tex_format::cUASTC_LDR_4x4);
+
if ((is_etc1s) || (is_uastc_ldr_4x4))
{
// The ETC1S and UASTC LDR 4x4 transcoders supply ASTC LDR 4x4 data assuming the decoder will NOT be using the sRGB read decode profile, which is likely the most common case (in geospatial rendering scenarios).
@@ -2917,7 +3678,7 @@ static bool unpack_and_validate_ktx2_file(
if ((!validate_flag) && (!opts.m_ktx_only))
{
std::string rgba_filename(base_filename + fmt_string("_unpacked_rgba_{}_level_{}_face_{}_layer{04}.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, face_index, layer_index));
- if (!save_png(rgba_filename, img, cImageSaveIgnoreAlpha))
+ if (!save_png(rgba_filename, img))
{
error_printf("Failed writing to .PNG file \"%s\"\n", rgba_filename.c_str());
return false;
@@ -3102,6 +3863,26 @@ static bool unpack_and_validate_ktx2_file(
return true;
}
+static bool vec_is_ascii(const uint8_vec& v)
+{
+ bool is_ascii = true;
+
+ for (uint32_t j = 0; j < v.size(); j++)
+ {
+ uint8_t c = v[j];
+ if (!c)
+ break;
+
+ if ((c < 32) || (c > 127))
+ {
+ is_ascii = false;
+ break;
+ }
+ } // j
+
+ return is_ascii;
+}
+
static bool unpack_and_validate_basis_file(
uint32_t file_index,
const std::string &base_filename,
@@ -3234,8 +4015,15 @@ static bool unpack_and_validate_basis_file(
pFmt_str = fmt_str_temp.c_str();
break;
}
+ case basist::basis_tex_format::cXUBC7:
+ {
+ pFmt_str = "XUBC7";
+ break;
+ }
default:
{
+ fmt_error_printf("Invalid/unknown format!\n");
+
assert(0);
return false;
}
@@ -3254,6 +4042,56 @@ static bool unpack_and_validate_basis_file(
printf("%u ", fileinfo.m_image_mipmap_levels[i]);
printf("\n");
+ basist::key_value_vec key_values;
+ if (!dec.get_key_values(&basis_file_data[0], (uint32_t)basis_file_data.size(), key_values))
+ {
+ error_printf("get_key_values() failed!\n");
+ return false;
+ }
+
+ fmt_printf("\nTotal key value fields: {}\n", key_values.size_u32());
+
+ if (key_values.size())\
+ {
+ for (uint32_t k = 0; k < key_values.size(); k++)
+ {
+ // Always zero terminated, always at least 1 character
+ if (vec_is_ascii(key_values[k].m_key))
+ fmt_printf(" {}. \"{}\" - {} bytes: ", k, (const char*)key_values[k].m_key.get_ptr(), key_values[k].m_value.size_u32());
+ else
+ fmt_printf(" {}. [Key not ASCII] - {} bytes: ", k, (const char*)key_values[k].m_key.get_ptr(), key_values[k].m_value.size_u32()); // shouldn't happen, but just in case - we could print as hex
+
+ const uint8_vec& val = key_values[k].m_value;
+
+ // val will always be empty, or it's guaranteed to be zero terminated (we always add an additional 0 at the end)
+ if (val.size() >= 100)
+ {
+ fmt_printf("[too large to print]\n");
+ continue;
+ }
+
+ if (vec_is_ascii(val))
+ {
+ fmt_printf("\"{}\"\n", (const char*)val.get_ptr());
+ }
+ else
+ {
+ for (uint32_t j = 0; j < val.size(); j++)
+ {
+ fmt_printf("0x{X} ", val[j]);
+
+ if ((j & 15) == 15)
+ fmt_printf("\n");
+ } // j
+
+ fmt_printf("\n");
+ }
+
+ } // k
+
+ //fmt_printf("\n");
+ }
+
// the sRGB transfer function to use while astc unpacking (we want this to ideally match what we used during astc encoding)
bool srgb_transfer_func_astc_unpacking = fileinfo.m_srgb;
@@ -3979,6 +4817,14 @@ static bool unpack_and_validate_basis_file(
if ((!validate_flag) && (!opts.m_ktx_only))
{
+ std::string rgba_filename(base_filename + string_format("_unpacked_rgba_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index));
+ if (!save_png(rgba_filename, img))
+ {
+ error_printf("Failed writing to PNG file \"%s\"\n", rgba_filename.c_str());
+ return false;
+ }
+ printf("Wrote .PNG file \"%s\"\n", rgba_filename.c_str());
+
std::string rgb_filename(base_filename + string_format("_unpacked_rgb_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index));
if (!save_png(rgb_filename, img, cImageSaveIgnoreAlpha))
{
@@ -4112,6 +4958,14 @@ static bool unpack_and_validate_basis_file(
if ((!validate_flag) && (!opts.m_ktx_only))
{
+ std::string rgba_filename(base_filename + string_format("_unpacked_rgba_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index));
+ if (!save_png(rgba_filename, img))
+ {
+ error_printf("Failed writing to PNG file \"%s\"\n", rgba_filename.c_str());
+ return false;
+ }
+ printf("Wrote .PNG file \"%s\"\n", rgba_filename.c_str());
+
std::string rgb_filename(base_filename + string_format("_unpacked_rgb_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index));
if (!save_png(rgb_filename, img, cImageSaveIgnoreAlpha))
{
@@ -4166,6 +5020,437 @@ static bool unpack_and_validate_basis_file(
return true;
}
+// Unpacks a Microsoft DDS file (DX9/DX10) using basist::dds_transcoder, bringing it up to the .ktx2/.basis bar.
+// The DDS transcoder is LDR-only, so it transcodes the source to each DDS-supported target format and:
+// (1) for the 12 block-compressed targets (BC1/3/4/5/7, ETC1/2, EAC R11/RG11, ASTC 4x4, PVRTC1) -- writes the
+// GPU container files (per-image whole-mip-chain, plus cubemap and DDS texture-array shapes; .KTX for all,
+// .DDS for BC1-7 only), AND round-trips each transcoded block image back to RGBA as one _unpacked_rgba_ PNG;
+// (2) for the 3 uncompressed targets (RGBA32, RGB565, RGBA4444) -- writes one _unpacked_rgba_ PNG each (the
+// RGBA32 decode is the source ground truth).
+// -validate runs the full pipeline (transcode AND round-trip unpack, so a broken unpack path is caught too) but
+// writes nothing. Honors -output_path, -no_ktx (skip GPU container files), -ktx_only (skip PNGs), and
+// -unpack_format_only N (restrict to the single transcoder format whose enum index == N). BC6H/HDR DDS is future work (post v2.5).
+static bool unpack_and_validate_dds_file(
+ const std::string& base_filename,
+ uint8_vec& dds_file_data,
+ command_line_params& opts,
+ uint32_t& total_unpack_warnings,
+ uint32_t& total_pvrtc_nonpow2_warnings)
+{
+ if (dds_file_data.size() > UINT32_MAX)
+ {
+ error_printf("DDS file too large!\n");
+ return false;
+ }
+
+ basist::dds_transcoder dec;
+
+ if (!dec.init(dds_file_data.data(), dds_file_data.size_u32()))
+ {
+ error_printf("dds_transcoder::init() failed! File either uses an unsupported feature, is invalid, was corrupted, or this is a bug.\n");
+ return false;
+ }
+
+ if (!dec.start_transcoding())
+ {
+ error_printf("dds_transcoder::start_transcoding() failed!\n");
+ return false;
+ }
+
+ const uint32_t levels = dec.get_levels();
+ const uint32_t faces = dec.get_faces();
+ const uint32_t num_layers = dec.get_layers(); // 0 == not an array (ktx2 convention)
+ const uint32_t eff_layers = num_layers ? num_layers : 1;
+ const basist::transcoder_texture_format src_fmt = dec.get_format();
+ const char* pFmt_name = basist::basis_get_format_name(src_fmt);
+
+ printf("Resolution: %ux%u\n", dec.get_width(), dec.get_height());
+ printf("Mipmap Levels: %u\n", levels);
+ printf("Texture Array Size (layers): %u\n", num_layers);
+ printf("Total Faces: %u (%s)\n", faces, (faces == 6) ? "CUBEMAP" : "2D");
+ printf("DDS source format: %s\n", basisu::get_dds_format_string(dec.get_dds_format()));
+ printf("Contained format: %s\n", pFmt_name);
+ // For uncompressed sources the "Contained format" is the CLOSEST-matching transcoder format, not necessarily
+ // the .DDS's physical pixel layout (e.g. an A8R8G8B8/X8R8G8B8 or B5G5R5A1 file reports RGBA32 / RGBA4444).
+ // The exact physical layout is shown above on the "DDS source format" line.
+ if (dec.get_source_kind() == basist::dds_transcoder::source_kind::cUncompressed)
+ printf(" (note: 'Contained format' is the closest matching transcoder format; use -tinydds_info for the full DDS header)\n");
+ printf("Has Alpha: %u, sRGB: %u\n", dec.get_has_alpha(), dec.is_srgb() ? 1 : 0);
+
+ // -info reports metadata only. -unpack writes files; -validate just decodes every image to exercise the path.
+ if (opts.m_mode == cInfo)
+ return true;
+ const bool write_files = (opts.m_mode == cUnpack);
+ const bool validating = (opts.m_mode == cValidate);
+ const bool write_gpu_files = write_files && !opts.m_no_ktx; // _transcoded_ .ktx/.dds container files
+ const bool write_png = write_files && !opts.m_ktx_only; // decoded _unpacked_rgba_ PNGs
+ const bool srgb = dec.is_srgb();
+ const bool status_output = opts.m_comp_params.m_status_output; // -no_status_output / -quiet silences progress
+
+ // -validate exercises every codec like -unpack but writes nothing. We go further than the .ktx2/.basis
+ // validate (which only verifies the transcode-to-GPU step): we also round-trip the block data back to pixels
+ // below, so a broken unpack path is caught too.
+ bool any_format_matched = false; // for the -unpack_format_only "nothing produced" warning
+
+ const bool is_cubemap = (faces == 6);
+ const bool is_array = (num_layers != 0); // ktx2 convention: 0 == not an array
+ const uint32_t MAX_DDS_TEXARRAY_SIZE = 2048; // Direct3D maximum array size
+
+ // NOTE: the DDS transcoder is currently LDR-only (sources: BC1-5 / BC7 / uncompressed). BC6H/HDR DDS support
+ // is planned for a future release (post v2.5); when it lands, the HDR target formats (BC6H, ASTC HDR,
+ // RGBA_HALF, RGB_9E5) and their EXR outputs would be added here, mirroring unpack_and_validate_ktx2_file().
+
+ // Block-compressed transcoder targets the DDS transcoder's get_target_info() accepts. Each goes to .ktx;
+ // BC1-7 additionally goes to .dds (does_dds_support_format). Listing them explicitly (rather than iterating
+ // the whole enum like the .ktx2/.basis unpackers) keeps us to exactly what the DDS transcoder can produce.
+ static const basist::transcoder_texture_format s_compressed_formats[] =
+ {
+ basist::transcoder_texture_format::cTFBC1_RGB, basist::transcoder_texture_format::cTFBC3_RGBA,
+ basist::transcoder_texture_format::cTFBC4_R, basist::transcoder_texture_format::cTFBC5_RG,
+ basist::transcoder_texture_format::cTFBC7_RGBA, basist::transcoder_texture_format::cTFETC1_RGB,
+ basist::transcoder_texture_format::cTFETC2_RGBA, basist::transcoder_texture_format::cTFETC2_EAC_R11,
+ basist::transcoder_texture_format::cTFETC2_EAC_RG11, basist::transcoder_texture_format::cTFASTC_4x4_RGBA,
+ basist::transcoder_texture_format::cTFPVRTC1_4_RGB, basist::transcoder_texture_format::cTFPVRTC1_4_RGBA
+ };
+
+ // Uncompressed LDR transcoder targets the DDS transcoder accepts. These have no GPU-container writer; we
+ // transcode them to pixels and write a single _unpacked_rgba_ PNG each (RGBA32 also serves as ground truth).
+ static const basist::transcoder_texture_format s_uncompressed_formats[] =
+ {
+ basist::transcoder_texture_format::cTFRGBA32,
+ basist::transcoder_texture_format::cTFRGB565,
+ basist::transcoder_texture_format::cTFRGBA4444
+ };
+
+ // -unpack_format_only N restricts processing to the single transcoder format whose enum index == N
+ // (consistent with the .ktx2/.basis unpackers). Formats not in our supported tables simply produce nothing.
+ const int only_fmt = opts.m_unpack_format_only;
+
+ // PVRTC1 needs power-of-2 dims; warn up front so the user understands the per-image skips below.
+ if (!is_pow2(dec.get_width()) || !is_pow2(dec.get_height()))
+ printf("Warning: dimensions %ux%u are not a power of 2 -- PVRTC1 output will be skipped (it requires power-of-2 dimensions).\n", dec.get_width(), dec.get_height());
+
+ // ============================ Block-compressed targets ============================
+ // For each format: transcode every (layer, face) into a full mip chain, round-trip each level back to RGBA
+ // (one _unpacked_rgba_ PNG), and write the GPU container files in per-image / cubemap / texture-array shapes.
+ for (basist::transcoder_texture_format tex_fmt : s_compressed_formats)
+ {
+ if ((only_fmt > -1) && ((int)tex_fmt != only_fmt))
+ continue;
+ any_format_matched = true;
+
+ const basisu::texture_format gpu_fmt = basis_get_basisu_texture_format(tex_fmt);
+ const char* pTarget_fmt_name = basist::basis_get_format_name(tex_fmt);
+ const bool dds_ok = does_dds_support_format(gpu_fmt);
+ const bool ktx_ok = does_ktx_support_format(gpu_fmt);
+ if (!dds_ok && !ktx_ok)
+ continue;
+
+ // Transcoded mip chains, indexed [layer * faces + face]. Built for both -unpack and -validate so
+ // -validate exercises every codec; only -unpack actually writes anything below.
+ basisu::vector chains;
+ chains.resize(eff_layers * faces);
+
+ bool format_complete = true; // false if any image dropped a level (PVRTC1 on non-power-of-2 dims)
+
+ for (uint32_t layer = 0; layer < eff_layers; layer++)
+ {
+ for (uint32_t face = 0; face < faces; face++)
+ {
+ gpu_image_vec& chain = chains[layer * faces + face];
+
+ for (uint32_t level = 0; level < levels; level++)
+ {
+ basist::ktx2_image_level_info level_info;
+ if (!dec.get_image_level_info(level_info, level, layer, face))
+ {
+ error_printf("dds_transcoder::get_image_level_info() failed (layer %u, face %u, level %u)\n", layer, face, level);
+ return false;
+ }
+
+ gpu_image gi(gpu_fmt, level_info.m_orig_width, level_info.m_orig_height);
+ // Pre-fill with pseudo-random bytes so a transcoder that fails to write part of the output
+ // is detectable (matches the .ktx2 unpacker's under-write detection).
+ fill_buffer_with_random_bytes(gi.get_ptr(), gi.get_size_in_bytes());
+
+ if (status_output)
+ printf("Transcoding to %s: layer %u, face %u, level %u, res %ux%u\n", pTarget_fmt_name, layer, face, level, level_info.m_orig_width, level_info.m_orig_height);
+
+ interval_timer tm;
+ tm.start();
+ const bool transcode_ok = dec.transcode_image_level(level, layer, face, gi.get_ptr(), gi.get_total_blocks(), tex_fmt);
+ const double transcode_ms = tm.get_elapsed_ms();
+
+ if (!transcode_ok)
+ {
+ // PVRTC1 on non-power-of-2 dims is an expected skip (warned up front), NOT a failure. Any
+ // other transcode failure is a real error -> fail (so -validate catches a broken codec path).
+ if ((tex_fmt == basist::transcoder_texture_format::cTFPVRTC1_4_RGB) || (tex_fmt == basist::transcoder_texture_format::cTFPVRTC1_4_RGBA))
+ {
+ total_pvrtc_nonpow2_warnings++;
+ format_complete = false;
+ break; // can't build a partial mip chain -- drop this whole image
+ }
+ error_printf("Failed transcoding image to %s (layer %u, face %u, level %u)\n", pTarget_fmt_name, layer, face, level);
+ return false;
+ }
+
+ if (status_output)
+ printf("Transcode to %s succeeded in %3.3f ms\n", pTarget_fmt_name, transcode_ms);
+
+ chain.push_back(gi);
+ }
+ }
+ }
+
+ // (a) Round-trip each transcoded block image back to RGBA. We always do the unpack when -validate (to
+ // exercise the unpack path), and write one _unpacked_rgba_ PNG only when actually unpacking to files.
+ if (write_png || validating)
+ {
+ for (uint32_t layer = 0; layer < eff_layers; layer++)
+ {
+ for (uint32_t face = 0; face < faces; face++)
+ {
+ const gpu_image_vec& chain = chains[layer * faces + face];
+ for (uint32_t level = 0; level < chain.size(); level++)
+ {
+ image img;
+ if (!chain[level].unpack(img, srgb))
+ {
+ // Under -unpack, mirror the .ktx2 unpacker: a failed round-trip unpack is a warning, and
+ // we unpack as much as possible. Under -validate it's a hard failure (catching it is the point).
+ error_printf("Failed unpacking %s block data to pixels (layer %u, face %u, level %u)\n", pTarget_fmt_name, layer, face, level);
+ if (validating)
+ return false;
+ total_unpack_warnings++;
+ continue;
+ }
+
+ if (!write_png)
+ continue; // -validate: exercised the unpack above, but don't write a file
+
+ std::string fn = (levels > 1)
+ ? base_filename + string_format("_unpacked_rgba_%s_level_%u_face_%u_layer_%04u.png", pTarget_fmt_name, level, face, layer)
+ : base_filename + string_format("_unpacked_rgba_%s_face_%u_layer_%04u.png", pTarget_fmt_name, face, layer);
+ if (!save_png(fn, img)) { error_printf("Failed writing to PNG file \"%s\"\n", fn.c_str()); return false; }
+ printf("Wrote .PNG file \"%s\"\n", fn.c_str());
+ }
+ }
+ }
+ }
+
+ // (b) GPU container files: cubemap (per layer), texture array (one DDS), and per-image whole-mip-chain.
+ // Skipped entirely if any image was incomplete (format_complete == false), since the chains don't line up.
+ if (write_gpu_files && format_complete)
+ {
+ // Cubemap: one file per layer holding all 6 faces (KTX for any format, DDS for BC1-7).
+ if (is_cubemap)
+ {
+ for (uint32_t layer = 0; layer < eff_layers; layer++)
+ {
+ basisu::vector cubemap;
+ for (uint32_t face = 0; face < 6; face++)
+ cubemap.push_back(chains[layer * faces + face]);
+
+ if (ktx_ok)
+ {
+ std::string fn = base_filename + string_format("_transcoded_cubemap_%s_layer_%u.ktx", pTarget_fmt_name, layer);
+ if (!write_compressed_texture_file(fn.c_str(), cubemap, true, srgb)) { error_printf("Failed writing KTX file \"%s\"\n", fn.c_str()); return false; }
+ printf("Wrote .KTX cubemap file \"%s\"\n", fn.c_str());
+ }
+ if (dds_ok)
+ {
+ std::string fn = base_filename + string_format("_transcoded_cubemap_%s_layer_%u.dds", pTarget_fmt_name, layer);
+ if (!write_compressed_texture_file(fn.c_str(), cubemap, true, srgb)) { error_printf("Failed writing DDS file \"%s\"\n", fn.c_str()); return false; }
+ printf("Wrote .DDS cubemap file \"%s\"\n", fn.c_str());
+ }
+ }
+ }
+
+ // Texture array: a single DDS holding every layer x face (DDS only, matching the .ktx2 unpacker).
+ if (is_array && dds_ok && (eff_layers <= MAX_DDS_TEXARRAY_SIZE))
+ {
+ basisu::vector tex_array;
+ for (uint32_t layer = 0; layer < eff_layers; layer++)
+ for (uint32_t face = 0; face < faces; face++)
+ tex_array.push_back(chains[layer * faces + face]);
+
+ std::string fn = base_filename + string_format("_transcoded_array_%s.dds", pTarget_fmt_name);
+ if (!write_compressed_texture_file(fn.c_str(), tex_array, is_cubemap, srgb)) { error_printf("Failed writing DDS file \"%s\"\n", fn.c_str()); return false; }
+ printf("Wrote .DDS texture array file \"%s\"\n", fn.c_str());
+ }
+
+ // Per-image whole-mip-chain files (one per layer/face).
+ for (uint32_t layer = 0; layer < eff_layers; layer++)
+ {
+ for (uint32_t face = 0; face < faces; face++)
+ {
+ const gpu_image_vec& chain = chains[layer * faces + face];
+ if (!chain.size())
+ continue;
+
+ if (ktx_ok)
+ {
+ std::string fn = is_cubemap
+ ? base_filename + string_format("_transcoded_%s_face_%u_layer_%04u.ktx", pTarget_fmt_name, face, layer)
+ : base_filename + string_format("_transcoded_%s_layer_%04u.ktx", pTarget_fmt_name, layer);
+ if (!write_compressed_texture_file(fn.c_str(), chain, srgb)) { error_printf("Failed writing KTX file \"%s\"\n", fn.c_str()); return false; }
+ printf("Wrote .KTX file \"%s\"\n", fn.c_str());
+ }
+ if (dds_ok)
+ {
+ std::string fn = is_cubemap
+ ? base_filename + string_format("_transcoded_%s_face_%u_layer_%04u.dds", pTarget_fmt_name, face, layer)
+ : base_filename + string_format("_transcoded_%s_layer_%04u.dds", pTarget_fmt_name, layer);
+ if (!write_compressed_texture_file(fn.c_str(), chain, srgb)) { error_printf("Failed writing DDS file \"%s\"\n", fn.c_str()); return false; }
+ printf("Wrote .DDS file \"%s\"\n", fn.c_str());
+ }
+ }
+ }
+ }
+ }
+
+ // ============================ Uncompressed LDR targets ============================
+ // Transcode the source to each uncompressed pixel format and write one _unpacked_rgba_ PNG per image. This
+ // exercises those transcode paths (for -validate) and the RGBA32 decode also serves as the corruption canary.
+ for (basist::transcoder_texture_format tex_fmt : s_uncompressed_formats)
+ {
+ if ((only_fmt > -1) && ((int)tex_fmt != only_fmt))
+ continue;
+ any_format_matched = true;
+
+ const char* pTarget_fmt_name = basist::basis_get_format_name(tex_fmt);
+
+ for (uint32_t layer = 0; layer < eff_layers; layer++)
+ {
+ for (uint32_t face = 0; face < faces; face++)
+ {
+ for (uint32_t level = 0; level < levels; level++)
+ {
+ basist::ktx2_image_level_info level_info;
+ if (!dec.get_image_level_info(level_info, level, layer, face))
+ {
+ error_printf("dds_transcoder::get_image_level_info() failed (layer %u, face %u, level %u)\n", layer, face, level);
+ return false;
+ }
+
+ const uint32_t mw = level_info.m_orig_width, mh = level_info.m_orig_height;
+ image img(mw, mh);
+
+ if (status_output)
+ printf("Transcoding to %s: layer %u, face %u, level %u, res %ux%u\n", pTarget_fmt_name, layer, face, level, mw, mh);
+
+ interval_timer tm;
+ double transcode_ms = 0.0;
+
+ if (tex_fmt == basist::transcoder_texture_format::cTFRGBA32)
+ {
+ // Direct RGBA decode of the source -- the ground-truth pixels (and a corruption canary when
+ // -unpack_format_only selects RGBA32; otherwise the compressed loop above fails first).
+ // Pre-fill with random bytes to detect a partial (under-)write, like the .ktx2 unpacker.
+ fill_buffer_with_random_bytes(img.get_ptr(), (size_t)img.get_total_pixels() * sizeof(color_rgba));
+ tm.start();
+ const bool transcode_ok = dec.transcode_image_level(level, layer, face, img.get_ptr(), img.get_total_pixels(), tex_fmt, 0, mw, mh);
+ transcode_ms = tm.get_elapsed_ms();
+ if (!transcode_ok)
+ {
+ error_printf("Failed decoding image to RGBA32 (layer %u, face %u, level %u) -- invalid or corrupt DDS data\n", layer, face, level);
+ return false;
+ }
+ }
+ else
+ {
+ // 16-bpp packed formats: transcode into a uint16 buffer, then expand to 8-bit RGBA for the PNG.
+ basisu::vector packed(mw * mh);
+ // Pre-fill with random bytes to detect a partial (under-)write, like the .ktx2 unpacker.
+ fill_buffer_with_random_bytes(packed.data(), packed.size() * sizeof(uint16_t));
+ tm.start();
+ const bool transcode_ok = dec.transcode_image_level(level, layer, face, packed.data(), (uint32_t)packed.size(), tex_fmt, 0, mw, mh);
+ transcode_ms = tm.get_elapsed_ms();
+ if (!transcode_ok)
+ {
+ error_printf("Failed transcoding image to %s (layer %u, face %u, level %u)\n", pTarget_fmt_name, layer, face, level);
+ return false;
+ }
+
+ for (uint32_t y = 0; y < mh; y++)
+ {
+ for (uint32_t x = 0; x < mw; x++)
+ {
+ const uint16_t p = packed[x + y * mw];
+ if (tex_fmt == basist::transcoder_texture_format::cTFRGB565)
+ {
+ uint32_t r = p >> 11, g = (p >> 5) & 63, b = p & 31;
+ r = (r << 3) | (r >> 2);
+ g = (g << 2) | (g >> 4);
+ b = (b << 3) | (b >> 2);
+ img(x, y).set(r, g, b, 255);
+ }
+ else // cTFRGBA4444
+ {
+ uint32_t r = p >> 12, g = (p >> 8) & 15, b = (p >> 4) & 15, a = p & 15;
+ img(x, y).set((r << 4) | r, (g << 4) | g, (b << 4) | b, (a << 4) | a);
+ }
+ }
+ }
+ }
+
+ if (status_output)
+ printf("Transcode to %s succeeded in %3.3f ms\n", pTarget_fmt_name, transcode_ms);
+
+ if (write_png)
+ {
+ std::string fn = (levels > 1)
+ ? base_filename + string_format("_unpacked_rgba_%s_level_%u_face_%u_layer_%04u.png", pTarget_fmt_name, level, face, layer)
+ : base_filename + string_format("_unpacked_rgba_%s_face_%u_layer_%04u.png", pTarget_fmt_name, face, layer);
+ if (!save_png(fn, img)) { error_printf("Failed writing to PNG file \"%s\"\n", fn.c_str()); return false; }
+ printf("Wrote .PNG file \"%s\"\n", fn.c_str());
+ }
+ }
+ }
+ }
+ }
+
+ // -unpack_format_only asked for a format that isn't a DDS-transcodable target -- say so rather than
+ // silently succeeding with no output.
+ if ((only_fmt > -1) && !any_format_matched)
+ printf("Warning: -unpack_format_only %d is not a DDS-transcodable format; no files were produced.\n", only_fmt);
+
+ return true;
+}
+
+// Prints basic header info about a PNG file (used by -info). Uses pv_png::get_png_info (no full decode).
+static bool print_png_info(const char* pInput_filename, const uint8_vec& file_data)
+{
+ pv_png::png_info info;
+ if (!pv_png::get_png_info(file_data.data(), file_data.size(), info))
+ {
+ error_printf("Failed parsing PNG file \"%s\"\n", pInput_filename);
+ return false;
+ }
+
+ const char* pColor_type = "?";
+ switch (info.m_color_type)
+ {
+ case pv_png::PNG_COLOR_TYPE_GREYSCALE: pColor_type = "Grayscale"; break;
+ case pv_png::PNG_COLOR_TYPE_TRUECOLOR: pColor_type = "Truecolor (RGB)"; break;
+ case pv_png::PNG_COLOR_TYPE_PALETTIZED: pColor_type = "Palettized"; break;
+ case pv_png::PNG_COLOR_TYPE_GREYSCALE_ALPHA: pColor_type = "Grayscale+Alpha"; break;
+ case pv_png::PNG_COLOR_TYPE_TRUECOLOR_ALPHA: pColor_type = "Truecolor+Alpha (RGBA)"; break;
+ default: break;
+ }
+
+ printf("Dimensions: %ux%u\n", info.m_width, info.m_height);
+ printf("Channels (incl. transparency): %u\n", info.m_num_chans);
+ printf("Bit depth: %u\n", info.m_bit_depth);
+ printf("Color type: %u (%s)\n", info.m_color_type, pColor_type);
+ printf("Colorkey transparency (tRNS): %u\n", info.m_has_trns);
+ if (info.m_has_gamma)
+ printf("Gamma (gAMA): %f\n", info.m_gamma_value / 100000.0f);
+
+ return true;
+}
+
static bool unpack_and_validate_mode(command_line_params &opts)
{
interval_timer tm;
@@ -4215,6 +5500,12 @@ static bool unpack_and_validate_mode(command_line_params &opts)
std::string base_filename;
string_split_path(pInput_filename, nullptr, nullptr, &base_filename, nullptr);
+ // Honor -output_path when unpacking: both the .basis and .KTX2 unpackers
+ // build all their output filenames by prefixing base_filename, so prefixing
+ // the output directory here redirects every unpacked file.
+ if (opts.m_output_path.size())
+ string_combine_path(base_filename, opts.m_output_path.c_str(), base_filename.c_str());
+
uint8_vec file_data;
if (!basisu::read_file_to_vec(pInput_filename, file_data))
{
@@ -4242,11 +5533,31 @@ static bool unpack_and_validate_mode(command_line_params &opts)
bool is_ktx2 = false;
if (file_data.size() >= sizeof(basist::g_ktx2_file_identifier))
- {
is_ktx2 = (memcmp(file_data.data(), basist::g_ktx2_file_identifier, sizeof(basist::g_ktx2_file_identifier)) == 0);
- }
- printf("\nInput file \"%s\", KTX2: %u\n", pInput_filename, is_ktx2);
+ // Microsoft DDS magic ("DDS ", 0x20534444 little-endian).
+ bool is_dds = false;
+ if (file_data.size() >= 4)
+ is_dds = (memcmp(file_data.data(), "DDS ", 4) == 0);
+
+ // KTX1 (.ktx) 12-byte identifier. KTX1 holds already-compressed GPU data we can't transcode here,
+ // so we just print its header info (via print_ktx_info), regardless of mode.
+ static const uint8_t s_ktx1_identifier[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A };
+ const bool is_ktx1 = (file_data.size() >= 12) && (memcmp(file_data.data(), s_ktx1_identifier, 12) == 0);
+
+ // .basis container 2-byte signature ("sB", i.e. cBASISSigValue stored little-endian).
+ bool is_basis = false;
+ if (file_data.size() >= 2)
+ is_basis = ((file_data[0] | (file_data[1] << 8)) == basist::basis_file_header::cBASISSigValue);
+
+ // PNG 8-byte signature.
+ static const uint8_t s_png_identifier[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
+ const bool is_png = (file_data.size() >= 8) && (memcmp(file_data.data(), s_png_identifier, 8) == 0);
+
+ // Detect by magic, not extension, so each file in a mixed input list routes correctly. The fallback
+ // (else) path below still attempts a .basis parse for an unrecognized file, which fails with a clear error.
+ const char* pFormat = is_ktx2 ? "KTX2" : (is_ktx1 ? "KTX1" : (is_dds ? "DDS" : (is_basis ? ".basis" : (is_png ? "PNG" : "unknown"))));
+ printf("\nInput file \"%s\", Format: %s\n", pInput_filename, pFormat);
bool status;
if (is_ktx2)
@@ -4261,6 +5572,43 @@ static bool unpack_and_validate_mode(command_line_params &opts)
total_unpack_warnings,
total_pvrtc_nonpow2_warnings);
}
+ else if (is_dds)
+ {
+ status = unpack_and_validate_dds_file(
+ base_filename,
+ file_data,
+ opts,
+ total_unpack_warnings,
+ total_pvrtc_nonpow2_warnings);
+ }
+ else if (is_ktx1)
+ {
+ // KTX1 holds already-compressed GPU data this tool can't transcode. -info dumps the header
+ // (same as -ktx_info); -unpack/-validate are unsupported and fail with a clear error rather
+ // than a misleading "Success".
+ if (opts.m_mode == cInfo)
+ {
+ status = print_ktx_info(pInput_filename);
+ }
+ else
+ {
+ error_printf("KTX1 (.ktx) files can't be unpacked or validated yet -- use -info (or -ktx_info) for header info\n");
+ status = false;
+ }
+ }
+ else if (is_png)
+ {
+ // PNG is a source image, not a GPU texture container: -info dumps its header; unpack/validate don't apply.
+ if (opts.m_mode == cInfo)
+ {
+ status = print_png_info(pInput_filename, file_data);
+ }
+ else
+ {
+ error_printf("PNG files can't be unpacked or validated (they're source images, not GPU textures) -- use -info for header info\n");
+ status = false;
+ }
+ }
else
{
status = unpack_and_validate_basis_file(
@@ -4364,6 +5712,206 @@ static bool hdr_compare_mode(command_line_params& opts)
return true;
}
+// Reference-free image diagnostics: per-channel RGBA statistics, plus optional single-pixel / 4x4-block dumps.
+// Intended to make image problems (e.g. a dropped/constant channel, an all-opaque or all-zero alpha, an
+// unexpected channel swizzle) obvious WITHOUT needing a second image to diff against. LDR/8-bit inputs only
+// for now (PNG/TGA/QOI/etc); HDR (.exr/.hdr) support can be added later.
+static bool image_stats_mode(command_line_params& opts)
+{
+ if (!opts.m_input_filenames.size())
+ {
+ error_printf("Must specify at least one image filename using -file\n");
+ return false;
+ }
+
+ const bool do_stats = opts.m_dump_image_stats;
+ const bool do_dump_pixel = (opts.m_dump_pixel_x >= 0) && (opts.m_dump_pixel_y >= 0);
+ const bool do_dump_block = (opts.m_dump_block_x >= 0) && (opts.m_dump_block_y >= 0);
+
+ bool any_failed = false;
+
+ for (uint32_t file_index = 0; file_index < opts.m_input_filenames.size(); file_index++)
+ {
+ const char* pFilename = opts.m_input_filenames[file_index].c_str();
+
+ std::string ext(string_get_extension(opts.m_input_filenames[file_index]));
+ if ((strcasecmp(ext.c_str(), "exr") == 0) || (strcasecmp(ext.c_str(), "hdr") == 0))
+ {
+ error_printf("Skipping HDR image \"%s\" (-image_stats/-dump_* are LDR/8-bit only for now)\n", pFilename);
+ any_failed = true;
+ continue;
+ }
+
+ image img;
+ if (!load_image(pFilename, img))
+ {
+ error_printf("Failed loading image from file \"%s\"!\n", pFilename);
+ any_failed = true;
+ continue;
+ }
+
+ const uint32_t width = img.get_width(), height = img.get_height();
+ printf("\nImage \"%s\": %ux%u (%llu pixels), loader reports has_alpha: %u\n",
+ pFilename, width, height, (unsigned long long)((uint64_t)width * height), img.has_alpha());
+
+ // ---- Per-channel statistics (single pass over the pixels) ----
+ if (do_stats)
+ {
+ // Resolve the region to gather stats over: the whole image by default, or the user's rectangle
+ // (-dump_image_stats ). The rectangle must lie fully inside the image.
+ uint32_t region_x0 = 0, region_y0 = 0, region_w = width, region_h = height;
+ bool region_valid = true;
+ if (opts.m_stats_region_w > 0)
+ {
+ region_x0 = (uint32_t)opts.m_stats_region_x;
+ region_y0 = (uint32_t)opts.m_stats_region_y;
+ region_w = (uint32_t)opts.m_stats_region_w;
+ region_h = (uint32_t)opts.m_stats_region_h;
+
+ if (((uint64_t)region_x0 + region_w > width) || ((uint64_t)region_y0 + region_h > height))
+ {
+ error_printf("stats region (%u,%u) %ux%u is out of range (image is %ux%u)\n",
+ region_x0, region_y0, region_w, region_h, width, height);
+ any_failed = true;
+ region_valid = false;
+ }
+ else
+ {
+ printf(" Stats restricted to region (%u,%u) %ux%u [x %u..%u, y %u..%u]\n",
+ region_x0, region_y0, region_w, region_h,
+ region_x0, region_x0 + region_w - 1, region_y0, region_y0 + region_h - 1);
+ }
+ }
+
+ if (region_valid)
+ {
+ const uint32_t region_x1 = region_x0 + region_w, region_y1 = region_y0 + region_h;
+ const uint64_t total_pixels = (uint64_t)region_w * region_h;
+
+ uint32_t chan_min[4] = { 255, 255, 255, 255 };
+ uint32_t chan_max[4] = { 0, 0, 0, 0 };
+ double chan_sum[4] = { 0, 0, 0, 0 };
+ double chan_sum_sq[4] = { 0, 0, 0, 0 };
+ bool seen[4][256];
+ memset(seen, 0, sizeof(seen));
+ uint64_t alpha_lt_255 = 0, alpha_eq_0 = 0;
+ bool premult_possible = true; // true while R,G,B <= A holds for every pixel (a hint of premultiplied alpha)
+ bool rgb_identical = true; // true while R==G==B for every pixel (effectively grayscale / duplicated channel)
+
+ for (uint32_t y = region_y0; y < region_y1; y++)
+ {
+ for (uint32_t x = region_x0; x < region_x1; x++)
+ {
+ const color_rgba& c = img(x, y);
+ for (uint32_t ch = 0; ch < 4; ch++)
+ {
+ const uint32_t v = c[ch];
+ if (v < chan_min[ch]) chan_min[ch] = v;
+ if (v > chan_max[ch]) chan_max[ch] = v;
+ chan_sum[ch] += (double)v;
+ chan_sum_sq[ch] += (double)v * (double)v;
+ seen[ch][v] = true;
+ }
+ if (c.a < 255) alpha_lt_255++;
+ if (c.a == 0) alpha_eq_0++;
+ if ((c.r > c.a) || (c.g > c.a) || (c.b > c.a)) premult_possible = false;
+ if ((c.r != c.g) || (c.g != c.b)) rgb_identical = false;
+ }
+ }
+
+ printf(" Chan | Min | Max | Mean | Variance | StdDev | Unique | Notes\n");
+ static const char chan_names[4] = { 'R', 'G', 'B', 'A' };
+ for (uint32_t ch = 0; ch < 4; ch++)
+ {
+ const double mean = total_pixels ? (chan_sum[ch] / (double)total_pixels) : 0.0;
+ double var = total_pixels ? (chan_sum_sq[ch] / (double)total_pixels - mean * mean) : 0.0;
+ if (var < 0.0) var = 0.0; // guard against tiny negative floating point error
+ const double stddev = sqrt(var);
+ uint32_t unique = 0;
+ for (uint32_t v = 0; v < 256; v++) unique += seen[ch][v] ? 1u : 0u;
+
+ char notes[64]; notes[0] = '\0';
+ if (chan_min[ch] == chan_max[ch])
+ snprintf(notes, sizeof(notes), "CONSTANT = %u", chan_min[ch]);
+
+ printf(" %c | %4u | %4u | %11.4f | %15.4f | %10.4f | %6u | %s\n",
+ chan_names[ch], chan_min[ch], chan_max[ch], mean, var, stddev, unique, notes);
+ }
+
+ // Alpha-focused summary -- a frequent source of bugs.
+ printf(" Alpha: ");
+ if (chan_min[3] == 255)
+ printf("fully opaque (all 255)\n");
+ else if (chan_max[3] == 0)
+ printf("fully transparent (all 0)\n");
+ else
+ printf("varies [%u..%u]; %llu px (%.2f%%) < 255, %llu px (%.2f%%) == 0\n",
+ chan_min[3], chan_max[3],
+ (unsigned long long)alpha_lt_255, total_pixels ? (100.0 * (double)alpha_lt_255 / (double)total_pixels) : 0.0,
+ (unsigned long long)alpha_eq_0, total_pixels ? (100.0 * (double)alpha_eq_0 / (double)total_pixels) : 0.0);
+
+ // Flags that commonly indicate a problem.
+ if (img.has_alpha() && (chan_min[3] == 255))
+ printf(" Note: image has an alpha channel but every alpha value is 255 (effectively opaque).\n");
+ if ((chan_min[3] != 255) && premult_possible)
+ printf(" Note: every pixel has R,G,B <= A -- the image may use premultiplied alpha.\n");
+ for (uint32_t ch = 0; ch < 3; ch++)
+ if (chan_min[ch] == chan_max[ch])
+ printf(" Note: %c channel is constant (%u) everywhere -- possible dropped/unused channel.\n", chan_names[ch], chan_min[ch]);
+ if (rgb_identical)
+ printf(" Note: R, G, B are identical for every pixel -- image is effectively grayscale (or a duplicated/swizzled channel).\n");
+ } // region_valid
+ }
+
+ // ---- Single-pixel dump ----
+ if (do_dump_pixel)
+ {
+ const uint32_t px = (uint32_t)opts.m_dump_pixel_x, py = (uint32_t)opts.m_dump_pixel_y;
+ if ((px >= width) || (py >= height))
+ printf(" dump_pixel: (%u,%u) is out of range (image is %ux%u)\n", px, py, width, height);
+ else
+ {
+ const color_rgba& c = img(px, py);
+ printf(" Pixel (%u,%u): R=%3u G=%3u B=%3u A=%3u (hex 0x%02X%02X%02X%02X)\n",
+ px, py, c.r, c.g, c.b, c.a, c.r, c.g, c.b, c.a);
+ }
+ }
+
+ // ---- 4x4 texel-block dump: source texels [bx*4..bx*4+3] x [by*4..by*4+3] (matches the 4x4 grid the
+ // block codecs use, so you can line this up against a transcoded/decoded block). ----
+ if (do_dump_block)
+ {
+ const uint32_t bx = (uint32_t)opts.m_dump_block_x, by = (uint32_t)opts.m_dump_block_y;
+ const uint32_t num_blocks_x = (width + 3) / 4, num_blocks_y = (height + 3) / 4;
+ if ((bx >= num_blocks_x) || (by >= num_blocks_y))
+ printf(" dump_block: block (%u,%u) out of range (image has %ux%u 4x4 blocks)\n", bx, by, num_blocks_x, num_blocks_y);
+ else
+ {
+ printf(" 4x4 block (%u,%u) -> texels (%u,%u)..(%u,%u) [R,G,B,A]:\n", bx, by, bx * 4, by * 4, bx * 4 + 3, by * 4 + 3);
+ for (uint32_t ty = 0; ty < 4; ty++)
+ {
+ const uint32_t sy = by * 4 + ty;
+ printf(" ");
+ for (uint32_t tx = 0; tx < 4; tx++)
+ {
+ const uint32_t sx = bx * 4 + tx;
+ if ((sx < width) && (sy < height))
+ {
+ const color_rgba& c = img(sx, sy);
+ printf(" [%3u,%3u,%3u,%3u]", c.r, c.g, c.b, c.a);
+ }
+ else
+ printf(" [ -- oob -- ]");
+ }
+ printf("\n");
+ }
+ }
+ }
+ }
+
+ return !any_failed;
+}
+
static bool compare_mode(command_line_params &opts)
{
if (opts.m_input_filenames.size() != 2)
@@ -4473,6 +6021,12 @@ static bool compare_mode(command_line_params &opts)
} // x
} // y
+ fmt_printf("\nPSNR-HVS and PSNR-HVS-M metrics:\n");
+ psnr_hvs_metrics hvs_metrics;
+ psnr_hvs_compute_metrics(a, b, hvs_metrics);
+ psnr_hvs_print_metrics(hvs_metrics);
+ fmt_printf("\n");
+
save_png("a_rgb.png", a, cImageSaveIgnoreAlpha);
save_png("a_alpha.png", a, cImageSaveGrayscale, 3);
printf("Wrote a_rgb.png and a_alpha.png\n");
@@ -4665,7 +6219,68 @@ static bool compare_mode(command_line_params &opts)
}
} // display_plot
-
+
+ return true;
+}
+
+// -compare_hvs: computes and prints standard image metrics plus PSNR-HVS / PSNR-HVS-M for two LDR images (specified via
+// -file). Uses the same psnr_hvs_compute_metrics()/print sequence the old dev test code used - the details matter.
+static bool compare_hvs_mode(command_line_params& opts)
+{
+ if (opts.m_input_filenames.size() != 2)
+ {
+ error_printf("Must specify two PNG filenames using -file\n");
+ return false;
+ }
+
+ for (uint32_t i = 0; i < 2; i++)
+ {
+ std::string ext(string_get_extension(opts.m_input_filenames[i]));
+ if ((strcasecmp(ext.c_str(), "exr") == 0) || (strcasecmp(ext.c_str(), "hdr") == 0))
+ {
+ error_printf("Can't compare HDR image files with this option (LDR only).\n");
+ return false;
+ }
+ }
+
+ image a, b;
+ if (!load_png(opts.m_input_filenames[0].c_str(), a))
+ {
+ error_printf("Failed loading image from file \"%s\"!\n", opts.m_input_filenames[0].c_str());
+ return false;
+ }
+ printf("Loaded \"%s\", %ux%u, has alpha: %u\n", opts.m_input_filenames[0].c_str(), a.get_width(), a.get_height(), a.has_alpha());
+
+ if (!load_png(opts.m_input_filenames[1].c_str(), b))
+ {
+ error_printf("Failed loading image from file \"%s\"!\n", opts.m_input_filenames[1].c_str());
+ return false;
+ }
+ printf("Loaded \"%s\", %ux%u, has alpha: %u\n", opts.m_input_filenames[1].c_str(), b.get_width(), b.get_height(), b.has_alpha());
+
+ if ((a.get_width() != b.get_width()) || (a.get_height() != b.get_height()))
+ {
+ printf("Images don't have the same dimensions - cropping input images to smallest common dimensions\n");
+
+ uint32_t w = minimum(a.get_width(), b.get_width());
+ uint32_t h = minimum(a.get_height(), b.get_height());
+
+ a.crop(w, h);
+ b.crop(w, h);
+ }
+
+ fmt_printf("Resolution: {}x{}\n", a.get_width(), a.get_height());
+
+ print_image_metrics(a, b);
+
+ psnr_hvs_metrics hvs_metrics;
+ if (!psnr_hvs_compute_metrics(a, b, hvs_metrics))
+ {
+ error_printf("psnr_hvs_compute_metrics() failed!\n");
+ return false;
+ }
+ psnr_hvs_print_metrics(hvs_metrics);
+
return true;
}
@@ -4734,7 +6349,7 @@ static bool combine_images_mode(command_line_params& opts)
printf("Loaded \"%s\", %ux%u, has alpha: %u\n", opts.m_input_filenames[1].c_str(), b.get_width(), b.get_height(), b.has_alpha());
const uint32_t width = minimum(a.get_width(), b.get_width());
- const uint32_t height = minimum(b.get_height(), b.get_height());
+ const uint32_t height = minimum(a.get_height(), b.get_height());
image combined_img(width, height);
for (uint32_t y = 0; y < height; y++)
@@ -4760,6 +6375,208 @@ static bool combine_images_mode(command_line_params& opts)
return true;
}
+// Resolves the optional rectangle for the -extract_* modes against an image's dimensions.
+// If no region was given (region_w <= 0), returns the whole image. Returns false (with a message) if the rectangle lies outside the image.
+static bool resolve_extract_region(const command_line_params& opts, uint32_t width, uint32_t height,
+ uint32_t& out_x, uint32_t& out_y, uint32_t& out_w, uint32_t& out_h)
+{
+ if (opts.m_extract_region_w <= 0)
+ {
+ out_x = 0; out_y = 0; out_w = width; out_h = height;
+ return true;
+ }
+
+ out_x = (uint32_t)opts.m_extract_region_x;
+ out_y = (uint32_t)opts.m_extract_region_y;
+ out_w = (uint32_t)opts.m_extract_region_w;
+ out_h = (uint32_t)opts.m_extract_region_h;
+
+ if (((uint64_t)out_x + out_w > width) || ((uint64_t)out_y + out_h > height))
+ {
+ error_printf("Region (%u,%u) %ux%u is out of range (image is %ux%u)\n", out_x, out_y, out_w, out_h, width, height);
+ return false;
+ }
+ return true;
+}
+
+// Applies one sRGB<->linear transform to an 8-bit value. The transform functions take/return normalized [0,1]
+// (and assert that range), so we normalize, transform, then round-to-nearest back to 8-bit.
+static uint8_t apply_extract_xform_u8(uint8_t v, int xform)
+{
+ const float f = (float)v * (1.0f / 255.0f); // exactly within [0,1] for v in [0,255]
+ const float g = (xform == cExtractXformToLinear) ? srgb_to_linear(f) : linear_to_srgb(f);
+ const int q = (int)(g * 255.0f + 0.5f); // round to nearest
+ return (uint8_t)clamp(q, 0, 255);
+}
+
+// Applies the optional color transform to an output image in place: RGB always, alpha only if xform_alpha is set.
+static void apply_extract_xform_image(image& img, int xform, bool xform_alpha)
+{
+ if (xform == cExtractXformNone)
+ return;
+ const uint32_t num_channels = xform_alpha ? 4u : 3u;
+ for (uint32_t y = 0; y < img.get_height(); y++)
+ for (uint32_t x = 0; x < img.get_width(); x++)
+ {
+ color_rgba& c = img(x, y);
+ for (uint32_t ch = 0; ch < num_channels; ch++)
+ c[ch] = apply_extract_xform_u8(c[ch], xform);
+ }
+}
+
+// Human-readable suffix describing the active transform (for the "Wrote ..." messages).
+static const char* extract_xform_desc(int xform)
+{
+ if (xform == cExtractXformToLinear) return " [sRGB->linear]";
+ if (xform == cExtractXformToSRGB) return " [linear->sRGB]";
+ return "";
+}
+
+static bool extract_channel_mode(command_line_params& opts)
+{
+ if (opts.m_input_filenames.size() != 1)
+ {
+ error_printf("Must specify one image filename using -file\n");
+ return false;
+ }
+
+ image img;
+ if (!load_image(opts.m_input_filenames[0].c_str(), img))
+ {
+ error_printf("Failed loading image from file \"%s\"!\n", opts.m_input_filenames[0].c_str());
+ return false;
+ }
+
+ const uint32_t width = img.get_width(), height = img.get_height();
+ printf("Loaded \"%s\", %ux%u, has alpha: %u\n", opts.m_input_filenames[0].c_str(), width, height, img.has_alpha());
+
+ uint32_t rx, ry, rw, rh;
+ if (!resolve_extract_region(opts, width, height, rx, ry, rw, rh))
+ return false;
+
+ // Copy the (possibly cropped) region; only the requested channel is later written out as grayscale.
+ const uint32_t channel = (uint32_t)opts.m_extract_channel;
+ image out_img(rw, rh);
+ for (uint32_t y = 0; y < rh; y++)
+ for (uint32_t x = 0; x < rw; x++)
+ out_img(x, y) = img(rx + x, ry + y);
+
+ // Optional sRGB<->linear transform on just the extracted channel. Alpha is only transformed if -extract_xform_alpha was given,
+ // so for the alpha channel the transform may be requested but not actually applied (reflected in the message below).
+ const bool channel_is_alpha = (channel == 3);
+ const bool xform_applied = (opts.m_extract_color_xform != cExtractXformNone) && (!channel_is_alpha || opts.m_extract_xform_alpha);
+ if (xform_applied)
+ {
+ for (uint32_t y = 0; y < rh; y++)
+ for (uint32_t x = 0; x < rw; x++)
+ out_img(x, y)[channel] = apply_extract_xform_u8(out_img(x, y)[channel], opts.m_extract_color_xform);
+ }
+
+ if (!save_png(opts.m_extract_filename.c_str(), out_img, cImageSaveGrayscale, channel))
+ {
+ error_printf("Failed writing file %s\n", opts.m_extract_filename.c_str());
+ return false;
+ }
+
+ printf("Wrote channel %c of region (%u,%u) %ux%u as grayscale to \"%s\"%s\n",
+ "RGBA"[channel], rx, ry, rw, rh, opts.m_extract_filename.c_str(), xform_applied ? extract_xform_desc(opts.m_extract_color_xform) : "");
+ return true;
+}
+
+static bool extract_swizzle_mode(command_line_params& opts)
+{
+ if (opts.m_input_filenames.size() != 1)
+ {
+ error_printf("Must specify one image filename using -file\n");
+ return false;
+ }
+
+ image img;
+ if (!load_image(opts.m_input_filenames[0].c_str(), img))
+ {
+ error_printf("Failed loading image from file \"%s\"!\n", opts.m_input_filenames[0].c_str());
+ return false;
+ }
+
+ const uint32_t width = img.get_width(), height = img.get_height();
+ printf("Loaded \"%s\", %ux%u, has alpha: %u\n", opts.m_input_filenames[0].c_str(), width, height, img.has_alpha());
+
+ uint32_t rx, ry, rw, rh;
+ if (!resolve_extract_region(opts, width, height, rx, ry, rw, rh))
+ return false;
+
+ // Build the per-output-channel source map: listed channels are swizzled, the rest pass through unchanged (identity).
+ uint32_t src_chan[4] = { 0, 1, 2, 3 };
+ for (int i = 0; i < opts.m_extract_swizzle_count; i++)
+ src_chan[i] = (uint32_t)opts.m_extract_swizzle[i];
+
+ image out_img(rw, rh);
+ for (uint32_t y = 0; y < rh; y++)
+ {
+ for (uint32_t x = 0; x < rw; x++)
+ {
+ const color_rgba& s = img(rx + x, ry + y);
+ color_rgba& d = out_img(x, y);
+ for (uint32_t c = 0; c < 4; c++)
+ d[c] = s[src_chan[c]];
+ }
+ }
+
+ // Optional sRGB<->linear transform: RGB always, alpha only with -extract_xform_alpha.
+ apply_extract_xform_image(out_img, opts.m_extract_color_xform, opts.m_extract_xform_alpha);
+
+ if (!save_png(opts.m_extract_filename.c_str(), out_img))
+ {
+ error_printf("Failed writing file %s\n", opts.m_extract_filename.c_str());
+ return false;
+ }
+
+ printf("Wrote swizzle R<-%c G<-%c B<-%c A<-%c of region (%u,%u) %ux%u to \"%s\"%s\n",
+ "RGBA"[src_chan[0]], "RGBA"[src_chan[1]], "RGBA"[src_chan[2]], "RGBA"[src_chan[3]],
+ rx, ry, rw, rh, opts.m_extract_filename.c_str(), extract_xform_desc(opts.m_extract_color_xform));
+ return true;
+}
+
+static bool extract_region_mode(command_line_params& opts)
+{
+ if (opts.m_input_filenames.size() != 1)
+ {
+ error_printf("Must specify one image filename using -file\n");
+ return false;
+ }
+
+ image img;
+ if (!load_image(opts.m_input_filenames[0].c_str(), img))
+ {
+ error_printf("Failed loading image from file \"%s\"!\n", opts.m_input_filenames[0].c_str());
+ return false;
+ }
+
+ const uint32_t width = img.get_width(), height = img.get_height();
+ printf("Loaded \"%s\", %ux%u, has alpha: %u\n", opts.m_input_filenames[0].c_str(), width, height, img.has_alpha());
+
+ uint32_t rx, ry, rw, rh;
+ if (!resolve_extract_region(opts, width, height, rx, ry, rw, rh))
+ return false;
+
+ image out_img(rw, rh);
+ for (uint32_t y = 0; y < rh; y++)
+ for (uint32_t x = 0; x < rw; x++)
+ out_img(x, y) = img(rx + x, ry + y);
+
+ // Optional sRGB<->linear transform: RGB always, alpha only with -extract_xform_alpha.
+ apply_extract_xform_image(out_img, opts.m_extract_color_xform, opts.m_extract_xform_alpha);
+
+ if (!save_png(opts.m_extract_filename.c_str(), out_img))
+ {
+ error_printf("Failed writing file %s\n", opts.m_extract_filename.c_str());
+ return false;
+ }
+
+ printf("Wrote region (%u,%u) %ux%u to \"%s\"%s\n", rx, ry, rw, rh, opts.m_extract_filename.c_str(), extract_xform_desc(opts.m_extract_color_xform));
+ return true;
+}
+
static bool tonemap_image_mode(command_line_params& opts)
{
if (opts.m_input_filenames.size() != 1)
@@ -4928,34 +6745,34 @@ const struct etc1s_uastc_4x4_ldr_test_file
float m_etc1s_128_psnr;
} g_etc1s_uastc_4x4_ldr_test_files[] =
{
- { "black_1x1.png", 189, 100.0f, 100.0f, 189, 100.0f },
- { "kodim01.png", 30993, 27.40f, 44.14f, 58354, 30.356064f },
- { "kodim02.png", 28529, 32.20f, 41.06f, 51411, 34.713940f },
- { "kodim03.png", 23411, 32.57f, 44.87f, 49282, 36.709675f },
- { "kodim04.png", 28256, 31.76f, 43.02f, 57003, 34.864861f },
- { "kodim05.png", 32646, 25.94f, 40.28f, 65731, 29.935091f },
- { "kodim06.png", 27336, 28.66f, 44.57f, 54963, 32.294220f },
- { "kodim07.png", 26618, 31.51f, 43.94f, 53352, 35.576595f },
- { "kodim08.png", 31133, 25.28f, 41.15f, 63347, 29.509914f },
- { "kodim09.png", 24777, 32.05f, 45.85f, 51355, 35.985966f },
- { "kodim10.png", 27247, 32.20f, 45.77f, 54291, 36.395000f },
- { "kodim11.png", 26579, 29.22f, 43.68f, 55491, 33.468971f },
- { "kodim12.png", 25102, 32.96f, 46.77f, 51465, 36.722233f },
- { "kodim13.png", 31604, 24.25f, 41.25f, 62629, 27.588623f },
- { "kodim14.png", 31162, 27.81f, 39.65f, 62866, 31.206463f },
- { "kodim15.png", 25528, 31.26f, 42.87f, 53343, 35.026314f },
- { "kodim16.png", 26894, 32.21f, 47.78f, 51325, 35.555458f },
- { "kodim17.png", 29334, 31.40f, 45.66f, 55630, 35.909283f },
- { "kodim18.png", 30929, 27.46f, 41.54f, 62421, 31.348171f },
- { "kodim19.png", 27889, 29.69f, 44.95f, 55055, 33.613987f },
- { "kodim20.png", 21104, 31.30f, 45.31f, 47136, 35.759407f },
- { "kodim21.png", 25943, 28.53f, 44.45f, 54768, 32.415817f },
- { "kodim22.png", 29277, 29.85f, 42.63f, 60889, 33.495415f },
- { "kodim23.png", 23550, 31.69f, 45.11f, 53774, 36.223492f },
- { "kodim24.png", 29613, 26.75f, 40.61f, 59014, 31.522869f },
- { "white_1x1.png", 189, 100.0f, 100.0f, 189, 100.000000f },
- { "wikipedia.png", 38961, 24.10f, 30.47f, 69558, 27.630802f },
- { "alpha0.png", 766, 100.0f, 56.16f, 747, 100.000000f }
+ { "black_1x1.png", 220, 100.0f, 100.0f, 220, 100.0f },
+ { "kodim01.png", 31003, 27.40f, 44.14f, 58385, 30.356064f },
+ { "kodim02.png", 28560, 32.20f, 41.06f, 51442, 34.713940f },
+ { "kodim03.png", 23442, 32.57f, 44.87f, 49548, 36.709675f },
+ { "kodim04.png", 28287, 31.76f, 43.02f, 57034, 34.864861f },
+ { "kodim05.png", 32677, 25.94f, 40.28f, 65742, 29.935091f },
+ { "kodim06.png", 27367, 28.66f, 44.57f, 54994, 32.294220f },
+ { "kodim07.png", 26649, 31.51f, 43.94f, 53374, 35.576595f },
+ { "kodim08.png", 31164, 25.28f, 41.15f, 63516, 29.509914f },
+ { "kodim09.png", 24808, 32.05f, 45.85f, 51402, 35.985966f },
+ { "kodim10.png", 27278, 32.20f, 45.77f, 54322, 36.395000f },
+ { "kodim11.png", 26610, 29.22f, 43.68f, 55526, 33.468971f },
+ { "kodim12.png", 25133, 32.96f, 46.77f, 51503, 36.722233f },
+ { "kodim13.png", 31635, 24.25f, 41.25f, 62660, 27.588623f },
+ { "kodim14.png", 31193, 27.81f, 39.65f, 62897, 31.206463f },
+ { "kodim15.png", 25559, 31.26f, 42.87f, 53424, 35.026314f },
+ { "kodim16.png", 26925, 32.21f, 47.78f, 51354, 35.555458f },
+ { "kodim17.png", 29365, 31.40f, 45.66f, 55675, 35.909283f },
+ { "kodim18.png", 30960, 27.46f, 41.54f, 62388, 31.348171f },
+ { "kodim19.png", 27920, 29.69f, 44.95f, 55098, 33.613987f },
+ { "kodim20.png", 21135, 31.30f, 45.31f, 47160, 35.759407f },
+ { "kodim21.png", 25974, 28.53f, 44.45f, 54799, 32.415817f },
+ { "kodim22.png", 29111, 29.85f, 42.63f, 60994, 33.495415f },
+ { "kodim23.png", 23825, 31.69f, 45.11f, 53614, 36.223492f },
+ { "kodim24.png", 29644, 26.75f, 40.61f, 58909, 31.522869f },
+ { "white_1x1.png", 220, 100.0f, 100.0f, 220, 100.000000f },
+ { "wikipedia.png", 38992, 24.10f, 30.47f, 69608, 27.630802f },
+ { "alpha0.png", 807, 100.0f, 56.16f, 810, 100.000000f }
};
static bool test_mode_ldr(command_line_params& opts)
@@ -5276,15 +7093,15 @@ static bool test_mode_hdr(command_line_params& opts, basist::basis_tex_format te
double delta1, delta2;
printf("ASTC PSNR: %f (expected %f, delta %f), BC6H PSNR: %f (expected %f, delta %f)\n",
- stats.m_basis_rgb_avg_log2_psnr, pTest_files[i].m_level_psnr_astc[uastc_hdr_level], delta1 = fabs(stats.m_basis_rgb_avg_log2_psnr - pTest_files[i].m_level_psnr_astc[uastc_hdr_level]),
+ stats.m_basis_rgb_avg_astc_hdr_log2_psnr, pTest_files[i].m_level_psnr_astc[uastc_hdr_level], delta1 = fabs(stats.m_basis_rgb_avg_astc_hdr_log2_psnr - pTest_files[i].m_level_psnr_astc[uastc_hdr_level]),
stats.m_basis_rgb_avg_bc6h_log2_psnr, pTest_files[i].m_level_psnr_bc6h[uastc_hdr_level], delta2 = fabs(stats.m_basis_rgb_avg_bc6h_log2_psnr - pTest_files[i].m_level_psnr_bc6h[uastc_hdr_level]));
highest_delta = maximum(highest_delta, delta1);
highest_delta = maximum(highest_delta, delta2);
- if (fabs(stats.m_basis_rgb_avg_log2_psnr - pTest_files[i].m_level_psnr_astc[uastc_hdr_level]) > PSNR_THRESHOLD)
+ if (fabs(stats.m_basis_rgb_avg_astc_hdr_log2_psnr - pTest_files[i].m_level_psnr_astc[uastc_hdr_level]) > PSNR_THRESHOLD)
{
- error_printf("Expected UASTC HDR RGB Avg PSNR was %f, but got %f instead!\n", pTest_files[i].m_level_psnr_astc[uastc_hdr_level], stats.m_basis_rgb_avg_log2_psnr);
+ error_printf("Expected UASTC HDR RGB Avg PSNR was %f, but got %f instead!\n", pTest_files[i].m_level_psnr_astc[uastc_hdr_level], stats.m_basis_rgb_avg_astc_hdr_log2_psnr);
total_mismatches++;
}
@@ -5294,7 +7111,7 @@ static bool test_mode_hdr(command_line_params& opts, basist::basis_tex_format te
total_mismatches++;
}
- astc_psnr(i, uastc_hdr_level) = stats.m_basis_rgb_avg_log2_psnr;
+ astc_psnr(i, uastc_hdr_level) = stats.m_basis_rgb_avg_astc_hdr_log2_psnr;
bc6h_psnr(i, uastc_hdr_level) = stats.m_basis_rgb_avg_bc6h_log2_psnr;
}
}
@@ -5364,36 +7181,218 @@ struct xuastc_ldr_test_file
xuastc_ldr_test_file g_xuastc_ldr_test_files_6x6[] =
{
- { "black_1x1.png", { { 100.000000f, 111, 100.000000f }, { 75.000000f, 112, 100.000000f }, { 35.000000f, 112, 100.000000f } } },
- { "kodim01.png", { { 100.000000f, 141064, 37.188324f }, { 75.000000f, 115385, 32.893822f }, { 35.000000f, 80001, 30.057878f } } },
- { "kodim02.png", { { 100.000000f, 135146, 40.280567f }, { 75.000000f, 82435, 36.618645f }, { 35.000000f, 57365, 34.556519f } } },
- { "kodim03.png", { { 100.000000f, 133654, 42.754337f }, { 75.000000f, 72161, 38.706654f }, { 35.000000f, 51462, 36.026749f } } },
- { "kodim04.png", { { 100.000000f, 138877, 40.671108f }, { 75.000000f, 84194, 36.773575f }, { 35.000000f, 61363, 34.570110f } } },
- { "kodim05.png", { { 100.000000f, 146600, 35.842682f }, { 75.000000f, 124004, 33.176735f }, { 35.000000f, 94508, 30.148199f } } },
- { "kodim06.png", { { 100.000000f, 134928, 38.721409f }, { 75.000000f, 94356, 34.459309f }, { 35.000000f, 65904, 31.435408f } } },
- { "kodim07.png", { { 100.000000f, 136807, 41.048141f }, { 75.000000f, 85150, 38.172615f }, { 35.000000f, 64387, 35.527702f } } },
- { "kodim08.png", { { 100.000000f, 145326, 35.896526f }, { 75.000000f, 119654, 33.047630f }, { 35.000000f, 92376, 29.980146f } } },
- { "kodim09.png", { { 100.000000f, 135074, 42.271267f }, { 75.000000f, 66568, 38.262554f }, { 35.000000f, 47686, 35.810940f } } },
- { "kodim10.png", { { 100.000000f, 137184, 41.879585f }, { 75.000000f, 73560, 37.980556f }, { 35.000000f, 54453, 35.449261f } } },
- { "kodim11.png", { { 100.000000f, 138275, 38.718960f }, { 75.000000f, 91902, 35.112244f }, { 35.000000f, 66243, 32.391891f } } },
- { "kodim12.png", { { 100.000000f, 132918, 42.822681f }, { 75.000000f, 71330, 38.155998f }, { 35.000000f, 49345, 35.743179f } } },
- { "kodim13.png", { { 100.000000f, 141033, 33.948277f }, { 75.000000f, 123631, 30.678318f }, { 35.000000f, 88403, 27.592640f } } },
- { "kodim14.png", { { 100.000000f, 141117, 36.902863f }, { 75.000000f, 108060, 33.896935f }, { 35.000000f, 77104, 31.451799f } } },
- { "kodim15.png", { { 100.000000f, 135981, 40.416115f }, { 75.000000f, 76564, 36.855175f }, { 35.000000f, 55002, 34.548985f } } },
- { "kodim16.png", { { 100.000000f, 134349, 42.286755f }, { 75.000000f, 80713, 36.828140f }, { 35.000000f, 55894, 33.982174f } } },
- { "kodim17.png", { { 100.000000f, 138778, 40.653671f }, { 75.000000f, 81391, 37.024017f }, { 35.000000f, 59293, 34.429058f } } },
- { "kodim18.png", { { 100.000000f, 142690, 36.400116f }, { 75.000000f, 104323, 33.398468f }, { 35.000000f, 74051, 30.714231f } } },
- { "kodim19.png", { { 100.000000f, 138584, 39.704021f }, { 75.000000f, 87574, 35.544052f }, { 35.000000f, 63776, 33.032051f } } },
- { "kodim20.png", { { 100.000000f, 121663, 41.099850f }, { 75.000000f, 64552, 37.174721f }, { 35.000000f, 44838, 34.739983f } } },
- { "kodim21.png", { { 100.000000f, 138337, 38.284393f }, { 75.000000f, 85878, 34.727512f }, { 35.000000f, 60879, 32.004494f } } },
- { "kodim22.png", { { 100.000000f, 142142, 38.583397f }, { 75.000000f, 93914, 35.047283f }, { 35.000000f, 65592, 32.702984f } } },
- { "kodim23.png", { { 100.000000f, 140280, 42.489117f }, { 75.000000f, 74579, 39.385365f }, { 35.000000f, 57354, 37.228970f } } },
- { "kodim24.png", { { 100.000000f, 138443, 36.158039f }, { 75.000000f, 101415, 33.512146f }, { 35.000000f, 75311, 30.575174f } } },
- { "white_1x1.png", { { 100.000000f, 111, 100.000000f }, { 75.000000f, 112, 100.000000f }, { 35.000000f, 112, 100.000000f } } },
- { "wikipedia.png", { { 100.000000f, 189589, 32.205330f }, { 75.000000f, 168732, 31.926851f }, { 35.000000f, 160971, 30.209082f } } },
+ { "black_1x1.png", { { 100.000000f, 142, 100.000000f }, { 75.000000f, 143, 100.000000f }, { 35.000000f, 143, 100.000000f } } },
+ { "kodim01.png", { { 100.000000f, 143338, 37.631470f }, { 75.000000f, 132606, 34.858498f }, { 35.000000f, 112536, 31.930468f } } },
+ { "kodim02.png", { { 100.000000f, 136308, 40.657822f }, { 75.000000f, 101839, 38.057598f }, { 35.000000f, 81085, 36.128090f } } },
+ { "kodim03.png", { { 100.000000f, 133461, 43.046989f }, { 75.000000f, 86361, 39.970840f }, { 35.000000f, 69200, 37.480732f } } },
+ { "kodim04.png", { { 100.000000f, 139788, 41.014957f }, { 75.000000f, 101258, 38.003296f }, { 35.000000f, 83902, 35.965012f } } },
+ { "kodim05.png", { { 100.000000f, 148943, 36.247421f }, { 75.000000f, 138468, 34.564281f }, { 35.000000f, 122126, 31.844128f } } },
+ { "kodim06.png", { { 100.000000f, 137452, 39.072002f }, { 75.000000f, 113603, 36.145702f }, { 35.000000f, 93972, 33.209007f } } },
+ { "kodim07.png", { { 100.000000f, 137948, 41.442890f }, { 75.000000f, 98386, 39.420319f }, { 35.000000f, 83659, 37.224255f } } },
+ { "kodim08.png", { { 100.000000f, 147884, 36.300182f }, { 75.000000f, 134661, 34.453938f }, { 35.000000f, 118202, 31.751554f } } },
+ { "kodim09.png", { { 100.000000f, 136609, 42.575459f }, { 75.000000f, 83721, 39.448776f }, { 35.000000f, 68192, 37.360222f } } },
+ { "kodim10.png", { { 100.000000f, 138614, 42.184914f }, { 75.000000f, 94047, 39.332645f }, { 35.000000f, 77621, 37.126518f } } },
+ { "kodim11.png", { { 100.000000f, 139548, 39.074104f }, { 75.000000f, 113306, 36.617252f }, { 35.000000f, 91571, 34.055752f } } },
+ { "kodim12.png", { { 100.000000f, 134259, 43.179798f }, { 75.000000f, 90453, 39.607330f }, { 35.000000f, 70234, 37.351040f } } },
+ { "kodim13.png", { { 100.000000f, 144618, 34.267788f }, { 75.000000f, 141867, 32.225410f }, { 35.000000f, 120881, 29.205767f } } },
+ { "kodim14.png", { { 100.000000f, 143775, 37.229355f }, { 75.000000f, 129872, 35.301926f }, { 35.000000f, 109048, 33.015049f } } },
+ { "kodim15.png", { { 100.000000f, 136213, 40.758198f }, { 75.000000f, 92132, 38.082176f }, { 35.000000f, 73418, 35.928169f } } },
+ { "kodim16.png", { { 100.000000f, 135975, 42.594410f }, { 75.000000f, 103275, 38.658211f }, { 35.000000f, 83547, 35.843338f } } },
+ { "kodim17.png", { { 100.000000f, 140399, 40.927052f }, { 75.000000f, 100822, 38.284622f }, { 35.000000f, 83755, 35.921692f } } },
+ { "kodim18.png", { { 100.000000f, 145298, 36.728806f }, { 75.000000f, 122486, 34.681892f }, { 35.000000f, 102720, 32.151287f } } },
+ { "kodim19.png", { { 100.000000f, 140296, 40.024555f }, { 75.000000f, 107528, 36.965836f }, { 35.000000f, 89350, 34.593811f } } },
+ { "kodim20.png", { { 100.000000f, 122203, 41.429142f }, { 75.000000f, 79399, 38.525173f }, { 35.000000f, 62388, 36.222507f } } },
+ { "kodim21.png", { { 100.000000f, 140177, 38.664650f }, { 75.000000f, 104123, 36.225704f }, { 35.000000f, 85020, 33.629925f } } },
+ { "kodim22.png", { { 100.000000f, 143846, 38.921207f }, { 75.000000f, 114520, 36.468395f }, { 35.000000f, 94574, 34.228367f } } },
+ { "kodim23.png", { { 100.000000f, 139961, 42.794750f }, { 75.000000f, 89146, 40.377716f }, { 35.000000f, 75422, 38.508953f } } },
+ { "kodim24.png", { { 100.000000f, 140310, 36.486050f }, { 75.000000f, 120326, 34.828285f }, { 35.000000f, 102832, 32.087605f } } },
+ { "white_1x1.png", { { 100.000000f, 142, 100.000000f }, { 75.000000f, 143, 100.000000f }, { 35.000000f, 143, 100.000000f } } },
+ { "wikipedia.png", { { 100.000000f, 190480, 32.429928f }, { 75.000000f, 173407, 32.292751f }, { 35.000000f, 168150, 31.088394f } } },
//{ "alpha0.png", { { 100.000000f, 1389, 49.883366f }, { 75.000000f, 1385, 49.125038f }, { 35.000000f, 1479, 42.865246f } } } // alpha0.png is minor nightmare for testing XUASTC LDR because it's very sensitive to tiny FP differences
};
+xuastc_ldr_test_file g_xuastc_ldr_test_files_4x4[] =
+{
+ { "black_1x1.png", { { 100.000000f, 142, 100.000000f }, { 75.000000f, 143, 100.000000f }, { 35.000000f, 143, 100.000000f } } },
+ { "kodim01.png", { { 100.000000f, 310218, 47.030079f }, { 75.000000f, 255162, 38.170372f }, { 35.000000f, 210620, 34.740185f } } },
+ { "kodim02.png", { { 100.000000f, 294357, 47.193066f }, { 75.000000f, 194442, 40.707787f }, { 35.000000f, 163870, 38.593933f } } },
+ { "kodim03.png", { { 100.000000f, 283110, 49.651531f }, { 75.000000f, 169617, 42.514809f }, { 35.000000f, 137983, 39.903645f } } },
+ { "kodim04.png", { { 100.000000f, 301581, 47.235493f }, { 75.000000f, 197552, 40.555023f }, { 35.000000f, 170542, 38.400024f } } },
+ { "kodim05.png", { { 100.000000f, 322641, 43.768642f }, { 75.000000f, 265551, 38.349422f }, { 35.000000f, 229721, 34.853809f } } },
+ { "kodim06.png", { { 100.000000f, 297732, 46.904057f }, { 75.000000f, 222107, 39.441322f }, { 35.000000f, 183761, 36.179142f } } },
+ { "kodim07.png", { { 100.000000f, 296921, 47.893196f }, { 75.000000f, 193982, 42.567802f }, { 35.000000f, 164339, 39.916515f } } },
+ { "kodim08.png", { { 100.000000f, 321301, 44.123753f }, { 75.000000f, 266214, 38.408657f }, { 35.000000f, 227531, 34.939663f } } },
+ { "kodim09.png", { { 100.000000f, 297129, 49.033165f }, { 75.000000f, 173342, 41.962223f }, { 35.000000f, 141332, 39.750557f } } },
+ { "kodim10.png", { { 100.000000f, 299737, 48.689640f }, { 75.000000f, 185178, 41.973049f }, { 35.000000f, 156008, 39.650200f } } },
+ { "kodim11.png", { { 100.000000f, 299730, 46.588585f }, { 75.000000f, 215553, 39.980545f }, { 35.000000f, 180852, 36.994793f } } },
+ { "kodim12.png", { { 100.000000f, 289459, 49.914066f }, { 75.000000f, 175999, 42.131413f }, { 35.000000f, 145621, 39.855633f } } },
+ { "kodim13.png", { { 100.000000f, 317109, 43.169689f }, { 75.000000f, 274743, 36.435371f }, { 35.000000f, 230455, 32.394821f } } },
+ { "kodim14.png", { { 100.000000f, 311466, 44.403767f }, { 75.000000f, 241362, 38.612110f }, { 35.000000f, 206563, 35.850140f } } },
+ { "kodim15.png", { { 100.000000f, 290996, 47.307865f }, { 75.000000f, 181963, 40.791069f }, { 35.000000f, 147984, 38.407547f } } },
+ { "kodim16.png", { { 100.000000f, 293439, 49.771076f }, { 75.000000f, 198284, 41.343952f }, { 35.000000f, 163840, 38.560184f } } },
+ { "kodim17.png", { { 100.000000f, 303429, 48.048100f }, { 75.000000f, 194161, 41.256020f }, { 35.000000f, 166376, 38.627644f } } },
+ { "kodim18.png", { { 100.000000f, 316790, 43.609463f }, { 75.000000f, 237898, 38.175785f }, { 35.000000f, 205271, 35.113762f } } },
+ { "kodim19.png", { { 100.000000f, 305786, 47.092293f }, { 75.000000f, 213024, 39.941761f }, { 35.000000f, 184103, 37.369053f } } },
+ { "kodim20.png", { { 100.000000f, 259950, 48.652317f }, { 75.000000f, 155659, 41.508587f }, { 35.000000f, 124257, 38.938896f } } },
+ { "kodim21.png", { { 100.000000f, 303954, 46.846046f }, { 75.000000f, 209365, 39.592907f }, { 35.000000f, 172113, 36.425140f } } },
+ { "kodim22.png", { { 100.000000f, 312642, 45.427174f }, { 75.000000f, 221411, 39.440510f }, { 35.000000f, 189278, 36.970325f } } },
+ { "kodim23.png", { { 100.000000f, 298345, 48.780674f }, { 75.000000f, 175964, 42.915535f }, { 35.000000f, 148486, 40.830143f } } },
+ { "kodim24.png", { { 100.000000f, 302608, 44.029816f }, { 75.000000f, 229249, 39.054703f }, { 35.000000f, 196935, 35.450607f } } },
+ { "white_1x1.png", { { 100.000000f, 142, 100.000000f }, { 75.000000f, 143, 100.000000f }, { 35.000000f, 143, 100.000000f } } },
+ { "wikipedia.png", { { 100.000000f, 360418, 39.591961f }, { 75.000000f, 335408, 38.473557f }, { 35.000000f, 318023, 35.556229f } } },
+};
+xuastc_ldr_test_file g_xuastc_ldr_test_files_5x4[] =
+{
+ { "black_1x1.png", { { 100.000000f, 142, 100.000000f }, { 75.000000f, 143, 100.000000f }, { 35.000000f, 143, 100.000000f } } },
+ { "kodim01.png", { { 100.000000f, 249211, 44.451683f }, { 75.000000f, 210509, 36.881168f }, { 35.000000f, 168587, 33.625820f } } },
+ { "kodim02.png", { { 100.000000f, 238603, 45.184010f }, { 75.000000f, 159386, 39.738209f }, { 35.000000f, 129436, 37.669529f } } },
+ { "kodim03.png", { { 100.000000f, 230397, 47.695618f }, { 75.000000f, 136798, 41.549713f }, { 35.000000f, 110021, 39.007256f } } },
+ { "kodim04.png", { { 100.000000f, 244119, 45.411850f }, { 75.000000f, 159148, 39.640678f }, { 35.000000f, 135726, 37.497944f } } },
+ { "kodim05.png", { { 100.000000f, 260814, 41.621056f }, { 75.000000f, 219953, 37.053875f }, { 35.000000f, 187075, 33.647770f } } },
+ { "kodim06.png", { { 100.000000f, 240122, 44.883125f }, { 75.000000f, 179382, 38.298389f }, { 35.000000f, 146560, 35.105358f } } },
+ { "kodim07.png", { { 100.000000f, 241474, 45.955803f }, { 75.000000f, 157918, 41.505169f }, { 35.000000f, 134039, 38.954472f } } },
+ { "kodim08.png", { { 100.000000f, 259620, 41.868359f }, { 75.000000f, 218900, 37.081158f }, { 35.000000f, 185281, 33.697517f } } },
+ { "kodim09.png", { { 100.000000f, 240072, 47.183537f }, { 75.000000f, 136701, 41.090607f }, { 35.000000f, 111111, 38.871655f } } },
+ { "kodim10.png", { { 100.000000f, 242688, 46.832684f }, { 75.000000f, 148827, 41.053978f }, { 35.000000f, 124419, 38.726421f } } },
+ { "kodim11.png", { { 100.000000f, 243015, 44.532112f }, { 75.000000f, 175753, 38.829491f }, { 35.000000f, 144384, 35.890057f } } },
+ { "kodim12.png", { { 100.000000f, 234426, 47.991623f }, { 75.000000f, 141044, 41.192932f }, { 35.000000f, 115000, 38.965549f } } },
+ { "kodim13.png", { { 100.000000f, 253290, 40.965210f }, { 75.000000f, 226116, 35.000126f }, { 35.000000f, 184823, 31.168385f } } },
+ { "kodim14.png", { { 100.000000f, 252166, 42.261086f }, { 75.000000f, 198054, 37.443531f }, { 35.000000f, 165845, 34.783745f } } },
+ { "kodim15.png", { { 100.000000f, 236533, 45.223949f }, { 75.000000f, 146577, 39.803493f }, { 35.000000f, 118822, 37.435871f } } },
+ { "kodim16.png", { { 100.000000f, 237555, 47.910408f }, { 75.000000f, 161093, 40.372368f }, { 35.000000f, 131482, 37.610260f } } },
+ { "kodim17.png", { { 100.000000f, 245639, 46.034199f }, { 75.000000f, 155299, 40.225517f }, { 35.000000f, 132135, 37.615314f } } },
+ { "kodim18.png", { { 100.000000f, 256813, 41.648430f }, { 75.000000f, 194254, 36.990253f }, { 35.000000f, 164193, 33.996323f } } },
+ { "kodim19.png", { { 100.000000f, 247269, 45.170345f }, { 75.000000f, 170744, 38.804966f }, { 35.000000f, 144924, 36.359180f } } },
+ { "kodim20.png", { { 100.000000f, 213141, 46.714931f }, { 75.000000f, 125433, 40.431580f }, { 35.000000f, 98170, 37.925316f } } },
+ { "kodim21.png", { { 100.000000f, 244810, 44.717579f }, { 75.000000f, 168321, 38.396568f }, { 35.000000f, 135060, 35.355556f } } },
+ { "kodim22.png", { { 100.000000f, 252431, 43.553535f }, { 75.000000f, 179142, 38.341515f }, { 35.000000f, 150546, 35.956043f } } },
+ { "kodim23.png", { { 100.000000f, 242860, 46.938763f }, { 75.000000f, 142099, 42.049194f }, { 35.000000f, 119089, 39.947498f } } },
+ { "kodim24.png", { { 100.000000f, 244829, 41.747345f }, { 75.000000f, 189205, 37.679653f }, { 35.000000f, 159214, 34.212723f } } },
+ { "white_1x1.png", { { 100.000000f, 142, 100.000000f }, { 75.000000f, 143, 100.000000f }, { 35.000000f, 143, 100.000000f } } },
+ { "wikipedia.png", { { 100.000000f, 307678, 37.244614f }, { 75.000000f, 282188, 36.445412f }, { 35.000000f, 269165, 33.900223f } } },
+};
+xuastc_ldr_test_file g_xuastc_ldr_test_files_8x8[] =
+{
+ { "black_1x1.png", { { 100.000000f, 142, 100.000000f }, { 75.000000f, 143, 100.000000f }, { 35.000000f, 143, 100.000000f } } },
+ { "kodim01.png", { { 100.000000f, 82815, 33.129543f }, { 75.000000f, 80163, 32.320469f }, { 35.000000f, 72960, 30.570271f } } },
+ { "kodim02.png", { { 100.000000f, 78367, 37.343704f }, { 75.000000f, 64920, 36.143528f }, { 35.000000f, 51500, 34.768459f } } },
+ { "kodim03.png", { { 100.000000f, 77599, 39.404751f }, { 75.000000f, 55460, 38.039928f }, { 35.000000f, 44970, 36.254837f } } },
+ { "kodim04.png", { { 100.000000f, 80792, 37.649517f }, { 75.000000f, 64576, 36.303604f }, { 35.000000f, 54242, 34.725628f } } },
+ { "kodim05.png", { { 100.000000f, 84711, 32.105507f }, { 75.000000f, 84081, 31.671597f }, { 35.000000f, 76634, 30.126841f } } },
+ { "kodim06.png", { { 100.000000f, 79855, 34.817501f }, { 75.000000f, 69603, 33.753742f }, { 35.000000f, 61087, 31.827003f } } },
+ { "kodim07.png", { { 100.000000f, 79449, 37.754784f }, { 75.000000f, 62135, 37.021587f }, { 35.000000f, 53152, 35.554993f } } },
+ { "kodim08.png", { { 100.000000f, 84365, 31.781384f }, { 75.000000f, 81169, 31.304081f }, { 35.000000f, 74352, 29.816698f } } },
+ { "kodim09.png", { { 100.000000f, 78971, 38.773418f }, { 75.000000f, 53689, 37.463520f }, { 35.000000f, 43699, 35.936676f } } },
+ { "kodim10.png", { { 100.000000f, 80147, 38.478813f }, { 75.000000f, 59821, 37.286610f }, { 35.000000f, 49992, 35.712532f } } },
+ { "kodim11.png", { { 100.000000f, 80255, 35.045494f }, { 75.000000f, 70796, 34.217140f }, { 35.000000f, 60030, 32.591511f } } },
+ { "kodim12.png", { { 100.000000f, 78152, 39.493549f }, { 75.000000f, 58381, 37.853058f }, { 35.000000f, 46241, 36.070549f } } },
+ { "kodim13.png", { { 100.000000f, 83603, 29.707319f }, { 75.000000f, 83231, 29.235767f }, { 35.000000f, 77262, 27.668018f } } },
+ { "kodim14.png", { { 100.000000f, 82796, 33.518745f }, { 75.000000f, 80312, 32.885574f }, { 35.000000f, 70597, 31.453365f } } },
+ { "kodim15.png", { { 100.000000f, 79262, 37.283611f }, { 75.000000f, 59147, 36.219261f }, { 35.000000f, 48554, 34.670193f } } },
+ { "kodim16.png", { { 100.000000f, 78792, 38.528458f }, { 75.000000f, 65336, 36.765572f }, { 35.000000f, 54384, 34.544426f } } },
+ { "kodim17.png", { { 100.000000f, 81297, 37.139481f }, { 75.000000f, 65278, 36.174728f }, { 35.000000f, 55032, 34.575806f } } },
+ { "kodim18.png", { { 100.000000f, 83288, 32.887947f }, { 75.000000f, 75361, 32.249966f }, { 35.000000f, 65759, 30.717367f } } },
+ { "kodim19.png", { { 100.000000f, 80857, 36.188713f }, { 75.000000f, 65937, 34.959888f }, { 35.000000f, 56324, 33.180534f } } },
+ { "kodim20.png", { { 100.000000f, 72423, 37.353615f }, { 75.000000f, 49889, 36.328712f }, { 35.000000f, 41065, 34.822941f } } },
+ { "kodim21.png", { { 100.000000f, 80646, 34.472473f }, { 75.000000f, 63150, 33.743328f }, { 35.000000f, 52850, 32.177635f } } },
+ { "kodim22.png", { { 100.000000f, 82579, 35.452717f }, { 75.000000f, 71076, 34.477604f }, { 35.000000f, 60499, 32.928665f } } },
+ { "kodim23.png", { { 100.000000f, 80795, 39.518650f }, { 75.000000f, 57122, 38.509109f }, { 35.000000f, 48485, 37.112495f } } },
+ { "kodim24.png", { { 100.000000f, 80427, 32.505745f }, { 75.000000f, 73730, 32.054939f }, { 35.000000f, 65558, 30.470564f } } },
+ { "white_1x1.png", { { 100.000000f, 142, 100.000000f }, { 75.000000f, 143, 100.000000f }, { 35.000000f, 143, 100.000000f } } },
+ { "wikipedia.png", { { 100.000000f, 112186, 29.022635f }, { 75.000000f, 109799, 29.001532f }, { 35.000000f, 109059, 28.538498f } } },
+};
+xuastc_ldr_test_file g_xuastc_ldr_test_files_10x5[] =
+{
+ { "black_1x1.png", { { 100.000000f, 142, 100.000000f }, { 75.000000f, 143, 100.000000f }, { 35.000000f, 143, 100.000000f } } },
+ { "kodim01.png", { { 100.000000f, 105404, 34.920200f }, { 75.000000f, 100965, 33.497791f }, { 35.000000f, 90611, 31.226894f } } },
+ { "kodim02.png", { { 100.000000f, 99914, 38.595970f }, { 75.000000f, 80665, 36.986778f }, { 35.000000f, 64270, 35.383049f } } },
+ { "kodim03.png", { { 100.000000f, 98251, 40.790932f }, { 75.000000f, 67869, 38.898525f }, { 35.000000f, 54886, 36.762691f } } },
+ { "kodim04.png", { { 100.000000f, 102414, 38.946281f }, { 75.000000f, 79893, 37.033978f }, { 35.000000f, 66785, 35.246025f } } },
+ { "kodim05.png", { { 100.000000f, 108302, 33.642941f }, { 75.000000f, 104798, 32.862797f }, { 35.000000f, 95050, 30.865231f } } },
+ { "kodim06.png", { { 100.000000f, 101243, 36.626984f }, { 75.000000f, 85666, 34.866810f }, { 35.000000f, 71982, 32.394196f } } },
+ { "kodim07.png", { { 100.000000f, 101129, 39.211941f }, { 75.000000f, 76369, 38.055550f }, { 35.000000f, 65894, 36.308075f } } },
+ { "kodim08.png", { { 100.000000f, 107566, 33.244900f }, { 75.000000f, 101592, 32.423939f }, { 35.000000f, 92874, 30.506023f } } },
+ { "kodim09.png", { { 100.000000f, 100574, 40.205162f }, { 75.000000f, 63935, 38.267521f }, { 35.000000f, 52720, 36.494003f } } },
+ { "kodim10.png", { { 100.000000f, 101792, 39.791870f }, { 75.000000f, 74618, 38.100864f }, { 35.000000f, 60932, 36.214828f } } },
+ { "kodim11.png", { { 100.000000f, 101858, 36.660149f }, { 75.000000f, 86801, 35.305096f }, { 35.000000f, 72462, 33.252529f } } },
+ { "kodim12.png", { { 100.000000f, 99075, 40.883018f }, { 75.000000f, 69568, 38.559566f }, { 35.000000f, 54291, 36.616749f } } },
+ { "kodim13.png", { { 100.000000f, 106502, 31.561871f }, { 75.000000f, 106013, 30.642658f }, { 35.000000f, 95531, 28.429173f } } },
+ { "kodim14.png", { { 100.000000f, 105379, 35.074528f }, { 75.000000f, 98775, 33.989864f }, { 35.000000f, 84924, 32.170376f } } },
+ { "kodim15.png", { { 100.000000f, 100550, 38.506466f }, { 75.000000f, 73302, 36.981686f }, { 35.000000f, 60268, 35.197380f } } },
+ { "kodim16.png", { { 100.000000f, 99833, 40.386166f }, { 75.000000f, 79010, 37.707489f }, { 35.000000f, 63900, 35.129288f } } },
+ { "kodim17.png", { { 100.000000f, 102975, 38.561710f }, { 75.000000f, 79270, 37.071556f }, { 35.000000f, 66965, 35.134377f } } },
+ { "kodim18.png", { { 100.000000f, 107301, 34.268379f }, { 75.000000f, 95912, 33.241734f }, { 35.000000f, 83236, 31.308125f } } },
+ { "kodim19.png", { { 100.000000f, 103796, 37.636776f }, { 75.000000f, 82419, 35.821537f }, { 35.000000f, 69996, 33.751148f } } },
+ { "kodim20.png", { { 100.000000f, 91331, 38.943798f }, { 75.000000f, 61619, 37.297562f }, { 35.000000f, 49093, 35.451199f } } },
+ { "kodim21.png", { { 100.000000f, 102857, 36.242493f }, { 75.000000f, 77121, 34.898647f }, { 35.000000f, 64434, 32.857597f } } },
+ { "kodim22.png", { { 100.000000f, 104989, 36.767838f }, { 75.000000f, 88040, 35.332642f }, { 35.000000f, 74724, 33.503368f } } },
+ { "kodim23.png", { { 100.000000f, 102295, 40.710884f }, { 75.000000f, 71039, 39.289032f }, { 35.000000f, 60360, 37.647507f } } },
+ { "kodim24.png", { { 100.000000f, 102518, 34.120747f }, { 75.000000f, 92503, 33.321411f }, { 35.000000f, 81974, 31.218332f } } },
+ { "white_1x1.png", { { 100.000000f, 142, 100.000000f }, { 75.000000f, 143, 100.000000f }, { 35.000000f, 143, 100.000000f } } },
+ { "wikipedia.png", { { 100.000000f, 138617, 30.214272f }, { 75.000000f, 130237, 30.166813f }, { 35.000000f, 127678, 29.492228f } } },
+};
+xuastc_ldr_test_file g_xuastc_ldr_test_files_10x10[] =
+{
+ { "black_1x1.png", { { 100.000000f, 164, 100.000000f }, { 75.000000f, 165, 100.000000f }, { 35.000000f, 165, 100.000000f } } },
+ { "kodim01.png", { { 100.000000f, 53866, 30.283733f }, { 75.000000f, 53616, 30.075840f }, { 35.000000f, 50347, 29.163128f } } },
+ { "kodim02.png", { { 100.000000f, 51026, 35.123016f }, { 75.000000f, 46980, 34.621269f }, { 35.000000f, 37703, 33.580551f } } },
+ { "kodim03.png", { { 100.000000f, 51031, 36.793110f }, { 75.000000f, 41431, 36.207535f }, { 35.000000f, 34960, 35.044708f } } },
+ { "kodim04.png", { { 100.000000f, 53041, 35.332870f }, { 75.000000f, 46533, 34.796043f }, { 35.000000f, 39862, 33.541615f } } },
+ { "kodim05.png", { { 100.000000f, 55189, 29.119299f }, { 75.000000f, 55108, 29.015081f }, { 35.000000f, 51969, 28.237648f } } },
+ { "kodim06.png", { { 100.000000f, 51951, 31.890593f }, { 75.000000f, 48228, 31.475647f }, { 35.000000f, 43607, 30.357023f } } },
+ { "kodim07.png", { { 100.000000f, 51629, 35.143024f }, { 75.000000f, 45495, 34.901260f }, { 35.000000f, 39163, 33.916348f } } },
+ { "kodim08.png", { { 100.000000f, 54592, 28.505661f }, { 75.000000f, 54535, 28.359890f }, { 35.000000f, 51421, 27.674751f } } },
+ { "kodim09.png", { { 100.000000f, 51854, 35.922009f }, { 75.000000f, 40015, 35.579926f }, { 35.000000f, 34664, 34.545792f } } },
+ { "kodim10.png", { { 100.000000f, 52237, 35.590847f }, { 75.000000f, 44712, 35.157581f }, { 35.000000f, 37465, 34.189934f } } },
+ { "kodim11.png", { { 100.000000f, 53091, 32.386562f }, { 75.000000f, 49709, 32.083656f }, { 35.000000f, 43594, 31.182316f } } },
+ { "kodim12.png", { { 100.000000f, 50822, 36.772594f }, { 75.000000f, 43481, 36.005375f }, { 35.000000f, 35465, 34.854721f } } },
+ { "kodim13.png", { { 100.000000f, 54490, 26.917747f }, { 75.000000f, 54727, 26.806589f }, { 35.000000f, 52410, 26.057467f } } },
+ { "kodim14.png", { { 100.000000f, 54156, 30.993803f }, { 75.000000f, 53868, 30.794903f }, { 35.000000f, 50038, 29.964369f } } },
+ { "kodim15.png", { { 100.000000f, 52157, 35.004284f }, { 75.000000f, 43205, 34.554562f }, { 35.000000f, 36555, 33.478485f } } },
+ { "kodim16.png", { { 100.000000f, 51024, 35.731007f }, { 75.000000f, 46169, 34.875183f }, { 35.000000f, 40174, 33.368984f } } },
+ { "kodim17.png", { { 100.000000f, 53562, 34.584801f }, { 75.000000f, 46775, 34.203426f }, { 35.000000f, 40370, 33.207817f } } },
+ { "kodim18.png", { { 100.000000f, 54710, 30.220909f }, { 75.000000f, 52152, 30.071476f }, { 35.000000f, 47304, 29.228931f } } },
+ { "kodim19.png", { { 100.000000f, 52609, 33.399029f }, { 75.000000f, 46521, 32.911697f }, { 35.000000f, 40742, 31.708160f } } },
+ { "kodim20.png", { { 100.000000f, 48131, 34.454784f }, { 75.000000f, 38410, 34.073807f }, { 35.000000f, 32410, 33.349335f } } },
+ { "kodim21.png", { { 100.000000f, 52704, 31.636679f }, { 75.000000f, 45878, 31.425013f }, { 35.000000f, 39147, 30.604250f } } },
+ { "kodim22.png", { { 100.000000f, 53864, 33.018543f }, { 75.000000f, 49948, 32.652431f }, { 35.000000f, 42747, 31.578960f } } },
+ { "kodim23.png", { { 100.000000f, 52922, 36.926800f }, { 75.000000f, 42386, 36.554390f }, { 35.000000f, 36771, 35.552139f } } },
+ { "kodim24.png", { { 100.000000f, 52621, 29.655001f }, { 75.000000f, 51282, 29.539488f }, { 35.000000f, 46518, 28.826057f } } },
+ { "white_1x1.png", { { 100.000000f, 164, 100.000000f }, { 75.000000f, 165, 100.000000f }, { 35.000000f, 165, 100.000000f } } },
+ { "wikipedia.png", { { 100.000000f, 86446, 26.514196f }, { 75.000000f, 82331, 26.514242f }, { 35.000000f, 83217, 26.387506f } } },
+};
+xuastc_ldr_test_file g_xuastc_ldr_test_files_12x12[] =
+{
+ { "black_1x1.png", { { 100.000000f, 164, 100.000000f }, { 75.000000f, 165, 100.000000f }, { 35.000000f, 165, 100.000000f } } },
+ { "kodim01.png", { { 100.000000f, 37797, 28.798088f }, { 75.000000f, 37597, 28.722265f }, { 35.000000f, 36100, 28.216297f } } },
+ { "kodim02.png", { { 100.000000f, 35764, 33.883194f }, { 75.000000f, 34288, 33.613396f }, { 35.000000f, 28049, 32.870983f } } },
+ { "kodim03.png", { { 100.000000f, 35991, 35.376232f }, { 75.000000f, 30705, 35.071907f }, { 35.000000f, 26302, 34.256138f } } },
+ { "kodim04.png", { { 100.000000f, 37256, 33.985607f }, { 75.000000f, 34480, 33.726845f }, { 35.000000f, 30001, 32.846050f } } },
+ { "kodim05.png", { { 100.000000f, 38362, 27.451393f }, { 75.000000f, 38445, 27.405621f }, { 35.000000f, 37828, 27.075932f } } },
+ { "kodim06.png", { { 100.000000f, 36824, 30.414103f }, { 75.000000f, 35002, 30.223726f }, { 35.000000f, 32224, 29.430540f } } },
+ { "kodim07.png", { { 100.000000f, 36160, 33.560291f }, { 75.000000f, 33870, 33.467850f }, { 35.000000f, 29863, 32.859840f } } },
+ { "kodim08.png", { { 100.000000f, 37995, 26.794462f }, { 75.000000f, 38093, 26.760965f }, { 35.000000f, 37094, 26.328840f } } },
+ { "kodim09.png", { { 100.000000f, 36401, 34.493057f }, { 75.000000f, 29870, 34.332375f }, { 35.000000f, 26454, 33.613277f } } },
+ { "kodim10.png", { { 100.000000f, 36501, 33.844082f }, { 75.000000f, 32742, 33.686195f }, { 35.000000f, 28238, 33.049423f } } },
+ { "kodim11.png", { { 100.000000f, 37149, 30.947369f }, { 75.000000f, 36099, 30.802794f }, { 35.000000f, 32881, 30.267765f } } },
+ { "kodim12.png", { { 100.000000f, 35849, 35.354534f }, { 75.000000f, 33053, 34.918404f }, { 35.000000f, 26873, 34.069679f } } },
+ { "kodim13.png", { { 100.000000f, 37949, 25.461622f }, { 75.000000f, 37959, 25.415657f }, { 35.000000f, 38216, 25.047762f } } },
+ { "kodim14.png", { { 100.000000f, 37660, 29.483248f }, { 75.000000f, 37843, 29.437609f }, { 35.000000f, 36214, 28.999678f } } },
+ { "kodim15.png", { { 100.000000f, 36649, 33.611992f }, { 75.000000f, 31835, 33.415279f }, { 35.000000f, 27625, 32.736401f } } },
+ { "kodim16.png", { { 100.000000f, 35972, 34.145802f }, { 75.000000f, 33835, 33.685509f }, { 35.000000f, 29950, 32.574253f } } },
+ { "kodim17.png", { { 100.000000f, 37435, 33.135811f }, { 75.000000f, 34680, 32.991879f }, { 35.000000f, 30935, 32.323273f } } },
+ { "kodim18.png", { { 100.000000f, 37958, 28.844978f }, { 75.000000f, 37232, 28.796362f }, { 35.000000f, 34487, 28.304411f } } },
+ { "kodim19.png", { { 100.000000f, 36608, 31.887293f }, { 75.000000f, 34105, 31.679951f }, { 35.000000f, 29955, 30.871086f } } },
+ { "kodim20.png", { { 100.000000f, 34010, 32.896221f }, { 75.000000f, 28133, 32.748100f }, { 35.000000f, 24280, 32.296890f } } },
+ { "kodim21.png", { { 100.000000f, 36704, 30.122864f }, { 75.000000f, 34125, 30.046625f }, { 35.000000f, 29272, 29.595934f } } },
+ { "kodim22.png", { { 100.000000f, 37758, 31.691223f }, { 75.000000f, 35986, 31.529528f }, { 35.000000f, 32299, 30.867397f } } },
+ { "kodim23.png", { { 100.000000f, 37121, 35.387283f }, { 75.000000f, 31453, 35.222927f }, { 35.000000f, 27471, 34.600704f } } },
+ { "kodim24.png", { { 100.000000f, 36877, 28.162710f }, { 75.000000f, 36580, 28.106171f }, { 35.000000f, 34104, 27.782391f } } },
+ { "white_1x1.png", { { 100.000000f, 164, 100.000000f }, { 75.000000f, 165, 100.000000f }, { 35.000000f, 165, 100.000000f } } },
+ { "wikipedia.png", { { 100.000000f, 64288, 24.992327f }, { 75.000000f, 62309, 24.994276f }, { 35.000000f, 61714, 24.962925f } } },
+};
+
+
static bool test_mode_xuastc_ldr(command_line_params& opts)
{
uint32_t total_mismatches = 0;
@@ -5401,118 +7400,178 @@ static bool test_mode_xuastc_ldr(command_line_params& opts)
// Minor differences in how floating point code is optimized can result in slightly different generated files.
// XUASTC LDR's IDCT is currently float - at low q's and high (>48) dB's tiny differences during decompression are noticeable
- const float XUASTC_PSNR_THRESHOLD = 1.0f;
+ const float XUASTC_PSNR_THRESHOLD = 1.0f;
const float XUASTC_FILESIZE_THRESHOLD = .045f;
- struct run_stats
+ // Dump mode (-test_xuastc_dump): run the compressor across all block sizes and print the
+ // expected-value tables (copy/paste into the code below), then return without validating.
+ // Uses g_xuastc_ldr_test_files_6x6 for the shared file list (filenames + DCT q's).
+ if (opts.m_xuastc_test_dump)
{
- size_t m_comp_size;
- image_stats m_stats;
+ const struct { basist::basis_tex_format m_fmt; const char* m_pName; } block_sizes[] =
+ {
+ { basist::basis_tex_format::cXUASTC_LDR_4x4, "4x4" },
+ { basist::basis_tex_format::cXUASTC_LDR_5x4, "5x4" },
+ { basist::basis_tex_format::cXUASTC_LDR_6x6, "6x6" },
+ { basist::basis_tex_format::cXUASTC_LDR_8x8, "8x8" },
+ { basist::basis_tex_format::cXUASTC_LDR_10x5, "10x5" },
+ { basist::basis_tex_format::cXUASTC_LDR_10x10, "10x10" },
+ { basist::basis_tex_format::cXUASTC_LDR_12x12, "12x12" },
+ };
+
+ const uint32_t effort_level = 8;
+ const bool use_srgb_metrics = false; // false = linear (1,1,1,1) weights
+
+ for (uint32_t bs = 0; bs < std::size(block_sizes); bs++)
+ {
+ fmt_printf("\nxuastc_ldr_test_file g_xuastc_ldr_test_files_{}[] =\n{{\n", block_sizes[bs].m_pName);
+
+ for (uint32_t i = 0; i < std::size(g_xuastc_ldr_test_files_6x6); i++)
+ {
+ const auto& test_file = g_xuastc_ldr_test_files_6x6[i];
+
+ std::string filename(opts.m_test_file_dir);
+ if (filename.size())
+ filename.push_back('/');
+ filename += std::string(test_file.m_pFilename);
+
+ basisu::vector source_images(1);
+ if (!load_png(filename.c_str(), source_images[0]))
+ {
+ error_printf("Failed loading test image \"%s\"\n", filename.c_str());
+ return false;
+ }
+
+ uint32_t run_sizes[XUASTC_LDR_TEST_FILE_NUM_RUNS];
+ float run_psnrs[XUASTC_LDR_TEST_FILE_NUM_RUNS];
+
+ for (uint32_t run_index = 0; run_index < XUASTC_LDR_TEST_FILE_NUM_RUNS; run_index++)
+ {
+ const auto& test_run = test_file.m_test_runs[run_index];
+
+ uint32_t flags_and_quality = (opts.m_comp_params.m_multithreading ? cFlagThreaded : 0) | cFlagPrintStats | cFlagPrintStatus | (use_srgb_metrics ? cFlagSRGB : 0);
+ flags_and_quality |= effort_level;
+
+ float uastc_rdo_quality = (test_run.m_dct_q < 100.0f) ? test_run.m_dct_q : 0.0f;
+ size_t data_size = 0;
+ image_stats stats;
+
+ void* pData = basis_compress(block_sizes[bs].m_fmt, source_images, flags_and_quality, uastc_rdo_quality, &data_size, &stats);
+ if (!pData)
+ {
+ error_printf("basis_compress() failed!\n");
+ return false;
+ }
+ basis_free_data(pData);
+
+ run_sizes[run_index] = (uint32_t)data_size;
+ run_psnrs[run_index] = stats.m_basis_rgba_avg_psnr;
+ }
+
+ fmt_printf("\t{{ \"{}\", {{", test_file.m_pFilename);
+ for (uint32_t run_index = 0; run_index < XUASTC_LDR_TEST_FILE_NUM_RUNS; run_index++)
+ {
+ fmt_printf(" {{ {}f, {}, {}f }", test_file.m_test_runs[run_index].m_dct_q, run_sizes[run_index], run_psnrs[run_index]);
+ if (run_index != (XUASTC_LDR_TEST_FILE_NUM_RUNS - 1))
+ fmt_printf(", ");
+ }
+ fmt_printf(" } },\n");
+ }
+
+ fmt_printf("};\n");
+ }
+
+ return true;
+ }
+
+ const bool use_srgb_metrics = false; // false = linear (1,1,1,1) weights, true = sRGB perceptual (9,11,1,11)
+ const uint32_t effort_level = 8;
+
+ // Validate each block size against its stored expected-value table.
+ struct block_size_test
+ {
+ basist::basis_tex_format m_fmt;
+ const char* m_pName;
+ const xuastc_ldr_test_file* m_pFiles;
+ uint32_t m_num_files;
};
- basisu::vector2D< run_stats > run_image_stats((uint32_t)std::size(g_xuastc_ldr_test_files_6x6), XUASTC_LDR_TEST_FILE_NUM_RUNS);
-
- for (uint32_t i = 0; i < std::size(g_xuastc_ldr_test_files_6x6); i++)
+ const block_size_test block_sizes[] =
{
- const auto& test_file = g_xuastc_ldr_test_files_6x6[i];
+ { basist::basis_tex_format::cXUASTC_LDR_4x4, "4x4", g_xuastc_ldr_test_files_4x4, (uint32_t)std::size(g_xuastc_ldr_test_files_4x4) },
+ { basist::basis_tex_format::cXUASTC_LDR_5x4, "5x4", g_xuastc_ldr_test_files_5x4, (uint32_t)std::size(g_xuastc_ldr_test_files_5x4) },
+ { basist::basis_tex_format::cXUASTC_LDR_6x6, "6x6", g_xuastc_ldr_test_files_6x6, (uint32_t)std::size(g_xuastc_ldr_test_files_6x6) },
+ { basist::basis_tex_format::cXUASTC_LDR_8x8, "8x8", g_xuastc_ldr_test_files_8x8, (uint32_t)std::size(g_xuastc_ldr_test_files_8x8) },
+ { basist::basis_tex_format::cXUASTC_LDR_10x5, "10x5", g_xuastc_ldr_test_files_10x5, (uint32_t)std::size(g_xuastc_ldr_test_files_10x5) },
+ { basist::basis_tex_format::cXUASTC_LDR_10x10, "10x10", g_xuastc_ldr_test_files_10x10, (uint32_t)std::size(g_xuastc_ldr_test_files_10x10) },
+ { basist::basis_tex_format::cXUASTC_LDR_12x12, "12x12", g_xuastc_ldr_test_files_12x12, (uint32_t)std::size(g_xuastc_ldr_test_files_12x12) },
+ };
- std::string filename(opts.m_test_file_dir);
- if (filename.size())
+ for (uint32_t bs = 0; bs < std::size(block_sizes); bs++)
+ {
+ const block_size_test& bsize = block_sizes[bs];
+
+ for (uint32_t i = 0; i < bsize.m_num_files; i++)
{
- filename.push_back('/');
- }
- filename += std::string(test_file.m_pFilename);
+ const xuastc_ldr_test_file& test_file = bsize.m_pFiles[i];
- basisu::vector source_images(1);
+ std::string filename(opts.m_test_file_dir);
+ if (filename.size())
+ filename.push_back('/');
+ filename += std::string(test_file.m_pFilename);
- image& source_image = source_images[0];
- if (!load_png(filename.c_str(), source_image))
- {
- error_printf("Failed loading test image \"%s\"\n", filename.c_str());
- return false;
- }
-
- printf("Loaded file \"%s\", dimensions %ux%u has alpha: %u\n", filename.c_str(), source_image.get_width(), source_image.get_height(), source_image.has_alpha());
-
- image_stats stats;
-
- uint32_t flags_and_quality;
-
- // Test XUASTC LDR
- flags_and_quality = (opts.m_comp_params.m_multithreading ? cFlagThreaded : 0) | cFlagPrintStats | cFlagPrintStatus | cFlagSRGB;
-
- for (uint32_t run_index = 0; run_index < XUASTC_LDR_TEST_FILE_NUM_RUNS; run_index++)
- {
- const auto& test_run = test_file.m_test_runs[run_index];
-
- float uastc_rdo_quality = 0.0f;
- size_t data_size = 0;
-
- const uint32_t effort_level = 8;
-
- flags_and_quality &= ~0xFF;
- flags_and_quality |= effort_level;
-
- if (test_run.m_dct_q < 100.0f)
+ basisu::vector source_images(1);
+ image& source_image = source_images[0];
+ if (!load_png(filename.c_str(), source_image))
{
- uastc_rdo_quality = test_run.m_dct_q;
- }
-
- basist::basis_tex_format tex_fmt = basist::basis_tex_format::cXUASTC_LDR_6x6;
-
- fmt_printf("**** Testing XUASTC LDR, DCT q {}, effort {}\n", test_run.m_dct_q, effort_level);
-
- void* pData = basis_compress(tex_fmt, source_images, flags_and_quality, uastc_rdo_quality, &data_size, &stats);
- if (!pData)
- {
- error_printf("basis_compress() failed!\n");
+ error_printf("Failed loading test image \"%s\"\n", filename.c_str());
return false;
}
- basis_free_data(pData);
- fmt_printf("XUASTC Size: {} (expected {}), RGBA PSNR: {3.3} dB (expected {3.3} dB)\n",
- (uint32_t)data_size, test_run.m_comp_size,
- stats.m_basis_rgba_avg_psnr, test_run.m_rgba_psnr);
+ printf("Loaded file \"%s\", dimensions %ux%u has alpha: %u\n", filename.c_str(), source_image.get_width(), source_image.get_height(), source_image.has_alpha());
- float file_size_ratio = fabs((data_size / (float)test_run.m_comp_size) - 1.0f);
+ image_stats stats;
- if (file_size_ratio > XUASTC_FILESIZE_THRESHOLD)
+ for (uint32_t run_index = 0; run_index < XUASTC_LDR_TEST_FILE_NUM_RUNS; run_index++)
{
- fmt_error_printf("Mismatch: Expected XUASTC LDR file size was {}, but got {} instead!\n", test_run.m_comp_size, (uint32_t)data_size);
- total_mismatches++;
- }
+ const auto& test_run = test_file.m_test_runs[run_index];
- if (fabs(stats.m_basis_rgba_avg_psnr - test_run.m_rgba_psnr) > XUASTC_PSNR_THRESHOLD)
- {
- fmt_error_printf("Mismatch: Expected XUASTC LDR RGBA Avg PSNR was {}, but got {} instead!\n", test_run.m_rgba_psnr, stats.m_basis_rgba_avg_psnr);
- total_mismatches++;
- }
+ uint32_t flags_and_quality = (opts.m_comp_params.m_multithreading ? cFlagThreaded : 0) | cFlagPrintStats | cFlagPrintStatus | (use_srgb_metrics ? cFlagSRGB : 0);
+ flags_and_quality |= effort_level;
- run_image_stats(i, run_index).m_comp_size = data_size;
- run_image_stats(i, run_index).m_stats = stats;
+ float uastc_rdo_quality = (test_run.m_dct_q < 100.0f) ? test_run.m_dct_q : 0.0f;
+ size_t data_size = 0;
+
+ fmt_printf("**** Testing XUASTC LDR {}, DCT q {}, effort {}\n", bsize.m_pName, test_run.m_dct_q, effort_level);
+
+ void* pData = basis_compress(bsize.m_fmt, source_images, flags_and_quality, uastc_rdo_quality, &data_size, &stats);
+ if (!pData)
+ {
+ error_printf("basis_compress() failed!\n");
+ return false;
+ }
+ basis_free_data(pData);
+
+ fmt_printf("XUASTC Size: {} (expected {}), RGBA PSNR: {3.3} dB (expected {3.3} dB)\n",
+ (uint32_t)data_size, test_run.m_comp_size,
+ stats.m_basis_rgba_avg_psnr, test_run.m_rgba_psnr);
+
+ float file_size_ratio = fabs((data_size / (float)test_run.m_comp_size) - 1.0f);
+ if (file_size_ratio > XUASTC_FILESIZE_THRESHOLD)
+ {
+ fmt_error_printf("Mismatch: XUASTC LDR {} expected file size {}, but got {} instead!\n", bsize.m_pName, test_run.m_comp_size, (uint32_t)data_size);
+ total_mismatches++;
+ }
+
+ if (fabs(stats.m_basis_rgba_avg_psnr - test_run.m_rgba_psnr) > XUASTC_PSNR_THRESHOLD)
+ {
+ fmt_error_printf("Mismatch: XUASTC LDR {} expected RGBA Avg PSNR {}, but got {} instead!\n", bsize.m_pName, test_run.m_rgba_psnr, stats.m_basis_rgba_avg_psnr);
+ total_mismatches++;
+ }
+ }
}
}
-#if 0
- for (uint32_t i = 0; i < std::size(g_xuastc_ldr_test_files_6x6); i++)
- {
- fmt_printf("{{ \"{}\", {{", g_xuastc_ldr_test_files_6x6[i].m_pFilename);
-
- for (uint32_t j = 0; j < XUASTC_LDR_TEST_FILE_NUM_RUNS; j++)
- {
- fmt_printf(" {{ {}f, {}, {}f }",
- g_xuastc_ldr_test_files_6x6[i].m_test_runs[j].m_dct_q,
- run_image_stats(i, j).m_comp_size,
- run_image_stats(i, j).m_stats.m_basis_rgba_avg_psnr);
-
- if (j != (XUASTC_LDR_TEST_FILE_NUM_RUNS - 1))
- fmt_printf(", ");
- }
-
- fmt_printf(" } },\n");
- }
-#endif
-
printf("Total XUASTC LDR mismatches: %u\n", total_mismatches);
bool result = true;
@@ -5547,6 +7606,443 @@ static bool clbench_mode(command_line_params& opts)
return true;
}
+// ---------------------------------------------------------------------------------------------------------------------
+// -test_codecs / -test_codecs_gen
+//
+// Sweeps every supported LDR and HDR codec across a quality x effort grid, driving the basis_compressor DIRECTLY
+// (filenames in, m_compute_stats + m_validate_output_data on). The compressor transcodes each result to >=2 formats and
+// computes their PSNRs. -test_codecs_gen writes the golden expected values to basisu_tool_test_codecs.inl; -test_codecs
+// re-runs the sweep and compares against that table (with build/OS/compiler-divergence tolerances).
+// ---------------------------------------------------------------------------------------------------------------------
+
+struct codec_test_case
+{
+ const char* m_pFilename;
+ basist::basis_tex_format m_fmt;
+ int m_quality; // [1,100]
+ int m_effort; // [0,100]
+ bool m_hdr;
+ uint32_t m_size; // expected KTX2 size, bytes
+ float m_rgb_psnr; // LDR: native RGB Avg | HDR: native ASTC-HDR log2
+ float m_rgba_psnr; // LDR: native RGBA Avg | HDR: -1 (n/a)
+ float m_2nd_psnr; // LDR: transcoded BC7 RGBA Avg | HDR: transcoded BC6H log2
+};
+
+#include "basisu_tool_test_codecs.inl"
+
+// The codec sweep (also used to emit the golden table). Order is LDR codecs first, then HDR.
+struct codec_sweep_desc { basist::basis_tex_format m_fmt; const char* m_pEnum_name; bool m_hdr; };
+static const codec_sweep_desc g_codec_sweep[] =
+{
+ { basist::basis_tex_format::cETC1S, "cETC1S", false },
+ { basist::basis_tex_format::cUASTC_LDR_4x4, "cUASTC_LDR_4x4", false },
+ { basist::basis_tex_format::cXUBC7, "cXUBC7", false },
+ { basist::basis_tex_format::cASTC_LDR_4x4, "cASTC_LDR_4x4", false },
+ { basist::basis_tex_format::cASTC_LDR_6x6, "cASTC_LDR_6x6", false },
+ { basist::basis_tex_format::cASTC_LDR_10x10, "cASTC_LDR_10x10", false },
+ { basist::basis_tex_format::cASTC_LDR_12x12, "cASTC_LDR_12x12", false },
+ { basist::basis_tex_format::cXUASTC_LDR_4x4, "cXUASTC_LDR_4x4", false },
+ { basist::basis_tex_format::cXUASTC_LDR_6x6, "cXUASTC_LDR_6x6", false },
+ { basist::basis_tex_format::cXUASTC_LDR_10x10, "cXUASTC_LDR_10x10", false },
+ { basist::basis_tex_format::cXUASTC_LDR_12x12, "cXUASTC_LDR_12x12", false },
+ { basist::basis_tex_format::cUASTC_HDR_4x4, "cUASTC_HDR_4x4", true },
+ { basist::basis_tex_format::cASTC_HDR_6x6, "cASTC_HDR_6x6", true },
+ { basist::basis_tex_format::cUASTC_HDR_6x6_INTERMEDIATE,"cUASTC_HDR_6x6_INTERMEDIATE", true },
+};
+
+static const char* g_codec_ldr_test_files[] = { "kodim03.png", "kodim23.png", "kodim18.png", "alpha0.png", "wikipedia.png", "black_1x1.png" };
+static const char* g_codec_hdr_test_files[] = { "Desk.exr", "atrium.exr", "yucca.exr", "kodim18.png" }; // kodim18.png is auto-upconverted LDR->linear->100 nit HDR
+static const int g_codec_test_qualities[] = { 10, 25, 50, 75, 100 };
+static const int g_codec_test_efforts[] = { 0, 3, 6 };
+
+// Case-insensitive token-boundary match: filter matches at the start of pName or immediately after a '_'.
+// So "ASTC" matches "ASTC_LDR_4x4" but NOT "XUASTC_LDR_4x4"/"UASTC_HDR_4x4"; "XUASTC" matches all XUASTC sizes; "HDR" matches all *_HDR_*.
+static bool codec_name_matches_filter(const char* pName, const std::string& filter)
+{
+ if (filter.empty())
+ return true;
+ std::string n(pName), f(filter);
+ for (char& c : n) c = (char)toupper((uint8_t)c);
+ for (char& c : f) c = (char)toupper((uint8_t)c);
+ size_t pos = 0;
+ while ((pos = n.find(f, pos)) != std::string::npos)
+ {
+ if ((pos == 0) || (n[pos - 1] == '_'))
+ return true;
+ pos++;
+ }
+ return false;
+}
+
+// Short (no leading 'c') enum name for a format, for filtering/reporting.
+static const char* codec_short_name(basist::basis_tex_format fmt)
+{
+ for (const auto& d : g_codec_sweep)
+ if (d.m_fmt == fmt)
+ return d.m_pEnum_name + 1; // skip leading 'c'
+ return "?";
+}
+
+// Pull the headline (native) + 2nd-format PSNRs out of an image_stats, per LDR/HDR.
+static void codec_extract_psnrs(const basisu::image_stats& s, bool hdr, float& rgb, float& rgba, float& second)
+{
+ if (hdr)
+ {
+ rgb = s.m_basis_rgb_avg_astc_hdr_log2_psnr;
+ rgba = -1.0f;
+ second = s.m_basis_rgb_avg_bc6h_log2_psnr;
+ }
+ else
+ {
+ rgb = s.m_basis_rgb_avg_psnr;
+ rgba = s.m_basis_rgba_avg_psnr;
+ second = s.m_bc7_rgba_avg_psnr;
+ }
+}
+
+// Run one case via the compressor directly. Returns false on compress failure.
+static bool run_codec_test_case(basisu::job_pool& jp, const std::string& dir, const char* pFilename,
+ basist::basis_tex_format fmt, [[maybe_unused]] bool hdr, int quality, int effort,
+ uint32_t& out_size, basisu::image_stats& out_stats, double& out_time_ms)
+{
+ out_time_ms = 0.0;
+
+ std::string path(dir);
+ if (path.size())
+ path.push_back('/');
+ path += pFilename;
+
+ basis_compressor_params params;
+ params.m_pJob_pool = &jp;
+ params.m_multithreading = true;
+ params.m_read_source_images = true; // let the compressor load the file (and upconvert LDR->HDR when m_hdr is set)
+ params.m_source_filenames.push_back(path);
+ params.m_create_ktx2_file = true; // measure the .KTX2 file size
+ params.m_write_output_basis_or_ktx2_files = false; // keep it in memory only
+ params.m_compute_stats = true; // fill image_stats (native + transcoded BC7/BC6H PSNRs)
+ params.m_print_stats = false;
+ params.m_validate_output_data = true; // validate the encoded output
+ params.m_perceptual = true; // sRGB-style metrics (held constant for gen + run)
+ params.m_status_output = false;
+ params.m_debug = false;
+
+ // Sets m_hdr/m_uastc and maps quality[1,100]/effort[0,100] to the codec's low-level knobs.
+ if (!params.set_format_mode_and_quality_effort(fmt, quality, effort, true))
+ return false;
+
+ interval_timer tm;
+ tm.start();
+
+ basis_compressor comp;
+ if (!comp.init(params))
+ return false;
+
+ if (comp.process() != basis_compressor::cECSuccess)
+ return false;
+
+ out_time_ms = tm.get_elapsed_ms();
+
+ out_size = (uint32_t)comp.get_output_ktx2_file().size();
+
+ const basisu::vector& sv = comp.get_stats();
+ if (!sv.size())
+ return false;
+ out_stats = sv[0];
+
+ return true;
+}
+
+static uint32_t codec_test_num_threads()
+{
+ uint32_t n = std::thread::hardware_concurrency();
+ return n ? n : 1;
+}
+
+// Returns the path prefix to the repo root so -test_codecs works whether run from the repo
+// root ("") or the bin/ subdir ("../"). Probes for test_files/kodim03.png.
+static std::string codec_test_root_prefix()
+{
+ FILE* f = fopen_safe("test_files/kodim03.png", "rb");
+ if (f) { fclose(f); return ""; }
+ f = fopen_safe("../test_files/kodim03.png", "rb");
+ if (f) { fclose(f); return "../"; }
+ return ""; // fallback: repo-root-relative
+}
+
+static bool test_codecs_generate(command_line_params& opts)
+{
+ const std::string dir = opts.m_test_dir_explicit ? opts.m_test_file_dir : (codec_test_root_prefix() + "test_files");
+ const std::string out_path = opts.m_test_codecs_gen_filename.size() ? opts.m_test_codecs_gen_filename : (codec_test_root_prefix() + "basisu_tool_test_codecs.inl");
+
+ fmt_printf("Generating codec test table: dir \"{}\", output \"{}\"", dir, out_path);
+ if (opts.m_codec_filter.size())
+ fmt_printf(", codec filter \"{}\"", opts.m_codec_filter);
+ fmt_printf("\n");
+
+ FILE* pFile = fopen_safe(out_path.c_str(), "wb");
+ if (!pFile)
+ {
+ error_printf("Failed creating output file \"%s\"\n", out_path.c_str());
+ return false;
+ }
+
+ fprintf(pFile, "// basisu_tool_test_codecs.inl\n");
+ fprintf(pFile, "//\n// AUTO-GENERATED by: basisu -test_codecs_gen [outfile.inl] [-test_dir ] [codec_filter]\n");
+ fprintf(pFile, "// Do NOT edit by hand. Regenerate whenever a codec changes its output.\n");
+ fprintf(pFile, "//\n// Row: file, basis_tex_format, quality[1,100], effort[0,100], is_hdr, ktx2_size,\n");
+ fprintf(pFile, "// rgb_psnr (LDR native RGB | HDR ASTC-HDR log2), rgba_psnr (LDR native RGBA | HDR -1),\n");
+ fprintf(pFile, "// 2nd_psnr (LDR BC7 RGBA | HDR BC6H log2).\n//\n");
+ fprintf(pFile, "static const codec_test_case g_codec_test_cases[] =\n{\n");
+
+ basisu::job_pool jp(codec_test_num_threads());
+
+ uint32_t total = 0, fails = 0;
+
+ for (const auto& codec : g_codec_sweep)
+ {
+ if (!codec_name_matches_filter(codec.m_pEnum_name + 1, opts.m_codec_filter))
+ continue;
+
+ const char* const* pFiles = codec.m_hdr ? g_codec_hdr_test_files : g_codec_ldr_test_files;
+ const uint32_t num_files = codec.m_hdr ? (uint32_t)std::size(g_codec_hdr_test_files) : (uint32_t)std::size(g_codec_ldr_test_files);
+
+ fprintf(pFile, "\t// ---- %s ----\n", codec.m_pEnum_name + 1);
+
+ for (uint32_t fi = 0; fi < num_files; fi++)
+ {
+ for (int q : g_codec_test_qualities)
+ {
+ for (int e : g_codec_test_efforts)
+ {
+ uint32_t size = 0;
+ basisu::image_stats stats;
+ double elapsed_ms = 0.0;
+
+ if (!run_codec_test_case(jp, dir, pFiles[fi], codec.m_fmt, codec.m_hdr, q, e, size, stats, elapsed_ms))
+ {
+ fmt_printf("COMPRESS FAILED: {} {} q{} e{}\n", codec.m_pEnum_name + 1, pFiles[fi], q, e);
+ fprintf(pFile, "\t// COMPRESS FAILED: { \"%s\", basist::basis_tex_format::%s, %d, %d, %s },\n",
+ pFiles[fi], codec.m_pEnum_name, q, e, codec.m_hdr ? "true" : "false");
+ fails++;
+ total++;
+ continue;
+ }
+
+ float rgb, rgba, second;
+ codec_extract_psnrs(stats, codec.m_hdr, rgb, rgba, second);
+
+ // Progress: print every ~10 runs unless the user passed -quiet/-no_status_output (m_status_output==false).
+ if (opts.m_comp_params.m_status_output && ((total % 10) == 0))
+ fmt_printf("[{}] {} q{} e{} {} size {} rgb {}\n", total, codec.m_pEnum_name + 1, q, e, pFiles[fi], size, rgb);
+
+ fprintf(pFile, "\t{ \"%s\", basist::basis_tex_format::%s, %d, %d, %s, %u, %.4ff, %.4ff, %.4ff },\n",
+ pFiles[fi], codec.m_pEnum_name, q, e, codec.m_hdr ? "true" : "false", size, rgb, rgba, second);
+
+ total++;
+ }
+ }
+ }
+ }
+
+ fprintf(pFile, "};\n");
+ fclose(pFile);
+
+ fmt_printf("\nWrote {} cases ({} compress failures) to \"{}\"\n", total, fails, out_path);
+ return fails == 0;
+}
+
+static bool test_codecs_run(command_line_params& opts)
+{
+ const std::string dir = opts.m_test_dir_explicit ? opts.m_test_file_dir : (codec_test_root_prefix() + "test_files");
+
+#ifdef USE_TIGHTER_TEST_TOLERANCES
+ const float PSNR_THRESHOLD = .125f;
+ const float XUASTC_PSNR_THRESHOLD = .5f;
+#else
+ const float PSNR_THRESHOLD = 5.0f;
+ const float XUASTC_PSNR_THRESHOLD = 5.0f; // XUASTC LDR has more cross-build variance (matches test_mode_xuastc_ldr)
+#endif
+ const float FILESIZE_THRESHOLD = .045f; // 4.5% relative, matches the other -test modes
+ // Tiny outputs (e.g. the XUASTC LDR runs on small alpha test images) jitter by a large RELATIVE amount across
+ // builds (MSVC vs clang) for a small absolute byte delta, tripping the 4.5% check spuriously. For golden sizes
+ // below this many bytes, relax to a much looser relative threshold so those don't fail the build.
+ const uint32_t SMALL_FILESIZE_BYTES = 2048;
+ const float SMALL_FILESIZE_THRESHOLD = .50f; // 50% relative, for golden sizes < SMALL_FILESIZE_BYTES
+ const float LDR_SENTINEL_PSNR = 99.0f; // lossless/degenerate inputs store ~100; don't require an exact match
+ const float HDR_SENTINEL_PSNR = 200.0f;
+
+ if (opts.m_codec_filter.size())
+ fmt_printf("Codec filter: \"{}\"\n", opts.m_codec_filter);
+
+ basisu::job_pool jp(codec_test_num_threads());
+
+ const uint32_t kNumFmts = (uint32_t)basist::basis_tex_format::cTotalFormats;
+ basisu::vector highest_psnr_delta(kNumFmts);
+ basisu::vector highest_size_ratio(kNumFmts);
+ basisu::vector fmt_used(kNumFmts);
+ basisu::vector codec_total_ms(kNumFmts); // per-codec compression time accumulators
+ basisu::vector codec_max_ms(kNumFmts);
+ basisu::vector codec_count(kNumFmts);
+ for (uint32_t i = 0; i < kNumFmts; i++) { highest_psnr_delta[i] = 0.0f; highest_size_ratio[i] = 0.0f; fmt_used[i] = 0; codec_total_ms[i] = 0.0; codec_max_ms[i] = 0.0; codec_count[i] = 0; }
+
+ uint32_t total = 0, tested = 0, mismatches = 0, run_fails = 0, total_skipped = 0;
+
+ for (const codec_test_case& tc : g_codec_test_cases)
+ {
+ total++;
+
+ if (!codec_name_matches_filter(codec_short_name(tc.m_fmt), opts.m_codec_filter))
+ continue;
+
+ if constexpr (sizeof(void*) == sizeof(uint32_t))
+ {
+ // TODO: disable testing some codecs in 32-bit builds to avoid running out of memory (remove once 32-bit memory issue is addressed)
+ if (basist::basis_tex_format_is_xuastc_ldr(tc.m_fmt) || basis_tex_format_is_astc_ldr(tc.m_fmt))
+ {
+ uint32_t block_width = 0, block_height = 0;
+ basist::get_basis_tex_format_block_size(tc.m_fmt, block_width, block_height);
+ if ((block_width < 6) || (block_height < 6))
+ {
+ fmt_printf("WARNING: Skipping XUASTC/ASTC format with block size {}x{} to avoid running out of memory in 32-bit builds\n", block_width, block_height);
+ total_skipped++;
+ continue;
+ }
+ }
+ }
+
+ tested++;
+
+ uint32_t size = 0;
+ basisu::image_stats stats;
+ double elapsed_ms = 0.0;
+
+ if (!run_codec_test_case(jp, dir, tc.m_pFilename, tc.m_fmt, tc.m_hdr, tc.m_quality, tc.m_effort, size, stats, elapsed_ms))
+ {
+ error_printf("COMPRESS FAILED: %s %s q%d e%d\n", codec_short_name(tc.m_fmt), tc.m_pFilename, tc.m_quality, tc.m_effort);
+ run_fails++;
+ continue;
+ }
+
+ float rgb, rgba, second;
+ codec_extract_psnrs(stats, tc.m_hdr, rgb, rgba, second);
+
+ // Progress: print every ~10 runs unless the user passed -quiet/-no_status_output (m_status_output==false).
+ if (opts.m_comp_params.m_status_output && ((tested == 1) || ((tested % 10) == 0)))
+ printf(" [%u] %-26s %-14s q%d e%d size=%u rgb=%.2f%s time=%.1fms\n",
+ tested, codec_short_name(tc.m_fmt), tc.m_pFilename, tc.m_quality, tc.m_effort, size, rgb, tc.m_hdr ? " (log2)" : "", elapsed_ms);
+
+ const float psnr_thresh = basist::basis_tex_format_is_xuastc_ldr(tc.m_fmt) ? XUASTC_PSNR_THRESHOLD : PSNR_THRESHOLD;
+ const float sentinel = tc.m_hdr ? HDR_SENTINEL_PSNR : LDR_SENTINEL_PSNR;
+
+ const uint32_t fi = (uint32_t)tc.m_fmt;
+ fmt_used[fi] = 1;
+ codec_total_ms[fi] += elapsed_ms;
+ if (elapsed_ms > codec_max_ms[fi]) codec_max_ms[fi] = elapsed_ms;
+ codec_count[fi]++;
+
+ // File size: relative ratio.
+ if (tc.m_size)
+ {
+ float ratio = fabsf((float)size / (float)tc.m_size - 1.0f);
+ if (ratio > highest_size_ratio[fi]) highest_size_ratio[fi] = ratio;
+ // Small golden sizes swing widely in relative terms across builds for a tiny absolute delta, so relax
+ // the threshold for them (see SMALL_FILESIZE_BYTES above).
+ const float size_thresh = (tc.m_size < SMALL_FILESIZE_BYTES) ? SMALL_FILESIZE_THRESHOLD : FILESIZE_THRESHOLD;
+ if (ratio > size_thresh)
+ {
+ error_printf("SIZE mismatch: %s %s q%d e%d: expected %u, got %u (%.2f%%)\n",
+ codec_short_name(tc.m_fmt), tc.m_pFilename, tc.m_quality, tc.m_effort, tc.m_size, size, ratio * 100.0f);
+ mismatches++;
+ }
+ }
+
+ // PSNR comparisons. Sentinel (lossless) values are only required to be "high", not exact.
+ struct { const char* m_pName; float m_expected; float m_measured; bool m_active; } checks[3] =
+ {
+ { "RGB", tc.m_rgb_psnr, rgb, true },
+ { "RGBA", tc.m_rgba_psnr, rgba, (!tc.m_hdr) && (tc.m_rgba_psnr >= 0.0f) },
+ { "2nd", tc.m_2nd_psnr, second, tc.m_2nd_psnr > 1.0f }, // skip when the 2nd format wasn't computed
+ };
+
+ for (const auto& c : checks)
+ {
+ if (!c.m_active)
+ continue;
+
+ if (c.m_expected >= sentinel)
+ {
+ // Lossless/degenerate: just require the measured value is also very high.
+ if (c.m_measured < (sentinel - 20.0f))
+ {
+ error_printf("%s PSNR mismatch (sentinel): %s %s q%d e%d: expected >=%.1f, got %.3f\n",
+ c.m_pName, codec_short_name(tc.m_fmt), tc.m_pFilename, tc.m_quality, tc.m_effort, sentinel - 20.0f, c.m_measured);
+ mismatches++;
+ }
+ continue;
+ }
+
+ float delta = fabsf(c.m_measured - c.m_expected);
+ if (delta > highest_psnr_delta[fi]) highest_psnr_delta[fi] = delta;
+ if ((c.m_measured < 55.0f) && (delta > psnr_thresh)) // ignore if PSNR is already very high
+ {
+ error_printf("%s PSNR mismatch: %s %s q%d e%d: expected %.3f, got %.3f (delta %.3f > %.3f)\n",
+ c.m_pName, codec_short_name(tc.m_fmt), tc.m_pFilename, tc.m_quality, tc.m_effort, c.m_expected, c.m_measured, delta, psnr_thresh);
+ mismatches++;
+ }
+ }
+ }
+
+ // Per-codec highest deltas, grouped LDR then HDR (g_codec_sweep order).
+ printf("\n---- Highest deltas per codec (PSNR dB / file-size ratio) ----\n");
+ for (uint32_t group = 0; group < 2; group++)
+ {
+ printf("%s:\n", group == 0 ? "LDR" : "HDR");
+ for (const auto& d : g_codec_sweep)
+ {
+ if ((d.m_hdr ? 1u : 0u) != group)
+ continue;
+ const uint32_t fi = (uint32_t)d.m_fmt;
+ if (!fmt_used[fi])
+ continue;
+ printf(" %-28s max PSNR delta %.4f dB, max size ratio %.2f%%\n",
+ d.m_pEnum_name + 1, highest_psnr_delta[fi], highest_size_ratio[fi] * 100.0f);
+ }
+ }
+
+ // Per-codec compression timing (gross-perf-regression signal), grouped LDR then HDR.
+ printf("\n---- Per-codec compression time (total / avg / max) ----\n");
+ double grand_total_ms = 0.0;
+ for (uint32_t group = 0; group < 2; group++)
+ {
+ printf("%s:\n", group == 0 ? "LDR" : "HDR");
+ for (const auto& d : g_codec_sweep)
+ {
+ if ((d.m_hdr ? 1u : 0u) != group)
+ continue;
+ const uint32_t fi = (uint32_t)d.m_fmt;
+ if (!codec_count[fi])
+ continue;
+ grand_total_ms += codec_total_ms[fi];
+ printf(" %-28s total %.1f ms, avg %.1f ms, max %.1f ms (%u runs)\n",
+ d.m_pEnum_name + 1, codec_total_ms[fi], codec_total_ms[fi] / (double)codec_count[fi], codec_max_ms[fi], codec_count[fi]);
+ }
+ }
+ printf("Grand total compression time: %.1f ms (%.2f s)\n", grand_total_ms, grand_total_ms / 1000.0);
+
+ if (total_skipped != 0)
+ fmt_printf("WARNING: {} encodes had to be skipped to avoid running out of memory in 32-bit builds\n", total_skipped);
+
+ printf("\nCodec test: %u cases in table, %u tested, %u mismatches, %u compress failures\n", total, tested, mismatches, run_fails);
+
+ if (mismatches || run_fails)
+ {
+ error_printf("Codec test FAILED\n");
+ return false;
+ }
+ printf("Codec test succeeded\n");
+ return true;
+}
+
#ifdef FORCE_SAN_FAILURE
static void force_san_failure()
{
@@ -5575,358 +8071,10 @@ static bool peek_astc_file(const char* pFilename)
return false;
}
- const uint32_t total_block_pixels = block_width * block_height;
-
- fmt_printf("Block dimensions in pixels: {}x{}, {} total pixels\n", block_width, block_height, total_block_pixels);
- fmt_printf("Image dimensions in pixels: {}x{}\n", image_width, image_height);
-
- fmt_printf("Extra cols/rows to pad image to ASTC block dimensions: {}x{}\n",
- blocks.get_width() * block_width - image_width,
- blocks.get_height() * block_height - image_height);
-
- image dec_image_srgb(image_width, image_height);
- image dec_image_linear(image_width, image_height);
- imagef dec_image_float(image_width, image_height);
-
- uint32_t cem_hist[16] = { };
- uint32_t cem_dp_hist[16] = { };
- uint32_t cem_used_bc_hist[16] = { };
- uint32_t total_dp = 0;
-
- uint32_t total_solid_blocks_ldr = 0;
- uint32_t total_solid_blocks_hdr = 0;
- uint32_t total_normal_blocks = 0;
-
- uint32_t part_hist[4] = { };
- uint32_t used_endpoint_levels_hist[astc_helpers::LAST_VALID_ENDPOINT_ISE_RANGE - astc_helpers::FIRST_VALID_ENDPOINT_ISE_RANGE + 1] = { };
- uint32_t used_weight_levels_hist[astc_helpers::LAST_VALID_WEIGHT_ISE_RANGE - astc_helpers::FIRST_VALID_WEIGHT_ISE_RANGE + 1] = { };
-
- uint32_t total_unequal_cem_blocks = 0;
- uint32_t total_unequal_cem_blocks_2subsets = 0;
- uint32_t total_unequal_cem_blocks_3subsets = 0;
- uint32_t total_unequal_cem_blocks_4subsets = 0;
-
- uint32_t highest_part_seed = 0;
-
- int min_weight_grid_width = INT_MAX, min_weight_grid_height = INT_MAX;
- int max_weight_grid_width = 0, max_weight_grid_height = 0;
-
- uint32_t total_ldr_blocks = 0, total_hdr_blocks = 0;
-
- basisu::hash_map weight_grid_histogram;
-
- struct log_astc_block_config_cmp_t
- {
- bool operator()(const astc_helpers::log_astc_block& a,
- const astc_helpers::log_astc_block& b) const
- {
- // This only compares the ASTC configuration for equality, NOT the contents.
- if (a.m_error_flag != b.m_error_flag)
- return false;
- if (a.m_error_flag)
- return true;
-
- if (a.m_grid_width != b.m_grid_width)
- return false;
- if (a.m_grid_height != b.m_grid_height)
- return false;
-
- if (a.m_solid_color_flag_ldr != b.m_solid_color_flag_ldr)
- return false;
- if (a.m_solid_color_flag_hdr != b.m_solid_color_flag_hdr)
- return false;
-
- if (a.m_solid_color_flag_ldr || a.m_solid_color_flag_hdr)
- return true;
-
- if (a.m_dual_plane != b.m_dual_plane)
- return false;
- if (a.m_color_component_selector != b.m_color_component_selector)
- return false;
-
- if (a.m_num_partitions != b.m_num_partitions)
- return false;
-
- if (a.m_endpoint_ise_range != b.m_endpoint_ise_range)
- return false;
- if (a.m_weight_ise_range != b.m_weight_ise_range)
- return false;
-
- for (uint32_t i = 0; i < a.m_num_partitions; i++)
- if (a.m_color_endpoint_modes[i] != b.m_color_endpoint_modes[i])
- return false;
-
- return true;
- }
- };
-
- basisu::hash_map, log_astc_block_config_cmp_t > unique_config_histogram;
-
- uint32_t total_subsets = 0;
-
- for (uint32_t by = 0; by < blocks.get_height(); by++)
- {
- for (uint32_t bx = 0; bx < blocks.get_width(); bx++)
- {
- astc_helpers::log_astc_block log_blk;
-
- if (!astc_helpers::unpack_block(&blocks(bx, by), log_blk, block_width, block_height))
- {
- fmt_error_printf("astc_helpers::unpack_block() failed on block {}x{}\n", bx, by);
- return false;
- }
-
- if (log_blk.m_error_flag)
- {
- fmt_error_printf("astc_helpers::unpack_block() returned an error flag on block {}x{}\n", bx, by);
- return false;
- }
-
- {
- astc_helpers::log_astc_block scrubbed_log_blk;
- memset(&scrubbed_log_blk, 0, sizeof(scrubbed_log_blk));
-
- // just record the config, not the contents, so only the config hashes
- scrubbed_log_blk.m_solid_color_flag_ldr = log_blk.m_solid_color_flag_ldr;
- scrubbed_log_blk.m_solid_color_flag_hdr = log_blk.m_solid_color_flag_hdr;
- scrubbed_log_blk.m_dual_plane = log_blk.m_dual_plane;
- scrubbed_log_blk.m_color_component_selector = log_blk.m_color_component_selector;
- scrubbed_log_blk.m_grid_width = log_blk.m_grid_width;
- scrubbed_log_blk.m_grid_height = log_blk.m_grid_height;
- scrubbed_log_blk.m_num_partitions = log_blk.m_num_partitions;
- scrubbed_log_blk.m_color_endpoint_modes[0] = log_blk.m_color_endpoint_modes[0];
- scrubbed_log_blk.m_color_endpoint_modes[1] = log_blk.m_color_endpoint_modes[1];
- scrubbed_log_blk.m_color_endpoint_modes[2] = log_blk.m_color_endpoint_modes[2];
- scrubbed_log_blk.m_color_endpoint_modes[3] = log_blk.m_color_endpoint_modes[3];
- scrubbed_log_blk.m_weight_ise_range = log_blk.m_weight_ise_range;
- scrubbed_log_blk.m_endpoint_ise_range = log_blk.m_endpoint_ise_range;
-
- auto ins_res(unique_config_histogram.insert(scrubbed_log_blk, 0));
- (ins_res.first)->second = (ins_res.first)->second + 1;
- }
-
- bool is_hdr = log_blk.m_solid_color_flag_hdr;
-
- if (log_blk.m_solid_color_flag_ldr)
- {
- total_solid_blocks_ldr++;
- total_ldr_blocks++;
- }
- else if (log_blk.m_solid_color_flag_hdr)
- {
- total_solid_blocks_hdr++;
- total_hdr_blocks++;
- }
- else
- {
- total_normal_blocks++;
-
- min_weight_grid_width = minimum(min_weight_grid_width, log_blk.m_grid_width);
- min_weight_grid_height = minimum(min_weight_grid_height, log_blk.m_grid_height);
-
- max_weight_grid_width = maximum(max_weight_grid_width, log_blk.m_grid_width);
- max_weight_grid_height = maximum(max_weight_grid_height, log_blk.m_grid_height);
-
- {
- uint32_t weight_grid_hash_key = log_blk.m_grid_width | (log_blk.m_grid_height << 8);
- auto ins_res(weight_grid_histogram.insert(weight_grid_hash_key, 0));
- (ins_res.first)->second = (ins_res.first)->second + 1;
- }
-
- if (log_blk.m_dual_plane)
- total_dp++;
-
- part_hist[log_blk.m_num_partitions - 1]++;
-
- // For debugging seed packing bugs
- highest_part_seed = basisu::maximum(highest_part_seed, log_blk.m_partition_id);
-
- uint32_t cur_endpoint_ofs = 0;
- bool has_unequal_cems = false;
-
- total_subsets += log_blk.m_num_partitions;
-
- for (uint32_t p = 0; p < log_blk.m_num_partitions; p++)
- {
- if (astc_helpers::is_cem_hdr(log_blk.m_color_endpoint_modes[p]))
- is_hdr = true;
-
- cem_hist[log_blk.m_color_endpoint_modes[p]]++;
-
- if (log_blk.m_dual_plane)
- cem_dp_hist[log_blk.m_color_endpoint_modes[p]]++;
-
- if ((p) && (log_blk.m_color_endpoint_modes[p] != log_blk.m_color_endpoint_modes[0]))
- {
- has_unequal_cems = true;
- }
-
- if (astc_helpers::is_cem_ldr(log_blk.m_color_endpoint_modes[p]))
- {
- bool uses_bc = astc_helpers::used_blue_contraction(log_blk.m_color_endpoint_modes[p], log_blk.m_endpoints + cur_endpoint_ofs, log_blk.m_endpoint_ise_range);
-
- cem_used_bc_hist[log_blk.m_color_endpoint_modes[p]] += uses_bc;
- }
-
- cur_endpoint_ofs += astc_helpers::get_num_cem_values(log_blk.m_color_endpoint_modes[p]);
- }
-
- if (log_blk.m_num_partitions >= 2)
- {
- total_unequal_cem_blocks += has_unequal_cems;
-
- if (log_blk.m_num_partitions == 2)
- total_unequal_cem_blocks_2subsets += has_unequal_cems;
- else if (log_blk.m_num_partitions == 3)
- total_unequal_cem_blocks_3subsets += has_unequal_cems;
- else if (log_blk.m_num_partitions == 4)
- total_unequal_cem_blocks_4subsets += has_unequal_cems;
- }
-
- used_weight_levels_hist[open_range_check(log_blk.m_weight_ise_range - astc_helpers::FIRST_VALID_WEIGHT_ISE_RANGE, std::size(used_weight_levels_hist))]++;
- used_endpoint_levels_hist[open_range_check(log_blk.m_endpoint_ise_range - astc_helpers::FIRST_VALID_ENDPOINT_ISE_RANGE, std::size(used_endpoint_levels_hist))]++;
- }
-
- if (is_hdr)
- {
- total_hdr_blocks++;
- }
- else
- {
- total_ldr_blocks++;
-
- color_rgba block_pixels[astc_helpers::MAX_BLOCK_PIXELS];
-
- // sRGB8 decode profile unpack
- bool status = astc_helpers::decode_block(log_blk, block_pixels, block_width, block_height, astc_helpers::cDecodeModeSRGB8);
- if (!status)
- {
- fmt_error_printf("astc_helpers::decode_block() failed on block {}x{}\n", bx, by);
- return false;
- }
-
- dec_image_srgb.set_block_clipped(block_pixels, bx * block_width, by * block_height, block_width, block_height);
-
- // linear8 decode profile unpack
- status = astc_helpers::decode_block(log_blk, block_pixels, block_width, block_height, astc_helpers::cDecodeModeLDR8);
- if (!status)
- {
- fmt_error_printf("astc_helpers::decode_block() failed on block {}x{}\n", bx, by);
- return false;
- }
-
- dec_image_linear.set_block_clipped(block_pixels, bx * block_width, by * block_height, block_width, block_height);
- }
-
- // half float unpack
- {
- basist::half_float block_pixels_half[astc_helpers::MAX_BLOCK_PIXELS][4];
-
- bool status = astc_helpers::decode_block(log_blk, block_pixels_half, block_width, block_height, astc_helpers::cDecodeModeHDR16);
- if (!status)
- {
- fmt_error_printf("astc_helpers::decode_block() failed on block {}x{}\n", bx, by);
- return false;
- }
-
- vec4F block_pixels_float[astc_helpers::MAX_BLOCK_PIXELS];
- for (uint32_t i = 0; i < total_block_pixels; i++)
- for (uint32_t j = 0; j < 4; j++)
- block_pixels_float[i][j] = basist::half_to_float(block_pixels_half[i][j]);
-
- dec_image_float.set_block_clipped(block_pixels_float, bx * block_width, by * block_height, block_width, block_height);
- }
-
- } // bx
-
- } //by
-
- fmt_printf("Total LDR blocks: {}, total HDR blocks: {}\n", total_ldr_blocks, total_hdr_blocks);
-
- save_png("astc_decoded_srgb8_ldr.png", dec_image_srgb);
- fmt_printf("Wrote astc_decoded_srgb8_ldr.png\n");
-
- save_png("astc_decoded_linear8_ldr.png", dec_image_linear);
- fmt_printf("Wrote astc_decoded_linear8_ldr.png\n");
-
- write_exr("astc_decoded_half.exr", dec_image_float, 4, 0);
- fmt_printf("Wrote astc_decoded_half.exr\n");
-
- fmt_printf("\nASTC file statistics:\n");
-
- const uint32_t total_blocks = (uint32_t)blocks.size();
-
- fmt_printf("Total blocks: {}, total void extent LDR: {}, total void extent HDR: {}, total normal: {}\n", total_blocks, total_solid_blocks_ldr, total_solid_blocks_hdr, total_normal_blocks);
- fmt_printf("Total dual plane: {} {3.2}%\n", total_dp, total_dp * 100.0f / (float)total_blocks);
- fmt_printf("Total subsets across all blocks: {}, Avg. subsets per block: {}\n", total_subsets, (float)total_subsets / (float)total_blocks);
-
- fmt_printf("Min weight grid dimensions: {}x{}\n", min_weight_grid_width, min_weight_grid_height);
- fmt_printf("Max weight grid width: {}, height: {}\n", max_weight_grid_width, max_weight_grid_height);
-
- fmt_printf("\nPartition usage histogram:\n");
- for (uint32_t i = 0; i < 4; i++)
- fmt_printf("{}: {} {3.2}%\n", i + 1, part_hist[i], (float)part_hist[i] * 100.0f / (float)total_blocks);
-
- fmt_printf("\nCEM usage histogram (percentages relative to total overall subsets used in texture):\n");
- for (uint32_t i = 0; i < 15; i++)
- {
- fmt_printf("{}: {} {3.2}%, total BC: {} {3.2}%, total DP: {} {3.2}%\n", i,
- cem_hist[i], (float)cem_hist[i] * 100.0f / (float)total_subsets,
- cem_used_bc_hist[i], (float)cem_used_bc_hist[i] * 100.0f / (float)total_subsets,
- cem_dp_hist[i], (float)cem_dp_hist[i] * 100.0f / (float)total_subsets);
- }
-
- fmt_printf("\nUsed endpoint ISE levels:\n");
- for (uint32_t i = 0; i < std::size(used_endpoint_levels_hist); i++)
- fmt_printf("{} levels: {}\n", astc_helpers::get_ise_levels(astc_helpers::FIRST_VALID_ENDPOINT_ISE_RANGE + i), used_endpoint_levels_hist[i]);
-
- fmt_printf("\nUsed weight ISE levels:\n");
- for (uint32_t i = 0; i < std::size(used_weight_levels_hist); i++)
- fmt_printf("{} levels: {}\n", astc_helpers::get_ise_levels(astc_helpers::FIRST_VALID_WEIGHT_ISE_RANGE + i), used_weight_levels_hist[i]);
-
- fmt_printf("\nTotal 2+ subset blocks using unequal CEM's: {} {3.2}%\n", total_unequal_cem_blocks, (float)total_unequal_cem_blocks * 100.0f / (float)total_blocks);
- fmt_printf("Total 2 subset blocks using unequal CEM's: {} {3.2}%\n", total_unequal_cem_blocks_2subsets, (float)total_unequal_cem_blocks_2subsets * 100.0f / (float)total_blocks);
- fmt_printf("Total 3 subset blocks using unequal CEM's: {} {3.2}%\n", total_unequal_cem_blocks_3subsets, (float)total_unequal_cem_blocks_3subsets * 100.0f / (float)total_blocks);
- fmt_printf("Total 4 subset blocks using unequal CEM's: {} {3.2}%\n", total_unequal_cem_blocks_4subsets, (float)total_unequal_cem_blocks_4subsets * 100.0f / (float)total_blocks);
-
- fmt_printf("\nHighest part ID seed: {}, 0x{0x}\n", highest_part_seed, highest_part_seed);
-
- fmt_printf("\nWeight grid usage histogram:\n");
-
- uint64_vec v;
- for (auto it = weight_grid_histogram.begin(); it != weight_grid_histogram.end(); ++it)
- v.push_back(((uint64_t)it->first << 32) | it->second);
-
- v.sort();
-
- for (uint32_t i = 0; i < v.size(); i++)
- fmt_printf(" {}x{}: total blocks {}\n", (v[i] >> 32) & 0xFF, (v[i] >> 40) & 0xFF, v[i] & UINT32_MAX);
-
- fmt_printf("\nTotal unique ASTC configurations: {}\n", unique_config_histogram.size_u32());
-
- uint32_t config_idx = 0;
- for (auto it = unique_config_histogram.begin(); it != unique_config_histogram.end(); ++it)
- {
- const auto& l = it->first;
- const uint32_t total = it->second;
-
- fmt_printf(" {}. Used {} {3.2}% times: Solid LDR: {} HDR: {}, Grid: {}x{}, Dual Plane: {}, CCS: {}, NumParts: {}, CEMS: {} {} {} {}, WeightISERange: {}, EndpointISERange: {}\n",
- config_idx, total, float(total) * 100.0f / total_blocks,
- l.m_solid_color_flag_ldr, l.m_solid_color_flag_hdr,
- l.m_grid_width, l.m_grid_height,
- l.m_dual_plane, l.m_color_component_selector,
- l.m_num_partitions, l.m_color_endpoint_modes[0], l.m_color_endpoint_modes[1], l.m_color_endpoint_modes[2], l.m_color_endpoint_modes[3],
- l.m_weight_ise_range, l.m_endpoint_ise_range);
-
- config_idx++;
- }
-
- fmt_printf("Success\n");
-
- return true;
+ return display_astc_statistics(blocks, block_width, block_height, image_width, image_height, true);
}
-bool xuastc_ldr_decoder_fuzz_test()
+static bool xuastc_ldr_decoder_fuzz_test()
{
basisu::rand rnd;
rnd.seed(1);
@@ -6056,6 +8204,1433 @@ bool xuastc_ldr_decoder_fuzz_test()
return true;
}
+[[maybe_unused]] static inline uint8_t get_601_y(int r, int g, int b)
+{
+ return (uint8_t)std::round(16.0f + 65.481f * (float)r / 255.0f + 128.553f * (float)g / 255.0f + 24.966f * (float)b / 255.0f);
+}
+
+// Calculates a proper average PSNR by converting each PSNR value to MSE, averaging the MSE values, and then converting back to PSNR.
+// This is necessary because PSNR is a logarithmic metric, so you can't just average the PSNR values directly.
+float calc_average_psnr(const float* psnr_values, uint32_t count, float lossless_db_thresh)
+{
+ if (!count)
+ return 0.0f;
+
+ float mse_sum = 0.0;
+ for (uint32_t i = 0; i < count; i++)
+ {
+ if (psnr_values[i] >= lossless_db_thresh)
+ continue;
+
+ mse_sum += powf(10.0f, -psnr_values[i] / 10.0f);
+ }
+
+ float mean_mse = mse_sum / (float)count;
+ return (mean_mse > 0.0f) ? clamp(-10.0f * log10f(mean_mse), 0.0f, 100.0f) : 100.0f;
+}
+
+static float print_psnr_stats(const float_vec &orig_psnrs, float lossless_db_thresh)
+{
+ const float avg_psnr = calc_average_psnr(orig_psnrs.get_ptr(), orig_psnrs.size_u32(), lossless_db_thresh);
+ fmt_printf(" Average (from all MSE's): {3.3} dB\n", avg_psnr);
+
+ uint32_t total_lossless = 0;
+ float_vec psnrs;
+ for (uint32_t i = 0; i < orig_psnrs.size(); i++)
+ {
+ if (orig_psnrs[i] >= lossless_db_thresh)
+ total_lossless++;
+ else
+ psnrs.push_back(orig_psnrs[i]);
+ }
+
+ fmt_printf(" Total > {3.2} dB (lossless): {}, lossy: {}\n", lossless_db_thresh, total_lossless, psnrs.size_u32());
+ fmt_printf(" Lossy-only statistics:\n");
+
+ stats psnrs_stats;
+ psnrs_stats.calc(psnrs.size_u32(), psnrs.get_ptr(), 1, true);
+
+ fmt_printf(" Average (of only lossy PSNR's): {3.3} dB\n", psnrs_stats.m_avg);
+ fmt_printf(" Std dev: {3.3} dB, Skewness: {3.3} dB\n", psnrs_stats.m_std_dev, psnrs_stats.m_skewness);
+ fmt_printf(" Min: {3.3} dB\n", psnrs_stats.m_min);
+ fmt_printf(" Median: {3.3} dB at file index: {}\n", psnrs_stats.m_median, psnrs_stats.m_median_index + 1);
+ fmt_printf(" Max: {3.3} dB\n", psnrs_stats.m_max);
+ fmt_printf(" Avg 5% low: {3.3} dB\n", psnrs_stats.m_five_percent_lo);
+ fmt_printf(" Avg 5% high: {3.3} dB\n", psnrs_stats.m_five_percent_hi);
+
+ return avg_psnr;
+}
+
+struct benchmark_results
+{
+ benchmark_results()
+ {
+ clear();
+ }
+
+ void clear()
+ {
+ clear_obj(*this);
+ }
+
+ basist::basis_tex_format m_tex_fmt;
+ uint32_t m_effort, m_quality, m_flags;
+
+ uint32_t m_total_images;
+ double m_total_time;
+ uint64_t m_total_input_texels;
+ uint64_t m_total_compressed_size;
+
+ // LDR averages - all calculated from MSE's then converted to PSNR.
+ float m_avg_rgba_psnr;
+ float m_avg_luma_709_psnr;
+ float m_avg_y_hvs_psnr;
+ float m_avg_y_hvsm_psnr;
+
+ // HDR averages: native ASTC HDR + transcoded BC6H, each in linear half-float and log2 space
+ // All calculated from MSE's then converted to PSNR.
+ float m_avg_astc_hdr_psnr;
+ float m_avg_astc_hdr_log2_psnr;
+ float m_avg_bc6h_psnr;
+ float m_avg_bc6h_log2_psnr;
+};
+
+// Parses a -benchmark_single format token (e.g. "XUASTC_LDR_4x4", "XUBC7", "ETC1S", "ASTC_LDR_6x6", "UASTC_LDR_4x4")
+// into a basis_tex_format. Matches case-insensitively against basis_get_tex_format_name() with spaces normalized to
+// underscores, so every codec the encoder supports (XUASTC LDR 4x4-12x12, XUBC7, ETC1S, ASTC LDR, etc.) is accepted.
+// Returns false if no format matches.
+static bool parse_benchmark_tex_format(const std::string& s, basist::basis_tex_format& out_fmt)
+{
+ auto normalize = [](std::string v) -> std::string
+ {
+ for (char& c : v)
+ c = (c == ' ') ? '_' : (char)toupper((uint8_t)c);
+ return v;
+ };
+
+ const std::string want(normalize(s));
+
+ for (uint32_t i = 0; i < (uint32_t)basist::basis_tex_format::cTotalFormats; i++)
+ {
+ const basist::basis_tex_format fmt = (basist::basis_tex_format)i;
+ if (normalize(basist::basis_get_tex_format_name(fmt)) == want)
+ {
+ out_fmt = fmt;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+[[maybe_unused]] static bool codec_benchmark(
+ const std::string &base_filename, //("d:/dev/test_images/photo_png/kodim");
+ const uint32_t first_test_file_index,// = 1;
+ const uint32_t last_test_file_index,// = 166,
+ basist::basis_tex_format tex_fmt, // = basist::basis_tex_format::cXUASTC_LDR_6x6;
+ uint32_t effort_level, uint32_t quality_level, uint32_t flags,
+ bool srgb_flag, // true = sRGB/perceptual metrics, false = linear
+ benchmark_results &results)
+{
+ results.m_tex_fmt = tex_fmt;
+ results.m_effort = effort_level;
+ results.m_quality = quality_level;
+ results.m_flags = flags;
+
+ fmt_printf("Base filename: {}, first file index: {}, last file index: {}\n", base_filename, first_test_file_index, last_test_file_index);
+
+ fmt_printf("Testing tex fmt: {}, Flags: 0x{X}, Effort: {}, Quality: {}\n", (uint32_t)tex_fmt, flags, effort_level, quality_level);
+
+ const bool is_hdr = basist::basis_tex_format_is_hdr(tex_fmt);
+
+ // sRGB vs. linear metrics only applies to LDR; HDR metrics are always linear half-float / log2, so don't set the
+ // sRGB flag or print the metric mode for HDR.
+ const bool srgb_metrics = srgb_flag && !is_hdr;
+ if (!is_hdr)
+ fmt_printf("Using {} metrics\n", srgb_metrics ? "sRGB (perceptual)" : "linear");
+
+ interval_timer tm;
+ tm.start();
+
+ // LDR metrics
+ float_vec rgba_psnrs, luma_709_psnrs, y_hvs_psnr, y_hvsm_psnr;
+ // HDR metrics: native ASTC HDR and transcoded BC6H RGB PSNRs, in linear half-float and log2 space
+ float_vec astc_hdr_psnrs, astc_hdr_log2_psnrs, bc6h_psnrs, bc6h_log2_psnrs;
+
+ uint64_t total_compressed_size = 0;
+ uint64_t total_input_texels = 0;
+
+ uint32_t flags_and_quality = cFlagThreaded | (srgb_metrics ? cFlagSRGB : 0) | flags;
+
+ const uint32_t total_test_images = (last_test_file_index - first_test_file_index) + 1;
+
+ for (uint32_t file_index = first_test_file_index; file_index <= last_test_file_index; file_index++)
+ {
+ // HDR formats benchmark against .exr inputs (loaded as float images); LDR against .png. The LDR sets (e.g.
+ // kodimNN.png) use 2-digit zero-padded indices; the HDR set (hdr_N.exr) is not padded.
+ std::string filename(base_filename);
+ const char* pExt = is_hdr ? ".exr" : ".png";
+ if (!is_hdr && (file_index < 10))
+ filename += fmt_string("{02}{}", file_index, pExt);
+ else
+ filename += fmt_string("{}{}", file_index, pExt);
+
+ image orig_img;
+ imagef orig_imgf;
+ uint32_t width = 0, height = 0;
+ bool has_alpha = false;
+
+ if (is_hdr)
+ {
+ if (!load_image_hdr(filename.c_str(), orig_imgf))
+ {
+ fmt_error_printf("Failed to load HDR image file {}\n", filename);
+ return false;
+ }
+ width = orig_imgf.get_width();
+ height = orig_imgf.get_height();
+ }
+ else
+ {
+ if (!load_png(filename, orig_img))
+ {
+ fmt_error_printf("Failed to load image file {}\n", filename);
+ return false;
+ }
+ width = orig_img.get_width();
+ height = orig_img.get_height();
+ has_alpha = orig_img.has_alpha();
+ }
+
+ fmt_printf("---- Loading image {}, {}x{}, has_alpha: {}\n", filename, width, height, has_alpha);
+
+ size_t data_size = 0;
+ basisu::image_stats stats;
+
+ // HDR uses the imagef (float) basis_compress2 overload; LDR uses the 8-bit image overload.
+ void* pData;
+ if (is_hdr)
+ {
+ basisu::vector source_images_hdr;
+ source_images_hdr.push_back(orig_imgf);
+ pData = basis_compress2(tex_fmt, source_images_hdr, flags_and_quality, quality_level, effort_level, &data_size, (flags & cFlagPrintStats) ? &stats : nullptr);
+ }
+ else
+ {
+ basisu::vector source_images;
+ source_images.push_back(orig_img);
+ pData = basis_compress2(tex_fmt, source_images, flags_and_quality, quality_level, effort_level, &data_size, (flags & cFlagPrintStats) ? &stats : nullptr);
+ }
+
+ if (!pData)
+ {
+ error_printf("basis_compress() failed!\n");
+ return false;
+ }
+
+ basis_free_data(pData);
+
+ const double bpp = ((double)data_size * 8.0f) / (double)(width * height);
+
+ if (is_hdr)
+ {
+ // HDR: native ASTC HDR + transcoded BC6H RGB PSNRs, each in linear half-float and log2 space (see image_stats).
+ const float astc_hdr_psnr = stats.m_basis_rgb_avg_psnr;
+ const float astc_hdr_log2_psnr = stats.m_basis_rgb_avg_astc_hdr_log2_psnr;
+ const float bc6h_psnr = stats.m_basis_rgb_avg_bc6h_psnr;
+ const float bc6h_log2_psnr = stats.m_basis_rgb_avg_bc6h_log2_psnr;
+
+ fmt_printf("Size: {}, bpp: {}, ASTC HDR RGB PSNR: {3.3} dB (log2: {3.3}), BC6H RGB PSNR: {3.3} dB (log2: {3.3})\n",
+ (uint64_t)data_size, bpp, astc_hdr_psnr, astc_hdr_log2_psnr, bc6h_psnr, bc6h_log2_psnr);
+
+ astc_hdr_psnrs.push_back(astc_hdr_psnr);
+ astc_hdr_log2_psnrs.push_back(astc_hdr_log2_psnr);
+ bc6h_psnrs.push_back(bc6h_psnr);
+ bc6h_log2_psnrs.push_back(bc6h_log2_psnr);
+ }
+ else
+ {
+ // XUBC7 is a native BC7 codec, so its native RGBA/Luma quality lives in the m_bc7_* stats; the m_basis_* stats
+ // for XUBC7 reflect the ASTC LDR 4x4 transcode, not the native output. All other codecs use the m_basis_* stats.
+ const bool is_xubc7 = (tex_fmt == basist::basis_tex_format::cXUBC7);
+ const float rgba_psnr = is_xubc7 ? stats.m_bc7_rgba_avg_psnr : stats.m_basis_rgba_avg_psnr;
+ const float luma_709_psnr = is_xubc7 ? stats.m_bc7_luma_709_psnr : stats.m_basis_luma_709_psnr;
+ const psnr_hvs_metrics& hvs = is_xubc7 ? stats.m_hvs_metrics_bc7 : stats.m_hvs_metrics;
+
+ fmt_printf("Size: {}, bpp: {}, RGBA PSNR: {3.3} dB, Luma 709 PSNR: {3.3} dB, Y PSNR-HVS: {3.3} dB, Y PSNR-HVS-M: {3.3} dB\n",
+ (uint64_t)data_size,
+ bpp,
+ rgba_psnr,
+ luma_709_psnr,
+ hvs.m_y_601_float.m_psnr_hvs,
+ hvs.m_y_601_float.m_psnr_hvsm);
+
+ rgba_psnrs.push_back(rgba_psnr);
+ luma_709_psnrs.push_back(luma_709_psnr);
+ y_hvs_psnr.push_back((float)hvs.m_y_601_float.m_psnr_hvs);
+ y_hvsm_psnr.push_back((float)hvs.m_y_601_float.m_psnr_hvsm);
+ }
+
+ total_compressed_size += data_size;
+ total_input_texels += (uint64_t)width * (uint64_t)height;
+
+ } // file_index
+
+ const double total_time = tm.get_elapsed_secs();
+
+ fmt_printf("\n-------------------------\n");
+
+ fmt_printf("tex fmt: {}, Flags: 0x{X}, Effort: {}, Quality: {}\n", (uint32_t)tex_fmt, flags_and_quality, effort_level, quality_level);
+
+ fmt_printf("Total time: {3.3} secs, average time per image: {3.3} secs\n", total_time, total_time / (double)total_test_images);
+
+ fmt_printf("Total input texels: {} megapixels, total compressed size: {} mebibytes, average bpp: {3.3}\n", (double)total_input_texels / (1e+6), (double)total_compressed_size / (1024.0f * 1024.0f), ((double)total_compressed_size * 8.0f) / (double)total_input_texels);
+
+ results.m_total_images = total_test_images;
+ results.m_total_input_texels = total_input_texels;
+ results.m_total_compressed_size = total_compressed_size;
+ results.m_total_time = total_time;
+
+ if (is_hdr)
+ {
+ fmt_printf("\nASTC HDR RGB PSNR:\n");
+ results.m_avg_astc_hdr_psnr = print_psnr_stats(astc_hdr_psnrs, 100.0f);
+
+ fmt_printf("\nASTC HDR RGB log2 PSNR:\n");
+ results.m_avg_astc_hdr_log2_psnr = print_psnr_stats(astc_hdr_log2_psnrs, 100.0f);
+
+ fmt_printf("\nBC6H RGB PSNR:\n");
+ results.m_avg_bc6h_psnr = print_psnr_stats(bc6h_psnrs, 100.0f);
+
+ fmt_printf("\nBC6H RGB log2 PSNR:\n");
+ results.m_avg_bc6h_log2_psnr = print_psnr_stats(bc6h_log2_psnrs, 100.0f);
+ }
+ else
+ {
+ fmt_printf("\nRGBA PSNR:\n");
+ results.m_avg_rgba_psnr = print_psnr_stats(rgba_psnrs, 100.0f);
+
+ fmt_printf("\nLuma 709 PSNR:\n");
+ results.m_avg_luma_709_psnr = print_psnr_stats(luma_709_psnrs, 100.0f);
+
+ fmt_printf("\nY PSNR-HVS:\n");
+ results.m_avg_y_hvs_psnr = print_psnr_stats(y_hvs_psnr, PSNR_HVS_LOSSLESS_DB);
+
+ fmt_printf("\nY PSNR-HVSM:\n");
+ results.m_avg_y_hvsm_psnr = print_psnr_stats(y_hvsm_psnr, PSNR_HVS_LOSSLESS_DB);
+ }
+
+ return true;
+}
+
+// -benchmark_single : single codec_benchmark() run over a numbered image set.
+// LDR loads NN.png (zero-padded); HDR loads N.exr (not padded). Defaults: LDR "../test_files/kodim" 1..24,
+// HDR "../test_files/hdr_" 1..6 (used only when -benchmark_path / -benchmark_range aren't given). Override via
+// -benchmark_path and -benchmark_range MIN MAX. LDR metrics are sRGB/perceptual by default (-benchmark_linear selects
+// linear); HDR metrics are always linear half-float / log2.
+// -benchmark_xubc7_encoder [0,7] selects the XUBC7 BC7 base encoder (0=bc7f, 1-7=bc7e_scalar level 0-6; XUBC7 only).
+// -benchmark_xuastc_profile [0,2] selects the XUASTC LDR entropy syntax/profile (0=full arith, 1=hybrid, 2=full zstd; XUASTC only).
+// FORMAT/EFFORT/QUALITY come from the command line. Not in the help text yet.
+// Example: basisu -benchmark_single XUBC7 9 100 [-benchmark_path ] [-benchmark_range 1 24] [-benchmark_linear] [-benchmark_xubc7_encoder 4] [-benchmark_xuastc_profile 2]
+// For HDR codecs, if the user didn't explicitly override the image path/range, switch from the LDR kodim default to the
+// HDR default set ("../test_files/hdr_N.exr", indices 1..6). LDR keeps its existing defaults.
+static void apply_hdr_benchmark_image_defaults(command_line_params& opts)
+{
+ if (!basist::basis_tex_format_is_hdr(opts.m_benchmark_fmt))
+ return;
+ if (!opts.m_benchmark_path_set)
+ opts.m_benchmark_path = "../test_files/hdr_";
+ if (!opts.m_benchmark_range_set)
+ {
+ opts.m_benchmark_first = 1;
+ opts.m_benchmark_last = 6;
+ }
+}
+
+// Packs the benchmark flag set (KTX2 + UASTC supercompression + print stats/status + the XUBC7 base encoder and XUASTC
+// profile selections) and runs one codec_benchmark() over the configured image set. Shared by -benchmark_single and -benchmark_sweep.
+static bool run_one_benchmark(const command_line_params& opts,
+ basist::basis_tex_format fmt, uint32_t effort, uint32_t quality, benchmark_results& results)
+{
+ // XUBC7-only: pack the BC7 base encoder selection into the high flag bits (0=bc7f, 1-7=bc7e_scalar level 0-6); ignored for other formats.
+ const uint32_t xubc7_encoder_flags = (opts.m_benchmark_xubc7_encoder & cFlagXUBC7BaseEncoderMask) << cFlagXUBC7BaseEncoderShift;
+ // XUASTC-only: pack the LDR entropy syntax/profile (0=full arith, 1=hybrid, 2=full zstd); ignored for other formats.
+ const uint32_t xuastc_profile_flags = (opts.m_benchmark_xuastc_profile & cFlagXUASTCLDRSyntaxMask) << cFlagXUASTCLDRSyntaxShift;
+ return codec_benchmark(opts.m_benchmark_path, opts.m_benchmark_first, opts.m_benchmark_last,
+ fmt, effort, quality,
+ cFlagKTX2 | cFlagKTX2UASTCSuperCompression | cFlagPrintStats | cFlagPrintStatus | xubc7_encoder_flags | xuastc_profile_flags,
+ !opts.m_benchmark_linear, // sRGB metrics by default; -benchmark_linear selects linear
+ results);
+}
+
+static bool benchmark_single_mode(command_line_params& opts)
+{
+ apply_hdr_benchmark_image_defaults(opts);
+ benchmark_results bres;
+ return run_one_benchmark(opts, opts.m_benchmark_fmt, opts.m_benchmark_effort, opts.m_benchmark_quality, bres);
+}
+
+// Maps a square-block-size sweep index [0,5] (0=4x4,1=5x5,2=6x6,3=8x8,4=10x10,5=12x12) to the basis_tex_format in
+// base_fmt's family. Only XUASTC LDR and ASTC LDR have selectable block sizes; returns false for any other family.
+static bool benchmark_block_index_to_fmt(basist::basis_tex_format base_fmt, uint32_t square_index, basist::basis_tex_format& out_fmt)
+{
+ typedef basist::basis_tex_format tf;
+
+ const bool is_xuastc = basist::basis_tex_format_is_xuastc_ldr(base_fmt);
+ const bool is_astc = basist::basis_tex_format_is_astc_ldr(base_fmt);
+ if (!is_xuastc && !is_astc)
+ return false;
+
+ switch (square_index)
+ {
+ case 0: out_fmt = is_xuastc ? tf::cXUASTC_LDR_4x4 : tf::cASTC_LDR_4x4; return true;
+ case 1: out_fmt = is_xuastc ? tf::cXUASTC_LDR_5x5 : tf::cASTC_LDR_5x5; return true;
+ case 2: out_fmt = is_xuastc ? tf::cXUASTC_LDR_6x6 : tf::cASTC_LDR_6x6; return true;
+ case 3: out_fmt = is_xuastc ? tf::cXUASTC_LDR_8x8 : tf::cASTC_LDR_8x8; return true;
+ case 4: out_fmt = is_xuastc ? tf::cXUASTC_LDR_10x10 : tf::cASTC_LDR_10x10; return true;
+ case 5: out_fmt = is_xuastc ? tf::cXUASTC_LDR_12x12 : tf::cASTC_LDR_12x12; return true;
+ default: return false;
+ }
+}
+
+// -benchmark_sweep : sweeps any combination of block size, effort, and quality for one codec, writing a CSV
+// (one row per combination) for spreadsheet analysis. Axis ranges (inclusive low/high): -benchmark_sweep_block [0,5]
+// (square sizes 4x4..12x12; XUASTC_LDR/ASTC_LDR only - error otherwise; if omitted, FORMAT's own block size is used and
+// no block sweep happens), -benchmark_sweep_effort [0,10] (default 3 3), -benchmark_sweep_quality [0,10] (quality =
+// clamp(q*10,1,100); default 10 10 => quality 100). Loop order is block (major), then effort, then quality (minor).
+// Metrics: LDR = RGBA + Y PSNR-HVS-M; HDR = non-log2 ASTC HDR RGB PSNR. Output CSV via -benchmark_sweep_csv (default
+// "benchmark_sweep.csv"). Other -benchmark_* options apply per run.
+// Not in the help text yet. Example: basisu -benchmark_sweep XUASTC_LDR_6x6 -benchmark_sweep_block 0 5 -benchmark_sweep_quality 0 10
+static bool benchmark_sweep_mode(command_line_params& opts)
+{
+ apply_hdr_benchmark_image_defaults(opts);
+
+ const basist::basis_tex_format base_fmt = opts.m_benchmark_fmt;
+ const bool is_hdr = basist::basis_tex_format_is_hdr(base_fmt);
+ const bool block_family = basist::basis_tex_format_is_xuastc_ldr(base_fmt) || basist::basis_tex_format_is_astc_ldr(base_fmt);
+
+ // Build the block-size axis. If -benchmark_sweep_block was given, sweep square block-size indices [lo,hi] within the
+ // codec's family. If it was NOT given, default to exactly the codec/block size specified (e.g. XUASTC_LDR_6x6 stays 6x6,
+ // ETC1S/XUBC7/HDR stay as-is) - no block sweep.
+ basisu::vector block_fmts;
+ if (opts.m_benchmark_sweep_block_set)
+ {
+ if (!block_family)
+ {
+ fmt_error_printf("-benchmark_sweep_block requires an XUASTC_LDR or ASTC_LDR codec family\n");
+ return false;
+ }
+ uint32_t blo = opts.m_benchmark_sweep_block_lo, bhi = opts.m_benchmark_sweep_block_hi;
+ if (blo > bhi) std::swap(blo, bhi);
+ for (uint32_t b = blo; b <= bhi; b++)
+ {
+ basist::basis_tex_format f;
+ if (!benchmark_block_index_to_fmt(base_fmt, b, f))
+ {
+ fmt_error_printf("-benchmark_sweep: invalid block size index {}\n", b);
+ return false;
+ }
+ block_fmts.push_back(f);
+ }
+ }
+ else
+ {
+ block_fmts.push_back(base_fmt); // default: the exact codec/block size specified
+ }
+
+ uint32_t elo = opts.m_benchmark_sweep_effort_lo, ehi = opts.m_benchmark_sweep_effort_hi;
+ if (elo > ehi) std::swap(elo, ehi);
+ uint32_t qlo = opts.m_benchmark_sweep_quality_lo, qhi = opts.m_benchmark_sweep_quality_hi;
+ if (qlo > qhi) std::swap(qlo, qhi);
+
+ const uint32_t num_blocks = (uint32_t)block_fmts.size();
+ const uint32_t num_efforts = (ehi - elo) + 1;
+ const uint32_t num_qualities = (qhi - qlo) + 1;
+
+ // Announce the actual sweep up front. With -benchmark_sweep_block the block axis sweeps square sizes within the codec's
+ // family (XUASTC/ASTC only; everything else is fixed 4x4); without it, the codec's own block size is used.
+ fmt_printf("Benchmark sweep: codec {}, {} block size(s), effort {}..{}, quality(x10) {}..{}, {} runs total\n",
+ basist::basis_get_tex_format_name(base_fmt), num_blocks, elo, ehi, qlo, qhi, num_blocks * num_efforts * num_qualities);
+ for (uint32_t bi = 0; bi < num_blocks; bi++)
+ {
+ uint32_t bw = 0, bh = 0;
+ basist::get_basis_tex_format_block_size(block_fmts[bi], bw, bh);
+ fmt_printf(" block[{}]: {} ({}x{})\n", bi, basist::basis_get_tex_format_name(block_fmts[bi]), bw, bh);
+ }
+
+ // Open the CSV up front so a bad path fails before the (potentially long) sweep runs.
+ FILE* pFile = fopen(opts.m_benchmark_sweep_csv.c_str(), "w");
+ if (!pFile)
+ {
+ fmt_error_printf("Failed to open output CSV file \"{}\" for writing!\n", opts.m_benchmark_sweep_csv);
+ return false;
+ }
+
+ if (is_hdr)
+ {
+ // HDR metrics are always linear half-float / log2 (sRGB vs linear doesn't apply), so no metrics field.
+ fmt_fprintf(pFile, "// codec: {}, images: {}N.exr indices {}..{}\n",
+ basist::basis_get_tex_format_name(base_fmt), opts.m_benchmark_path, opts.m_benchmark_first, opts.m_benchmark_last);
+ fmt_fprintf(pFile, "// fmt, block, effort, quality, bitrate, astc_hdr_rgb_psnr\n");
+ }
+ else
+ {
+ fmt_fprintf(pFile, "// codec: {}, metrics: {}, images: {}NN.png indices {}..{}\n",
+ basist::basis_get_tex_format_name(base_fmt), opts.m_benchmark_linear ? "linear" : "sRGB (perceptual)",
+ opts.m_benchmark_path, opts.m_benchmark_first, opts.m_benchmark_last);
+ fmt_fprintf(pFile, "// fmt, block, effort, quality, bitrate, rgba_psnr, y_hvsm_psnr\n");
+ }
+
+ // Run all combinations, storing results in a 3D [block][effort][quality] array, then write the CSV (one row per
+ // combination; loop order block major, then effort, then quality).
+ basisu::vector>> results;
+ results.resize(num_blocks);
+
+ for (uint32_t bi = 0; bi < num_blocks; bi++)
+ {
+ results[bi].resize(num_efforts);
+ for (uint32_t ei = 0; ei < num_efforts; ei++)
+ {
+ results[bi][ei].resize(num_qualities);
+ for (uint32_t qi = 0; qi < num_qualities; qi++)
+ {
+ const uint32_t effort = elo + ei;
+ const uint32_t quality = (uint32_t)clamp((int)(qlo + qi) * 10, 1, 100);
+
+ if (!run_one_benchmark(opts, block_fmts[bi], effort, quality, results[bi][ei][qi]))
+ {
+ fclose(pFile);
+ return false;
+ }
+ }
+ }
+ }
+
+ for (uint32_t bi = 0; bi < num_blocks; bi++)
+ {
+ for (uint32_t ei = 0; ei < num_efforts; ei++)
+ {
+ for (uint32_t qi = 0; qi < num_qualities; qi++)
+ {
+ const benchmark_results& r = results[bi][ei][qi];
+
+ uint32_t bw = 0, bh = 0;
+ basist::get_basis_tex_format_block_size(r.m_tex_fmt, bw, bh);
+ const double bitrate = (double)r.m_total_compressed_size * 8.0 / (double)r.m_total_input_texels;
+
+ if (is_hdr)
+ fmt_fprintf(pFile, "{}, {}x{}, {}, {}, {3.4}, {3.4}\n",
+ basist::basis_get_tex_format_name(r.m_tex_fmt), bw, bh, r.m_effort, r.m_quality, bitrate, r.m_avg_astc_hdr_psnr);
+ else
+ fmt_fprintf(pFile, "{}, {}x{}, {}, {}, {3.4}, {3.4}, {3.4}\n",
+ basist::basis_get_tex_format_name(r.m_tex_fmt), bw, bh, r.m_effort, r.m_quality, bitrate, r.m_avg_rgba_psnr, r.m_avg_y_hvsm_psnr);
+ }
+ }
+ }
+
+ fclose(pFile);
+
+ fmt_printf("Wrote benchmark sweep CSV \"{}\"\n", opts.m_benchmark_sweep_csv);
+ return true;
+}
+
+// -test_dds: standalone test harness for the basist::dds_transcoder DX9/DX10 DDS reader+transcoder.
+// Usage: basisu -test_dds [output_prefix]
+// Generate inputs with the existing exporter, e.g.:
+// basisu -ktx2 -uastc
.png ; basisu -file
.ktx2 -export_dds (or BC7/array/cubemap variants)
+static bool test_dds(int argc, const char* argv[])
+{
+ if (argc < 3)
+ {
+ fmt_error_printf("Usage: basisu -test_dds [output_prefix]\n");
+ return false;
+ }
+
+ const char* pFilename = argv[2];
+ const std::string out_prefix = (argc >= 4) ? std::string(argv[3]) : std::string("dds_test");
+
+ uint8_vec file_data;
+ if (!read_file_to_vec(pFilename, file_data))
+ {
+ fmt_error_printf("Failed reading file \"{}\"\n", pFilename);
+ return false;
+ }
+ fmt_printf("Read {} bytes from \"{}\"\n", file_data.size_u32(), pFilename);
+
+ basist::dds_transcoder dds;
+ if (!dds.init(file_data.data(), file_data.size_u32()))
+ {
+ fmt_error_printf("dds_transcoder::init() failed (unsupported or malformed DDS)\n");
+ return false;
+ }
+ if (!dds.start_transcoding())
+ {
+ fmt_error_printf("dds_transcoder::start_transcoding() failed\n");
+ return false;
+ }
+
+ const uint32_t levels = dds.get_levels();
+ const uint32_t eff_layers = dds.get_layers() ? dds.get_layers() : 1;
+ const uint32_t faces = dds.get_faces();
+ const basist::transcoder_texture_format contained = dds.get_format();
+
+ fmt_printf("DDS: {}x{}, levels: {}, layers: {} ({}), faces: {} ({})\n",
+ dds.get_width(), dds.get_height(), levels, dds.get_layers(), dds.get_layers() ? "array" : "non-array",
+ faces, dds.get_is_cubemap() ? "cubemap" : "2D");
+ fmt_printf("Contained format: {} (enum {}), has_alpha: {}, sRGB: {}\n",
+ basist::basis_get_format_name(contained), (int)contained, (uint32_t)dds.get_has_alpha(), dds.is_srgb() ? 1 : 0);
+
+ // Exercise is_transcode_format_supported() (PVRTC1 depends on pow2 dims; cTFBC6H/PVRTC2 are unsupported).
+ {
+ const bool pow2 = is_pow2(dds.get_width()) && is_pow2(dds.get_height());
+ const basist::transcoder_texture_format probe[] = {
+ basist::transcoder_texture_format::cTFBC7_RGBA, basist::transcoder_texture_format::cTFASTC_4x4_RGBA,
+ basist::transcoder_texture_format::cTFRGBA32, basist::transcoder_texture_format::cTFPVRTC1_4_RGB,
+ basist::transcoder_texture_format::cTFBC6H, basist::transcoder_texture_format::cTFPVRTC2_4_RGB
+ };
+ fmt_printf("is_transcode_format_supported (pow2 dims: {}):\n", pow2 ? 1 : 0);
+ for (basist::transcoder_texture_format f : probe)
+ fmt_printf(" {}: {}\n", basist::basis_get_format_name(f), dds.is_transcode_format_supported(f) ? "yes" : "no");
+ }
+
+ // Dump per-image info.
+ for (uint32_t layer = 0; layer < eff_layers; layer++)
+ for (uint32_t face = 0; face < faces; face++)
+ for (uint32_t level = 0; level < levels; level++)
+ {
+ basist::ktx2_image_level_info li;
+ if (!dds.get_image_level_info(li, level, layer, face))
+ {
+ fmt_error_printf("get_image_level_info failed at A{} F{} M{}\n", layer, face, level);
+ return false;
+ }
+ fmt_printf(" A{} F{} M{}: {}x{} ({}x{} blocks, {} total)\n",
+ layer, face, level, li.m_orig_width, li.m_orig_height, li.m_num_blocks_x, li.m_num_blocks_y, li.m_total_blocks);
+ }
+
+ // Decode every image to RGBA32 and save as PNG (visual verification).
+ uint32_t png_count = 0;
+ for (uint32_t layer = 0; layer < eff_layers; layer++)
+ {
+ for (uint32_t face = 0; face < faces; face++)
+ {
+ for (uint32_t level = 0; level < levels; level++)
+ {
+ basist::ktx2_image_level_info li;
+ dds.get_image_level_info(li, level, layer, face);
+
+ image img(li.m_orig_width, li.m_orig_height);
+ if (!dds.transcode_image_level(level, layer, face, img.get_ptr(), img.get_total_pixels(),
+ basist::transcoder_texture_format::cTFRGBA32, 0, img.get_width(), img.get_height()))
+ {
+ fmt_error_printf("transcode to RGBA32 failed at A{} F{} M{}\n", layer, face, level);
+ return false;
+ }
+
+ char fn[512];
+ snprintf(fn, sizeof(fn), "%s_A%u_F%u_M%u.png", out_prefix.c_str(), layer, face, level);
+ if (!save_png(fn, img))
+ {
+ fmt_error_printf("save_png failed for \"{}\"\n", fn);
+ return false;
+ }
+ png_count++;
+ }
+ }
+ }
+ fmt_printf("Wrote {} decoded RGBA32 PNG(s) (prefix \"{}\")\n", png_count, out_prefix.c_str());
+
+ // Reference RGBA32 decode of the base image, for round-trip PSNR of re-encoded targets.
+ basist::ktx2_image_level_info base_li;
+ dds.get_image_level_info(base_li, 0, 0, 0);
+ image ref_img(base_li.m_orig_width, base_li.m_orig_height);
+ dds.transcode_image_level(0, 0, 0, ref_img.get_ptr(), ref_img.get_total_pixels(),
+ basist::transcoder_texture_format::cTFRGBA32, 0, ref_img.get_width(), ref_img.get_height());
+
+ // Exercise every supported re-encode target on the base image.
+ struct target_row { basist::transcoder_texture_format fmt; basisu::texture_format unpack_fmt; bool can_unpack; };
+ const target_row targets[] =
+ {
+ { basist::transcoder_texture_format::cTFBC1_RGB, basisu::texture_format::cBC1, true },
+ { basist::transcoder_texture_format::cTFBC3_RGBA, basisu::texture_format::cBC3, true },
+ { basist::transcoder_texture_format::cTFBC4_R, basisu::texture_format::cBC4, true },
+ { basist::transcoder_texture_format::cTFBC5_RG, basisu::texture_format::cBC5, true },
+ { basist::transcoder_texture_format::cTFBC7_RGBA, basisu::texture_format::cBC7, true },
+ { basist::transcoder_texture_format::cTFETC1_RGB, basisu::texture_format::cInvalidTextureFormat, false },
+ { basist::transcoder_texture_format::cTFETC2_RGBA, basisu::texture_format::cInvalidTextureFormat, false },
+ { basist::transcoder_texture_format::cTFASTC_4x4_RGBA, basisu::texture_format::cInvalidTextureFormat, false },
+ { basist::transcoder_texture_format::cTFPVRTC1_4_RGB, basisu::texture_format::cInvalidTextureFormat, false },
+ { basist::transcoder_texture_format::cTFRGB565, basisu::texture_format::cInvalidTextureFormat, false },
+ { basist::transcoder_texture_format::cTFRGBA4444, basisu::texture_format::cInvalidTextureFormat, false },
+ };
+
+ // For the re-encode tests use a 4x4-grid sized image (covers compressed targets cleanly).
+ const uint32_t grid_blocks_x = (base_li.m_orig_width + 3) / 4;
+ const uint32_t grid_blocks_y = (base_li.m_orig_height + 3) / 4;
+
+ for (const target_row& t : targets)
+ {
+ const bool uncompressed = (t.fmt == basist::transcoder_texture_format::cTFRGBA32) ||
+ (t.fmt == basist::transcoder_texture_format::cTFRGB565) || (t.fmt == basist::transcoder_texture_format::cTFRGBA4444);
+ const bool is_pvrtc1 = (t.fmt == basist::transcoder_texture_format::cTFPVRTC1_4_RGB) || (t.fmt == basist::transcoder_texture_format::cTFPVRTC1_4_RGBA);
+
+ if (is_pvrtc1 && (!is_pow2(base_li.m_orig_width) || !is_pow2(base_li.m_orig_height)))
+ {
+ fmt_printf(" {}: skipped (PVRTC1 needs power-of-2 dims)\n", basist::basis_get_format_name(t.fmt));
+ continue;
+ }
+
+ const uint32_t bytes_per = basist::basis_get_bytes_per_block_or_pixel(t.fmt);
+ uint32_t buf_units, byte_size;
+ if (uncompressed)
+ {
+ buf_units = base_li.m_orig_width * base_li.m_orig_height;
+ byte_size = buf_units * bytes_per;
+ }
+ else
+ {
+ buf_units = grid_blocks_x * grid_blocks_y;
+ byte_size = buf_units * bytes_per;
+ }
+
+ uint8_vec out_buf(byte_size);
+ const uint32_t row_pitch = uncompressed ? base_li.m_orig_width : 0;
+ const uint32_t rows = uncompressed ? base_li.m_orig_height : 0;
+
+ if (!dds.transcode_image_level(0, 0, 0, out_buf.data(), buf_units, t.fmt, 0, row_pitch, rows))
+ {
+ fmt_error_printf(" {}: transcode FAILED\n", basist::basis_get_format_name(t.fmt));
+ return false;
+ }
+
+ // Round-trip PSNR for the BC formats we can unpack directly.
+ if (t.can_unpack)
+ {
+ image rt(grid_blocks_x * 4, grid_blocks_y * 4);
+ for (uint32_t by = 0; by < grid_blocks_y; by++)
+ for (uint32_t bx = 0; bx < grid_blocks_x; bx++)
+ {
+ color_rgba pixels[16];
+ unpack_block(t.unpack_fmt, out_buf.data() + (by * grid_blocks_x + bx) * bytes_per, pixels, false);
+ for (uint32_t y = 0; y < 4; y++)
+ for (uint32_t x = 0; x < 4; x++)
+ rt.set_clipped(bx * 4 + x, by * 4 + y, pixels[y * 4 + x]);
+ }
+
+ image_metrics m;
+ m.calc(ref_img, rt, 0, 3);
+ const double rgb_psnr = m.m_psnr;
+ m.calc(ref_img, rt, 0, 4);
+ fmt_printf(" {}: OK {} bytes, RGB PSNR {}, RGBA PSNR {}\n",
+ basist::basis_get_format_name(t.fmt), byte_size, rgb_psnr, m.m_psnr);
+ }
+ else
+ {
+ fmt_printf(" {}: OK {} bytes\n", basist::basis_get_format_name(t.fmt), byte_size);
+ }
+ }
+
+ fmt_printf("-test_dds: success\n");
+ return true;
+}
+
+// ---- -test_dds_overwrite: transcoder output-buffer SANITY (overrun) test ----
+// This is a SEPARATE test from -test_dds, and a different kind of test: it does NOT check "did we get the right
+// pixels?" -- it checks "did dds_transcoder::transcode_image_level() ever write past the buffer I declared, and does
+// it correctly REJECT bogus arguments?". For every .dds under the given root (default c:\dev\dds_files), for every
+// (level,layer,face) and every supported output format (compressed AND uncompressed), we transcode into a
+// basisu::vector that is deliberately LARGER than required and pre-filled with the 0xDEADBEEF byte pattern,
+// then verify the bytes at/after the declared buffer size are still pristine (nothing was overwritten). We exercise
+// the optional output_row_pitch / output_rows overrides -- expand AND shrink for uncompressed targets (e.g. a 4x4 DDS
+// unpacked into a clipped 3x3 region), expand-only for compressed -- plus a battery of must-reject argument errors
+// (undersize buffer, shrunken compressed pitch, PVRTC1 non-tight pitch, out-of-range level/layer/face, null output).
+// A rejected call must also leave the buffer entirely pristine. Intended to be run against a DEBUG build.
+// Usage: basisu -test_dds_overwrite [root_dir_or_file]
+static const uint8_t s_deadbeef[4] = { 0xEF, 0xBE, 0xAD, 0xDE }; // little-endian 0xDEADBEEF
+
+static void fill_deadbeef(uint8_vec& buf)
+{
+ for (size_t i = 0; i < buf.size(); i++)
+ buf[i] = s_deadbeef[i & 3];
+}
+
+struct dds_file_entry { std::string m_path; uint64_t m_size; };
+
+// Returns the index of the first byte in [start,end) that is no longer the 0xDEADBEEF pattern, or SIZE_MAX if clean.
+static size_t check_deadbeef(const uint8_vec& buf, size_t start, size_t end)
+{
+ for (size_t i = start; i < end; i++)
+ if (buf[i] != s_deadbeef[i & 3])
+ return i;
+ return SIZE_MAX;
+}
+
+static bool test_dds_overwrite(int argc, const char* argv[])
+{
+ const std::string root = (argc >= 3) ? std::string(argv[2]) : std::string("c:\\dev\\dds_files");
+
+ // Gather every .dds file under root (recursively) via C++17 std::filesystem; allow a single file too.
+ std::vector dds_files;
+ {
+ std::error_code ec;
+ const std::filesystem::path root_path(root);
+ const std::filesystem::file_status st = std::filesystem::status(root_path, ec);
+ if (ec || !std::filesystem::exists(st))
+ {
+ fmt_error_printf("Root path \"{}\" does not exist\n", root.c_str());
+ return false;
+ }
+ if (std::filesystem::is_directory(st))
+ {
+ for (std::filesystem::recursive_directory_iterator it(root_path, std::filesystem::directory_options::skip_permission_denied, ec), end; it != end; it.increment(ec))
+ {
+ if (ec) { ec.clear(); continue; }
+ std::error_code fec;
+ if (!it->is_regular_file(fec))
+ continue;
+ std::string ext = it->path().extension().string();
+ for (char& c : ext) c = (char)tolower((unsigned char)c);
+ if (ext == ".dds")
+ {
+ const uintmax_t sz = it->file_size(fec);
+ dds_files.push_back({ it->path().string(), fec ? 0ull : (uint64_t)sz });
+ }
+ }
+ }
+ else
+ {
+ dds_files.push_back({ root, 0 }); // explicit single file: size 0 => never skipped by the cap below
+ }
+ }
+
+ if (dds_files.empty())
+ {
+ fmt_error_printf("No .dds files found under \"{}\"\n", root.c_str());
+ return false;
+ }
+ std::sort(dds_files.begin(), dds_files.end(), [](const dds_file_entry& a, const dds_file_entry& b) { return a.m_path < b.m_path; });
+ fmt_printf("-test_dds_overwrite: found {} .dds file(s) under \"{}\"\n", (uint64_t)dds_files.size(), root.c_str());
+
+ uint64_t total_calls = 0, total_overwrite_failures = 0, total_expectation_mismatches = 0;
+ uint32_t files_skipped = 0;
+
+ const size_t GUARD_BYTES = 1024; // sentinel margin past the declared buffer to catch overruns
+
+ enum call_kind { KIND_PROBE, KIND_POSITIVE, KIND_MUST_REJECT };
+
+ // A Debug re-encode of an enormous file (e.g. a multi-hundred-MB BC7 texture array) to every output format x
+ // pitch/rows combo is impractical; skip files past this cap. Bounds behavior doesn't depend on image size.
+ const uint64_t MAX_FILE_BYTES = 64ull * 1024 * 1024;
+
+ for (const dds_file_entry& fe : dds_files)
+ {
+ const std::string& fn = fe.m_path;
+ if (fe.m_size > MAX_FILE_BYTES)
+ {
+ fmt_printf(" SKIP (too large for Debug test, {} MB): \"{}\"\n", (uint64_t)(fe.m_size / (1024 * 1024)), fn.c_str());
+ files_skipped++;
+ continue;
+ }
+
+ uint8_vec file_data;
+ if (!read_file_to_vec(fn.c_str(), file_data))
+ {
+ fmt_printf(" SKIP (read failed): \"{}\"\n", fn.c_str());
+ files_skipped++;
+ continue;
+ }
+
+ basist::dds_transcoder dds;
+ if (!dds.init(file_data.data(), file_data.size_u32()) || !dds.start_transcoding())
+ {
+ // The corpus may legitimately contain DDS variants this transcoder doesn't accept -- not a test failure.
+ fmt_printf(" SKIP (init failed): \"{}\"\n", fn.c_str());
+ files_skipped++;
+ continue;
+ }
+
+ const uint32_t levels = dds.get_levels();
+ const uint32_t eff_layers = dds.get_layers() ? dds.get_layers() : 1;
+ const uint32_t faces = dds.get_faces();
+
+ uint64_t file_calls = 0, file_fail = 0;
+
+ // One transcode into a guarded, sentinel-filled buffer. Verifies: (1) nothing written at/after the declared
+ // size; (2) a rejected call wrote nothing anywhere; (3) the success/failure expectation. Returns the result.
+ auto run_one = [&](uint32_t lvl, uint32_t lyr, uint32_t fc,
+ basist::transcoder_texture_format f, uint32_t bytes_per,
+ uint32_t buf_units, uint32_t P, uint32_t R,
+ call_kind kind, bool path_active, const char* desc) -> bool
+ {
+ total_calls++; file_calls++;
+ const size_t declared_bytes = (size_t)buf_units * bytes_per;
+ uint8_vec buf(declared_bytes + GUARD_BYTES);
+ fill_deadbeef(buf);
+
+ const bool ok = dds.transcode_image_level(lvl, lyr, fc, buf.data(), buf_units, f, 0, P, R);
+
+ // (1) CORE CHECK: the transcoder may never touch a byte at/after the declared buffer size.
+ const size_t bad_guard = check_deadbeef(buf, declared_bytes, buf.size());
+ if (bad_guard != SIZE_MAX)
+ {
+ fmt_error_printf(" *** OVERWRITE PAST BUFFER: {} | {} [{}] L{} A{} F{} P={} R={} units={} -> wrote +{} bytes past declared {} ***\n",
+ fn.c_str(), basist::basis_get_format_name(f), desc, lvl, lyr, fc, P, R, buf_units,
+ (uint64_t)(bad_guard - declared_bytes), (uint64_t)declared_bytes);
+ total_overwrite_failures++; file_fail++;
+ }
+
+ // (2) A rejected transcode must not have written ANYTHING into the buffer.
+ if (!ok)
+ {
+ const size_t bad_any = check_deadbeef(buf, 0, buf.size());
+ if (bad_any != SIZE_MAX)
+ {
+ fmt_error_printf(" *** REJECTED CALL WROTE TO BUFFER: {} | {} [{}] L{} A{} F{} P={} R={} at byte {} ***\n",
+ fn.c_str(), basist::basis_get_format_name(f), desc, lvl, lyr, fc, P, R, (uint64_t)bad_any);
+ total_overwrite_failures++; file_fail++;
+ }
+ }
+
+ // (3) Expectation. A must-reject call that succeeds, or a should-succeed call (when the path is active)
+ // that fails, is a mismatch. The PROBE kind only establishes whether the path is active (no expectation).
+ if ((kind == KIND_MUST_REJECT) && ok)
+ {
+ fmt_error_printf(" *** EXPECTED REJECTION BUT SUCCEEDED: {} | {} [{}] L{} A{} F{} P={} R={} units={} ***\n",
+ fn.c_str(), basist::basis_get_format_name(f), desc, lvl, lyr, fc, P, R, buf_units);
+ total_expectation_mismatches++; file_fail++;
+ }
+ else if ((kind == KIND_POSITIVE) && path_active && !ok)
+ {
+ fmt_error_printf(" *** EXPECTED SUCCESS BUT FAILED: {} | {} [{}] L{} A{} F{} P={} R={} units={} ***\n",
+ fn.c_str(), basist::basis_get_format_name(f), desc, lvl, lyr, fc, P, R, buf_units);
+ total_expectation_mismatches++; file_fail++;
+ }
+ return ok;
+ };
+
+ for (uint32_t lyr = 0; lyr < eff_layers; lyr++)
+ for (uint32_t fc = 0; fc < faces; fc++)
+ for (uint32_t lvl = 0; lvl < levels; lvl++)
+ {
+ basist::ktx2_image_level_info li;
+ if (!dds.get_image_level_info(li, lvl, lyr, fc))
+ {
+ fmt_error_printf(" get_image_level_info failed L{} A{} F{} in \"{}\"\n", lvl, lyr, fc, fn.c_str());
+ file_fail++;
+ continue;
+ }
+ const uint32_t W = li.m_orig_width, H = li.m_orig_height;
+ // DDS blocks are always 4x4; the output 4x4-block grid is ceil(dim/4) regardless of source kind.
+ const uint32_t nbx = (W + 3) / 4, nby = (H + 3) / 4;
+
+ for (int fi = 0; fi < (int)basist::transcoder_texture_format::cTFTotalTextureFormats; fi++)
+ {
+ const basist::transcoder_texture_format f = (basist::transcoder_texture_format)fi;
+ if (!dds.is_transcode_format_supported(f))
+ continue;
+
+ const bool uncompressed = basist::basis_transcoder_format_is_uncompressed(f);
+ const bool is_pvrtc = (f == basist::transcoder_texture_format::cTFPVRTC1_4_RGB) ||
+ (f == basist::transcoder_texture_format::cTFPVRTC1_4_RGBA);
+ const uint32_t bytes_per = basist::basis_get_bytes_per_block_or_pixel(f);
+
+ // Declared output units for a given (pitch,rows) per the transcode contract:
+ // uncompressed: (pitch?:width) * (rows?:height) pixels; compressed: (pitch?:num_blocks_x) * num_blocks_y blocks.
+ auto declared_units = [&](uint32_t P, uint32_t R) -> uint32_t
+ {
+ if (uncompressed)
+ return (P ? P : W) * (R ? R : H);
+ return (P ? P : nbx) * nby;
+ };
+
+ // PROBE the default pitch/rows: is this format's transcode path active for this source+build?
+ // (A decode->repack target returns false in a build without BASISD_SUPPORT_XUASTC; don't flag that.)
+ const uint32_t du_default = declared_units(0, 0);
+ const bool path_active = run_one(lvl, lyr, fc, f, bytes_per, du_default, 0, 0, KIND_PROBE, false, "default");
+
+ // ---- Positive combos (should succeed whenever the path is active) ----
+ struct PR { uint32_t P, R; const char* desc; };
+ std::vector pos;
+ if (uncompressed)
+ {
+ pos.push_back({ W + 5, 0, "expand-pitch" });
+ pos.push_back({ W + 5, H + 3, "expand-pitch+rows" });
+ pos.push_back({ 0, H + 3, "expand-rows" });
+ if (W >= 2) { pos.push_back({ W - 1, 0, "shrink-pitch-1" }); pos.push_back({ (W + 1) / 2, 0, "shrink-pitch-half" }); }
+ if (H >= 2) { pos.push_back({ 0, H - 1, "shrink-rows-1" }); pos.push_back({ 0, (H + 1) / 2, "shrink-rows-half" }); }
+ if ((W >= 3) && (H >= 3)) pos.push_back({ 3, 3, "clip-to-3x3" });
+ }
+ else if (!is_pvrtc)
+ {
+ pos.push_back({ nbx + 2, 0, "expand-pitch" });
+ }
+ for (const PR& c : pos)
+ run_one(lvl, lyr, fc, f, bytes_per, declared_units(c.P, c.R), c.P, c.R, KIND_POSITIVE, path_active, c.desc);
+
+ // ---- Must-reject combos ----
+ if (du_default >= 1) // buffer one unit too small for the default extent
+ run_one(lvl, lyr, fc, f, bytes_per, du_default - 1, 0, 0, KIND_MUST_REJECT, false, "undersize-buffer");
+ if (is_pvrtc) // PVRTC1 is whole-image: any non-tight pitch must be rejected
+ run_one(lvl, lyr, fc, f, bytes_per, declared_units(0, 0), nbx + 2, 0, KIND_MUST_REJECT, false, "pvrtc-nontight-pitch");
+ else if (!uncompressed && (nbx >= 2)) // compressed pitch < num_blocks_x would drop blocks (generous buffer, so only the pitch is bad)
+ run_one(lvl, lyr, fc, f, bytes_per, nbx * nby, nbx - 1, 0, KIND_MUST_REJECT, false, "compressed-shrink-pitch");
+ } // fmt
+
+ // ---- Per-(level,layer,face) must-reject: out-of-range indices & null output (format-independent) ----
+ {
+ const basist::transcoder_texture_format f = basist::transcoder_texture_format::cTFRGBA32;
+ const uint32_t bytes_per = basist::basis_get_bytes_per_block_or_pixel(f);
+ const uint32_t du = W * H;
+ run_one(levels, lyr, fc, f, bytes_per, du, 0, 0, KIND_MUST_REJECT, false, "bad-level");
+ run_one(lvl, eff_layers, fc, f, bytes_per, du, 0, 0, KIND_MUST_REJECT, false, "bad-layer");
+ run_one(lvl, lyr, faces, f, bytes_per, du, 0, 0, KIND_MUST_REJECT, false, "bad-face");
+
+ total_calls++; file_calls++;
+ if (dds.transcode_image_level(lvl, lyr, fc, nullptr, du, f, 0, 0, 0))
+ {
+ fmt_error_printf(" *** EXPECTED REJECTION (null output) BUT SUCCEEDED: {} L{} A{} F{} ***\n", fn.c_str(), lvl, lyr, fc);
+ total_expectation_mismatches++; file_fail++;
+ }
+ }
+ } // level/layer/face
+
+ if (file_fail)
+ fmt_error_printf(" {} : {} call(s), {} FAILURE(s)\n", fn.c_str(), (uint64_t)file_calls, (uint64_t)file_fail);
+ else
+ fmt_printf(" {} : {} calls OK\n", fn.c_str(), (uint64_t)file_calls);
+ fflush(stdout); // stream per-file progress (stdout is block-buffered when redirected)
+ } // file
+
+ fmt_printf("\n-test_dds_overwrite summary: {} call(s) across {} file(s) ({} skipped)\n",
+ (uint64_t)total_calls, (uint64_t)dds_files.size(), files_skipped);
+ fmt_printf(" buffer-overwrite failures: {}\n", (uint64_t)total_overwrite_failures);
+ fmt_printf(" expectation mismatches: {}\n", (uint64_t)total_expectation_mismatches);
+
+ const bool success = (total_overwrite_failures == 0) && (total_expectation_mismatches == 0);
+ fmt_printf("-test_dds_overwrite: {}\n", success ? "SUCCESS" : "*** FAILED ***");
+ return success;
+}
+
+// Maps an -export_dds format string to a transcoder_texture_format. Accepts the
+// DirectX BC formats DDS supports plus uncompressed 32-bit RGBA. Case-insensitive.
+static bool parse_export_dds_format(const std::string& s, basist::transcoder_texture_format& fmt)
+{
+ typedef basist::transcoder_texture_format tf;
+
+ if (strcasecmp(s.c_str(), "BC1") == 0)
+ fmt = tf::cTFBC1_RGB;
+ else if (strcasecmp(s.c_str(), "BC3") == 0)
+ fmt = tf::cTFBC3_RGBA;
+ else if (strcasecmp(s.c_str(), "BC4") == 0)
+ fmt = tf::cTFBC4_R;
+ else if (strcasecmp(s.c_str(), "BC5") == 0)
+ fmt = tf::cTFBC5_RG;
+ else if (strcasecmp(s.c_str(), "BC6H") == 0)
+ fmt = tf::cTFBC6H;
+ else if (strcasecmp(s.c_str(), "BC7") == 0)
+ fmt = tf::cTFBC7_RGBA;
+ else if (strcasecmp(s.c_str(), "RGBA32") == 0)
+ fmt = tf::cTFRGBA32;
+ else
+ return false;
+
+ return true;
+}
+
+// Writes a basic DX10 .dds file from source image(s). Uses basis_compressor in process_source_images()
+// mode to load/mipmap/lay-out the slices, then packs them to the requested format (see basisu_dds_export.*).
+static bool dds_mode(command_line_params& opts)
+{
+ if (!opts.m_input_filenames.size())
+ {
+ error_printf("-dds: no input image file(s) specified (use -file or list them on the command line)\n");
+ return false;
+ }
+
+ dds_export_params dds_params;
+ if (!opts.m_dds_format.size())
+ {
+ // No -dds_format given: default to bc7 (a reasonable general-purpose default), but warn.
+ printf("Warning: -dds: no -dds_format specified; defaulting to BC7\n");
+ dds_params.m_format = cDDSFmtBC7;
+ }
+ else if (!parse_dds_output_format(opts.m_dds_format.c_str(), dds_params.m_format))
+ {
+ error_printf("-dds: invalid -dds_format \"%s\". Valid: BC1 BC2 BC3 BC4 BC5 BC7 A8R8G8B8 A8B8G8R8 R8 R8G8 R5G6B5 A1R5G5B5 A4R4G4B4\n", opts.m_dds_format.c_str());
+ return false;
+ }
+
+ // Apply optional BC7 packer overrides (only meaningful for -dds_format bc7).
+ if (opts.m_dds_bc7_encoder != cDDSBC7Encoder_Default)
+ dds_params.m_bc7_encoder = opts.m_dds_bc7_encoder;
+ if (opts.m_dds_bc7f_level >= 0)
+ dds_params.m_bc7f_level = opts.m_dds_bc7f_level;
+ if (opts.m_dds_bc7e_scalar_level >= 0)
+ dds_params.m_bc7e_scalar_level = opts.m_dds_bc7e_scalar_level;
+
+ // Only 2D, 2D arrays, and cubemaps/cubemap arrays are supported. Reject volume/video (we always write a
+ // 2D-dimension DX10 header), rather than silently producing a mislabeled 2D-array file.
+ const basist::basis_texture_type tex_type = opts.m_comp_params.m_tex_type;
+ if ((tex_type != basist::cBASISTexType2D) && (tex_type != basist::cBASISTexType2DArray) && (tex_type != basist::cBASISTexTypeCubemapArray))
+ {
+ error_printf("-dds: -tex_type %u is not supported; use 2d, 2darray, or cubemap.\n", (uint32_t)tex_type);
+ return false;
+ }
+
+ // -dds is LDR only (we force the XUBC7/LDR prep path below); reject HDR up front rather than silently
+ // down-converting an HDR request.
+ if (opts.m_comp_params.m_hdr)
+ {
+ error_printf("-dds: HDR is not supported (LDR only).\n");
+ return false;
+ }
+
+ // Thread pool size, same approach as compress_mode().
+ uint32_t num_threads = 1;
+ if (opts.m_comp_params.m_multithreading)
+ {
+ num_threads = get_num_hardware_threads();
+ if (num_threads < 1)
+ num_threads = 1;
+ if (num_threads > opts.m_max_threads)
+ num_threads = opts.m_max_threads;
+ }
+ job_pool jpool(num_threads);
+
+ // Run the compressor in source-images-only mode: it loads the images, generates mipmaps, and lays out the
+ // cubemap/array slices, but does NOT encode. We inherit the relevant CLI options (mip/cubemap/array,
+ // sRGB/linear, swizzle, resample, y_flip, ...) from m_comp_params.
+ basis_compressor_params params = opts.m_comp_params;
+ params.m_pJob_pool = &jpool;
+ params.m_read_source_images = true;
+ params.m_source_filenames = opts.m_input_filenames;
+ params.m_write_output_basis_or_ktx2_files = false;
+
+ // Spoof the format to XUBC7 so the LDR prep path runs (keeps RGBA together, 4x4 block layout), and enable
+ // the KTX2 constraint check (all images same resolution + same mip count == the DDS array/cubemap constraints).
+ params.set_format_mode(basist::basis_tex_format::cXUBC7);
+ params.m_create_ktx2_file = true;
+
+ basis_compressor comp;
+ if (!comp.init(params))
+ {
+ error_printf("-dds: basis_compressor::init() failed\n");
+ return false;
+ }
+
+ basis_compressor::error_code ec = comp.process_source_images();
+ if (ec != basis_compressor::cECSuccess)
+ {
+ error_printf("-dds: failed loading/preparing source images (error code %u)\n", (uint32_t)ec);
+ return false;
+ }
+
+ // Output filename: -output_file if given, else .dds (honoring -output_path).
+ std::string output_filename;
+ if (opts.m_output_filename.size())
+ {
+ output_filename = opts.m_output_filename;
+ }
+ else
+ {
+ std::string base;
+ string_get_filename(opts.m_input_filenames[0].c_str(), base);
+ string_remove_extension(base);
+ output_filename = base + ".dds";
+
+ if (opts.m_output_path.size())
+ string_combine_path(output_filename, opts.m_output_path.c_str(), output_filename.c_str());
+ }
+
+ uint32_t width = 0, height = 0, levels = 0, layers = 0, faces = 0;
+ uint8_vec dds_data;
+ std::string err;
+ if (!build_dds(dds_data, comp, dds_params, err, &width, &height, &levels, &layers, &faces))
+ {
+ error_printf("-dds: %s\n", err.c_str());
+ return false;
+ }
+
+ if (!write_vec_to_file(output_filename.c_str(), dds_data))
+ {
+ error_printf("-dds: failed writing output file \"%s\"\n", output_filename.c_str());
+ return false;
+ }
+
+ // Report the variant actually written: sRGB only when the format has an sRGB DXGI variant AND it was requested.
+ // Gated on status output so -quiet / -no_status_output silences it.
+ const bool wrote_srgb = comp.get_params().m_ktx2_and_basis_srgb_transfer_function && dds_output_format_has_srgb_variant(dds_params.m_format);
+ if (opts.m_comp_params.m_status_output)
+ printf("Wrote DDS file \"%s\": %u bytes, format %s %s, %ux%u, %u level(s), %u layer(s), %u face(s)\n",
+ output_filename.c_str(), dds_data.size_u32(), get_dds_output_format_string(dds_params.m_format),
+ wrote_srgb ? "(sRGB)" : "(UNORM)",
+ width, height, levels, layers, faces);
+
+ return true;
+}
+
+// -export_dds : transcodes each input .ktx2 file to FORMAT and writes a
+// .DDS file (all mip levels/array layers/cubemap faces). FORMAT is one of
+// BC1 BC3 BC4 BC5 BC6H BC7 RGBA32.
+static bool export_dds_mode(command_line_params& opts)
+{
+ if (!opts.m_input_filenames.size())
+ {
+ error_printf("-export_dds: no input .ktx2 file(s) specified (use -file)\n");
+ return false;
+ }
+
+ basist::transcoder_texture_format fmt;
+ if (!parse_export_dds_format(opts.m_export_dds_format, fmt))
+ {
+ error_printf("-export_dds: invalid format \"%s\". Valid formats: BC1 BC3 BC4 BC5 BC6H BC7 RGBA32\n", opts.m_export_dds_format.c_str());
+ return false;
+ }
+
+ if ((opts.m_output_filename.size()) && (opts.m_input_filenames.size() > 1))
+ printf("Warning: -output_file is ignored with multiple inputs; using _.dds names\n");
+
+ uint32_t num_succeeded = 0;
+
+ // Per-file errors skip-and-continue so one bad file doesn't abandon the batch.
+ for (size_t file_index = 0; file_index < opts.m_input_filenames.size(); file_index++)
+ {
+ const std::string& input_filename = opts.m_input_filenames[file_index];
+
+ std::string ext(string_get_extension(input_filename));
+ if (strcasecmp(ext.c_str(), "ktx2") != 0)
+ {
+ error_printf("-export_dds: input file \"%s\" is not a .ktx2 file, skipping\n", input_filename.c_str());
+ continue;
+ }
+
+ uint8_vec ktx2_file_data;
+ if (!read_file_to_vec(input_filename.c_str(), ktx2_file_data))
+ {
+ error_printf("Failed reading input file \"%s\", skipping\n", input_filename.c_str());
+ continue;
+ }
+
+ basist::ktx2_transcoder dec;
+ if (!dec.init(ktx2_file_data.data(), ktx2_file_data.size_u32()))
+ {
+ error_printf("Failed parsing KTX2 file \"%s\", skipping\n", input_filename.c_str());
+ continue;
+ }
+
+ uint8_vec dds_data;
+ // -1 = auto sRGB; decode flags from the same options -unpack uses (deblocking, HQ, etc.).
+ if (!transcode_ktx2_to_dds(dec, fmt, dds_data, -1, get_transcode_flags_from_options(opts)))
+ {
+ error_printf("Failed exporting \"%s\" to a %s DDS file, skipping\n", input_filename.c_str(), opts.m_export_dds_format.c_str());
+ continue;
+ }
+
+ // Output name: -output_file (single input only), else _.dds.
+ // -output_path sets the directory.
+ std::string output_filename;
+ if ((opts.m_output_filename.size()) && (opts.m_input_filenames.size() == 1))
+ {
+ output_filename = opts.m_output_filename;
+ }
+ else
+ {
+ std::string base;
+ string_get_filename(input_filename.c_str(), base);
+ string_remove_extension(base);
+ output_filename = string_format("%s_%s.dds", base.c_str(), opts.m_export_dds_format.c_str());
+
+ if (opts.m_output_path.size())
+ string_combine_path(output_filename, opts.m_output_path.c_str(), output_filename.c_str());
+ }
+
+ if (!write_vec_to_file(output_filename.c_str(), dds_data))
+ {
+ error_printf("Failed writing output DDS file \"%s\", skipping\n", output_filename.c_str());
+ continue;
+ }
+
+ printf("Wrote DDS file \"%s\": %u bytes, format %s, %ux%u, %u level(s), %u layer(s), %u face(s)\n",
+ output_filename.c_str(), dds_data.size_u32(), opts.m_export_dds_format.c_str(),
+ dec.get_width(), dec.get_height(), dec.get_levels(), maximum(1, dec.get_layers()), dec.get_faces());
+
+ num_succeeded++;
+ }
+
+ return num_succeeded != 0;
+}
+
+// Maps an -export_ktx format string to a transcoder_texture_format. Compressed
+// formats only (the KTX1 writer doesn't do uncompressed yet). Case-insensitive.
+static bool parse_export_ktx_format(const std::string& s, basist::transcoder_texture_format& fmt)
+{
+ typedef basist::transcoder_texture_format tf;
+
+ static const struct { const char* pName; tf fmt; } s_map[] =
+ {
+ { "BC1", tf::cTFBC1_RGB }, { "BC3", tf::cTFBC3_RGBA }, { "BC4", tf::cTFBC4_R }, { "BC5", tf::cTFBC5_RG },
+ { "BC6H", tf::cTFBC6H }, { "BC7", tf::cTFBC7_RGBA },
+ { "ETC1", tf::cTFETC1_RGB }, { "ETC2", tf::cTFETC2_RGBA },
+ { "EAC_R11", tf::cTFETC2_EAC_R11 }, { "EAC_RG11", tf::cTFETC2_EAC_RG11 },
+ { "PVRTC1", tf::cTFPVRTC1_4_RGBA }, { "PVRTC1_RGBA", tf::cTFPVRTC1_4_RGBA }, { "PVRTC1_RGB", tf::cTFPVRTC1_4_RGB },
+ { "PVRTC2", tf::cTFPVRTC2_4_RGBA }, { "PVRTC2_RGBA", tf::cTFPVRTC2_4_RGBA }, { "PVRTC2_RGB", tf::cTFPVRTC2_4_RGB },
+ { "ASTC", tf::cTFASTC_LDR_4x4_RGBA }, { "ASTC_4x4", tf::cTFASTC_LDR_4x4_RGBA },
+ { "ASTC_5x4", tf::cTFASTC_LDR_5x4_RGBA }, { "ASTC_5x5", tf::cTFASTC_LDR_5x5_RGBA },
+ { "ASTC_6x5", tf::cTFASTC_LDR_6x5_RGBA }, { "ASTC_6x6", tf::cTFASTC_LDR_6x6_RGBA },
+ { "ASTC_8x5", tf::cTFASTC_LDR_8x5_RGBA }, { "ASTC_8x6", tf::cTFASTC_LDR_8x6_RGBA },
+ { "ASTC_10x5", tf::cTFASTC_LDR_10x5_RGBA }, { "ASTC_10x6", tf::cTFASTC_LDR_10x6_RGBA },
+ { "ASTC_8x8", tf::cTFASTC_LDR_8x8_RGBA }, { "ASTC_10x8", tf::cTFASTC_LDR_10x8_RGBA },
+ { "ASTC_10x10", tf::cTFASTC_LDR_10x10_RGBA }, { "ASTC_12x10", tf::cTFASTC_LDR_12x10_RGBA },
+ { "ASTC_12x12", tf::cTFASTC_LDR_12x12_RGBA },
+ { "ASTC_HDR_4x4", tf::cTFASTC_HDR_4x4_RGBA }, { "ASTC_HDR_6x6", tf::cTFASTC_HDR_6x6_RGBA },
+ };
+
+ for (uint32_t i = 0; i < (uint32_t)std::size(s_map); i++)
+ {
+ if (strcasecmp(s.c_str(), s_map[i].pName) == 0)
+ {
+ fmt = s_map[i].fmt;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// -export_ktx : transcodes each input .ktx2 file to a compressed KTX1 (.ktx)
+// file (all mip levels/array layers/cubemap faces). Compressed formats only.
+static bool export_ktx_mode(command_line_params& opts)
+{
+ if (!opts.m_input_filenames.size())
+ {
+ error_printf("-export_ktx: no input .ktx2 file(s) specified (use -file)\n");
+ return false;
+ }
+
+ basist::transcoder_texture_format fmt;
+ if (!parse_export_ktx_format(opts.m_export_ktx_format, fmt))
+ {
+ error_printf("-export_ktx: invalid/unsupported format \"%s\". Valid: BC1 BC3 BC4 BC5 BC6H BC7 ETC1 ETC2 EAC_R11 EAC_RG11 PVRTC1[_RGB] PVRTC2[_RGB] ASTC ASTC_ ASTC_HDR_4x4 ASTC_HDR_6x6\n", opts.m_export_ktx_format.c_str());
+ return false;
+ }
+
+ if ((opts.m_output_filename.size()) && (opts.m_input_filenames.size() > 1))
+ printf("Warning: -output_file is ignored with multiple inputs; using _.ktx names\n");
+
+ uint32_t num_succeeded = 0;
+
+ // Per-file errors skip-and-continue so one bad file doesn't abandon the batch.
+ for (size_t file_index = 0; file_index < opts.m_input_filenames.size(); file_index++)
+ {
+ const std::string& input_filename = opts.m_input_filenames[file_index];
+
+ std::string ext(string_get_extension(input_filename));
+ if (strcasecmp(ext.c_str(), "ktx2") != 0)
+ {
+ error_printf("-export_ktx: input file \"%s\" is not a .ktx2 file, skipping\n", input_filename.c_str());
+ continue;
+ }
+
+ uint8_vec ktx2_file_data;
+ if (!read_file_to_vec(input_filename.c_str(), ktx2_file_data))
+ {
+ error_printf("Failed reading input file \"%s\", skipping\n", input_filename.c_str());
+ continue;
+ }
+
+ basist::ktx2_transcoder dec;
+ if (!dec.init(ktx2_file_data.data(), ktx2_file_data.size_u32()))
+ {
+ error_printf("Failed parsing KTX2 file \"%s\", skipping\n", input_filename.c_str());
+ continue;
+ }
+
+ uint8_vec ktx_data;
+ // -1 = auto sRGB; decode flags from the same options -unpack uses (deblocking, HQ, etc.).
+ if (!transcode_ktx2_to_ktx(dec, fmt, ktx_data, -1, get_transcode_flags_from_options(opts)))
+ {
+ error_printf("Failed exporting \"%s\" to a %s KTX file, skipping\n", input_filename.c_str(), opts.m_export_ktx_format.c_str());
+ continue;
+ }
+
+ // Output name: -output_file (single input only), else _.ktx.
+ // -output_path sets the directory.
+ std::string output_filename;
+ if ((opts.m_output_filename.size()) && (opts.m_input_filenames.size() == 1))
+ {
+ output_filename = opts.m_output_filename;
+ }
+ else
+ {
+ std::string base;
+ string_get_filename(input_filename.c_str(), base);
+ string_remove_extension(base);
+ output_filename = string_format("%s_%s.ktx", base.c_str(), opts.m_export_ktx_format.c_str());
+
+ if (opts.m_output_path.size())
+ string_combine_path(output_filename, opts.m_output_path.c_str(), output_filename.c_str());
+ }
+
+ if (!write_vec_to_file(output_filename.c_str(), ktx_data))
+ {
+ error_printf("Failed writing output KTX file \"%s\", skipping\n", output_filename.c_str());
+ continue;
+ }
+
+ printf("Wrote KTX file \"%s\": %u bytes, format %s, %ux%u, %u level(s), %u layer(s), %u face(s)\n",
+ output_filename.c_str(), ktx_data.size_u32(), opts.m_export_ktx_format.c_str(),
+ dec.get_width(), dec.get_height(), dec.get_levels(), maximum(1, dec.get_layers()), dec.get_faces());
+
+ num_succeeded++;
+ }
+
+ return num_succeeded != 0;
+}
+
+// --tinydds_info : prints high-level header info for each input .DDS file (via tinydds)
+// (header only, no pixel decode) -- a quick sanity check for exported DDS files.
+static bool tinydds_info_mode(command_line_params& opts)
+{
+ if (!opts.m_input_filenames.size())
+ {
+ error_printf("--tinydds_info: no input .DDS file(s) specified (use -file)\n");
+ return false;
+ }
+
+ uint32_t num_ok = 0;
+ for (size_t i = 0; i < opts.m_input_filenames.size(); i++)
+ {
+ if (i)
+ printf("\n");
+ if (print_dds_info(opts.m_input_filenames[i].c_str()))
+ num_ok++;
+ }
+
+ return num_ok != 0;
+}
+
+// --ktx_info : prints the KTX1 (.ktx) header fields for each input file
+// (header only, no key/value or image data) -- a quick sanity check for exports.
+static bool ktx_info_mode(command_line_params& opts)
+{
+ if (!opts.m_input_filenames.size())
+ {
+ error_printf("--ktx_info: no input .ktx file(s) specified (use -file)\n");
+ return false;
+ }
+
+ uint32_t num_ok = 0;
+ for (size_t i = 0; i < opts.m_input_filenames.size(); i++)
+ {
+ if (i)
+ printf("\n");
+ if (print_ktx_info(opts.m_input_filenames[i].c_str()))
+ num_ok++;
+ }
+
+ return num_ok != 0;
+}
+
static int main_internal(int argc, const char** argv)
{
printf("Basis Universal LDR/HDR GPU Texture Supercompression System v" BASISU_TOOL_VERSION
@@ -6068,13 +9643,13 @@ static int main_internal(int argc, const char** argv)
" (x64)"
#elif defined(__wasi__)
" (WASI"
- #if BASISU_WASI_THREADS
+#if BASISU_WASI_THREADS
" Threaded"
- #endif
+#endif
")"
#endif
- "\nCopyright (C) 2019-2026 Binomial LLC, All rights reserved\n");
+ "\nCopyright (C) 2019-2026 Binomial LLC, All rights reserved\n");
#ifdef FORCE_SAN_FAILURE
force_san_failure();
@@ -6082,7 +9657,7 @@ static int main_internal(int argc, const char** argv)
//interval_timer tm;
//tm.start();
-
+
// See if OpenCL support has been disabled. We don't want to parse the command line until the lib is initialized
bool use_opencl = false;
bool opencl_force_serialization = false;
@@ -6091,16 +9666,16 @@ static int main_internal(int argc, const char** argv)
for (int i = 1; i < argc; i++)
{
- if ((strcmp(argv[i], "-opencl") == 0) || (strcmp(argv[i], "-clbench") == 0))
+ if (opt_match(argv[i], {"-opencl", "-clbench"}))
use_opencl = true;
- if (strcmp(argv[i], "-opencl_serialize") == 0)
+ if (opt_match(argv[i], "-opencl_serialize"))
opencl_force_serialization = true;
-
- if ((strcmp(argv[i], "-peek_astc") == 0) || (strcmp(argv[i], "-peek") == 0))
+
+ if (opt_match(argv[i], {"-peek_astc", "-peek"}))
astc_peek_flag = true;
- if (strcmp(argv[i], "-dev_astc_fuzz") == 0)
+ if (opt_match(argv[i], "-dev_astc_fuzz"))
astc_fuzz_flag = true;
}
@@ -6110,9 +9685,74 @@ static int main_internal(int argc, const char** argv)
fprintf(stderr, "WARNING: -opencl specified, but OpenCL support was not defined or enabled at compile time! With cmake, use -D BASISU_OPENCL=1. Falling back to CPU compression.\n");
}
#endif
-
+
basisu_encoder_init(use_opencl, opencl_force_serialization);
+
+#if 0
+ image a, b;
+ if (!load_png("d:/dev/test_images/bik/bik23.png", a))
+ return EXIT_FAILURE;
+ if (!load_png("c:/dev/e23_4.png", b))
+ return EXIT_FAILURE;
+
+ fmt_printf("Resolution: {}x{}\n", a.get_width(), a.get_height());
+
+ print_image_metrics(a, b);
+
+ psnr_hvs_metrics hvs_metrics;
+ if (!psnr_hvs_compute_metrics(a, b, hvs_metrics))
+ return EXIT_FAILURE;
+ psnr_hvs_print_metrics(hvs_metrics);
+#if 0
+ for (uint32_t y = 0; y < a.get_height(); y++)
+ {
+ for (uint32_t x = 0; x < a.get_width(); x++)
+ {
+ a(x, y).set(get_601_y(a(x, y).r, a(x, y).g, a(x, y).b));
+ b(x, y).set(get_601_y(b(x, y).r, b(x, y).g, b(x, y).b));
+ }
+ }
+
+ image_metrics im;
+ image_metrics::hvs_metrics hvs[3];
+
+ double sum_hvs_mseh = 0.0f;
+ double sum_hvsm_mseh = 0.0f;
+
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ if (!im.compute_hvs_chan(a, b, c, hvs[c]))
+ return EXIT_FAILURE;
+
+ fmt_printf("{c}: PSNR-HVS: {}, PSNR-HVS-M: {}\n", (int)("RGB"[c]), hvs[c].psnr_hvs, hvs[c].psnr_hvsm);
+
+ sum_hvs_mseh += hvs[c].mseh_hvs;
+ sum_hvsm_mseh += hvs[c].mseh_hvsm;
+ }
+
+ sum_hvs_mseh /= 3.0f;
+ sum_hvsm_mseh /= 3.0f;
+
+ fmt_printf("RGB PSNR-HVS: {}\n", psnr_hvs_calc_psnr(sum_hvs_mseh, 1.0f));
+ fmt_printf("RGB PSNR-HVS-M: {}\n", psnr_hvs_calc_psnr(sum_hvsm_mseh, 1.0f));
+#endif
+
+ exit(0);
+#endif
+
+ // Standalone DDS reader/transcoder test harness (see test_dds()).
+ if ((argc >= 2) && (strcmp(argv[1], "-test_dds") == 0))
+ {
+ return test_dds(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE;
+ }
+
+ // DDS transcoder output-buffer overrun / argument-rejection sanity test (see test_dds_overwrite()).
+ if ((argc >= 2) && (strcmp(argv[1], "-test_dds_overwrite") == 0))
+ {
+ return test_dds_overwrite(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE;
+ }
+
if (astc_fuzz_flag)
{
bool status = xuastc_ldr_decoder_fuzz_test();
@@ -6167,13 +9807,19 @@ static int main_internal(int argc, const char** argv)
std::string ext(string_get_extension(opts.m_input_filenames[i]));
if ((strcasecmp(ext.c_str(), "basis") == 0) || (strcasecmp(ext.c_str(), "ktx") == 0) || (strcasecmp(ext.c_str(), "ktx2") == 0))
{
- // If they haven't specified any modes, and they give us a .basis file, then assume they want to unpack it.
+ // If they haven't specified any modes, and they give us a .basis/.ktx2 file, then assume they want to unpack it.
+ // NOTE: a bare .dds is intentionally NOT auto-unpacked -- it is a valid compressor SOURCE image
+ // (read_uncompressed_dds_file). To unpack a .dds use -unpack, or -info for header info.
opts.m_mode = cUnpack;
break;
}
}
}
+ // Note: -info/-validate/-unpack all flow through unpack_and_validate_mode(), which sniffs each input's
+ // magic per-file (KTX2 / DDS / KTX1 / .basis) and routes it to the right handler -- so a mixed-type input
+ // list (e.g. a .ktx2 and a .dds on one command line) works without any extension-based mode rerouting.
+
bool status = false;
switch (opts.m_mode)
@@ -6190,9 +9836,15 @@ static int main_internal(int argc, const char** argv)
case cCompare:
status = compare_mode(opts);
break;
+ case cCompareHVS:
+ status = compare_hvs_mode(opts);
+ break;
case cHDRCompare:
status = hdr_compare_mode(opts);
break;
+ case cImageDumpStats:
+ status = image_stats_mode(opts);
+ break;
case cVersion:
status = true; // We printed the version at the beginning of main_internal
break;
@@ -6208,6 +9860,12 @@ static int main_internal(int argc, const char** argv)
case cTestXUASTCLDR:
status = test_mode_xuastc_ldr(opts);
break;
+ case cTestCodecs:
+ status = test_codecs_run(opts);
+ break;
+ case cTestCodecsGen:
+ status = test_codecs_generate(opts);
+ break;
case cTestHDR_4x4:
status = test_mode_hdr(opts, basist::basis_tex_format::cUASTC_HDR_4x4, std::size(g_hdr_4x4_test_files), g_hdr_4x4_test_files, 0.0f);
break;
@@ -6232,9 +9890,45 @@ static int main_internal(int argc, const char** argv)
case cCombineImages:
status = combine_images_mode(opts);
break;
+ case cExtractChannel:
+ status = extract_channel_mode(opts);
+ break;
+ case cExtractSwizzle:
+ status = extract_swizzle_mode(opts);
+ break;
+ case cExtractRegion:
+ status = extract_region_mode(opts);
+ break;
+ case cTextToPng:
+ status = text_to_png(opts.m_text_image_in.c_str(), opts.m_text_image_out.c_str());
+ break;
+ case cPngToText:
+ status = png_to_text(opts.m_text_image_in.c_str(), opts.m_text_image_out.c_str());
+ break;
case cTonemapImage:
status = tonemap_image_mode(opts);
break;
+ case cDDS:
+ status = dds_mode(opts);
+ break;
+ case cExportDDS:
+ status = export_dds_mode(opts);
+ break;
+ case cExportKTX:
+ status = export_ktx_mode(opts);
+ break;
+ case cTinyDDSInfo:
+ status = tinydds_info_mode(opts);
+ break;
+ case cKTXInfo:
+ status = ktx_info_mode(opts);
+ break;
+ case cBenchmarkSingle:
+ status = benchmark_single_mode(opts);
+ break;
+ case cBenchmarkSweep:
+ status = benchmark_sweep_mode(opts);
+ break;
default:
assert(0);
break;
@@ -6345,4 +10039,3 @@ int main(int argc, const char** argv)
return status;
}
-
diff --git a/basisu_tool_help.h b/basisu_tool_help.h
index 486e166..ee530ed 100644
--- a/basisu_tool_help.h
+++ b/basisu_tool_help.h
@@ -4,12 +4,59 @@
"The default processing mode is compression of one or more .PNG/.TGA/.JPG/.QOI/\n"
".DDS/.EXR/.HDR files to a LDR or HDR .KTX2 file. Alternate modes:\n"
"\n"
-" -unpack: Use transcoder to unpack a .basis/.KTX2 file to one or more .KTX, \n"
-" .DDS, .PNG, .ASTC, etc. files.\n"
+" -unpack: Use transcoder to unpack a .basis/.KTX2/.DDS file to one or more .KTX,\n"
+" .DDS, .PNG, .ASTC, etc. files. For a .DDS input, each layer/face/mip image is\n"
+" decoded to _unpacked_rgb_/_a_/_rgba_ PNGs and transcoded to every GPU format\n"
+" as single-image _transcoded_ files (BC1-7 to .DDS+.KTX, others .KTX only),\n"
+" similar to unpacking a .KTX2/.basis (flat per-image output; the exact format\n"
+" set differs). (-no_ktx skips the GPU files, -ktx_only skips PNGs.) DDS unpack\n"
+" uses a fixed transcode-target set -- -format_only/-etc1_only are ignored for\n"
+" .dds. A bare .dds with no mode is a compress SOURCE, not unpacked -- pass\n"
+" -unpack to unpack it.\n"
"\n"
-" -info: Display high-level information about a .basis/.KTX2 file\n"
+" -export_dds : Transcode each input .ktx2 to a .DDS file (all mips,\n"
+" array layers, cubemap faces). FORMAT: BC1 BC3 BC4 BC5 BC6H BC7 RGBA32. Output\n"
+" is _.dds; -output_file/-output_path override the name/dir.\n"
+"\n"
+" -dds: Write a basic DX10 .DDS file directly from source image(s). Use with\n"
+" -dds_format . Honors -mipmap, -cubemap, -tex_type 2darray, -srgb/-linear,\n"
+" -swizzle, -resample, etc. (the same source-prep options as compression). All\n"
+" inputs form a single .dds (one per layer/face). Output is .dds;\n"
+" -output_file/-output_path override the name/dir. LDR only.\n"
+" -dds_format : Output format for -dds (defaults to BC7 if omitted). Block\n"
+" compressed: BC1 BC2 BC3 BC4 BC5 BC7. Uncompressed: A8R8G8B8 A8B8G8R8 R8 R8G8\n"
+" R5G6B5 A1R5G5B5 A4R4G4B4. The sRGB DXGI variant is used for BC1/BC2/BC3/BC7/\n"
+" A8R8G8B8/A8B8G8R8 unless -linear is given (other formats are always UNORM). No\n"
+" transfer-function conversion is applied to texel data.\n"
+" -dds_bc7f / -dds_bc7e_scalar (alias -dds_bc7e): select the BC7 packer for -dds\n"
+" -dds_format BC7. bc7f = built-in fast real-time packer (default); bc7e_scalar =\n"
+" higher-quality, slower scalar encoder.\n"
+" -dds_bc7f_level <0-2>: bc7f quality: 0=analytical, 1=partially analytical\n"
+" (default), 2=non analytical (slowest/best).\n"
+" -dds_bc7e_scalar_level <0-6>: bc7e_scalar quality: 0=ultrafast..6=slowest\n"
+" (default 2); implies -dds_bc7e_scalar.\n"
+"\n"
+" -export_ktx : Transcode each input .ktx2 to a compressed KTX1 (.ktx)\n"
+" file (all mips, array layers, cubemap faces). FORMAT (compressed only): BC1\n"
+" BC3 BC4 BC5 BC6H BC7 ETC1 ETC2 EAC_R11 EAC_RG11 PVRTC1 PVRTC2 ASTC ASTC_\n"
+" ASTC_HDR_4x4 ASTC_HDR_6x6. Output is _.ktx.\n"
+"\n"
+" -tinydds_info: Print header info (dimensions, type, mips, array slices, format)\n"
+" for each input .DDS file, via the tinydds parser. Header only -- no decode.\n"
+" (-info also prints DDS header info, via the dds_transcoder.)\n"
+"\n"
+" -ktx_info: Print KTX1 (.ktx) header fields (GL type/format/internalFormat,\n"
+" dimensions, array elements, faces, mip levels) for each input file. Header\n"
+" only -- no key/value or image data parsing.\n"
+"\n"
+" -info: Display high-level information about a .basis/.KTX2/.DDS/.KTX1 file.\n"
+" Each input is detected by content, so mixed file types on one command line\n"
+" are fine (a .DDS prints its header info; a .KTX1 prints its KTX header). For\n"
+" a .DDS this prints the transcoder's view (closest-matching format); use\n"
+" -tinydds_info for the raw DDS header (physical pixel layout).\n"
" \n"
-" -validate: Validate and display information about a .basis/.KTX2 file\n"
+" -validate: Validate and display information about a .basis/.KTX2/.DDS file\n"
+" (decodes/transcodes every image to exercise the codecs; writes nothing).\n"
" \n"
" -compare: Compare two LDR PNG/BMP/TGA/JPG/QOI images specified with -file,\n"
" output PSNR and SSIM statistics and RGB/A delta images\n"
@@ -17,9 +64,57 @@
" -compare_hdr: Compare two HDR .EXR/.HDR images specified with -file, output\n"
" PSNR statistics and RGB delta images\n"
"\n"
+" -dump_image_stats: Print reference-free per-channel RGBA statistics (min/max/\n"
+" mean/variance/std dev/unique count) for each LDR image given with -file, plus\n"
+" flags for common issues (constant/dropped channel, all-opaque or all-zero\n"
+" alpha, possibly-premultiplied alpha, R==G==B grayscale). LDR/8-bit only for now.\n"
+" -dump_image_stats : As above, but restrict the stats to the\n"
+" given rectangle (must lie fully inside the image).\n"
+"\n"
+" -dump_pixel : Print the exact RGBA value of texel (x,y) of each -file\n"
+" image (LDR only). Can be combined with -dump_image_stats / -dump_block.\n"
+"\n"
+" -dump_block : Print the 4x4 texel block at block coords (bx,by) (i.e.\n"
+" source texels [bx*4..bx*4+3] x [by*4..by*4+3]) of each -file image (LDR only).\n"
+" Can be combined with -dump_image_stats / -dump_pixel.\n"
+"\n"
" -tonemap: Tonemap an HDR or EXR image to PNG at multiple exposures, use -file\n"
" to specify filename\n"
"\n"
+" -split: Split the -file input image into split_rgb.png plus per-channel\n"
+" grayscale split_R/G/B/A.png files\n"
+"\n"
+" -combine: Combine two -file images: RGB from the first plus the second's\n"
+" green channel as alpha, written to combined.png (or -output_file)\n"
+"\n"
+" -extract_channel [x y w h]: Write one channel (c = r/g/b/a or\n"
+" 0..3) of the -file image as a grayscale PNG. Optionally restrict to the given\n"
+" rectangle (must lie fully inside the image). LDR only.\n"
+"\n"
+" -extract_swizzle [x y w h]: Write an RGBA PNG whose R,G,B,A\n"
+" come from the source channels named by , a list of 1..4 comma-\n"
+" separated channels (each r/g/b/a or 0..3), e.g. \"0,1,2,3\" (identity),\n"
+" \"2,1,0\" (swap R/B), \"a,a,a,a\" (broadcast alpha). Output channels not listed\n"
+" pass through unchanged. Optional rectangle as with -extract_channel. LDR only.\n"
+"\n"
+" -extract_region : Write the given rectangle of the\n"
+" -file image (full RGBA) to a PNG. The rectangle must lie fully inside the\n"
+" image. LDR only.\n"
+"\n"
+" -extract_to_linear / -extract_to_srgb: Optional transforms applied by the\n"
+" -extract_* commands above: convert the output from sRGB to linear, or from\n"
+" linear to sRGB. Applied to RGB only by default (alpha passes through).\n"
+" -extract_xform_alpha: Also apply the -extract_to_linear/-extract_to_srgb\n"
+" transform to the alpha channel (off by default).\n"
+"\n"
+" -text_to_png : Compose a small PNG from a comma-separated\n"
+" pixel text file. Line 1 is the header \"W,H,C\" (W,H in [1,1024], C channels in\n"
+" [1,4]); then one pixel per line with C decimal values 0..255 in row-major\n"
+" order (1=L, 2=LA, 3=RGB, 4=RGBA). Blank lines and '#' comments are ignored.\n"
+" Strict: malformed values / wrong counts are a hard error with the line number.\n"
+" -png_to_text : Inverse of -text_to_png. Auto-detects the\n"
+" minimal channel count (L/LA/RGB/RGBA) and writes the text format above.\n"
+"\n"
" -peek_astc: Read an .astc file and calculate statistics (for testing/dev)\n"
"\n"
" -version or --version: Print version and exit\n"
@@ -34,6 +129,8 @@
"unpack .KTX2/.basis files to .PNG, .KTX (v1), .ASTC, or .DDS files using its\n"
"single source file transcoder library.\n"
"\n"
+"Options may be given in either -opt or Unix-style --opt form.\n"
+"\n"
"The list of supported texture/supercompressed texture codecs:\n"
" -LDR: ETC1S (SDR default), RDO UASTC LDR 4x4, ASTC or XUASTC LDR 4x4-12x12\n"
" -HDR: UASTC HDR 4x4 (HDR default), RDO ASTC HDR 6x6, UASTC HDR 6x6\n"
@@ -66,8 +163,10 @@
"brittle compression (higher distortion on tough image/texture features).\n"
"Effort=0 fastest compression, effort=10=extremely slow.\n"
"\n"
-"Unless an explicit mode is specified, if one or more files have the .basis\n"
-"or .KTX2 extension this tool defaults to unpack mode.\n"
+"Unless an explicit mode is specified, if one or more files have the .basis,\n"
+".KTX2, or .ktx (KTX1) extension this tool defaults to unpack mode. (A .ktx is\n"
+"KTX1, which can't be unpacked -- it errors with a pointer to -info/-ktx_info.\n"
+"A bare .dds is treated as a compress SOURCE, not unpacked; use -unpack for it.)\n"
"\n"
"By default, the compressor assumes the input is in the sRGB colorspace (like\n"
"typical photos/albedo textures). If the input is NOT sRGB (like a normal map),\n"
@@ -78,10 +177,12 @@
"does not support all .EXR compression methods. For unsupported images, you can\n"
"use a tool like ImageMagick to convert them to uncompressed .EXR.\n"
"\n"
-"For .DDS source files: Mipmapped or plain 2D textures (but not cubemaps) are\n"
-"supported. Only uncompressed 32-bit RGBA/BGRA, half float RGBA, or float\n"
-"RGBA .DDS files are supported. In -tex_array mode, if a .DDS file is specified,\n"
-"all source files must be in .DDS format.\n"
+"For .DDS COMPRESSION SOURCE files (i.e. compressing a .dds as an input image --\n"
+"this does NOT limit -unpack): Mipmapped or plain 2D textures (but not cubemaps)\n"
+"are supported, and only uncompressed 32-bit RGBA/BGRA, half float RGBA, or\n"
+"float RGBA .DDS files. In -tex_array mode, if a .DDS file is specified, all\n"
+"source files must be in .DDS format. (To UNPACK a compressed .dds -- BC1-7,\n"
+"cubemaps, arrays, etc. -- use -unpack, which has no such restrictions.)\n"
"\n"
"Filenames prefixed with a @ symbol are read as filename listing files. Listing\n"
"text files specify which actual filenames to process (one filename per line).\n"
@@ -130,6 +231,15 @@
" options (-effort, -quality, -xy, -ts, -tl, etc.) below. .89-8 bpp before\n"
" ZStd compression.\n"
"\n"
+" 8. XUBC7: -xubc7. Lossless or lossy supercompressed BC7. Options:\n"
+" Use -quality [1,100], -effort [0,10], and -xubc7_rdo_level [0,100]\n"
+" (0=no RDO - default, 100=aggressive RDO)\n"
+" -xubc7_num_stripes [1,16]: # of encode stripes (default 8; more=faster\n"
+" decode/encode parallelism, slightly larger file)\n"
+" -xubc7_bc7f / -xubc7_bc7e_scalar: select the BC7 base encoder (default\n"
+" bc7f - fast; bc7e_scalar - bc7e.ispc-based: slower, higher quality)\n"
+" -xubc7_bc7e_scalar_level [0,6]: use bc7e_scalar+set quality level (def=2)\n"
+"\n"
"--- Tool Options:\n"
"\n"
" -ktx2: Write .KTX2 files (the default). By default, UASTC LDR/HDR 4x4 and ASTC\n"
@@ -152,12 +262,21 @@
"\n"
" -output_path: Output .basis/.KTX2 files to specified directory.\n"
"\n"
-" -debug or -verbose: Enable codec debug print to stdout (slightly slower).\n"
+" -debug: Enable codec debug print to stdout (slightly slower).\n"
+"\n"
+" -verbose: Shorthand for -debug -stats (debug output plus quality metrics).\n"
"\n"
" -debug_images: Enable codec debug images (much slower).\n"
"\n"
" -stats: Compute and display image quality metrics (slightly to much slower).\n"
"\n"
+" -no_stats: Disable image quality metrics (overrides -verbose/-csv_file).\n"
+"\n"
+" -validate_output: Validate output texture data after compression.\n"
+"\n"
+" -csv_file filename.csv: Log image quality metrics to a CSV file (also enables\n"
+" -stats computation).\n"
+"\n"
" -individual: Process input images individually and output\n"
" multiple .basis/.KTX2 files (not as a texture array - this is now the default\n"
" as of v1.16)\n"
@@ -338,6 +457,9 @@
" light. Default is 100 nits. Note: Previous builds used 1 nit. Common\n"
" values are 80-100 nits.\n"
"\n"
+" -hdr_ldr_upconversion_black_bias X: Black bias [0,1] added during SDR->HDR\n"
+" upconversion (default 0).\n"
+"\n"
"--- ASTC LDR/XUASTC LDR specific options (-ldr_4x4 or -ldr_4x4i, up to 12x12):\n"
"\n"
"Internally both modes (ASTC 4x4-12x12 and XUASTC 4x4-12x12) use the same\n"
@@ -373,14 +495,18 @@
" -xp: Force disable RGB dual plane usage in all effort levels (lower quality\n"
" but faster compression and faster transcoding to BC7 at certain block sizes)\n"
"\n"
+" -xsu, -xpu: Inverses of -xs/-xp - force-enable 2-3 subset usage / RGB dual\n"
+" plane, respectively.\n"
+"\n"
" -ts: Use LDR sRGB ASTC decoding profile - the default. This parameter should\n"
" match how the developer will decode or sample the ASTC texture data. Inverse\n"
" of -tl. Same as -srgb.\n"
"\n"
" -tl: Use LDR Linear ASTC decoding profile. Inverse of -ts. Same as -linear.\n"
"\n"
-" -weights X Y Z W: Set unsigned integer channel error weights. Defaults are\n"
-" 1,1,1,1. Useful to favor certain channels during compression.\n"
+" -weights X Y Z W: Set unsigned integer channel error weights. Default is\n"
+" 9,11,1,11 (sRGB); -linear or -normal_map use 1,1,1,1. Useful to favor\n"
+" certain channels during compression.\n"
"\n"
" -ls_min_psnr X, -ls_min_alpha_psnr X, -ls_thresh_psnr X, -ls_thresh_alpha_psnr\n"
" X, -ls_thresh_edge_psnr X, -ls_thresh_edge_alpha_psnr X: Windowed/bounded RDO\n"
@@ -389,6 +515,52 @@
" -xuastc_blurring: Experimental - enable blurred block candidates (MUCH slower\n"
" compression, higher quality).\n"
"\n"
+" -xuastc_sharpen X: Enable sharpening, set amount [0,10] (0 disables; higher\n"
+" = sharper, but more likely to add artifacts). Off by default; default amount\n"
+" is 2.0. -xuastc_no_sharpen disables.\n"
+"\n"
+" -xuastc_heavy_subset_usage, -xuastc_no_heavy_subset_usage: Enable/disable\n"
+" heavier 2-3 subset usage (higher quality, slower; disabled by default).\n"
+"\n"
+"Encode-time deblocking (XUASTC LDR only): control deblocking awareness (SCD)\n"
+"and/or the deblocking filter applied during compression. Default is\n"
+"-xuastc_deblocking_largest. Each flag also has a -xuastc_ldr_* alias, e.g.\n"
+"-xuastc_ldr_deblocking_all.\n"
+"\n"
+" -xuastc_no_deblocking: Disable both SCD and filtering.\n"
+"\n"
+" -xuastc_deblocking_largest: Use SCD and filtering only on the largest block\n"
+" sizes (the codec default).\n"
+"\n"
+" -xuastc_deblocking_all: Use SCD and filtering on all block sizes.\n"
+"\n"
+" -xuastc_deblocking_scd_no_filtering: Use SCD only, no filtering.\n"
+"\n"
+" -xuastc_deblocking_no_scd_filtering_largest: No SCD, filter largest blocks.\n"
+"\n"
+" -xuastc_deblocking_no_scd_filtering_all: No SCD, filter all block sizes.\n"
+"\n"
+" -xuastc_deblocking_num_passes X: Set number of deblocking filter passes\n"
+" [2,256].\n"
+"\n"
+"Internal ASTC encoder selection (advanced/experimental). The astcenc-based\n"
+"options (-xuastc_use_astcenc, -xuastc_merge_astcenc, -xuastc_merge_all) require\n"
+"building with BASISU_SUPPORT_ASTCENC=1:\n"
+"\n"
+" -xuastc_use_auto_comp: Auto-pick the ASTC encoder by effort level (default).\n"
+"\n"
+" -xuastc_use_basisu: Use the built-in basisu ASTC encoder.\n"
+"\n"
+" -xuastc_use_astcf: Use the astcf ASTC encoder.\n"
+"\n"
+" -xuastc_use_astcenc: Use the astcenc ASTC encoder.\n"
+"\n"
+" -xuastc_merge_astcf: Try basisu + astcf, keep the best result per block.\n"
+"\n"
+" -xuastc_merge_astcenc: Try basisu + astcenc, keep the best result per block.\n"
+"\n"
+" -xuastc_merge_all: Try all available encoders, keep the best result per block.\n"
+"\n"
"These modes support all the standard ASTC block sizes. The larger the block\n"
"size, the lower the bitrate on disk and in memory, but the more noticeable the\n"
"artifacts. Some block sizes (4x4, 6x6, and 8x6) have specially optimized direct\n"
@@ -422,6 +594,16 @@
" -test_xuastc: Run an automated XUASTC LDR encoding and transcoding test.\n"
" Returns EXIT_FAILURE on any failures.\n"
"\n"
+" -test_codecs [codec]: Sweep all LDR+HDR codecs across quality 10/25/50/75/100\n"
+" and effort 0/3/6, comparing size and RGB/RGBA (LDR) or RGB (HDR) PSNRs vs the\n"
+" golden table in basisu_tool_test_codecs.inl. Optional codec name filter, e.g.\n"
+" -test_codecs ETC1S | XUASTC | ASTC | XUBC7 | HDR. Defaults test dir to\n"
+" \"test_files\". Returns EXIT_FAILURE on any failures.\n"
+"\n"
+" -test_codecs_gen [outfile.inl] [codec]: (Re)generate the golden table for\n"
+" -test_codecs by running the same sweep and writing the measured results.\n"
+" Defaults to basisu_tool_test_codecs.inl. Use after a codec changes.\n"
+"\n"
" -test_dir: Optional directory of test files. Defaults to \"../test_files\".\n"
"\n"
" -y_flip: Flip input images vertically before compression\n"
@@ -429,6 +611,10 @@
" -normal_map: Tunes codec parameters for better quality on normal maps (linear\n"
" colorspace metrics, linear mipmap filtering, no selector RDO, no sRGB)\n"
"\n"
+" -photo: Tune for photographic content (the codec defaults): sRGB metrics,\n"
+" endpoint+selector RDO on, sRGB channel weights, sharpening off, deblock\n"
+" largest blocks. Opposite of -normal_map/-texture.\n"
+"\n"
" -no_alpha: Always output non-alpha basis files, even if one or more inputs has\n"
" alpha\n"
"\n"
@@ -451,9 +637,13 @@
" -wasi_threads: Set number of threads to use in WASI threading builds\n"
" (default=8, only used in WASI threading builds)\n"
"\n"
-" -no_ktx: Disable KTX writing when unpacking (faster, less output files)\n"
+" -no_ktx (alias -no_dds): When unpacking, skip the transcoded GPU files (the\n"
+" .ktx, and .dds for BC1-7) and write only the decoded PNGs (faster, fewer\n"
+" files). The two names are identical -- both gate the same .ktx+.dds output.\n"
"\n"
-" -ktx_only: Only write KTX files when unpacking (faster, less output files)\n"
+" -ktx_only (alias -dds_only): When unpacking, write only the transcoded GPU\n"
+" files (.ktx, plus .dds for BC1-7) and skip the decoded PNGs. Note not every\n"
+" format yields a .dds -- only BC1-7 do; the rest are .ktx-only.\n"
"\n"
" -write_out: Write 3dfx OUT files when unpacking FXT1 textures\n"
"\n"
@@ -543,16 +733,14 @@
"\n"
" -higher_quality_transcoding: Enable higher quality, but slower, transcoding\n"
"\n"
-" -no_deblocking: Always disable adaptive deblocking filter on all block sizes\n"
-" (XUASTC/ASTC LDR 4x4-12x12 only). By default only block sizes >8x6 are\n"
-" deblocked while transcoding. (No deblocking ever occurs when transcoding to\n"
-" ASTC: only when re-encoding ASTC to another format, to lower artifacts.)\n"
+" -transcode_no_deblocking: Always disable deblocking filter on all block\n"
+" sizes (XUASTC/ASTC LDR 4x4-12x12 only). Alias:\n"
+" -transcode_disable_deblocking. By default only sizes >= 10x8 are deblocked\n"
+" while transcoding. (No CPU deblocking ever occurs when transcoding to ASTC:\n"
+" only when transcoding ASTC to another formats.)\n"
"\n"
-" -force_deblocking: Always use adaptive deblocking filter, even for block sizes\n"
-" <= 8x6 (XUASTC/ASTC LDR 4x4-12x12 only)\n"
-"\n"
-" -stronger_deblocking: Use stronger adaptive deblocking filtering (XUASTC/ASTC\n"
-" LDR 4x4-12x12 only)\n"
+" -transcode_force_deblocking: Always use deblocking filter, even for block\n"
+" sizes smaller than 10x8 (XUASTC/ASTC LDR 4x4-12x12 only).\n"
"\n"
" -no_etc1s_chroma_filtering: Disable adaptive ETC1S transcode chroma filter,\n"
" for faster transcoding to BC7.\n"
@@ -594,7 +782,7 @@
" - basisu -basis x.qoi : Compress sRGB image x.qoi to x.basis (supports 24-bit\n"
" or 32-bit .QOI files)\n"
"\n"
-" - basisu x.ktx2 : Unpack x.basis to PNG/KTX files (multiple filenames OK)\n"
+" - basisu x.ktx2 : Unpack x.ktx2 to PNG/KTX files (multiple filenames OK)\n"
"\n"
" - basisu x.basis : Unpack x.basis to PNG/KTX files (multiple filenames OK)\n"
"\n"
@@ -671,9 +859,9 @@
"\n"
"--- Example ASTC/XUASTC LDR 4x4-12x12 command lines:\n"
"\n"
-" - basisu -ldr_6x6i -quality 75 -xuastc_arith test.png : Compress test.png to XUASTC\n"
-" LDR 6x6 using weight grid DCT with setting 75 and the arith profile for\n"
-" higher compression.\n"
+" - basisu -ldr_6x6i -quality 75 -xuastc_arith test.png : Compress test.png\n"
+" to XUASTC LDR 6x6 using weight grid DCT with setting 75 and the arith\n"
+" profile for higher compression.\n"
"\n"
" - basisu -ldr_4x4 -effort 5 test.png : Compress test.png to ASTC LDR 4x4\n"
"\n"
diff --git a/encoder/3rdparty/android_astc_decomp.cpp b/encoder/3rdparty/android_astc_decomp.cpp
index 40f3104..cfd7167 100644
--- a/encoder/3rdparty/android_astc_decomp.cpp
+++ b/encoder/3rdparty/android_astc_decomp.cpp
@@ -726,7 +726,7 @@ inline void setASTCErrorColorBlock (void* dst, int blockWidth, int blockHeight,
}
}
-DecompressResult decodeVoidExtentBlock (void* dst, const Block128& blockData, int blockWidth, int blockHeight, bool isSRGB, bool isLDRMode)
+DecompressResult decodeVoidExtentBlock(void* dst, const Block128& blockData, int blockWidth, int blockHeight, bool isSRGB, bool isLDRMode, bool rejectVoidExtentNaNInf)
{
// rg: Adding check here to match ARM's decoder and basisu's.
// 18.23.Void - Extent Blocks
@@ -773,13 +773,16 @@ DecompressResult decodeVoidExtentBlock (void* dst, const Block128& blockData, in
if (isHDRBlock)
{
- for (int c = 0; c < 4; c++)
+ if (rejectVoidExtentNaNInf)
{
- if (isFloat16InfOrNan((deFloat16)rgba[c]))
+ for (int c = 0; c < 4; c++)
{
- //throw InternalError("Infinity or NaN color component in HDR void extent block in ASTC texture (behavior undefined by ASTC specification)");
- setASTCErrorColorBlock(dst, blockWidth, blockHeight, isSRGB);
- return DECOMPRESS_RESULT_ERROR;
+ if (isFloat16InfOrNan((deFloat16)rgba[c]))
+ {
+ //throw InternalError("Infinity or NaN color component in HDR void extent block in ASTC texture (behavior undefined by ASTC specification)");
+ setASTCErrorColorBlock(dst, blockWidth, blockHeight, isSRGB);
+ return DECOMPRESS_RESULT_ERROR;
+ }
}
}
@@ -1754,7 +1757,7 @@ DecompressResult setTexelColors (void* dst, ColorEndpointPair* colorEndpoints, T
return result;
}
-DecompressResult decompressBlock (void* dst, const Block128& blockData, int blockWidth, int blockHeight, bool isSRGB, bool isLDR)
+DecompressResult decompressBlock(void* dst, const Block128& blockData, int blockWidth, int blockHeight, bool isSRGB, bool isLDR, bool rejectVoidExtentNaNInf)
{
DE_ASSERT(isLDR || !isSRGB);
@@ -1770,7 +1773,7 @@ DecompressResult decompressBlock (void* dst, const Block128& blockData, int bloc
// Separate path for void-extent.
if (blockMode.isVoidExtent)
- return decodeVoidExtentBlock(dst, blockData, blockWidth, blockHeight, isSRGB, isLDR);
+ return decodeVoidExtentBlock(dst, blockData, blockWidth, blockHeight, isSRGB, isLDR, rejectVoidExtentNaNInf);
// Compute weight grid values.
const int numWeights = computeNumWeights(blockMode);
@@ -2022,14 +2025,14 @@ static void convert_from_half_to_float_prec(uint32_t n, float* pVals)
}
// Assumes the decode_unorm8 extension is active (only upper 8 bits used).
-bool decompress_ldr(uint8_t *pDst, const uint8_t * data, bool isSRGB, int blockWidth, int blockHeight)
+bool decompress_ldr(uint8_t *pDst, const uint8_t * data, bool isSRGB, int blockWidth, int blockHeight, bool rejectVoidExtentNaNInf)
{
float linear[MAX_BLOCK_WIDTH * MAX_BLOCK_HEIGHT * 4];
const Block128 blockData(data);
// isSRGB is true, this writes uint8_t's. Otherwise it writes floats.
- if (decompressBlock(isSRGB ? (void*)pDst : (void*)&linear[0], blockData, blockWidth, blockHeight, isSRGB, true) != DECOMPRESS_RESULT_VALID_BLOCK)
+ if (decompressBlock(isSRGB ? (void*)pDst : (void*)&linear[0], blockData, blockWidth, blockHeight, isSRGB, true, rejectVoidExtentNaNInf) != DECOMPRESS_RESULT_VALID_BLOCK)
{
return false;
}
@@ -2053,11 +2056,11 @@ bool decompress_ldr(uint8_t *pDst, const uint8_t * data, bool isSRGB, int blockW
return true;
}
-bool decompress_hdr(float* pDstRGBA, const uint8_t* data, int blockWidth, int blockHeight)
+bool decompress_hdr(float* pDstRGBA, const uint8_t* data, int blockWidth, int blockHeight, bool rejectVoidExtentNaNInf)
{
const Block128 blockData(data);
- if (decompressBlock(pDstRGBA, blockData, blockWidth, blockHeight, false, false) != DECOMPRESS_RESULT_VALID_BLOCK)
+ if (decompressBlock(pDstRGBA, blockData, blockWidth, blockHeight, false, false, rejectVoidExtentNaNInf) != DECOMPRESS_RESULT_VALID_BLOCK)
{
return false;
}
diff --git a/encoder/basisu_astc_hdr_common.h b/encoder/basisu_astc_hdr_common.h
index e019992..e9f6089 100644
--- a/encoder/basisu_astc_hdr_common.h
+++ b/encoder/basisu_astc_hdr_common.h
@@ -402,7 +402,7 @@ namespace basisu
uint8_t* pDst_weights); // [wy][wx]
void downsample_ise_weights(
- uint32_t weight_ise_range, uint32_t quant_weight_ise_range,
+ uint32_t dequant_weight_ise_range, uint32_t quant_weight_ise_range,
uint32_t block_w, uint32_t block_h,
uint32_t grid_w, uint32_t grid_h,
const uint8_t* pSrc_weights, uint8_t* pDst_weights);
diff --git a/encoder/basisu_astc_ldr_common.cpp b/encoder/basisu_astc_ldr_common.cpp
index a66969a..97eb01e 100644
--- a/encoder/basisu_astc_ldr_common.cpp
+++ b/encoder/basisu_astc_ldr_common.cpp
@@ -17,7 +17,8 @@
#include "basisu_astc_hdr_common.h"
#include "basisu_astc_ldr_common.h"
-#define BASISU_ASTC_LDR_DEBUG_MSGS (1)
+#define BASISU_FORCE_EXHAUSTIVE_EVAL (0)
+#define BASISU_USE_ORIG_GET_COLORS (0)
namespace basisu
{
@@ -717,10 +718,17 @@ namespace astc_ldr
}
// Returned in ISE order
+ // TODO: Investigate alpha handling here vs. latest spec.
+ // https://raw.githubusercontent.com/KhronosGroup/DataFormat/refs/heads/main/astc.txt
+ // The safest thing to do may be to assume non-sRGB in the encoder. I don't know yet.
+ // How should alpha be handled here for lowest divergence from actual ASTC decoding hardware?
+ // See https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_compression_astc_decode_mode.txt
+ // All channels including alpha >>8.
uint32_t get_colors(const color_rgba& l, const color_rgba& h, uint32_t weight_ise_index, color_rgba* pColors, bool decode_mode_srgb)
{
const uint32_t total_weights = astc_helpers::get_ise_levels(weight_ise_index);
+#if BASISU_USE_ORIG_GET_COLORS
for (uint32_t i = 0; i < total_weights; i++)
{
uint32_t w = basisu::g_ise_weight_lerps[weight_ise_index][1 + i];
@@ -728,11 +736,7 @@ namespace astc_ldr
for (uint32_t c = 0; c < 4; c++)
{
int le = l[c], he = h[c];
-
- // TODO: Investigate alpha handling here vs. latest spec.
- // https://raw.githubusercontent.com/KhronosGroup/DataFormat/refs/heads/main/astc.txt
- // The safest thing to do may be to assume non-sRGB in the encoder. I don't know yet.
- // How should alpha be handled here for lowest divergence from actual ASTC decoding hardware?
+
if (decode_mode_srgb)
{
le = (le << 8) | 0x80;
@@ -745,12 +749,80 @@ namespace astc_ldr
}
uint32_t k = astc_helpers::weight_interpolate(le, he, w);
-
- // See https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_compression_astc_decode_mode.txt
- // All channels including alpha >>8.
+
pColors[i][c] = (uint8_t)(k >> 8);
} // c
} // i
+#else
+ int le_r = l.r, he_r = h.r, le_g = l.g, he_g = h.g, le_b = l.b, he_b = h.b, le_a = l.a, he_a = h.a;
+
+ const bool rgb_only = (le_a == 255) && (he_a == 255);
+
+ if (decode_mode_srgb)
+ {
+ le_r = (le_r << 8) | 0x80;
+ he_r = (he_r << 8) | 0x80;
+
+ le_g = (le_g << 8) | 0x80;
+ he_g = (he_g << 8) | 0x80;
+
+ le_b = (le_b << 8) | 0x80;
+ he_b = (he_b << 8) | 0x80;
+
+ le_a = (le_a << 8) | 0x80;
+ he_a = (he_a << 8) | 0x80;
+ }
+ else
+ {
+ le_r = (le_r << 8) | le_r;
+ he_r = (he_r << 8) | he_r;
+
+ le_g = (le_g << 8) | le_g;
+ he_g = (he_g << 8) | he_g;
+
+ le_b = (le_b << 8) | le_b;
+ he_b = (he_b << 8) | he_b;
+
+ le_a = (le_a << 8) | le_a;
+ he_a = (he_a << 8) | he_a;
+ }
+
+ if (rgb_only)
+ {
+ for (uint32_t i = 0; i < total_weights; i++)
+ {
+ const uint32_t w = basisu::g_ise_weight_lerps[weight_ise_index][1 + i];
+
+ const uint32_t k_r = astc_helpers::weight_interpolate(le_r, he_r, w);
+ const uint32_t k_g = astc_helpers::weight_interpolate(le_g, he_g, w);
+ const uint32_t k_b = astc_helpers::weight_interpolate(le_b, he_b, w);
+
+ pColors[i].r = (uint8_t)(k_r >> 8);
+ pColors[i].g = (uint8_t)(k_g >> 8);
+ pColors[i].b = (uint8_t)(k_b >> 8);
+ pColors[i].a = 255;
+
+ } // i
+ }
+ else
+ {
+ for (uint32_t i = 0; i < total_weights; i++)
+ {
+ const uint32_t w = basisu::g_ise_weight_lerps[weight_ise_index][1 + i];
+
+ const uint32_t k_r = astc_helpers::weight_interpolate(le_r, he_r, w);
+ const uint32_t k_g = astc_helpers::weight_interpolate(le_g, he_g, w);
+ const uint32_t k_b = astc_helpers::weight_interpolate(le_b, he_b, w);
+ const uint32_t k_a = astc_helpers::weight_interpolate(le_a, he_a, w);
+
+ pColors[i].r = (uint8_t)(k_r >> 8);
+ pColors[i].g = (uint8_t)(k_g >> 8);
+ pColors[i].b = (uint8_t)(k_b >> 8);
+ pColors[i].a = (uint8_t)(k_a >> 8);
+
+ } // i
+ }
+#endif
return total_weights;
}
@@ -758,16 +830,13 @@ namespace astc_ldr
// Returns 65 colors (NOT just 64 - 0-64 weight levels, so 65).
uint32_t get_colors_raw_weights(const color_rgba& l, const color_rgba& h, color_rgba* pColors, bool decode_mode_srgb)
{
+#if BASISU_USE_ORIG_GET_COLORS
for (uint32_t w = 0; w <= 64; w++)
{
for (uint32_t c = 0; c < 4; c++)
{
int le = l[c], he = h[c];
-
- // TODO: Investigate alpha handling here vs. latest spec.
- // https://raw.githubusercontent.com/KhronosGroup/DataFormat/refs/heads/main/astc.txt
- // The safest thing to do may be to assume non-sRGB in the encoder. I don't know yet.
- // How should alpha be handled here for lowest divergence from actual ASTC decoding hardware?
+
if (decode_mode_srgb)
{
le = (le << 8) | 0x80;
@@ -781,12 +850,76 @@ namespace astc_ldr
uint32_t k = astc_helpers::weight_interpolate(le, he, w);
- // See https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_compression_astc_decode_mode.txt
- // All channels including alpha >>8.
pColors[w][c] = (uint8_t)(k >> 8);
} // c
} // i
+#else
+ int le_r = l.r, he_r = h.r, le_g = l.g, he_g = h.g, le_b = l.b, he_b = h.b, le_a = l.a, he_a = h.a;
+
+ const bool rgb_only = (le_a == 255) && (he_a == 255);
+
+ if (decode_mode_srgb)
+ {
+ le_r = (le_r << 8) | 0x80;
+ he_r = (he_r << 8) | 0x80;
+
+ le_g = (le_g << 8) | 0x80;
+ he_g = (he_g << 8) | 0x80;
+
+ le_b = (le_b << 8) | 0x80;
+ he_b = (he_b << 8) | 0x80;
+
+ le_a = (le_a << 8) | 0x80;
+ he_a = (he_a << 8) | 0x80;
+ }
+ else
+ {
+ le_r = (le_r << 8) | le_r;
+ he_r = (he_r << 8) | he_r;
+
+ le_g = (le_g << 8) | le_g;
+ he_g = (he_g << 8) | he_g;
+
+ le_b = (le_b << 8) | le_b;
+ he_b = (he_b << 8) | he_b;
+
+ le_a = (le_a << 8) | le_a;
+ he_a = (he_a << 8) | he_a;
+ }
+
+ if (rgb_only)
+ {
+ for (uint32_t w = 0; w <= 64; w++)
+ {
+ const uint32_t k_r = astc_helpers::weight_interpolate(le_r, he_r, w);
+ const uint32_t k_g = astc_helpers::weight_interpolate(le_g, he_g, w);
+ const uint32_t k_b = astc_helpers::weight_interpolate(le_b, he_b, w);
+
+ pColors[w].r = (uint8_t)(k_r >> 8);
+ pColors[w].g = (uint8_t)(k_g >> 8);
+ pColors[w].b = (uint8_t)(k_b >> 8);
+ pColors[w].a = 255;
+
+ } // i
+ }
+ else
+ {
+ for (uint32_t w = 0; w <= 64; w++)
+ {
+ const uint32_t k_r = astc_helpers::weight_interpolate(le_r, he_r, w);
+ const uint32_t k_g = astc_helpers::weight_interpolate(le_g, he_g, w);
+ const uint32_t k_b = astc_helpers::weight_interpolate(le_b, he_b, w);
+ const uint32_t k_a = astc_helpers::weight_interpolate(le_a, he_a, w);
+
+ pColors[w].r = (uint8_t)(k_r >> 8);
+ pColors[w].g = (uint8_t)(k_g >> 8);
+ pColors[w].b = (uint8_t)(k_b >> 8);
+ pColors[w].a = (uint8_t)(k_a >> 8);
+
+ } // i
+ }
+#endif
return ASTC_LDR_MAX_RAW_WEIGHTS;
}
@@ -877,8 +1010,7 @@ namespace astc_ldr
return mean_axis;
}
#endif
-
- // TODO: Try two-step Lanczos iteration/Rayleigh–Ritz approximation in a 2-dimensional Krylov subspace method vs. power method.
+
static vec4F calc_pca_4D(uint32_t num_pixels, const vec4F* pPixels, const vec4F& mean_f)
{
float m00 = 0, m01 = 0, m02 = 0, m03 = 0;
@@ -1006,7 +1138,7 @@ namespace astc_ldr
m_mean_rel_axis3 = calc_pca_3D(m_num_pixels, m_pixels_f, m_mean_f);
m_zero_rel_axis3 = calc_pca_3D(m_num_pixels, m_pixels_f, vec4F(0.0f));
- // Mean and zero relative RGBA (4D) PCA axes
+ // Mean relative RGBA (4D) PCA axes
m_mean_rel_axis4 = calc_pca_4D(m_num_pixels, m_pixels_f, m_mean_f);
for (uint32_t c = 0; c < 4u; c++)
@@ -1022,7 +1154,7 @@ namespace astc_ldr
return (uint32_t)(d * d);
}
- uint64_t eval_solution(
+ static uint64_t eval_solution(
const pixel_stats_t& pixel_stats,
uint32_t total_weights, const color_rgba* pWeight_colors,
uint8_t* pWeight_vals, uint32_t weight_ise_index,
@@ -1031,57 +1163,167 @@ namespace astc_ldr
BASISU_NOTE_UNUSED(weight_ise_index);
assert((total_weights <= 32) || (total_weights == 65));
+ const bool all_one_weights = (params.m_comp_weights[0] == 1) && (params.m_comp_weights[1] == 1) && (params.m_comp_weights[2] == 1) && (params.m_comp_weights[3] == 1);
+
+ bool exhaustive_flag = params.m_use_exhaustive_weight_eval;
+ if (BASISU_FORCE_EXHAUSTIVE_EVAL)
+ exhaustive_flag = true;
+ else if (!all_one_weights)
+ exhaustive_flag = true;
+
uint64_t total_err = 0;
if (params.m_pForced_weight_vals0)
{
- for (uint32_t c = 0; c < pixel_stats.m_num_pixels; c++)
+ if (all_one_weights)
{
- const color_rgba& px = pixel_stats.m_pixels[c];
+ for (uint32_t c = 0; c < pixel_stats.m_num_pixels; c++)
+ {
+ const color_rgba& px = pixel_stats.m_pixels[c];
- const uint32_t w = params.m_pForced_weight_vals0[c];
- assert(w < total_weights);
+ const uint32_t w = params.m_pForced_weight_vals0[c];
+ assert(w < total_weights);
- uint32_t err =
- params.m_comp_weights[0] * square_of_diff(px.r, pWeight_colors[w].r) +
- params.m_comp_weights[1] * square_of_diff(px.g, pWeight_colors[w].g) +
- params.m_comp_weights[2] * square_of_diff(px.b, pWeight_colors[w].b) +
- params.m_comp_weights[3] * square_of_diff(px.a, pWeight_colors[w].a);
+ uint32_t err = square_of_diff(px.r, pWeight_colors[w].r) +
+ square_of_diff(px.g, pWeight_colors[w].g) +
+ square_of_diff(px.b, pWeight_colors[w].b) +
+ square_of_diff(px.a, pWeight_colors[w].a);
- total_err += err;
-
- pWeight_vals[c] = (uint8_t)w;
+ total_err += err;
+
+ pWeight_vals[c] = (uint8_t)w;
+ }
+ }
+ else
+ {
+ for (uint32_t c = 0; c < pixel_stats.m_num_pixels; c++)
+ {
+ const color_rgba& px = pixel_stats.m_pixels[c];
+
+ const uint32_t w = params.m_pForced_weight_vals0[c];
+ assert(w < total_weights);
+
+ uint32_t err =
+ params.m_comp_weights[0] * square_of_diff(px.r, pWeight_colors[w].r) +
+ params.m_comp_weights[1] * square_of_diff(px.g, pWeight_colors[w].g) +
+ params.m_comp_weights[2] * square_of_diff(px.b, pWeight_colors[w].b) +
+ params.m_comp_weights[3] * square_of_diff(px.a, pWeight_colors[w].a);
+
+ total_err += err;
+
+ pWeight_vals[c] = (uint8_t)w;
+ }
+ }
+ }
+ else if (exhaustive_flag)
+ {
+ if (all_one_weights)
+ {
+ for (uint32_t c = 0; c < pixel_stats.m_num_pixels; c++)
+ {
+ const color_rgba& px = pixel_stats.m_pixels[c];
+
+ uint32_t best_err = UINT32_MAX;
+ uint32_t best_sel = 0;
+
+ for (uint32_t i = 0; i < total_weights; i++)
+ {
+ uint32_t err =
+ square_of_diff(px.r, pWeight_colors[i].r) +
+ square_of_diff(px.g, pWeight_colors[i].g) +
+ square_of_diff(px.b, pWeight_colors[i].b) +
+ square_of_diff(px.a, pWeight_colors[i].a);
+
+ if (err < best_err)
+ {
+ best_err = err;
+ best_sel = i;
+ }
+ }
+
+ total_err += best_err;
+ pWeight_vals[c] = (uint8_t)best_sel;
+ }
+ }
+ else
+ {
+ for (uint32_t c = 0; c < pixel_stats.m_num_pixels; c++)
+ {
+ const color_rgba& px = pixel_stats.m_pixels[c];
+
+ uint32_t best_err = UINT32_MAX;
+ uint32_t best_sel = 0;
+
+ for (uint32_t i = 0; i < total_weights; i++)
+ {
+ uint32_t err =
+ params.m_comp_weights[0] * square_of_diff(px.r, pWeight_colors[i].r) +
+ params.m_comp_weights[1] * square_of_diff(px.g, pWeight_colors[i].g) +
+ params.m_comp_weights[2] * square_of_diff(px.b, pWeight_colors[i].b) +
+ params.m_comp_weights[3] * square_of_diff(px.a, pWeight_colors[i].a);
+
+ if (err < best_err)
+ {
+ best_err = err;
+ best_sel = i;
+ }
+ }
+
+ total_err += best_err;
+ pWeight_vals[c] = (uint8_t)best_sel;
+ }
}
}
else
{
+ assert(all_one_weights);
+
+ // low endpoint must be the first color (w=0)
+ const color_rgba& e_l = pWeight_colors[0];
+
+ // high endpoint must be either the last color or the 2nd depending on the weight BISE range
+ color_rgba e_h(pWeight_colors[1]);
+ if ((total_weights == 65) || (g_ise_weight_lerps[weight_ise_index][total_weights] == 64))
+ e_h = pWeight_colors[total_weights - 1];
+ else
+ {
+ assert(g_ise_weight_lerps[weight_ise_index][1 + 1] == 64);
+ }
+
+ const int dr = e_h[0] - e_l[0], dg = e_h[1] - e_l[1], db = e_h[2] - e_l[2], da = e_h[3] - e_l[3];
+
+ // computes weights between [0,64]
+ const float f = 64.0f / (float)(basisu::squarei(dr) + basisu::squarei(dg) + basisu::squarei(db) + basisu::squarei(da) + .00000125f);
+
+ const auto *pTab = (weight_ise_index <= astc_helpers::BISE_32_LEVELS) ? &astc_helpers::g_dequant_tables.get_weight_tab(weight_ise_index).m_val_to_ise : nullptr;
+
+ const int sofs = -(e_l[0] * dr + e_l[1] * dg + e_l[2] * db + e_l[3] * da);
+
for (uint32_t c = 0; c < pixel_stats.m_num_pixels; c++)
{
const color_rgba& px = pixel_stats.m_pixels[c];
- uint32_t best_err = UINT32_MAX;
- uint32_t best_sel = 0;
+ int sel = (int)(float(px[0] * dr + px[1] * dg + px[2] * db + px[3] * da + sofs) * f + .5f);
- for (uint32_t i = 0; i < total_weights; i++)
- {
- uint32_t err =
- params.m_comp_weights[0] * square_of_diff(px.r, pWeight_colors[i].r) +
- params.m_comp_weights[1] * square_of_diff(px.g, pWeight_colors[i].g) +
- params.m_comp_weights[2] * square_of_diff(px.b, pWeight_colors[i].b) +
- params.m_comp_weights[3] * square_of_diff(px.a, pWeight_colors[i].a);
+ if (sel < 0)
+ sel = 0;
+ else if (sel > 64)
+ sel = 64;
- if (err < best_err)
- {
- best_err = err;
- best_sel = i;
- }
- }
+ int best_sel = pTab ? (*pTab)[sel] : sel;
+
+ assert(best_sel < (int)total_weights);
+
+ total_err +=
+ square_of_diff(px.r, pWeight_colors[best_sel].r) +
+ square_of_diff(px.g, pWeight_colors[best_sel].g) +
+ square_of_diff(px.b, pWeight_colors[best_sel].b) +
+ square_of_diff(px.a, pWeight_colors[best_sel].a);
- total_err += best_err;
pWeight_vals[c] = (uint8_t)best_sel;
- }
- } // if (params.m_pForced_weight_vals0)
-
+ } // c
+ }
+
return total_err;
}
@@ -1118,7 +1360,7 @@ namespace astc_ldr
}
// Evaluates against raw weights [0,64], or to ISE quantized weights, depending on weight_ise_index.
- uint64_t eval_solution_dp(
+ static uint64_t eval_solution_dp(
uint32_t ccs_index,
const pixel_stats_t& pixel_stats,
uint32_t total_weights, const color_rgba* pWeight_colors,
@@ -1127,9 +1369,17 @@ namespace astc_ldr
{
BASISU_NOTE_UNUSED(weight_ise_index);
- assert((ccs_index >= 0) && (ccs_index <= 3));
+ assert(ccs_index <= 3);
assert((total_weights <= 32) || (total_weights == 65));
+ const bool all_one_weights = (params.m_comp_weights[0] == 1) && (params.m_comp_weights[1] == 1) && (params.m_comp_weights[2] == 1) && (params.m_comp_weights[3] == 1);
+
+ bool exhaustive_flag = params.m_use_exhaustive_weight_eval;
+ if (BASISU_FORCE_EXHAUSTIVE_EVAL)
+ exhaustive_flag = true;
+ else if (!all_one_weights)
+ exhaustive_flag = true;
+
uint64_t total_err = 0;
if (params.m_pForced_weight_vals0)
@@ -1151,7 +1401,7 @@ namespace astc_ldr
pWeight_vals0[c] = (uint8_t)w;
}
}
- else
+ else if (exhaustive_flag)
{
for (uint32_t c = 0; c < pixel_stats.m_num_pixels; c++)
{
@@ -1178,6 +1428,61 @@ namespace astc_ldr
pWeight_vals0[c] = (uint8_t)best_sel;
}
}
+ else
+ {
+ assert(all_one_weights);
+
+ // low endpoint must be the first color (w=0)
+ color_rgba e_l(pWeight_colors[0]);
+
+ // high endpoint must be either the last color or the 2nd depending on the weight BISE range
+ color_rgba e_h(pWeight_colors[1]);
+
+ if ((total_weights == 65) || (g_ise_weight_lerps[weight_ise_index][total_weights] == 64))
+ e_h = pWeight_colors[total_weights - 1];
+ else
+ {
+ assert(g_ise_weight_lerps[weight_ise_index][1 + 1] == 64);
+ }
+
+ std::swap(e_l[ccs_index], e_l[3]);
+ std::swap(e_h[ccs_index], e_h[3]);
+
+ const int dr = e_h[0] - e_l[0], dg = e_h[1] - e_l[1], db = e_h[2] - e_l[2];
+
+ const float f = 64.0f / (float)(basisu::squarei(dr) + basisu::squarei(dg) + basisu::squarei(db) + .00000125f);
+
+ const int sofs = -(e_l[0] * dr + e_l[1] * dg + e_l[2] * db);
+
+ const auto* pTab = (weight_ise_index <= astc_helpers::BISE_32_LEVELS) ? &astc_helpers::g_dequant_tables.get_weight_tab(weight_ise_index).m_val_to_ise : nullptr;
+
+ for (uint32_t c = 0; c < pixel_stats.m_num_pixels; c++)
+ {
+ color_rgba px(pixel_stats.m_pixels[c]);
+ std::swap(px[ccs_index], px[3]);
+
+ int sel = (int)(float(px[0] * dr + px[1] * dg + px[2] * db + sofs) * f + .5f);
+
+ if (sel < 0)
+ sel = 0;
+ else if (sel > 64)
+ sel = 64;
+
+ int best_sel = pTab ? (*pTab)[sel] : sel;
+
+ assert(best_sel < (int)total_weights);
+
+ color_rgba wc(pWeight_colors[best_sel]);
+ std::swap(wc[ccs_index], wc[3]);
+
+ total_err +=
+ square_of_diff(px.r, wc.r) +
+ square_of_diff(px.g, wc.g) +
+ square_of_diff(px.b, wc.b);
+
+ pWeight_vals0[c] = (uint8_t)best_sel;
+ } // c
+ }
if (params.m_pForced_weight_vals1)
{
@@ -1194,7 +1499,7 @@ namespace astc_ldr
pWeight_vals1[c] = (uint8_t)w;
}
}
- else
+ else if (exhaustive_flag)
{
for (uint32_t c = 0; c < pixel_stats.m_num_pixels; c++)
{
@@ -1218,6 +1523,53 @@ namespace astc_ldr
pWeight_vals1[c] = (uint8_t)best_sel;
}
}
+ else
+ {
+ assert(all_one_weights);
+
+ // low endpoint must be the first color (w=0)
+ color_rgba e_l(pWeight_colors[0]);
+
+ // high endpoint must be either the last color or the 2nd depending on the weight BISE range
+ color_rgba e_h(pWeight_colors[1]);
+
+ if ((total_weights == 65) || (g_ise_weight_lerps[weight_ise_index][total_weights] == 64))
+ e_h = pWeight_colors[total_weights - 1];
+ else
+ {
+ assert(g_ise_weight_lerps[weight_ise_index][1 + 1] == 64);
+ }
+
+ const int la = e_l[ccs_index];
+ const int ha = e_h[ccs_index];
+ const int da = ha - la;
+
+ const float f = 64.0f / (float)(da + .00000125f);
+
+ const auto* pTab = (weight_ise_index <= astc_helpers::BISE_32_LEVELS) ? &astc_helpers::g_dequant_tables.get_weight_tab(weight_ise_index).m_val_to_ise : nullptr;
+
+ for (uint32_t c = 0; c < pixel_stats.m_num_pixels; c++)
+ {
+ const color_rgba &px = pixel_stats.m_pixels[c];
+
+ const int pxa = px[ccs_index];
+
+ int sel = (int)(float(pxa - la) * f + .5f);
+
+ if (sel < 0)
+ sel = 0;
+ else if (sel > 64)
+ sel = 64;
+
+ int best_sel = pTab ? (*pTab)[sel] : sel;
+
+ assert(best_sel < (int)total_weights);
+
+ total_err += square_of_diff(pxa, pWeight_colors[best_sel][ccs_index]);
+
+ pWeight_vals1[c] = (uint8_t)best_sel;
+ } // c
+ }
return total_err;
}
@@ -1837,6 +2189,53 @@ namespace astc_ldr
return improved_flag;
}
+ static uint32_t compute_endpoint_distance(
+ const color_rgba &low_color1, const color_rgba &high_color1,
+ const color_rgba &low_color2, const color_rgba &high_color2)
+ {
+ uint32_t ll = color_distance(low_color2, low_color1, true);
+ uint32_t lh = color_distance(low_color2, high_color1, true);
+
+ uint32_t hl = color_distance(high_color2, low_color1, true);
+ uint32_t hh = color_distance(high_color2, high_color1, true);
+
+ uint32_t d0 = ll + hh;
+ uint32_t d1 = lh + hl;
+
+ return minimum(d0, d1);
+ }
+
+ // true if the direct encoding would be better vs. the current base+ofs encodiing
+ static bool compare_base_ofs_vs_direct_encoding(
+ uint32_t cem_index,
+ const uint8_t *pTrial_endpoint_vals, uint32_t endpoint_ise_range,
+ const color_rgba &low_color, const color_rgba &high_color,
+ bool try_blue_contract)
+ {
+ assert((cem_index == astc_helpers::CEM_LDR_RGB_BASE_PLUS_OFFSET) || (cem_index == astc_helpers::CEM_LDR_RGBA_BASE_PLUS_OFFSET));
+
+ color_rgba base_ofs_l, base_ofs_h;
+ decode_endpoints(cem_index, pTrial_endpoint_vals, endpoint_ise_range, base_ofs_l, base_ofs_h);
+
+ const uint32_t baseofs_best_d = compute_endpoint_distance(base_ofs_l, base_ofs_h, low_color, high_color);
+ if (!baseofs_best_d)
+ return false;
+
+ uint8_t direct_endpoint_vals[astc_helpers::NUM_MODE12_ENDPOINTS] = { 0 };
+
+ const uint32_t direct_cem_index = (cem_index == astc_helpers::CEM_LDR_RGB_BASE_PLUS_OFFSET) ? astc_helpers::CEM_LDR_RGB_DIRECT : astc_helpers::CEM_LDR_RGBA_DIRECT;
+
+ cem_encode_ldr_rgb_or_rgba_direct_result direct_res = cem_encode_ldr_rgb_or_rgba_direct(direct_cem_index, endpoint_ise_range, low_color, high_color, direct_endpoint_vals, try_blue_contract);
+ BASISU_NOTE_UNUSED(direct_res); // call populates direct_endpoint_vals (used below); its returned struct is unused - silence -Wunused-variable
+
+ color_rgba direct_l, direct_h;
+ decode_endpoints(direct_cem_index, direct_endpoint_vals, endpoint_ise_range, direct_l, direct_h);
+
+ const uint32_t direct_best_d = compute_endpoint_distance(direct_l, direct_h, low_color, high_color);
+
+ return direct_best_d < baseofs_best_d;
+ }
+
// base+offset rgb/rgba, single or dual plane
static bool try_cem9_13_sp_or_dp(
uint32_t cem_index, int ccs_index,
@@ -1844,7 +2243,8 @@ namespace astc_ldr
uint32_t endpoint_ise_range, uint32_t weight_ise_range,
const vec4F& low_color_f, const vec4F& high_color_f,
uint8_t* pTrial_endpoint_vals, uint8_t* pTrial_weight_vals0, uint8_t* pTrial_weight_vals1, uint64_t& trial_blk_error, bool& trial_used_blue_contraction,
- bool try_blue_contract, bool& tried_used_blue_contraction, bool &tried_base_ofs_clamped)
+ bool try_blue_contract, bool& tried_used_blue_contraction,
+ bool &try_direct_encoding_flag) // try_direct_encoding_flag is now a flag that indicates the base_ofs encoding clamped OR if the rgb(a) direct encoding may be better to try
{
assert(g_initialized);
assert((cem_index == astc_helpers::CEM_LDR_RGB_BASE_PLUS_OFFSET) || (cem_index == astc_helpers::CEM_LDR_RGBA_BASE_PLUS_OFFSET));
@@ -1855,7 +2255,7 @@ namespace astc_ldr
assert(pTrial_weight_vals0);
assert((ccs_index == -1) || (pTrial_weight_vals1));
-
+
//const uint32_t num_endpoint_vals = astc_helpers::get_num_cem_values(cem_index);
const uint32_t num_comps = (cem_index == astc_helpers::CEM_LDR_RGB_BASE_PLUS_OFFSET) ? 3 : 4;
@@ -1873,11 +2273,19 @@ namespace astc_ldr
rgb_base_offset_res res = cem_encode_ldr_rgb_or_rgba_base_offset(cem_index, endpoint_ise_range, low_color, high_color, trial_endpoint_vals, try_blue_contract);
tried_used_blue_contraction = res.m_used_blue_contraction;
- tried_base_ofs_clamped = res.m_delta_clamped;
+ try_direct_encoding_flag = res.m_delta_clamped;
if (res.m_failed_flag)
return false;
+ if (!try_direct_encoding_flag)
+ {
+ if (compare_base_ofs_vs_direct_encoding(cem_index, trial_endpoint_vals, endpoint_ise_range, low_color, high_color, try_blue_contract))
+ {
+ try_direct_encoding_flag = true;
+ }
+ }
+
bool improved_flag = false;
if (ccs_index == -1)
@@ -1952,7 +2360,7 @@ namespace astc_ldr
if (res.m_failed_flag)
continue;
-
+
if (ccs_index == -1)
{
uint64_t trial_err = eval_solution(
@@ -1970,8 +2378,16 @@ namespace astc_ldr
memset(pTrial_weight_vals1, 0, pixel_stats.m_num_pixels);
trial_used_blue_contraction = res.m_used_blue_contraction;
if (res.m_delta_clamped)
- tried_base_ofs_clamped = true;
+ try_direct_encoding_flag = true;
improved_flag = true;
+
+ if (!try_direct_encoding_flag)
+ {
+ if (compare_base_ofs_vs_direct_encoding(cem_index, trial_endpoint_vals, endpoint_ise_range, low_color, high_color, try_blue_contract))
+ {
+ try_direct_encoding_flag = true;
+ }
+ }
}
}
else
@@ -1990,8 +2406,16 @@ namespace astc_ldr
memcpy(pTrial_weight_vals1, trial_weight_vals1, pixel_stats.m_num_pixels);
trial_used_blue_contraction = res.m_used_blue_contraction;
if (res.m_delta_clamped)
- tried_base_ofs_clamped = true;
+ try_direct_encoding_flag = true;
improved_flag = true;
+
+ if (!try_direct_encoding_flag)
+ {
+ if (compare_base_ofs_vs_direct_encoding(cem_index, trial_endpoint_vals, endpoint_ise_range, low_color, high_color, try_blue_contract))
+ {
+ try_direct_encoding_flag = true;
+ }
+ }
}
}
@@ -2029,8 +2453,16 @@ namespace astc_ldr
memset(pTrial_weight_vals1, 0, pixel_stats.m_num_pixels);
trial_used_blue_contraction = res.m_used_blue_contraction;
if (res.m_delta_clamped)
- tried_base_ofs_clamped = true;
+ try_direct_encoding_flag = true;
improved_flag = true;
+
+ if (!try_direct_encoding_flag)
+ {
+ if (compare_base_ofs_vs_direct_encoding(cem_index, trial_endpoint_vals, endpoint_ise_range, low_color, high_color, try_blue_contract))
+ {
+ try_direct_encoding_flag = true;
+ }
+ }
}
}
else
@@ -2049,13 +2481,21 @@ namespace astc_ldr
memcpy(pTrial_weight_vals1, trial_weight_vals1, pixel_stats.m_num_pixels);
trial_used_blue_contraction = res.m_used_blue_contraction;
if (res.m_delta_clamped)
- tried_base_ofs_clamped = true;
+ try_direct_encoding_flag = true;
improved_flag = true;
+
+ if (!try_direct_encoding_flag)
+ {
+ if (compare_base_ofs_vs_direct_encoding(cem_index, trial_endpoint_vals, endpoint_ise_range, low_color, high_color, try_blue_contract))
+ {
+ try_direct_encoding_flag = true;
+ }
+ }
}
}
}
}
-
+
return improved_flag;
}
@@ -2582,7 +3022,7 @@ namespace astc_ldr
uint32_t cem_index,
const pixel_stats_t& pixel_stats, const cem_encode_params& enc_params,
uint32_t endpoint_ise_range, uint32_t weight_ise_range,
- uint8_t* pEndpoint_vals, uint8_t* pWeight_vals, uint64_t cur_blk_error, bool use_blue_contraction, bool* pBase_ofs_clamped_flag)
+ uint8_t* pEndpoint_vals, uint8_t* pWeight_vals, uint64_t cur_blk_error, bool use_blue_contraction, bool* pTry_direct_encoding_flag)
{
assert(g_initialized);
assert((cem_index == astc_helpers::CEM_LDR_RGB_DIRECT) || (cem_index == astc_helpers::CEM_LDR_RGBA_DIRECT) ||
@@ -2592,8 +3032,8 @@ namespace astc_ldr
assert((endpoint_ise_range >= astc_helpers::FIRST_VALID_ENDPOINT_ISE_RANGE) && (endpoint_ise_range <= astc_helpers::LAST_VALID_ENDPOINT_ISE_RANGE));
assert(((weight_ise_range >= astc_helpers::FIRST_VALID_WEIGHT_ISE_RANGE) && (weight_ise_range <= astc_helpers::LAST_VALID_WEIGHT_ISE_RANGE)) || (weight_ise_range == astc_helpers::BISE_64_LEVELS));
- if (pBase_ofs_clamped_flag)
- *pBase_ofs_clamped_flag = false;
+ if (pTry_direct_encoding_flag)
+ *pTry_direct_encoding_flag = false;
const bool cem_has_alpha = (cem_index == astc_helpers::CEM_LDR_RGBA_DIRECT) || (cem_index == astc_helpers::CEM_LDR_RGBA_BASE_PLUS_OFFSET);
const bool cem_is_base_offset = (cem_index == astc_helpers::CEM_LDR_RGB_BASE_PLUS_OFFSET) || (cem_index == astc_helpers::CEM_LDR_RGBA_BASE_PLUS_OFFSET);
@@ -2650,18 +3090,18 @@ namespace astc_ldr
if (cem_is_base_offset)
{
- bool tried_base_ofs_clamped = false;
-
+ bool try_direct_encoding_flag = false;
+
try_cem9_13_sp_or_dp(
cem_index, -1, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
low_color_f, high_color_f,
trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, use_blue_contraction,
- tried_used_blue_contraction, tried_base_ofs_clamped);
-
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ tried_used_blue_contraction, try_direct_encoding_flag);
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
+
if (tried_used_blue_contraction)
{
try_cem9_13_sp_or_dp(
@@ -2669,10 +3109,10 @@ namespace astc_ldr
endpoint_ise_range, weight_ise_range,
low_color_f, high_color_f,
trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, false,
- tried_used_blue_contraction, tried_base_ofs_clamped);
+ tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
}
}
else
@@ -2725,16 +3165,16 @@ namespace astc_ldr
if (cem_is_base_offset)
{
- bool tried_base_ofs_clamped = false;
+ bool try_direct_encoding_flag = false;
try_cem9_13_sp_or_dp(
cem_index, -1, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
xl, xh,
- trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, use_blue_contraction, tried_used_blue_contraction, tried_base_ofs_clamped);
+ trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, use_blue_contraction, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
if (tried_used_blue_contraction)
{
@@ -2743,10 +3183,10 @@ namespace astc_ldr
cem_index, -1, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
xl, xh,
- trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, false, tried_used_blue_contraction, tried_base_ofs_clamped);
+ trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, false, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
}
}
else
@@ -2806,16 +3246,16 @@ namespace astc_ldr
if (cem_is_base_offset)
{
- bool tried_base_ofs_clamped = false;
+ bool try_direct_encoding_flag = false;
try_cem9_13_sp_or_dp(
cem_index, -1, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
xl, xh,
- trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, use_blue_contraction, tried_used_blue_contraction, tried_base_ofs_clamped);
+ trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, use_blue_contraction, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
if (tried_used_blue_contraction)
{
@@ -2824,10 +3264,10 @@ namespace astc_ldr
cem_index, -1, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
xl, xh,
- trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, false, tried_used_blue_contraction, tried_base_ofs_clamped);
+ trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, false, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
}
}
else
@@ -2929,16 +3369,16 @@ namespace astc_ldr
if (cem_is_base_offset)
{
- bool tried_base_ofs_clamped = false;
-
+ bool try_direct_encoding_flag = false;
+
try_cem9_13_sp_or_dp(
cem_index, -1, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
xl, xh,
- trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, use_blue_contraction, tried_used_blue_contraction, tried_base_ofs_clamped);
+ trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, use_blue_contraction, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
if (tried_used_blue_contraction)
{
@@ -2947,10 +3387,10 @@ namespace astc_ldr
cem_index, -1, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
xl, xh,
- trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, false, tried_used_blue_contraction, tried_base_ofs_clamped);
+ trial_blk_endpoints, trial_blk_weights, nullptr, trial_blk_error, trial_used_blue_contraction, false, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
}
}
else
@@ -3041,7 +3481,7 @@ namespace astc_ldr
const pixel_stats_t& pixel_stats, const cem_encode_params& enc_params,
uint32_t endpoint_ise_range, uint32_t weight_ise_range,
uint8_t* pEndpoint_vals, uint8_t* pWeight_vals0, uint8_t* pWeight_vals1,
- uint64_t cur_blk_error, bool use_blue_contraction, bool *pBase_ofs_clamped_flag)
+ uint64_t cur_blk_error, bool use_blue_contraction, bool * pTry_direct_encoding_flag)
{
assert(g_initialized);
assert(ccs_index <= 3);
@@ -3049,8 +3489,8 @@ namespace astc_ldr
assert((endpoint_ise_range >= astc_helpers::FIRST_VALID_ENDPOINT_ISE_RANGE) && (endpoint_ise_range <= astc_helpers::LAST_VALID_ENDPOINT_ISE_RANGE));
assert(((weight_ise_range >= astc_helpers::FIRST_VALID_WEIGHT_ISE_RANGE) && (weight_ise_range <= astc_helpers::LAST_VALID_WEIGHT_ISE_RANGE)) || (weight_ise_range == astc_helpers::BISE_64_LEVELS));
- if (pBase_ofs_clamped_flag)
- *pBase_ofs_clamped_flag = false;
+ if (pTry_direct_encoding_flag)
+ *pTry_direct_encoding_flag = false;
bool cem_has_alpha = false, cem_is_base_offset = false;
switch (cem_index)
@@ -3137,17 +3577,17 @@ namespace astc_ldr
if (cem_is_base_offset)
{
- bool tried_base_ofs_clamped = false;
+ bool try_direct_encoding_flag = false;
try_cem9_13_sp_or_dp(
cem_index, ccs_index, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
low_color_f, high_color_f,
trial_blk_endpoints, trial_blk_weights0, trial_blk_weights1,
- trial_blk_error, trial_used_blue_contraction, use_blue_contraction, tried_used_blue_contraction, tried_base_ofs_clamped);
+ trial_blk_error, trial_used_blue_contraction, use_blue_contraction, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
if (tried_used_blue_contraction)
{
@@ -3155,10 +3595,10 @@ namespace astc_ldr
cem_index, ccs_index, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
low_color_f, high_color_f,
- trial_blk_endpoints, trial_blk_weights0, trial_blk_weights1, trial_blk_error, trial_used_blue_contraction, false, tried_used_blue_contraction, tried_base_ofs_clamped);
+ trial_blk_endpoints, trial_blk_weights0, trial_blk_weights1, trial_blk_error, trial_used_blue_contraction, false, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
}
}
else
@@ -3217,17 +3657,17 @@ namespace astc_ldr
if (cem_is_base_offset)
{
- bool tried_base_ofs_clamped = false;
+ bool try_direct_encoding_flag = false;
try_cem9_13_sp_or_dp(
cem_index, ccs_index, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
xl, xh,
trial_blk_endpoints, trial_blk_weights0, trial_blk_weights1, trial_blk_error, trial_used_blue_contraction,
- use_blue_contraction, tried_used_blue_contraction, tried_base_ofs_clamped);
+ use_blue_contraction, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
if (tried_used_blue_contraction)
{
@@ -3237,10 +3677,10 @@ namespace astc_ldr
endpoint_ise_range, weight_ise_range,
xl, xh,
trial_blk_endpoints, trial_blk_weights0, trial_blk_weights1, trial_blk_error, trial_used_blue_contraction,
- false, tried_used_blue_contraction, tried_base_ofs_clamped);
+ false, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
}
}
else
@@ -3314,17 +3754,17 @@ namespace astc_ldr
if (cem_is_base_offset)
{
- bool tried_base_ofs_clamped = false;
+ bool try_direct_encoding_flag = false;
try_cem9_13_sp_or_dp(
cem_index, ccs_index, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
vl, vh,
trial_blk_endpoints, trial_blk_weights0, trial_blk_weights1, trial_blk_error, trial_used_blue_contraction,
- use_blue_contraction, tried_used_blue_contraction, tried_base_ofs_clamped);
+ use_blue_contraction, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
if (tried_used_blue_contraction)
{
@@ -3334,10 +3774,10 @@ namespace astc_ldr
endpoint_ise_range, weight_ise_range,
vl, vh,
trial_blk_endpoints, trial_blk_weights0, trial_blk_weights1, trial_blk_error, trial_used_blue_contraction,
- false, tried_used_blue_contraction, tried_base_ofs_clamped);
+ false, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
}
}
else
@@ -3398,17 +3838,17 @@ namespace astc_ldr
if (cem_is_base_offset)
{
- bool tried_base_ofs_clamped = false;
+ bool try_direct_encoding_flag = false;
try_cem9_13_sp_or_dp(
cem_index, ccs_index, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
xl, xh,
trial_blk_endpoints, trial_blk_weights0, trial_blk_weights1, trial_blk_error, trial_used_blue_contraction,
- use_blue_contraction, tried_used_blue_contraction, tried_base_ofs_clamped);
+ use_blue_contraction, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
if (tried_used_blue_contraction)
{
@@ -3418,10 +3858,10 @@ namespace astc_ldr
endpoint_ise_range, weight_ise_range,
xl, xh,
trial_blk_endpoints, trial_blk_weights0, trial_blk_weights1, trial_blk_error, trial_used_blue_contraction,
- false, tried_used_blue_contraction, tried_base_ofs_clamped);
+ false, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
}
}
else
@@ -3494,18 +3934,18 @@ namespace astc_ldr
if (cem_is_base_offset)
{
- bool tried_base_ofs_clamped = false;
+ bool try_direct_encoding_flag = false;
did_improve_res = try_cem9_13_sp_or_dp(
cem_index, ccs_index, pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
vl, vh,
trial_blk_endpoints, trial_blk_weights0, trial_blk_weights1, trial_blk_error, trial_used_blue_contraction,
- use_blue_contraction, tried_used_blue_contraction, tried_base_ofs_clamped);
+ use_blue_contraction, tried_used_blue_contraction, try_direct_encoding_flag);
BASISU_NOTE_UNUSED(did_improve_res);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
if (tried_used_blue_contraction)
{
@@ -3515,10 +3955,10 @@ namespace astc_ldr
endpoint_ise_range, weight_ise_range,
vl, vh,
trial_blk_endpoints, trial_blk_weights0, trial_blk_weights1, trial_blk_error, trial_used_blue_contraction,
- false, tried_used_blue_contraction, tried_base_ofs_clamped);
+ false, tried_used_blue_contraction, try_direct_encoding_flag);
- if ((pBase_ofs_clamped_flag) && (tried_base_ofs_clamped))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
}
}
else
@@ -4156,7 +4596,7 @@ namespace astc_ldr
const pixel_stats_t& pixel_stats, const cem_encode_params& enc_params,
uint32_t endpoint_ise_range, uint32_t weight_ise_range,
uint8_t* pEndpoint_vals, uint8_t* pWeight_vals0, uint8_t* pWeight_vals1, uint64_t cur_blk_error,
- bool use_blue_contraction, bool *pBase_ofs_clamped_flag)
+ bool use_blue_contraction, bool * pTry_direct_encoding_flag)
{
assert(g_initialized);
assert((ccs_index >= -1) && (ccs_index <= 3));
@@ -4166,8 +4606,8 @@ namespace astc_ldr
const bool dual_plane = (ccs_index >= 0);
- if (pBase_ofs_clamped_flag)
- *pBase_ofs_clamped_flag = false;
+ if (pTry_direct_encoding_flag)
+ *pTry_direct_encoding_flag = false;
uint64_t blk_error = UINT64_MAX;
@@ -4221,7 +4661,7 @@ namespace astc_ldr
cem_index, ccs_index,
pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
- pEndpoint_vals, pWeight_vals0, pWeight_vals1, cur_blk_error, use_blue_contraction, pBase_ofs_clamped_flag);
+ pEndpoint_vals, pWeight_vals0, pWeight_vals1, cur_blk_error, use_blue_contraction, pTry_direct_encoding_flag);
}
else
{
@@ -4229,7 +4669,7 @@ namespace astc_ldr
cem_index,
pixel_stats, enc_params,
endpoint_ise_range, weight_ise_range,
- pEndpoint_vals, pWeight_vals0, cur_blk_error, use_blue_contraction, pBase_ofs_clamped_flag);
+ pEndpoint_vals, pWeight_vals0, cur_blk_error, use_blue_contraction, pTry_direct_encoding_flag);
}
break;
}
@@ -4407,14 +4847,13 @@ namespace astc_ldr
}
return total_err;
-
}
float surrogate_evaluate_rgba_dp(uint32_t ccs_index, const pixel_stats_t& ps, const vec4F& l, const vec4F& h, float* pWeights0, float* pWeights1, uint32_t num_weight_levels,
const cem_encode_params& enc_params, uint32_t flags)
{
assert(g_initialized);
- assert((ccs_index >= 0) && (ccs_index <= 3));
+ assert(ccs_index <= 3);
assert((ps.m_num_pixels) && (ps.m_num_pixels <= ASTC_LDR_MAX_BLOCK_PIXELS));
assert(pWeights0 && pWeights1);
@@ -4830,7 +5269,7 @@ namespace astc_ldr
uint32_t endpoint_ise_range, uint32_t weight_ise_range,
vec4F& low_endpoint, vec4F& high_endpoint, float* pWeights0, float *pWeights1, uint32_t flags)
{
- assert((ccs_index >= 0) && (ccs_index <= 3));
+ assert(ccs_index <= 3);
const uint32_t num_endpoint_levels = astc_helpers::get_ise_levels(endpoint_ise_range);
// astc_helpers::BISE_64_LEVELS=raw weights ([0,64], NOT [0,63])
@@ -5153,14 +5592,99 @@ namespace astc_ldr
return *this;
}
- // misnamed- just SAD distance, not square
+ // originally was SAD distance, but that is slower
int partition_pattern_vec::get_squared_distance(const partition_pattern_vec& other) const
{
const uint32_t total_pixels = get_total();
+#if 0
int total_dist = 0;
for (uint32_t i = 0; i < total_pixels; i++)
- total_dist += iabs((int)m_parts[i] - (int)other.m_parts[i]);
+ total_dist += squarei((int)m_parts[i] - (int)other.m_parts[i]);
+
+ return total_dist;
+#else
+ int total_dist0 = 0, total_dist1 = 0, total_dist2 = 0, total_dist3 = 0;
+
+ uint32_t idx = 0;
+ while ((idx + 4) <= total_pixels)
+ {
+ total_dist0 += squarei((int)m_parts[idx] - (int)other.m_parts[idx]);
+ total_dist1 += squarei((int)m_parts[idx + 1] - (int)other.m_parts[idx + 1]);
+ total_dist2 += squarei((int)m_parts[idx + 2] - (int)other.m_parts[idx + 2]);
+ total_dist3 += squarei((int)m_parts[idx + 3] - (int)other.m_parts[idx + 3]);
+
+ idx += 4;
+ }
+
+ while (idx < total_pixels)
+ {
+ total_dist0 += squarei((int)m_parts[idx] - (int)other.m_parts[idx]);
+ ++idx;
+ }
+
+ int total_dist = total_dist0 + total_dist1 + total_dist2 + total_dist3;
+
+ return total_dist;
+#endif
+ }
+
+ int partition_pattern_vec::get_squared_distance_2subsets(const partition_pattern_vec& other) const
+ {
+ const uint32_t total_pixels = get_total();
+
+ int total_dist0 = 0, total_dist1 = 0, total_dist2 = 0, total_dist3 = 0;
+
+ uint32_t idx = 0;
+ while ((idx + 4) <= total_pixels)
+ {
+ total_dist0 += (m_parts[idx] ^ other.m_parts[idx]);
+ total_dist1 += (m_parts[idx + 1] ^ other.m_parts[idx + 1]);
+ total_dist2 += (m_parts[idx + 2] ^ other.m_parts[idx + 2]);
+ total_dist3 += (m_parts[idx + 3] ^ other.m_parts[idx + 3]);
+
+ idx += 4;
+ }
+
+ while (idx < total_pixels)
+ {
+ total_dist0 += squarei(m_parts[idx] ^ other.m_parts[idx]);
+ ++idx;
+ }
+
+ int total_dist = total_dist0 + total_dist1 + total_dist2 + total_dist3;
+
+ return total_dist;
+ }
+
+ int partition_pattern_vec::get_squared_distance(const partition_pattern_vec& other, uint32_t num_subsets, uint32_t other_perm_index) const
+ {
+ assert((num_subsets >= 2) && (num_subsets <= 3));
+
+ if (num_subsets == 2)
+ {
+ assert(other_perm_index < 2);
+ }
+ else
+ {
+ assert(other_perm_index < NUM_PART3_MAPPINGS);
+ }
+
+ const uint32_t total_pixels = get_total();
+
+ int total_dist = 0;
+
+ for (uint32_t i = 0; i < total_pixels; i++)
+ {
+ int p = other.m_parts[i];
+
+ if (num_subsets == 2)
+ p ^= other_perm_index;
+ else
+ p = g_part3_mapping[other_perm_index][p];
+
+ total_dist += squarei((int)m_parts[i] - p);
+ }
return total_dist;
}
@@ -5566,7 +6090,177 @@ namespace astc_ldr
return std::pair(best_split_pat, best_split_dist);
}
- void partitions_data::init(uint32_t num_partitions, uint32_t block_width, uint32_t block_height, bool init_vp_tree)
+ void partition_lsh_map::init(uint32_t num_subsets, uint32_t n, const partition_pattern_vec* pUnique_pats)
+ {
+ assert((num_subsets >= 2) && (num_subsets <= 3));
+ assert(n && pUnique_pats);
+
+ const uint32_t total_texels = pUnique_pats[0].get_width() * pUnique_pats[0].get_height();
+
+ m_num_subsets = num_subsets;
+ m_num_samples = (num_subsets == 2) ? 6 : 5;
+ //m_num_hash_tabs = (total_texels > 36) ? 12 : 8;
+ m_num_hash_tabs = 12;
+
+ const uint32_t hash_key_shift = (num_subsets == 2) ? 1 : 2;
+
+ const uint32_t hash_tab_size = 1 << (m_num_samples * hash_key_shift);
+
+ assert(m_num_samples <= cMaxSamples);
+ assert(m_num_hash_tabs <= cMaxHashTabs);
+
+ basisu::rand rnd;
+ rnd.seed(33155);
+
+ clear_obj(m_sample_indices);
+
+ for (uint32_t h = 0; h < m_num_hash_tabs; h++)
+ {
+ for (uint32_t s = 0; s < m_num_samples; s++)
+ {
+ for (uint32_t t = 0; t < 4; t++)
+ {
+ const uint8_t rnd_index = (uint8_t)rnd.irand(0, total_texels - 1);
+
+ m_sample_indices[h][s] = rnd_index;
+
+ uint32_t b;
+ for (b = 0; b < s; b++)
+ if (rnd_index == m_sample_indices[h][b])
+ break;
+
+ if (b == s)
+ break;
+ }
+ }
+
+ m_hash_tabs[h].resize(hash_tab_size);
+ } // h
+
+ for (uint32_t unique_pat_index = 0; unique_pat_index < n; unique_pat_index++)
+ {
+ const partition_pattern_vec* pVec = &pUnique_pats[unique_pat_index];
+
+ const uint32_t max_perm_index = (num_subsets == 2) ? (uint32_t)partition_pattern_vec::cMaxPermute2Index : (uint32_t)partition_pattern_vec::cMaxPermute3Index;
+
+ for (uint32_t perm_index = 0; perm_index <= max_perm_index; perm_index++)
+ {
+ const partition_pattern_vec pv((num_subsets == 2) ? pVec->get_permuted2(perm_index) : pVec->get_permuted3(perm_index));
+
+ for (uint32_t hash_tab_index = 0; hash_tab_index < m_num_hash_tabs; hash_tab_index++)
+ {
+ uint32_t hash_key = 0;
+
+ for (uint32_t sample_index = 0; sample_index < m_num_samples; sample_index++)
+ {
+ hash_key <<= hash_key_shift;
+
+ const uint32_t texel_index = m_sample_indices[hash_tab_index][sample_index];
+ assert(texel_index < total_texels);
+
+ hash_key |= pv[texel_index];
+ } // i
+
+ m_hash_tabs[hash_tab_index][hash_key].push_back(unique_pat_index);
+
+ } // h
+
+ } // perm_index
+
+ } // unique_pat_index
+ }
+
+ static inline size_t unique_duplicates_only(uint32_t* pResults, size_t total_results)
+ {
+ if (total_results < 2)
+ return 0;
+
+ size_t i = 0, out = 0;
+ while (i < total_results)
+ {
+ size_t j = i + 1;
+ while ((j < total_results) && (pResults[j] == pResults[i]))
+ ++j;
+
+ // j - i is the run length; keep this value only if it appeared 2+ times
+ if ((j - i) >= 2)
+ pResults[out++] = pResults[i];
+
+ i = j;
+ }
+ return out;
+ }
+
+ uint32_t partition_lsh_map::find(const partition_pattern_vec& desired_pat, uint32_t* pResults, uint32_t max_results, bool filter_flag) const
+ {
+ assert(pResults && max_results);
+
+ uint32_t total_results = 0;
+
+ const uint32_t hash_key_shift = (m_num_subsets == 2) ? 1 : 2;
+
+ for (uint32_t hash_tab_index = 0; hash_tab_index < m_num_hash_tabs; hash_tab_index++)
+ {
+ uint32_t hash_key = 0;
+
+ for (uint32_t sample_index = 0; sample_index < m_num_samples; sample_index++)
+ {
+ hash_key <<= hash_key_shift;
+
+ const uint32_t texel_index = m_sample_indices[hash_tab_index][sample_index];
+
+ const uint32_t subset_index = desired_pat[texel_index];
+ assert(subset_index < m_num_subsets);
+
+ hash_key |= subset_index;
+ } // i
+
+ const auto& h_tab = m_hash_tabs[hash_tab_index][hash_key];
+
+ uint32_t num_to_copy = h_tab.size_u32();
+
+ uint32_t room_remaining = max_results - total_results;
+ if (!room_remaining)
+ break;
+
+ if (num_to_copy > room_remaining)
+ num_to_copy = room_remaining;
+
+ assert((total_results + num_to_copy) <= max_results);
+
+ // num_to_copy can be 0 for an empty hash bucket, in which case get_ptr()
+ // returns nullptr -- guard the copy so we don't pass null to memcpy
+ // (UB / UBSAN nonnull violation even with a 0 length).
+ if (num_to_copy)
+ memcpy(pResults + total_results, h_tab.get_ptr(), num_to_copy * sizeof(uint32_t));
+ total_results += num_to_copy;
+
+ if (total_results >= max_results)
+ break;
+
+ } // h
+
+ if (!total_results)
+ return 0;
+
+ std::sort(pResults, pResults + total_results);
+
+ size_t num_unique;
+ if (filter_flag)
+ {
+ num_unique = unique_duplicates_only(pResults, total_results);
+ assert((num_unique <= total_results) && (num_unique <= max_results));
+ }
+ else
+ {
+ num_unique = std::unique(pResults, pResults + total_results) - pResults;
+ assert((num_unique >= 1) && (num_unique <= total_results) && (num_unique <= max_results));
+ }
+
+ return (uint32_t)num_unique;
+ }
+
+ void partitions_data::init(uint32_t num_partitions, uint32_t block_width, uint32_t block_height, bool init_vp_tree, bool init_lsh)
{
assert((num_partitions >= 2) && (num_partitions <= 4));
@@ -5659,7 +6353,14 @@ namespace astc_ldr
} // seed_index
if (init_vp_tree)
+ {
m_part_vp_tree.init(m_total_unique_patterns, m_partition_pats);
+ }
+
+ if (init_lsh)
+ {
+ m_part_lhs_map.init(num_partitions, m_total_unique_patterns, m_partition_pats);
+ }
}
} // namespace astc_ldr
diff --git a/encoder/basisu_astc_ldr_common.h b/encoder/basisu_astc_ldr_common.h
index 76e7e3f..ff9789f 100644
--- a/encoder/basisu_astc_ldr_common.h
+++ b/encoder/basisu_astc_ldr_common.h
@@ -32,6 +32,7 @@ namespace astc_ldr
uint32_t m_max_ls_passes, m_total_weight_refine_passes;
bool m_worst_weight_nudging_flag;
bool m_endpoint_refinement_flag;
+ bool m_use_exhaustive_weight_eval;
cem_encode_params()
{
@@ -54,6 +55,7 @@ namespace astc_ldr
m_total_weight_refine_passes = 0;
m_worst_weight_nudging_flag = false;
m_endpoint_refinement_flag = false;
+ m_use_exhaustive_weight_eval = false;
}
float get_total_comp_weights() const
@@ -111,12 +113,6 @@ namespace astc_ldr
//int apply_delta_to_bise_endpoint_val(uint32_t endpoint_ise_range, int ise_val, int delta);
int apply_delta_to_bise_weight_val(uint32_t weight_ise_range, int ise_val, int delta);
- uint64_t eval_solution(
- const pixel_stats_t& pixel_stats,
- uint32_t total_weights, const color_rgba* pWeight_colors,
- uint8_t* pWeight_vals, uint32_t weight_ise_index,
- const cem_encode_params& params);
-
uint64_t eval_solution(
const pixel_stats_t& pixel_stats,
uint32_t cem_index,
@@ -124,13 +120,6 @@ namespace astc_ldr
uint8_t* pWeight_vals, uint32_t weight_ise_index,
const cem_encode_params& params);
- uint64_t eval_solution_dp(
- uint32_t ccs_index,
- const pixel_stats_t& pixel_stats,
- uint32_t total_weights, const color_rgba* pWeight_colors,
- uint8_t* pWeight_vals0, uint8_t* pWeight_vals1, uint32_t weight_ise_index,
- const cem_encode_params& params);
-
uint64_t eval_solution_dp(
const pixel_stats_t& pixel_stats,
uint32_t cem_index, uint32_t ccs_index,
@@ -147,7 +136,7 @@ namespace astc_ldr
const pixel_stats_t& pixel_stats, const cem_encode_params& enc_params,
uint32_t endpoint_ise_range, uint32_t weight_ise_range,
uint8_t* pEndpoint_vals, uint8_t* pWeight_vals0, uint8_t* pWeight_vals1, uint64_t cur_blk_error,
- bool use_blue_contraction, bool* pBase_ofs_clamped_flag);
+ bool use_blue_contraction, bool* pTry_direct_encoding_flag);
// TODO: Rename, confusing vs. std::vector or basisu::vector or vec4F etc.
struct partition_pattern_vec
@@ -169,19 +158,21 @@ namespace astc_ldr
partition_pattern_vec& operator= (const partition_pattern_vec& rhs);
- uint32_t get_width() const { return m_width; }
- uint32_t get_height() const { return m_height; }
- uint32_t get_total() const { return m_width * m_height; }
+ inline uint32_t get_width() const { return m_width; }
+ inline uint32_t get_height() const { return m_height; }
+ inline uint32_t get_total() const { return m_width * m_height; }
- uint8_t operator[] (uint32_t i) const { assert(i < get_total()); return m_parts[i]; }
- uint8_t& operator[] (uint32_t i) { assert(i < get_total()); return m_parts[i]; }
+ inline uint8_t operator[] (uint32_t i) const { assert(i < get_total()); return m_parts[i]; }
+ inline uint8_t& operator[] (uint32_t i) { assert(i < get_total()); return m_parts[i]; }
- uint8_t operator() (uint32_t x, uint32_t y) const { assert((x < m_width) && (y < m_height)); return m_parts[x + y * m_width]; }
- uint8_t& operator() (uint32_t x, uint32_t y) { assert((x < m_width) && (y < m_height)); return m_parts[x + y * m_width]; }
+ inline uint8_t operator() (uint32_t x, uint32_t y) const { assert((x < m_width) && (y < m_height)); return m_parts[x + y * m_width]; }
+ inline uint8_t& operator() (uint32_t x, uint32_t y) { assert((x < m_width) && (y < m_height)); return m_parts[x + y * m_width]; }
int get_squared_distance(const partition_pattern_vec& other) const;
+ int get_squared_distance_2subsets(const partition_pattern_vec& other) const;
+ int get_squared_distance(const partition_pattern_vec& other, uint32_t num_subsets, uint32_t other_perm_index) const;
- float get_distance(const partition_pattern_vec& other) const
+ inline float get_distance(const partition_pattern_vec& other) const
{
return sqrtf((float)get_squared_distance(other));
}
@@ -381,6 +372,32 @@ namespace astc_ldr
void clear() { clear_obj(m_hist); }
};
+ class partition_lsh_map
+ {
+ public:
+ partition_lsh_map()
+ {
+ }
+
+ bool is_valid() const { return m_num_subsets != 0; }
+
+ void init(uint32_t num_subsets, uint32_t n, const partition_pattern_vec* pUnique_pats);
+
+ uint32_t find(const partition_pattern_vec& desired_pat, uint32_t *pResults, uint32_t max_results, bool filter_flag) const;
+
+ private:
+ uint32_t m_num_subsets = 0;
+
+ uint32_t m_num_samples = 0;
+ uint32_t m_num_hash_tabs = 0;
+
+ enum { cMaxSamples = 10, cMaxHashTabs = 16 };
+
+ uint8_t m_sample_indices[cMaxHashTabs][cMaxSamples];
+
+ basisu::vector m_hash_tabs[cMaxHashTabs];
+ };
+
struct partitions_data
{
uint32_t m_width, m_height, m_num_partitions;
@@ -397,8 +414,10 @@ namespace astc_ldr
// VP tree used to rapidly find nearby/similar patterns.
vp_tree m_part_vp_tree;
+
+ partition_lsh_map m_part_lhs_map;
- void init(uint32_t num_partitions, uint32_t block_width, uint32_t block_height, bool init_vp_tree = true);
+ void init(uint32_t num_partitions, uint32_t block_width, uint32_t block_height, bool init_vp_tree = true, bool init_lsh = true);
};
float surrogate_quant_endpoint_val(float e, uint32_t num_endpoint_levels, uint32_t flags);
@@ -419,27 +438,6 @@ namespace astc_ldr
uint32_t endpoint_ise_range, uint32_t weight_ise_range,
vec4F& low_endpoint, vec4F& high_endpoint, float& s, float* pWeights0, float* pWeights1, uint32_t flags = 0);
-#if 0
- bool requantize_ise_endpoints(uint32_t cem,
- uint32_t src_ise_endpoint_range, const uint8_t* pSrc_endpoints,
- uint32_t dst_ise_endpoint_range, uint8_t* pDst_endpoints);
-
- uint32_t get_base_cem_without_alpha(uint32_t cem);
-
- bool pack_base_offset(
- uint32_t cem_index, uint32_t dst_ise_endpoint_range, uint8_t* pPacked_endpoints,
- const color_rgba& l, const color_rgba& h,
- bool use_blue_contraction, bool auto_disable_blue_contraction_if_clamped,
- bool& blue_contraction_clamped_flag, bool& base_ofs_clamped_flag, bool& endpoints_swapped);
-
- bool convert_endpoints_across_cems(
- uint32_t prev_cem, uint32_t prev_endpoint_ise_range, const uint8_t* pPrev_endpoints,
- uint32_t dst_cem, uint32_t dst_endpoint_ise_range, uint8_t* pDst_endpoints,
- bool always_repack,
- bool use_blue_contraction, bool auto_disable_blue_contraction_if_clamped,
- bool& blue_contraction_clamped_flag, bool& base_ofs_clamped_flag);
-#endif
-
} // namespace astc_ldr
} // namespace basisu
diff --git a/encoder/basisu_astc_ldr_encode.cpp b/encoder/basisu_astc_ldr_encode.cpp
index 414ee6c..b9603ae 100644
--- a/encoder/basisu_astc_ldr_encode.cpp
+++ b/encoder/basisu_astc_ldr_encode.cpp
@@ -19,6 +19,8 @@
// pick up BASISD_SUPPORT_KTX2_ZSTD macro (this defines it automatically and sets to 1 if not defined)
#include "../transcoder/basisu_transcoder.h"
+#include "basisu_astc_ldr_fencode.h"
+
#include
#ifndef BASISD_SUPPORT_KTX2_ZSTD
@@ -29,6 +31,17 @@
#include "../zstd/zstd.h"
#endif
+#ifndef BASISU_SUPPORT_ASTCENC
+#define BASISU_SUPPORT_ASTCENC (0)
+#endif
+
+#if BASISU_SUPPORT_ASTCENC
+#include "3rdparty/astc-encoder-main/Source/astcenc.h"
+#endif
+
+// Compensate for endpoint adjustment (otherwise we're too pessimestic/underranks 2-3 levels)
+#define BASISU_MODIFIED_WEIGHT_QUANT_MSE_ESTIMATE (1)
+
namespace basisu {
namespace astc_ldr {
@@ -37,6 +50,21 @@ const bool ASTC_LDR_CONSISTENCY_CHECKING = true;
bool g_initialized;
+const uint32_t BLUR_ID_GAUSSIAN_ALTERNATE = 1;
+const uint32_t BLUR_ID_GRID_DIM_BASE = 32; // experimental
+const uint32_t BLUR_ID_DC_LATENT_BASE = 128;
+const uint32_t BLUR_ID_AC_LATENT_BASE = 138;
+const uint32_t BLUR_ID_BEST_CANDIDATE = 256; // experimental
+
+[[maybe_unused]] const uint32_t BLUR_ID_ASTCENC = 512;
+const uint32_t BLUR_ID_ASTCF = 513;
+
+const uint32_t BLUR_ID_EXP = 1024;
+
+// Number of blurred source images used during ASTC LDR encoding. Defined at module scope (not as a function local)
+// so the per-block job lambda can reference it directly without capturing it (avoids MSVC C3493 and clang/gcc -Wunused-lambda-capture).
+const uint32_t TOTAL_BLURRED_IMAGES = 1 * 3;
+
const uint32_t EXPECTED_SUPERBUCKET_HASH_SIZE = 8192;
const uint32_t EXPECTED_SHORTLIST_HASH_SIZE = 4096;
@@ -47,16 +75,219 @@ const uint32_t PART_ESTIMATE_STAGE1_MULTIPLIER = 4;
const uint32_t MAX_WIDTH = 65535, MAX_HEIGHT = 65535;
-void code_block_weights(
+static inline uint32_t apply_kernel(uint32_t a, uint32_t b, uint32_t c)
+{
+ return (a + b + c + 1u) / 3u;
+}
+
+static inline color_rgba filter_horiz(const image& src, int x, int y)
+{
+ color_rgba l(src.get_clamped(x - 1, y));
+ color_rgba c(src.get_clamped(x, y));
+ color_rgba r(src.get_clamped(x + 1, y));
+
+ color_rgba out;
+ for (uint32_t ch = 0; ch < 4; ch++)
+ out[ch] = (uint8_t)apply_kernel(l[ch], c[ch], r[ch]);
+ return out;
+}
+
+static inline color_rgba filter_vert(const image& src, int x, int y)
+{
+ color_rgba u(src.get_clamped(x, y - 1));
+ color_rgba c(src.get_clamped(x, y));
+ color_rgba d(src.get_clamped(x, y + 1));
+
+ color_rgba out;
+ for (uint32_t ch = 0; ch < 4; ch++)
+ out[ch] = (uint8_t)apply_kernel(u[ch], c[ch], d[ch]);
+ return out;
+}
+
+#define BASISU_CORNER(SX, SY, TX, TY) do { \
+ const color_rgba h_l = src_img.get_clamped((SX) - 1, (SY)); \
+ const color_rgba h_c = src_img.get_clamped((SX), (SY)); \
+ const color_rgba h_r = src_img.get_clamped((SX) + 1, (SY)); \
+ const color_rgba v_u = src_img.get_clamped((SX), (SY) - 1); \
+ const color_rgba v_d = src_img.get_clamped((SX), (SY) + 1); \
+ \
+ color_rgba out; \
+ out.r = (uint8_t)minimum(255, basisu::fast_roundf_pos_int( \
+ (float)(h_l.r + 2 * h_c.r + h_r.r + v_u.r + v_d.r) * (1.0f / 6.0f))); \
+ out.g = (uint8_t)minimum(255, basisu::fast_roundf_pos_int( \
+ (float)(h_l.g + 2 * h_c.g + h_r.g + v_u.g + v_d.g) * (1.0f / 6.0f))); \
+ out.b = (uint8_t)minimum(255, basisu::fast_roundf_pos_int( \
+ (float)(h_l.b + 2 * h_c.b + h_r.b + v_u.b + v_d.b) * (1.0f / 6.0f))); \
+ out.a = (uint8_t)minimum(255, basisu::fast_roundf_pos_int( \
+ (float)(h_l.a + 2 * h_c.a + h_r.a + v_u.a + v_d.a) * (1.0f / 6.0f))); \
+ dst_tile.set_clipped((TX), (TY), out); \
+} while (0)
+
+// bx,by=texel offsets
+static void deblock_block_region(int fbw, int fbh, const image& src_img, int bx, int by, image& dst_tile)
+{
+ assert(&src_img != &dst_tile);
+ assert((int)dst_tile.get_width() == (fbw + 2));
+ assert((int)dst_tile.get_height() == (fbh + 2));
+
+ for (int ty = 0; ty < (fbh + 2); ty++)
+ {
+ const bool on_horiz_edge = (ty <= 1) || (ty >= fbh);
+ const int sy = by - 1 + ty;
+
+ for (int tx = 0; tx < (fbw + 2); tx++)
+ {
+ const bool on_vert_edge = (tx <= 1) || (tx >= fbw);
+ const int sx = bx - 1 + tx;
+
+ if (on_vert_edge && on_horiz_edge)
+ {
+ BASISU_CORNER(sx, sy, tx, ty);
+ continue;
+ }
+
+ color_rgba out;
+ if (on_vert_edge)
+ out = filter_horiz(src_img, sx, sy);
+ else if (on_horiz_edge)
+ out = filter_vert(src_img, sx, sy);
+ else
+ out = src_img.get_clamped(sx, sy);
+
+ dst_tile.set_clipped(tx, ty, out);
+ }
+ }
+}
+
+static void deblock_block_region_interior(int fbw, int fbh, const image& src_img, int bx, int by, image& dst_tile, int dst_x, int dst_y)
+{
+ assert((bx >= 0) && (bx < (int)src_img.get_width()));
+ assert((by >= 0) && (by < (int)src_img.get_height()));
+ assert(fbw >= 3);
+ assert(fbh >= 3);
+ assert(&src_img != &dst_tile);
+ assert(src_img.get_width() == dst_tile.get_width());
+ assert(src_img.get_height() == dst_tile.get_height());
+
+ const int x_left = bx;
+ const int x_right = bx + fbw - 1;
+ const int y_top = by;
+ const int y_bottom = by + fbh - 1;
+
+ // --- Four corners -------------------------------------------------------
+
+ BASISU_CORNER(x_left, y_top, dst_x, dst_y);
+ BASISU_CORNER(x_right, y_top, dst_x + fbw - 1, dst_y);
+ BASISU_CORNER(x_left, y_bottom, dst_x, dst_y + fbh - 1);
+ BASISU_CORNER(x_right, y_bottom, dst_x + fbw - 1, dst_y + fbh - 1);
+
+ // --- Top and Bottom edges: rows y_top and y_bottom, columns (x_left, x_right) exclusive.
+ for (int sy = y_top; sy <= y_bottom; sy += (fbh - 1))
+ {
+ const int ty = dst_y + (sy - by);
+ for (int tx_offset = 1; tx_offset < fbw - 1; tx_offset++)
+ {
+ const int sx = bx + tx_offset;
+ const color_rgba u = src_img.get_clamped(sx, sy - 1);
+ const color_rgba c = src_img.get_clamped(sx, sy);
+ const color_rgba d = src_img.get_clamped(sx, sy + 1);
+
+ color_rgba out;
+ out.r = (uint8_t)(((uint32_t)u.r + (uint32_t)c.r + (uint32_t)d.r + 1u) / 3u);
+ out.g = (uint8_t)(((uint32_t)u.g + (uint32_t)c.g + (uint32_t)d.g + 1u) / 3u);
+ out.b = (uint8_t)(((uint32_t)u.b + (uint32_t)c.b + (uint32_t)d.b + 1u) / 3u);
+ out.a = (uint8_t)(((uint32_t)u.a + (uint32_t)c.a + (uint32_t)d.a + 1u) / 3u);
+
+ dst_tile.set_clipped(dst_x + tx_offset, ty, out);
+ }
+ }
+
+ // --- Left and Right edges: columns x_left and x_right, rows (y_top, y_bottom) exclusive.
+ for (int sx = x_left; sx <= x_right; sx += (fbw - 1))
+ {
+ const int tx = dst_x + (sx - bx);
+ for (int ty_offset = 1; ty_offset < fbh - 1; ty_offset++)
+ {
+ const int sy = by + ty_offset;
+ const color_rgba l = src_img.get_clamped(sx - 1, sy);
+ const color_rgba c = src_img.get_clamped(sx, sy);
+ const color_rgba r = src_img.get_clamped(sx + 1, sy);
+
+ color_rgba out;
+ out.r = (uint8_t)(((uint32_t)l.r + (uint32_t)c.r + (uint32_t)r.r + 1u) / 3u);
+ out.g = (uint8_t)(((uint32_t)l.g + (uint32_t)c.g + (uint32_t)r.g + 1u) / 3u);
+ out.b = (uint8_t)(((uint32_t)l.b + (uint32_t)c.b + (uint32_t)r.b + 1u) / 3u);
+ out.a = (uint8_t)(((uint32_t)l.a + (uint32_t)c.a + (uint32_t)r.a + 1u) / 3u);
+
+ dst_tile.set_clipped(tx, dst_y + ty_offset, out);
+ }
+ }
+
+ // --- Interior: pass-through copy from source.
+ // Fast path: interior fully in-bounds -> memcpy each row.
+ const int interior_last_sx = bx + fbw - 2;
+ const int interior_last_sy = by + fbh - 2;
+ const bool interior_in_bounds =
+ (interior_last_sx < (int)src_img.get_width()) &&
+ (interior_last_sy < (int)src_img.get_height());
+
+ if (interior_in_bounds)
+ {
+ const uint32_t bytes_per_row = (fbw - 2) * (uint32_t)sizeof(color_rgba);
+ const uint32_t src_pitch = src_img.get_pitch();
+ const uint32_t dst_pitch = dst_tile.get_pitch();
+
+ for (int ty_offset = 1; ty_offset < (fbh - 1); ty_offset++)
+ {
+ const color_rgba* pSrc = src_img.get_ptr() + (bx + 1) + (by + ty_offset) * src_pitch;
+ color_rgba* pDst = dst_tile.get_ptr() + (dst_x + 1) + (dst_y + ty_offset) * dst_pitch;
+ memcpy(pDst, pSrc, bytes_per_row);
+ }
+ }
+ else
+ {
+ for (int ty_offset = 1; ty_offset < (fbh - 1); ty_offset++)
+ {
+ const int sy = by + ty_offset;
+ for (int tx_offset = 1; tx_offset < (fbw - 1); tx_offset++)
+ {
+ const int sx = bx + tx_offset;
+ dst_tile.set_clipped(dst_x + tx_offset, dst_y + ty_offset, src_img.get_clamped(sx, sy));
+ }
+ }
+ }
+}
+
+static void deblock_image(int fbw, int fbh, const image& src_img, image& dst_img)
+{
+ assert(&src_img != &dst_img);
+ dst_img.match_dimensions(src_img);
+
+ const uint32_t num_blocks_x = src_img.get_block_width(fbw);
+ const uint32_t num_blocks_y = src_img.get_block_height(fbh);
+
+ for (uint32_t by = 0; by < num_blocks_y; by++)
+ {
+ for (uint32_t bx = 0; bx < num_blocks_x; bx++)
+ {
+ deblock_block_region_interior(fbw, fbh,
+ src_img, bx * (uint32_t)fbw, by * (uint32_t)fbh,
+ dst_img, bx * (uint32_t)fbw, by * (uint32_t)fbh);
+ } // bx
+ } // by
+}
+
+#undef BASISU_CORNER
+
+static void code_block_weights(
basist::astc_ldr_t::grid_weight_dct &gw_dct,
float q, uint32_t plane_index,
const astc_helpers::log_astc_block& log_blk,
- const basist::astc_ldr_t::astc_block_grid_data* pGrid_data,
- basisu::bitwise_coder& c,
- basist::astc_ldr_t::dct_syms& syms)
+ basist::astc_ldr_t::dct_syms& syms,
+ basist::astc_ldr_t::fvec &dct_work)
{
assert(q > 0.0f);
-
+
syms.clear();
const uint32_t grid_width = log_blk.m_grid_width, grid_height = log_blk.m_grid_height;
@@ -103,12 +334,10 @@ void code_block_weights(
const float span_len = gw_dct.get_max_span_len(log_blk, plane_index);
float dct_weights[astc_helpers::MAX_BLOCK_PIXELS];
-
- // TODO - temp alloc
- basist::astc_ldr_t::fvec dct_work;
+
grid_dim_vals.m_dct.forward(orig_weights, dct_weights, dct_work);
- const float level_scale = gw_dct.compute_level_scale(q, span_len, pGrid_data->m_weight_gamma, grid_width, grid_height, log_blk.m_weight_ise_range);
+ const float level_scale = gw_dct.compute_level_scale(q, span_len, grid_width, grid_height, log_blk.m_weight_ise_range);
int dct_quant_tab[astc_helpers::MAX_BLOCK_PIXELS];
gw_dct.compute_quant_table(q, grid_width, grid_height, level_scale, dct_quant_tab);
@@ -119,13 +348,11 @@ void code_block_weights(
quant_state.init(q, gw_dct.m_block_width, gw_dct.m_block_height, level_scale);
#endif
- c.put_truncated_binary((int)scaled_mean_weight, (uint32_t)(64.0f * scaled_weight_coding_scale) + 1);
-
syms.m_dc_sym = (int)scaled_mean_weight;
syms.m_num_dc_levels = (uint32_t)(64.0f * scaled_weight_coding_scale) + 1;
assert(syms.m_num_dc_levels == gw_dct.get_num_weight_dc_levels(log_blk.m_weight_ise_range));
- int dct_coeffs[astc_helpers::MAX_BLOCK_PIXELS];
+ int dct_coeffs[astc_helpers::MAX_BLOCK_PIXELS]; // TODO: max grid size is 8x8
for (uint32_t y = 0; y < grid_height; y++)
{
@@ -156,6 +383,8 @@ void code_block_weights(
const basisu::int_vec& zigzag = grid_dim_vals.m_zigzag;
assert(zigzag.size() == total_grid_samples);
+
+ syms.m_coeffs.reserve(65);
int total_zeros = 0;
for (uint32_t i = 0; i < total_grid_samples; i++)
@@ -174,19 +403,20 @@ void code_block_weights(
basist::astc_ldr_t::dct_syms::coeff cf;
cf.m_num_zeros = basisu::safe_cast_uint16(total_zeros);
cf.m_coeff = basisu::safe_cast_int16(coeff);
+
syms.m_coeffs.push_back(cf);
+
syms.m_max_coeff_mag = basisu::maximum(syms.m_max_coeff_mag, basisu::iabs(coeff));
syms.m_max_zigzag_index = basisu::maximum(syms.m_max_zigzag_index, i);
- c.put_rice(total_zeros, gw_dct.m_zero_run);
total_zeros = 0;
-
- c.put_bits(coeff < 0 ? 1 : 0, 1);
-
- if (coeff < 0)
- coeff = -coeff;
-
- c.put_rice(coeff, gw_dct.m_coeff);
+ }
+
+ {
+ // reduce allocation
+ basisu::vector temp_coeffs(syms.m_coeffs);
+
+ syms.m_coeffs.swap(temp_coeffs);
}
if (total_zeros)
@@ -195,12 +425,10 @@ void code_block_weights(
cf.m_num_zeros = basisu::safe_cast_uint16(total_zeros);
cf.m_coeff = INT16_MAX;
syms.m_coeffs.push_back(cf);
-
- c.put_rice(total_zeros, gw_dct.m_zero_run);
}
}
-void astc_ldr_requantize_astc_weights(uint32_t n, const uint8_t* pSrc_ise_vals, uint32_t from_ise_range, uint8_t* pDst_ise_vals, uint32_t to_ise_range)
+static void astc_ldr_requantize_astc_weights(uint32_t n, const uint8_t* pSrc_ise_vals, uint32_t from_ise_range, uint8_t* pDst_ise_vals, uint32_t to_ise_range)
{
if (from_ise_range == to_ise_range)
{
@@ -237,7 +465,7 @@ void astc_ldr_requantize_astc_weights(uint32_t n, const uint8_t* pSrc_ise_vals,
}
}
-void astc_ldr_downsample_ise_weights(
+static void astc_ldr_downsample_ise_weights(
uint32_t dequant_weight_ise_range, uint32_t quant_weight_ise_range,
uint32_t block_w, uint32_t block_h,
uint32_t grid_w, uint32_t grid_h,
@@ -340,7 +568,8 @@ void downsample_weight_residual_grid(
}
}
-void downsample_weightsf(
+#if 0
+static void downsample_weightsf(
const float* pMatrix_weights,
uint32_t bx, uint32_t by, // source/from dimension (block size)
uint32_t wx, uint32_t wy, // dest/to dimension (grid size)
@@ -365,9 +594,11 @@ void downsample_weightsf(
}
}
}
+#endif
static inline uint32_t weighted_color_error(const color_rgba& a, const color_rgba& b, const astc_ldr::cem_encode_params& p)
{
+#if 0
uint32_t total_e = 0;
for (uint32_t c = 0; c < 4; c++)
{
@@ -376,11 +607,19 @@ static inline uint32_t weighted_color_error(const color_rgba& a, const color_rgb
int ev = av - bv;
total_e += (uint32_t)(ev * ev) * p.m_comp_weights[c];
}
-
+
return total_e;
+#else
+ const uint32_t total_e2 = squarei(a[0] - b[0]) * p.m_comp_weights[0] +
+ squarei(a[1] - b[1]) * p.m_comp_weights[1] +
+ squarei(a[2] - b[2]) * p.m_comp_weights[2] +
+ squarei(a[3] - b[3]) * p.m_comp_weights[3];
+
+ return total_e2;
+#endif
}
-uint64_t eval_error(
+static uint64_t eval_error(
uint32_t block_width, uint32_t block_height,
const astc_helpers::log_astc_block& enc_log_block,
const astc_ldr::pixel_stats_t& pixel_stats,
@@ -423,7 +662,7 @@ uint64_t eval_error(
return total_err;
}
-uint64_t eval_error(
+static uint64_t eval_error(
uint32_t block_width, uint32_t block_height,
const astc_ldr::pixel_stats_t& pixel_stats,
uint32_t cem_index,
@@ -433,7 +672,7 @@ uint64_t eval_error(
const uint8_t* pEndpoint_vals, const uint8_t* pWeight_grid_vals0, const uint8_t* pWeight_grid_vals1,
const astc_ldr::cem_encode_params& params)
{
- const uint32_t total_block_pixels = block_width * block_height;
+ //const uint32_t total_block_pixels = block_width * block_height;
const uint32_t total_grid_pixels = grid_width * grid_height;
astc_helpers::log_astc_block enc_log_block;
@@ -468,22 +707,10 @@ uint64_t eval_error(
memcpy(enc_log_block.m_weights, pWeight_grid_vals0, total_grid_pixels);
}
- color_rgba decoded_pixels[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
- bool status = astc_helpers::decode_block(enc_log_block, decoded_pixels, block_width, block_height, params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8);
- assert(status);
-
- if (!status)
- return UINT64_MAX;
-
- uint64_t total_err = 0;
-
- for (uint32_t i = 0; i < total_block_pixels; i++)
- total_err += weighted_color_error(pixel_stats.m_pixels[i], decoded_pixels[i], params);
-
- return total_err;
+ return eval_error(block_width, block_height, enc_log_block, pixel_stats, params);
}
-float compute_psnr_from_wsse(uint32_t block_width, uint32_t block_height, uint64_t sse, float total_comp_weights)
+static float compute_psnr_from_wsse(uint32_t block_width, uint32_t block_height, uint64_t sse, float total_comp_weights)
{
const uint32_t total_block_pixels = block_width * block_height;
const float wmse = (float)sse / (total_comp_weights * (float)total_block_pixels);
@@ -695,8 +922,260 @@ namespace qcd
} // namespace qcd
+const uint32_t NUM_WEIGHT_POLISH_PASSES = 1;
+
+#if 0
+// true if improved
+static bool polish_block_weights_final_slow(
+ astc_helpers::log_astc_block& enc_log_block, // assumes there is already a good encoding to improve here
+ uint8_t* pWeights0, uint8_t* pWeights1, // the latest weights, will be updated if improved
+ uint32_t block_width, uint32_t block_height, uint32_t grid_width, uint32_t grid_height,
+ const astc_ldr::pixel_stats_t& pixel_stats,
+ const astc_ldr::cem_encode_params& params,
+ uint64_t &cur_err)
+{
+ const bool dual_plane_flag = enc_log_block.m_dual_plane;
+
+ //const uint32_t endpoint_ise_range = enc_log_block.m_endpoint_ise_range;
+ const uint32_t weight_ise_range = enc_log_block.m_weight_ise_range;
+
+ //const uint32_t total_block_pixels = block_width * block_height;
+ const uint32_t total_grid_pixels = grid_width * grid_height;
+
+ bool improved_flag = false;
+
+ for (uint32_t polish_pass = 0; polish_pass < NUM_WEIGHT_POLISH_PASSES; polish_pass++)
+ {
+ for (uint32_t y = 0; y < grid_height; y++)
+ {
+ for (uint32_t x = 0; x < grid_width; x++)
+ {
+ for (uint32_t plane_iter = 0; plane_iter < (dual_plane_flag ? 2u : 1u); plane_iter++)
+ {
+ uint8_t base_grid_weights0[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS], base_grid_weights1[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+
+ memcpy(base_grid_weights0, pWeights0, total_grid_pixels);
+ if (dual_plane_flag)
+ memcpy(base_grid_weights1, pWeights1, total_grid_pixels);
+
+ for (int delta = -1; delta <= 1; delta += 2)
+ {
+ uint8_t trial_grid_weights0[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS], trial_grid_weights1[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+
+ memcpy(trial_grid_weights0, base_grid_weights0, total_grid_pixels);
+
+ if (dual_plane_flag)
+ memcpy(trial_grid_weights1, base_grid_weights1, total_grid_pixels);
+
+ if (plane_iter == 0)
+ trial_grid_weights0[x + y * grid_width] = (uint8_t)astc_ldr::apply_delta_to_bise_weight_val(weight_ise_range, base_grid_weights0[x + y * grid_width], delta);
+ else
+ trial_grid_weights1[x + y * grid_width] = (uint8_t)astc_ldr::apply_delta_to_bise_weight_val(weight_ise_range, base_grid_weights1[x + y * grid_width], delta);
+
+ astc_helpers::log_astc_block trial_log_block(enc_log_block);
+
+ astc_helpers::set_weights(trial_log_block, trial_grid_weights0, 0);
+
+ if (dual_plane_flag)
+ astc_helpers::set_weights(trial_log_block, trial_grid_weights1, 1);
+
+ uint64_t trial_err = eval_error(block_width, block_height, trial_log_block, pixel_stats, params);
+
+ if (trial_err < cur_err)
+ {
+ cur_err = trial_err;
+
+ memcpy(pWeights0, trial_grid_weights0, total_grid_pixels);
+
+ if (dual_plane_flag)
+ memcpy(pWeights1, trial_grid_weights1, total_grid_pixels);
+
+ improved_flag = true;
+ }
+
+ } // delta
+
+ } // plane_iter
+
+ } // x
+ } // y
+
+ } // polish_pass
+
+ return improved_flag;
+}
+#endif
+
+#define BASISU_POLISH_DEBUG (0)
+
+// true if improved
+static bool polish_block_weights_final_fast(
+ astc_helpers::log_astc_block& enc_log_block, // assumes there is already a good encoding to improve here
+ uint8_t* pWeights0, uint8_t* pWeights1, // the latest weights, will be updated if improved
+ uint32_t block_width, uint32_t block_height, uint32_t grid_width, uint32_t grid_height,
+ const astc_ldr::pixel_stats_t& pixel_stats,
+ const astc_ldr::cem_encode_params& params,
+ const basist::astc_ldr_t::astc_block_grid_data* pGrid_data,
+ uint64_t& cur_err)
+{
+ if (!cur_err)
+ return false;
+
+ const bool dual_plane_flag = enc_log_block.m_dual_plane;
+
+ //const uint32_t endpoint_ise_range = enc_log_block.m_endpoint_ise_range;
+ const uint32_t weight_ise_range = enc_log_block.m_weight_ise_range;
+
+ const uint32_t total_block_pixels = block_width * block_height;
+ BASISU_NOTE_UNUSED(total_block_pixels);
+ //const uint32_t total_grid_pixels = grid_width * grid_height;
+
+ bool improved_flag = false;
+
+ astc_helpers::log_astc_block cur_log_block(enc_log_block);
+
+ astc_helpers::set_weights(cur_log_block, pWeights0, 0);
+ if (dual_plane_flag)
+ astc_helpers::set_weights(cur_log_block, pWeights1, 1);
+
+ astc_helpers::xuastc_ldr_block_decoder block_decoder(
+ cur_log_block, block_width, block_height,
+ params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8,
+ pGrid_data->m_upsample_weights.get_ptr());
+
+ for (uint32_t polish_pass = 0; polish_pass < NUM_WEIGHT_POLISH_PASSES; polish_pass++)
+ {
+ for (uint32_t y = 0; y < grid_height; y++)
+ {
+ for (uint32_t x = 0; x < grid_width; x++)
+ {
+ const basisu::uint16_vec& influenced_texels = pGrid_data->m_grid_to_texel_influence_list[x + y * grid_width];
+
+ for (uint32_t plane_iter = 0; plane_iter < (dual_plane_flag ? 2u : 1u); plane_iter++)
+ {
+
+#if BASISU_POLISH_DEBUG
+#if defined(_DEBUG) || defined(DEBUG)
+ assert(cur_err == eval_error(block_width, block_height, cur_log_block, pixel_stats, params));
+
+ {
+ color_rgba alt_block_pixels[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+ bool status = astc_helpers::decode_block_xuastc_ldr(cur_log_block, alt_block_pixels, block_width, block_height, params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8);
+ assert(status);
+
+ uint64_t alt_err = 0;
+ for (uint32_t k = 0; k < total_block_pixels; k++)
+ {
+ const uint32_t texel_x = k % block_width;
+ const uint32_t texel_y = k / block_width;
+
+ color_rgba dec_c;
+ block_decoder.decode_texel(texel_x, texel_y, (astc_helpers::color_rgba&)dec_c);
+
+ if ((dec_c.r != alt_block_pixels[k].r) ||
+ (dec_c.g != alt_block_pixels[k].g) ||
+ (dec_c.b != alt_block_pixels[k].b) ||
+ (dec_c.a != alt_block_pixels[k].a))
+ {
+ assert(0);
+ }
+
+ uint64_t w = weighted_color_error(dec_c, pixel_stats.m_pixels[k], params);
+ alt_err += w;
+ }
+ assert(alt_err == cur_err);
+ }
+#endif
+#endif
+
+ const uint8_t base_weight = astc_helpers::get_weight(cur_log_block, plane_iter, x + y * grid_width);
+
+ for (int delta = -1; delta <= 1; delta += 2)
+ {
+ const uint8_t new_weight = (uint8_t)astc_ldr::apply_delta_to_bise_weight_val(weight_ise_range, base_weight, delta);
+ if (new_weight == base_weight)
+ continue;
+
+ // remove out influence from the current weight
+ uint64_t trial_err = cur_err;
+ for (uint32_t j = 0; j < influenced_texels.size(); j++)
+ {
+ const uint32_t packed_texel_index = influenced_texels[j];
+ const uint32_t texel_x = packed_texel_index & 0xFF;
+ const uint32_t texel_y = packed_texel_index >> 8;
+ const uint32_t texel_index = texel_x + texel_y * block_width;
+
+ color_rgba dec_c;
+ block_decoder.decode_texel(texel_x, texel_y, (astc_helpers::color_rgba &)dec_c);
+
+ uint64_t w = weighted_color_error(dec_c, pixel_stats.m_pixels[texel_index], params);
+ assert(trial_err >= w);
+ trial_err -= w;
+ }
+
+ // save weight in case it's worse
+ const uint8_t saved_weight = astc_helpers::get_weight(cur_log_block, plane_iter, x + y * grid_width);
+
+ // change current weight
+ astc_helpers::get_weight(cur_log_block, plane_iter, x + y * grid_width) = new_weight;
+
+ // now add in influence from the new weight
+ for (uint32_t j = 0; j < influenced_texels.size(); j++)
+ {
+ const uint32_t packed_texel_index = influenced_texels[j];
+ const uint32_t texel_x = packed_texel_index & 0xFF;
+ const uint32_t texel_y = packed_texel_index >> 8;
+ const uint32_t texel_index = texel_x + texel_y * block_width;
+
+ color_rgba dec_c;
+ block_decoder.decode_texel(texel_x, texel_y, (astc_helpers::color_rgba&)dec_c);
+
+ trial_err += weighted_color_error(dec_c, pixel_stats.m_pixels[texel_index], params);
+ }
+
+#if BASISU_POLISH_DEBUG
+ assert(trial_err == eval_error(block_width, block_height, cur_log_block, pixel_stats, params));
+#endif
+
+ // see if we've improved the solution by this one weight change
+ if (trial_err < cur_err)
+ {
+ cur_err = trial_err;
+
+ improved_flag = true;
+ }
+ else
+ {
+ // candidate was worse, so restore the weight
+ astc_helpers::get_weight(cur_log_block, plane_iter, x + y * grid_width) = saved_weight;
+ }
+
+ } // delta
+
+#if BASISU_POLISH_DEBUG
+ assert(cur_err == eval_error(block_width, block_height, cur_log_block, pixel_stats, params));
+#endif
+
+ } // plane_iter
+
+ } // x
+ } // y
+
+ } // polish_pass
+
+ if (improved_flag)
+ {
+ astc_helpers::extract_weights(cur_log_block, pWeights0, 0);
+
+ if (dual_plane_flag)
+ astc_helpers::extract_weights(cur_log_block, pWeights1, 1);
+ }
+
+ return improved_flag;
+}
+
// 1-3 subsets, requires initial weights
-bool polish_block_weights(
+static bool polish_block_weights(
uint32_t block_width, uint32_t block_height,
const astc_ldr::pixel_stats_t& pixel_stats,
astc_helpers::log_astc_block& enc_log_block, // assumes there is already a good encoding to improve here
@@ -723,6 +1202,8 @@ bool polish_block_weights(
const auto& dequant_tab = astc_helpers::g_dequant_tables.get_weight_tab(weight_ise_range).m_ISE_to_val;
const auto& quant_tab = astc_helpers::g_dequant_tables.get_weight_tab(weight_ise_range).m_val_to_ise;
+ const basist::astc_ldr_t::astc_block_grid_data *pGrid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, grid_width, grid_height);
+
//const bool is_downsampling = (grid_width < block_width) || (grid_height < block_height);
#if defined(_DEBUG) || defined(DEBUG)
@@ -734,9 +1215,7 @@ bool polish_block_weights(
}
}
#endif
-
- //const astc_block_grid_data* pBlock_grid_data = find_astc_block_grid_data(block_width, block_height, grid_width, grid_height);
-
+
const uint32_t total_block_pixels = block_width * block_height;
const uint32_t total_grid_pixels = grid_width * grid_height;
@@ -753,16 +1232,12 @@ bool polish_block_weights(
const bool global_gradient_desc_enabled = true;
const bool global_qcd_enabled = true;
const bool global_polish_weights_enabled = true;
-
- const uint32_t NUM_WEIGHT_POLISH_PASSES = 1;
-
+
// Gradient descent
if ((gradient_descent_flag) && (global_gradient_desc_enabled))
{
// Downsample the residuals to grid res
- vector2D upsample_matrix;
- compute_upsample_matrix(upsample_matrix, block_width, block_height, grid_width, grid_height);
-
+
// First compute the block's ideal raw weights given the current endpoints at full block/texel res
// TODO: Move to helper
uint8_t ideal_block_raw_weights0[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS], ideal_block_raw_weights1[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
@@ -869,13 +1344,8 @@ bool polish_block_weights(
float weight_grid_residuals_downsampled0[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS], weight_grid_residuals_downsampled1[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
- basisu::vector unweighted_downsample_matrix;
-
- // TODO: precompute, store in weight grid data
- compute_upsample_matrix_transposed(unweighted_downsample_matrix, block_width, block_height, grid_width, grid_height);
-
- basisu::vector diag_AtA(total_grid_pixels);
- compute_diag_AtA_vector(block_width, block_height, grid_width, grid_height, upsample_matrix, diag_AtA.get_ptr());
+ const basisu::vector& unweighted_downsample_matrix = pGrid_data->m_unweighted_downsample_matrix;
+ const basisu::vector& one_over_diag_AtA = pGrid_data->m_one_over_diag_AtA;
downsample_weight_residual_grid(
unweighted_downsample_matrix.get_ptr(),
@@ -885,7 +1355,7 @@ bool polish_block_weights(
weight_grid_residuals_downsampled0); // [wy][wx]
for (uint32_t i = 0; i < total_grid_pixels; i++)
- weight_grid_residuals_downsampled0[i] /= diag_AtA[i];
+ weight_grid_residuals_downsampled0[i] *= one_over_diag_AtA[i];
if (dual_plane_flag)
{
@@ -897,7 +1367,7 @@ bool polish_block_weights(
weight_grid_residuals_downsampled1); // [wy][wx]
for (uint32_t i = 0; i < total_grid_pixels; i++)
- weight_grid_residuals_downsampled1[i] /= diag_AtA[i];
+ weight_grid_residuals_downsampled1[i] *= one_over_diag_AtA[i];
}
// Apply the residuals at grid res and quantize
@@ -936,7 +1406,7 @@ bool polish_block_weights(
astc_helpers::log_astc_block refined_log_block(enc_log_block);
- // TODO: This refines both weight planes simultanously, probably not optimal, could do individually.
+ // TODO: This refines both weight planes simultaneously, probably not optimal, could do individually.
astc_helpers::set_weights(refined_log_block, refined_grid_weights0, 0);
if (dual_plane_flag)
@@ -968,7 +1438,8 @@ bool polish_block_weights(
ideal_block_weights1[i] = (float)ideal_block_raw_weights1[i];
}
- const float* pUpsample_matrix = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, grid_width, grid_height)->m_upsample_matrix.get_ptr();
+ //const float* pUpsample_matrix = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, grid_width, grid_height)->m_upsample_matrix.get_ptr();
+ const float* pUpsample_matrix = pGrid_data->m_upsample_matrix.get_ptr();
qcd::qcd_min_solver solver;
@@ -1053,113 +1524,112 @@ bool polish_block_weights(
if ((polish_weights_flag) && (global_polish_weights_enabled))
{
- // Final, expensive, weight polish. Much can be done to improve this, but it's hopefully not ran much in the first place.
- // TODO: The dB gain from this is large, must optimize.
- for (uint32_t polish_pass = 0; polish_pass < NUM_WEIGHT_POLISH_PASSES; polish_pass++)
+#if BASISU_POLISH_DEBUG
+ uint64_t cur_err_fast = cur_err;
+
+ uint8_t weights0_fast[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+ uint8_t weights1_fast[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+
+ memcpy(weights0_fast, weights0, total_grid_pixels);
+ if (dual_plane_flag)
+ memcpy(weights1_fast, weights1, total_grid_pixels);
+
+ bool improved_flag_fast = polish_block_weights_final_fast(
+ enc_log_block,
+ weights0_fast, weights1_fast,
+ block_width, block_height, grid_width, grid_height,
+ pixel_stats,
+ params,
+ pGrid_data,
+ cur_err_fast);
+
+ if (polish_block_weights_final_slow(
+ enc_log_block,
+ weights0, weights1, // the latest weights, will be updated if improved
+ block_width, block_height, grid_width, grid_height,
+ pixel_stats,
+ params,
+ cur_err))
{
- for (uint32_t y = 0; y < grid_height; y++)
+ assert(improved_flag_fast);
+
+ assert(cur_err == cur_err_fast);
+
+ assert(memcmp(weights0, weights0_fast, total_grid_pixels) == 0);
+ if (dual_plane_flag)
{
- for (uint32_t x = 0; x < grid_width; x++)
- {
- for (uint32_t plane_iter = 0; plane_iter < (dual_plane_flag ? 2u : 1u); plane_iter++)
- {
- uint8_t base_grid_weights0[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS], base_grid_weights1[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+ assert(memcmp(weights1, weights1_fast, total_grid_pixels) == 0);
+ }
- memcpy(base_grid_weights0, weights0, total_grid_pixels);
- if (dual_plane_flag)
- memcpy(base_grid_weights1, weights1, total_grid_pixels);
-
- for (int delta = -1; delta <= 1; delta += 2)
- {
- uint8_t trial_grid_weights0[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS], trial_grid_weights1[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
-
- memcpy(trial_grid_weights0, base_grid_weights0, total_grid_pixels);
-
- if (dual_plane_flag)
- memcpy(trial_grid_weights1, base_grid_weights1, total_grid_pixels);
-
- if (plane_iter == 0)
- trial_grid_weights0[x + y * grid_width] = (uint8_t)astc_ldr::apply_delta_to_bise_weight_val(weight_ise_range, base_grid_weights0[x + y * grid_width], delta);
- else
- trial_grid_weights1[x + y * grid_width] = (uint8_t)astc_ldr::apply_delta_to_bise_weight_val(weight_ise_range, base_grid_weights1[x + y * grid_width], delta);
-
- astc_helpers::log_astc_block trial_log_block(enc_log_block);
-
- astc_helpers::set_weights(trial_log_block, trial_grid_weights0, 0);
-
- if (dual_plane_flag)
- astc_helpers::set_weights(trial_log_block, trial_grid_weights1, 1);
-
- uint64_t trial_err = eval_error(block_width, block_height, trial_log_block, pixel_stats, params);
-
- if (trial_err < cur_err)
- {
- cur_err = trial_err;
-
- memcpy(weights0, trial_grid_weights0, total_grid_pixels);
-
- if (dual_plane_flag)
- memcpy(weights1, trial_grid_weights1, total_grid_pixels);
-
- improved_flag = true;
- }
-
- } // delta
-
- } // plane_iter
-
- } // x
- } // y
-
- } // polish_pass
+ improved_flag = true;
+ }
+ else
+ {
+ assert(!improved_flag_fast);
+ }
+#else
+ if (polish_block_weights_final_fast(
+ enc_log_block,
+ weights0, weights1, // the latest weights, will be updated if improved
+ block_width, block_height, grid_width, grid_height,
+ pixel_stats,
+ params,
+ pGrid_data,
+ cur_err))
+ {
+ improved_flag = true;
+ }
+#endif
} // polish_flag
- astc_helpers::log_astc_block new_log_block(enc_log_block);
-
- astc_helpers::set_weights(new_log_block, weights0, 0);
-
- if (dual_plane_flag)
- astc_helpers::set_weights(new_log_block, weights1, 1);
-
#if defined(_DEBUG) || defined(DEBUG)
- uint64_t new_err = eval_error(block_width, block_height, new_log_block, pixel_stats, params);
-
- assert(cur_err == new_err);
-
+ // sanity checking
if (improved_flag)
{
uint64_t orig_err = eval_error(block_width, block_height, enc_log_block, pixel_stats, params);
-
- assert(new_err < orig_err);
+ assert(cur_err < orig_err);
}
#endif
- enc_log_block = new_log_block;
+ if (improved_flag)
+ {
+ astc_helpers::set_weights(enc_log_block, weights0, 0);
+
+ if (dual_plane_flag)
+ astc_helpers::set_weights(enc_log_block, weights1, 1);
+ }
+
+#if defined(_DEBUG) || defined(DEBUG)
+ // sanity checking
+ uint64_t new_err = eval_error(block_width, block_height, enc_log_block, pixel_stats, params);
+ assert(cur_err == new_err);
+#endif
return true;
}
-bool encode_trial_subsets(
+static bool encode_trial_subsets(
uint32_t block_width, uint32_t block_height,
const astc_ldr::pixel_stats_t& pixel_stats,
uint32_t cem_index, uint32_t num_parts,
uint32_t pat_seed_index, const astc_ldr::partition_pattern_vec* pPat, // seed index is a ASTC partition pattern index
uint32_t endpoint_ise_range, uint32_t weight_ise_range,
uint32_t grid_width, uint32_t grid_height,
+ float early_out_thresh,
astc_helpers::log_astc_block& enc_log_block,
const astc_ldr::cem_encode_params& params,
bool refine_only_flag = false,
bool gradient_descent_flag = true, bool polish_weights_flag = true, bool qcd_enabled_flag = true,
bool use_blue_contraction = true,
- bool* pBase_ofs_clamped_flag = nullptr)
+ bool* pTry_direct_encoding_flag = nullptr)
{
assert((num_parts >= 2) && (num_parts <= astc_helpers::MAX_PARTITIONS));
assert(pPat);
assert(pat_seed_index < astc_helpers::NUM_PARTITION_PATTERNS);
- if (pBase_ofs_clamped_flag)
- *pBase_ofs_clamped_flag = false;
+ if (pTry_direct_encoding_flag)
+ *pTry_direct_encoding_flag = false;
const bool is_downsampling = (grid_width < block_width) || (grid_height < block_height);
//const uint32_t total_block_pixels = block_width * block_height;
@@ -1201,18 +1671,18 @@ bool encode_trial_subsets(
if (!refine_only_flag)
{
- bool base_ofs_clamped_flag = false;
+ bool try_direct_encoding_flag = false;
// Encode at block res, but with quantized weights
uint64_t block_err = astc_ldr::cem_encode_pixels(cem_index, -1, part_pixel_stats[part_index], params,
endpoint_ise_range, weight_ise_range,
- &part_endpoints[part_index][0], &part_weights[part_index][0], nullptr, UINT64_MAX, use_blue_contraction, &base_ofs_clamped_flag);
+ &part_endpoints[part_index][0], &part_weights[part_index][0], nullptr, UINT64_MAX, use_blue_contraction, &try_direct_encoding_flag);
if (block_err == UINT64_MAX)
return false;
- if ((pBase_ofs_clamped_flag) && (base_ofs_clamped_flag))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
}
} // part_index
@@ -1271,11 +1741,28 @@ bool encode_trial_subsets(
memcpy(enc_log_block.m_endpoints + num_endpoint_vals * p, &part_endpoints[p][0], num_endpoint_vals);
}
+ uint64_t prev_cur_err = UINT64_MAX;
+
// attempt endpoint refinement given the current weights
// TODO: Expose to caller
const uint32_t NUM_REFINEMENT_PASSES = 3;
for (uint32_t refine_pass = 0; refine_pass < NUM_REFINEMENT_PASSES; refine_pass++)
{
+ uint64_t cur_err = eval_error(block_width, block_height, enc_log_block, pixel_stats, params);
+
+ if (!cur_err)
+ break;
+
+ if ((early_out_thresh != 0.0f) && (refine_pass) && (prev_cur_err))
+ {
+ double percentage_improvement = (double)(prev_cur_err - cur_err) / (double)prev_cur_err;
+
+ if (percentage_improvement < early_out_thresh)
+ break;
+ }
+
+ prev_cur_err = cur_err;
+
uint8_t dequantized_raw_weights0[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
uint8_t upsampled_weights0[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS]; // raw weights, NOT ISE
@@ -1311,18 +1798,18 @@ bool encode_trial_subsets(
uint8_t temp_weights[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
- bool base_ofs_clamped_flag = false;
+ bool try_direct_encoding_flag = false;
// Encode at block res, but with quantized weights
uint64_t block_err = astc_ldr::cem_encode_pixels(cem_index, -1, part_pixel_stats[part_index], temp_params,
endpoint_ise_range, astc_helpers::BISE_64_LEVELS,
- &alt_enc_log_block.m_endpoints[num_endpoint_vals * part_index], temp_weights, nullptr, UINT64_MAX, use_blue_contraction, &base_ofs_clamped_flag);
+ &alt_enc_log_block.m_endpoints[num_endpoint_vals * part_index], temp_weights, nullptr, UINT64_MAX, use_blue_contraction, &try_direct_encoding_flag);
if (block_err == UINT64_MAX)
return false;
- if ((pBase_ofs_clamped_flag) && (base_ofs_clamped_flag))
- *pBase_ofs_clamped_flag = true;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = true;
#if defined(_DEBUG) || defined(DEBUG)
for (uint32_t i = 0; i < part_pixel_stats[part_index].m_num_pixels; i++)
@@ -1332,8 +1819,7 @@ bool encode_trial_subsets(
#endif
} // part_index
-
- uint64_t cur_err = eval_error(block_width, block_height, enc_log_block, pixel_stats, params);
+
uint64_t ref_err = eval_error(block_width, block_height, alt_enc_log_block, pixel_stats, params);
if (ref_err < cur_err)
@@ -1365,31 +1851,33 @@ bool encode_trial_subsets(
return true;
}
-bool encode_trial(
+static bool encode_trial(
uint32_t block_width, uint32_t block_height,
const astc_ldr::pixel_stats_t& pixel_stats,
uint32_t cem_index,
bool dual_plane_flag, int ccs_index,
uint32_t endpoint_ise_range, uint32_t weight_ise_range,
uint32_t grid_width, uint32_t grid_height,
+ float early_out_thresh,
astc_helpers::log_astc_block& enc_log_block,
const astc_ldr::cem_encode_params& params,
bool gradient_descent_flag = true, bool polish_weights_flag = true, bool qcd_enabled_flag = true,
bool use_blue_contraction = true,
- bool* pBase_ofs_clamped_flag = nullptr)
+ bool* pTry_direct_encoding_flag = nullptr)
{
assert(dual_plane_flag || (ccs_index == -1));
- if (pBase_ofs_clamped_flag)
- *pBase_ofs_clamped_flag = false;
+ if (pTry_direct_encoding_flag)
+ *pTry_direct_encoding_flag = false;
const bool is_downsampling = (grid_width < block_width) || (grid_height < block_height);
-
- const basist::astc_ldr_t::astc_block_grid_data* pBlock_grid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, grid_width, grid_height);
-
+
const float* pDownsample_matrix = nullptr;
if (is_downsampling)
+ {
+ const basist::astc_ldr_t::astc_block_grid_data* pBlock_grid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, grid_width, grid_height);
pDownsample_matrix = pBlock_grid_data->m_downsample_matrix.get_ptr();
+ }
//const uint32_t total_block_pixels = block_width * block_height;
const uint32_t total_grid_pixels = grid_width * grid_height;
@@ -1424,17 +1912,18 @@ bool encode_trial(
if ((grid_width == block_width) && (grid_height == block_height))
{
- bool base_ofs_clamped_flag = false;
+ // No downsampling: a lot easier.
+ bool try_direct_encoding_flag = false;
uint64_t block_err = astc_ldr::cem_encode_pixels(cem_index, ccs_index, pixel_stats, params,
endpoint_ise_range, weight_ise_range,
- fullres_endpoints, weights0, weights1, UINT64_MAX, use_blue_contraction, &base_ofs_clamped_flag);
+ fullres_endpoints, weights0, weights1, UINT64_MAX, use_blue_contraction, &try_direct_encoding_flag);
if (block_err == UINT64_MAX)
return false;
- if ((pBase_ofs_clamped_flag) && (base_ofs_clamped_flag))
- *pBase_ofs_clamped_flag = base_ofs_clamped_flag;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = try_direct_encoding_flag;
if (dual_plane_flag)
{
@@ -1459,18 +1948,18 @@ bool encode_trial(
uint8_t fullres_raw_weights0[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
uint8_t fullres_raw_weights1[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
- bool base_ofs_clamped_flag = false;
+ bool try_direct_encoding_flag = false;
// Encode at block res, but with quantized weights
uint64_t block_err = astc_ldr::cem_encode_pixels(cem_index, ccs_index, pixel_stats, params,
endpoint_ise_range, weight_ise_range,
- fullres_endpoints, fullres_raw_weights0, fullres_raw_weights1, UINT64_MAX, use_blue_contraction, &base_ofs_clamped_flag);
+ fullres_endpoints, fullres_raw_weights0, fullres_raw_weights1, UINT64_MAX, use_blue_contraction, &try_direct_encoding_flag);
if (block_err == UINT64_MAX)
return false;
- if ((pBase_ofs_clamped_flag) && (base_ofs_clamped_flag))
- *pBase_ofs_clamped_flag = base_ofs_clamped_flag;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = try_direct_encoding_flag;
// Now downsample the weight grid (quantized to quantized)
astc_ldr_downsample_ise_weights(
@@ -1497,10 +1986,34 @@ bool encode_trial(
memcpy(enc_log_block.m_endpoints, fullres_endpoints, astc_helpers::get_num_cem_values(cem_index));
- // TODO: Expose to caller
+ uint64_t prev_cur_err = UINT64_MAX;
+
const uint32_t NUM_OUTER_PASSES = 3;
for (uint32_t outer_pass = 0; outer_pass < NUM_OUTER_PASSES; outer_pass++)
{
+ uint64_t cur_err = eval_error(
+ block_width, block_height,
+ pixel_stats,
+ cem_index,
+ dual_plane_flag, ccs_index,
+ endpoint_ise_range, weight_ise_range,
+ grid_width, grid_height,
+ enc_log_block.m_endpoints, weights0, weights1,
+ params);
+
+ if (!cur_err)
+ break;
+
+ if ((early_out_thresh != 0.0f) && (outer_pass) && (prev_cur_err))
+ {
+ double percentage_improvement = (double)(prev_cur_err - cur_err) / (double)prev_cur_err;
+
+ if (percentage_improvement < early_out_thresh)
+ break;
+ }
+
+ prev_cur_err = cur_err;
+
// endpoint refinement, given current upsampled weights
{
astc_helpers::extract_weights(enc_log_block, weights0, 0);
@@ -1528,7 +2041,7 @@ bool encode_trial(
astc_helpers::upsample_weight_grid(block_width, block_height, grid_width, grid_height, dequantized_raw_weights1, upsampled_weights1);
}
- // Jam in the weights to the actual raw [0,64] weights the decoder is going to use after upsampling the grid.
+ // Jam in the actual raw [0,64] weights the decoder is going to use after upsampling the grid.
astc_ldr::cem_encode_params refine_params(params);
refine_params.m_pForced_weight_vals0 = upsampled_weights0;
if (dual_plane_flag)
@@ -1540,35 +2053,22 @@ bool encode_trial(
uint64_t refined_block_err = astc_ldr::cem_encode_pixels(cem_index, ccs_index, pixel_stats, refine_params,
endpoint_ise_range, astc_helpers::BISE_64_LEVELS,
- refined_endpoints, refined_weights0, refined_weights1, UINT64_MAX, use_blue_contraction, &base_ofs_clamped_flag);
- assert(refined_block_err != UINT64_MAX);
-
- if ((pBase_ofs_clamped_flag) && (base_ofs_clamped_flag))
- *pBase_ofs_clamped_flag = base_ofs_clamped_flag;
-
+ refined_endpoints, refined_weights0, refined_weights1, UINT64_MAX, use_blue_contraction, &try_direct_encoding_flag);
+
if (refined_block_err != UINT64_MAX)
{
- uint64_t cur_err = eval_error(
- block_width, block_height,
- pixel_stats,
- cem_index,
- dual_plane_flag, ccs_index,
- endpoint_ise_range, weight_ise_range,
- grid_width, grid_height,
- enc_log_block.m_endpoints, weights0, weights1,
- params);
-
if (refined_block_err < cur_err)
{
memcpy(enc_log_block.m_endpoints, refined_endpoints, astc_helpers::get_num_cem_values(cem_index));
+
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = try_direct_encoding_flag;
}
}
}
- if (outer_pass == (NUM_OUTER_PASSES - 1))
- break;
-
- if ((!gradient_descent_flag) && (!polish_weights_flag))
+ if ( (outer_pass == (NUM_OUTER_PASSES - 1)) ||
+ ((!gradient_descent_flag) && (!polish_weights_flag)) )
break;
bool improved_flag = false;
@@ -1598,19 +2098,19 @@ bool encode_trial(
return true;
}
-// 1 part only, refines endpoints given current weights
-bool encode_trial_refine_only(
+// 1 subset only, refines endpoints given current weights
+static bool encode_trial_refine_only(
uint32_t block_width, uint32_t block_height,
const astc_ldr::pixel_stats_t& pixel_stats,
astc_helpers::log_astc_block& enc_log_block,
const astc_ldr::cem_encode_params& params,
bool use_blue_contraction = true,
- bool* pBase_ofs_clamped_flag = nullptr)
+ bool* pTry_direct_encoding_flag = nullptr)
{
assert(enc_log_block.m_num_partitions == 1);
- if (pBase_ofs_clamped_flag)
- *pBase_ofs_clamped_flag = false;
+ if (pTry_direct_encoding_flag)
+ *pTry_direct_encoding_flag = false;
const uint32_t cem_index = enc_log_block.m_color_endpoint_modes[0];
const bool dual_plane_flag = enc_log_block.m_dual_plane;
@@ -1668,15 +2168,15 @@ bool encode_trial_refine_only(
//bool use_blue_contraction = true;
- bool base_ofs_clamped_flag = false;
+ bool try_direct_encoding_flag = false;
uint64_t refined_block_err = astc_ldr::cem_encode_pixels(cem_index, ccs_index, pixel_stats, refine_params,
endpoint_ise_range, astc_helpers::BISE_64_LEVELS,
- refined_endpoints, refined_weights0, refined_weights1, UINT64_MAX, use_blue_contraction, &base_ofs_clamped_flag);
+ refined_endpoints, refined_weights0, refined_weights1, UINT64_MAX, use_blue_contraction, &try_direct_encoding_flag);
assert(refined_block_err != UINT64_MAX);
- if ((pBase_ofs_clamped_flag) && (base_ofs_clamped_flag))
- *pBase_ofs_clamped_flag = base_ofs_clamped_flag;
+ if ((pTry_direct_encoding_flag) && (try_direct_encoding_flag))
+ *pTry_direct_encoding_flag = try_direct_encoding_flag;
#if defined(_DEBUG) || defined(DEBUG)
for (uint32_t i = 0; i < total_grid_pixels; i++)
@@ -1716,6 +2216,7 @@ bool encode_trial_refine_only(
struct log_surrogate_astc_blk
{
+ // Important: If not downsampling, grid width/height may match the block width/height and may not be valid ASTC (which has a limit of 64 weight samples).
int m_grid_width, m_grid_height;
uint32_t m_cem_index; // base+scale or direct variants only
@@ -1742,7 +2243,7 @@ struct log_surrogate_astc_blk
void decode(uint32_t block_width, uint32_t block_height, vec4F* pPixels, const astc_ldr::partitions_data* pPat_data) const;
};
-void upsample_surrogate_weights(
+static void upsample_surrogate_weights(
const astc_helpers::weighted_sample* pWeighted_samples,
const float* pSrc_weights,
float* pDst_weights,
@@ -1770,7 +2271,7 @@ void upsample_surrogate_weights(
const uint32_t sx = pS->m_src_x, sy = pS->m_src_y;
float total = 0.0f;
-
+
if (w00) total += pSrc_weights[bounds_check(sx + sy * wx, 0U, total_src_weights)] * (float)w00;
if (w01) total += pSrc_weights[bounds_check(sx + 1 + sy * wx, 0U, total_src_weights)] * (float)w01;
if (w10) total += pSrc_weights[bounds_check(sx + (sy + 1) * wx, 0U, total_src_weights)] * (float)w10;
@@ -1802,16 +2303,15 @@ void log_surrogate_astc_blk::decode(uint32_t block_width, uint32_t block_height,
if (needs_upsampling)
{
- // TODO: Precompute these in tables
- astc_helpers::weighted_sample up_weights[astc_helpers::MAX_BLOCK_DIM * astc_helpers::MAX_BLOCK_DIM];
- astc_helpers::compute_upsample_weights(block_width, block_height, m_grid_width, m_grid_height, up_weights);
+ const basist::astc_ldr_t::astc_block_grid_data* pGrid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, m_grid_width, m_grid_height);
+ const astc_helpers::weighted_sample* pUp_weights = pGrid_data->m_upsample_weights.get_ptr();
- upsample_surrogate_weights(up_weights, m_weights0, upsampled_weights0, block_width, block_height, m_grid_width, m_grid_height, m_num_weight_levels);
+ upsample_surrogate_weights(pUp_weights, m_weights0, upsampled_weights0, block_width, block_height, m_grid_width, m_grid_height, m_num_weight_levels);
pWeights0 = upsampled_weights0;
if (dual_plane)
{
- upsample_surrogate_weights(up_weights, m_weights1, upsampled_weights1, block_width, block_height, m_grid_width, m_grid_height, m_num_weight_levels);
+ upsample_surrogate_weights(pUp_weights, m_weights1, upsampled_weights1, block_width, block_height, m_grid_width, m_grid_height, m_num_weight_levels);
pWeights1 = upsampled_weights1;
}
}
@@ -1859,7 +2359,7 @@ void log_surrogate_astc_blk::decode(uint32_t block_width, uint32_t block_height,
return decode(block_width, block_height, pPixels, &pPat_data->m_partition_pats[unique_pat_index]);
}
-void downsample_float_weight_grid(
+static void downsample_float_weight_grid(
const float* pMatrix_weights,
uint32_t bx, uint32_t by, // source/from dimension (block size)
uint32_t wx, uint32_t wy, // dest/to dimension (grid size)
@@ -1889,7 +2389,7 @@ void downsample_float_weight_grid(
}
}
-float decode_surrogate_and_compute_error(
+static float decode_surrogate_and_compute_error(
uint32_t block_width, uint32_t block_height,
const astc_ldr::pixel_stats_t& pixel_stats,
log_surrogate_astc_blk& log_block,
@@ -1904,6 +2404,7 @@ float decode_surrogate_and_compute_error(
const float wb = (float)params.m_comp_weights[2];
const float wa = (float)params.m_comp_weights[3];
+#if 0
float total_err = 0.0f;
for (uint32_t by = 0; by < block_height; by++)
{
@@ -1921,12 +2422,51 @@ float decode_surrogate_and_compute_error(
} // bx
} // by
+#else
+ float total_err_alt = 0.0f;
+
+ const uint32_t total_texels = block_width * block_height;
+
+ if ((wr == 1.0f) && (wg == 1.0f) && (wb == 1.0f) && (wa == 1.0f))
+ {
+ for (uint32_t i = 0; i < total_texels; i++)
+ {
+ const vec4F& s = pixel_stats.m_pixels_f[i];
+ const vec4F& d = dec_pixels[i];
+
+ float dr = s[0] - d[0];
+ float dg = s[1] - d[1];
+ float db = s[2] - d[2];
+ float da = s[3] - d[3];
+
+ total_err_alt += (dr * dr) + (dg * dg) + (db * db) + (da * da);
+ } // i
+ }
+ else
+ {
+ for (uint32_t i = 0; i < total_texels; i++)
+ {
+ const vec4F& s = pixel_stats.m_pixels_f[i];
+ const vec4F& d = dec_pixels[i];
+
+ float dr = s[0] - d[0];
+ float dg = s[1] - d[1];
+ float db = s[2] - d[2];
+ float da = s[3] - d[3];
+
+ total_err_alt += (wr * dr * dr) + (wg * dg * dg) + (wb * db * db) + (wa * da * da);
+ } // i
+ }
+
+ //assert(equal_tol(total_err, total_err_alt, .000125f));
+ float total_err = total_err_alt;
+#endif
return total_err;
}
// Returns WSSE error
-float encode_surrogate_trial(
+static float encode_surrogate_trial(
uint32_t block_width, uint32_t block_height,
const astc_ldr::pixel_stats_t& pixel_stats,
uint32_t cem_index,
@@ -1939,12 +2479,13 @@ float encode_surrogate_trial(
{
const bool is_downsampling = (grid_width < block_width) || (grid_height < block_height);
const bool dual_plane_flag = (ccs_index >= 0);
-
- const basist::astc_ldr_t::astc_block_grid_data* pBlock_grid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, grid_width, grid_height);
-
+
const float* pDownsample_matrix = nullptr;
if (is_downsampling)
+ {
+ const basist::astc_ldr_t::astc_block_grid_data* pBlock_grid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, grid_width, grid_height);
pDownsample_matrix = pBlock_grid_data->m_downsample_matrix.get_ptr();
+ }
//const uint32_t total_block_pixels = block_width * block_height;
//const uint32_t total_grid_pixels = grid_width * grid_height;
@@ -2006,7 +2547,8 @@ float encode_surrogate_trial(
#if defined(_DEBUG) || defined(DEBUG)
{
float alt_wsse_err = decode_surrogate_and_compute_error(block_width, block_height, pixel_stats, log_block, nullptr, params);
- assert(fabs(wsse_err - alt_wsse_err) < .00125f);
+ //assert(fabs(wsse_err - alt_wsse_err) < .125f);
+ assert(basisu::equal_rel_tol(wsse_err, alt_wsse_err, .0125f));
}
#endif
#endif
@@ -2015,7 +2557,7 @@ float encode_surrogate_trial(
return wsse_err;
}
-float encode_surrogate_trial_subsets(
+static float encode_surrogate_trial_subsets(
uint32_t block_width, uint32_t block_height,
const astc_ldr::pixel_stats_t& pixel_stats,
uint32_t cem_index,
@@ -2034,12 +2576,13 @@ float encode_surrogate_trial_subsets(
const uint32_t num_weight_levels = astc_helpers::get_ise_levels(weight_ise_range);
const uint32_t num_endpoint_levels = astc_helpers::get_ise_levels(endpoint_ise_range);
-
- const basist::astc_ldr_t::astc_block_grid_data* pBlock_grid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, grid_width, grid_height);
-
+
const float* pDownsample_matrix = nullptr;
if (is_downsampling)
+ {
+ const basist::astc_ldr_t::astc_block_grid_data* pBlock_grid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, grid_width, grid_height);
pDownsample_matrix = pBlock_grid_data->m_downsample_matrix.get_ptr();
+ }
color_rgba part_pixels[astc_helpers::MAX_PARTITIONS][astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
uint32_t num_part_pixels[astc_helpers::MAX_PARTITIONS] = { 0 };
@@ -2132,7 +2675,8 @@ float encode_surrogate_trial_subsets(
{
float alt_subset_err = decode_surrogate_and_compute_error(block_width, block_height, pixel_stats, log_block, pPat, params);
- assert(fabs(total_subset_err - alt_subset_err) < .00125f);
+ //assert(fabs(total_subset_err - alt_subset_err) < .00125f);
+ assert(basisu::equal_rel_tol(total_subset_err, (double)alt_subset_err, (double).0125f));
}
#endif
#endif
@@ -2149,13 +2693,356 @@ static inline vec4F vec4F_norm_approx(vec4F axis)
}
#endif
+// if cov[] wasn't divided by the # of pixels, this is SSE
+static inline float estimate_slam_to_line_sse_3D(const float cov[6], float xr, float yr, float zr)
+{
+ // total var
+ const float total_var = cov[0] + cov[3] + cov[5];
+
+ float l = sqrtf(xr * xr + yr * yr + zr * zr);
+ if (l < basisu::SMALL_FLOAT_VAL)
+ {
+ xr = yr = zr = 0.577350269f;
+ }
+ else
+ {
+ l = 1.0f / l;
+ xr *= l; yr *= l; zr *= l;
+ }
+
+ float xr2 = cov[0] * xr + cov[1] * yr + cov[2] * zr;
+ float xg2 = cov[1] * xr + cov[3] * yr + cov[4] * zr;
+ float xb2 = cov[2] * xr + cov[4] * yr + cov[5] * zr;
+
+ // Rayleigh quotient/est var of principal axis
+ const float principal_axis_var = xr2 * xr + xg2 * yr + xb2 * zr;
+
+ // Compute leftover var, this is the var unexplaind by the principal axis
+ const float ortho_var = basisu::maximum(0.0f, total_var - principal_axis_var);
+
+ return ortho_var;
+}
+
+static inline float estimate_slam_to_line_sse_4D(const float cov[10], float xr, float yr, float zr, float wr)
+{
+ // total var
+ const float total_var = cov[0] + cov[4] + cov[7] + cov[9];
+
+ float l = sqrtf(xr * xr + yr * yr + zr * zr + wr * wr);
+ if (l < basisu::SMALL_FLOAT_VAL)
+ {
+ xr = yr = zr = wr = .5f;
+ }
+ else
+ {
+ l = 1.0f / l;
+ xr *= l; yr *= l; zr *= l; wr *= l;
+ }
+
+ float xr2 = cov[0] * xr + cov[1] * yr + cov[2] * zr + cov[3] * wr;
+ float xg2 = cov[1] * xr + cov[4] * yr + cov[5] * zr + cov[6] * wr;
+ float xb2 = cov[2] * xr + cov[5] * yr + cov[7] * zr + cov[8] * wr;
+ float xa2 = cov[3] * xr + cov[6] * yr + cov[8] * zr + cov[9] * wr;
+
+ // Rayleigh quotient/est var of principal axis
+ const float principal_axis_var = xr2 * xr + xg2 * yr + xb2 * zr + xa2 * wr;
+
+ // Compute leftover var, this is the var unexplaind by the principal axis
+ const float ortho_var = basisu::maximum(0.0f, total_var - principal_axis_var);
+
+ return ortho_var;
+}
+
+static float estimate_partition_rgb_sse(
+ const astc_ldr::pixel_stats_t& pixel_stats,
+ bool base_scale_flag,
+ uint32_t block_width, uint32_t block_height,
+ uint32_t num_subsets,
+ const astc_ldr::partition_pattern_vec* pPat)
+{
+ assert((num_subsets >= 2) && (num_subsets <= astc_helpers::MAX_PARTITIONS));
+
+ color_rgba part_pixels[astc_helpers::MAX_PARTITIONS][astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+ uint32_t num_part_pixels[astc_helpers::MAX_PARTITIONS] = { 0 };
+
+ int part_means[astc_helpers::MAX_PARTITIONS][3];
+ clear_obj(part_means);
+
+ const uint32_t total_block_pixels = block_width * block_height;
+
+ // TODO: A moment-based approach could do both RGB direct and base+scale simultaneously.
+
+ if (base_scale_flag)
+ {
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ {
+ const color_rgba& px = pixel_stats.m_pixels[i];
+
+ const uint32_t part_index = (*pPat)[i];
+ assert(part_index < num_subsets);
+
+ part_pixels[part_index][num_part_pixels[part_index]] = px;
+
+ num_part_pixels[part_index]++;
+
+ } // i
+ }
+ else
+ {
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ {
+ const color_rgba& px = pixel_stats.m_pixels[i];
+
+ const uint32_t part_index = (*pPat)[i];
+ assert(part_index < num_subsets);
+
+ part_pixels[part_index][num_part_pixels[part_index]] = px;
+
+ part_means[part_index][0] += px.r;
+ part_means[part_index][1] += px.g;
+ part_means[part_index][2] += px.b;
+
+ num_part_pixels[part_index]++;
+
+ } // i
+
+ for (uint32_t i = 0; i < num_subsets; i++)
+ {
+ assert(num_part_pixels[i] > 0);
+
+ const int n = num_part_pixels[i];
+ const int r = n >> 1;
+
+ part_means[i][0] = (part_means[i][0] + r) / n;
+ part_means[i][1] = (part_means[i][1] + r) / n;
+ part_means[i][2] = (part_means[i][2] + r) / n;
+ } // i
+ }
+
+ // rr, rg, rb, gg, gb, bb
+ int part_icov[astc_helpers::MAX_PARTITIONS][6];
+ clear_obj(part_icov);
+
+ for (uint32_t p = 0; p < num_subsets; p++)
+ {
+ const uint32_t np = num_part_pixels[p];
+
+ const int mean_r = part_means[p][0];
+ const int mean_g = part_means[p][1];
+ const int mean_b = part_means[p][2];
+
+ int* pCov = &part_icov[p][0];
+
+ for (uint32_t i = 0; i < np; i++)
+ {
+ const color_rgba& px = part_pixels[p][i];
+
+ const int r = (int)px.r - mean_r;
+ const int g = (int)px.g - mean_g;
+ const int b = (int)px.b - mean_b;
+
+ pCov[0] += r * r; pCov[1] += r * g; pCov[2] += r * b;
+ pCov[3] += g * g; pCov[4] += g * b;
+ pCov[5] += b * b;
+
+ } // i
+
+ } // p
+
+ float slam_to_line_sse_est = 0.0f;
+
+ for (uint32_t p = 0; p < num_subsets; p++)
+ {
+ const int block_max_var = basisu::maximum(part_icov[p][0], part_icov[p][3], part_icov[p][5]);
+
+ float cov[6];
+ for (uint32_t i = 0; i < 6; i++)
+ cov[i] = (float)part_icov[p][i];
+
+ const float sc = block_max_var ? (1.0f / (float)block_max_var) : 0;
+ const float wx = sc * cov[0], wy = sc * cov[3], wz = sc * cov[5];
+
+ // estimate principle axis using one iteration of the power method
+ const float alt_xr = cov[0] * wx + cov[1] * wy + cov[2] * wz;
+ const float alt_xg = cov[1] * wx + cov[3] * wy + cov[4] * wz;
+ const float alt_xb = cov[2] * wx + cov[4] * wy + cov[5] * wz;
+
+ slam_to_line_sse_est += estimate_slam_to_line_sse_3D(cov, alt_xr, alt_xg, alt_xb);
+
+ } // p
+
+ return slam_to_line_sse_est;
+}
+
+static float estimate_partition_rgba_sse(
+ const astc_ldr::pixel_stats_t& pixel_stats,
+ bool base_scale_flag,
+ uint32_t block_width, uint32_t block_height,
+ uint32_t num_subsets,
+ const astc_ldr::partition_pattern_vec* pPat)
+{
+ assert((num_subsets >= 2) && (num_subsets <= astc_helpers::MAX_PARTITIONS));
+
+ color_rgba part_pixels[astc_helpers::MAX_PARTITIONS][astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+ uint32_t num_part_pixels[astc_helpers::MAX_PARTITIONS] = { 0 };
+
+ int part_means[astc_helpers::MAX_PARTITIONS][4];
+ clear_obj(part_means);
+
+ const uint32_t total_block_pixels = block_width * block_height;
+
+ if (base_scale_flag)
+ {
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ {
+ const color_rgba& px = pixel_stats.m_pixels[i];
+
+ const uint32_t part_index = (*pPat)[i];
+ assert(part_index < num_subsets);
+
+ part_pixels[part_index][num_part_pixels[part_index]] = px;
+
+ num_part_pixels[part_index]++;
+
+ } // i
+ }
+ else
+ {
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ {
+ const color_rgba& px = pixel_stats.m_pixels[i];
+
+ const uint32_t part_index = (*pPat)[i];
+ assert(part_index < num_subsets);
+
+ part_pixels[part_index][num_part_pixels[part_index]] = px;
+
+ part_means[part_index][0] += px.r;
+ part_means[part_index][1] += px.g;
+ part_means[part_index][2] += px.b;
+ part_means[part_index][3] += px.a;
+
+ num_part_pixels[part_index]++;
+
+ } // i
+
+ for (uint32_t i = 0; i < num_subsets; i++)
+ {
+ assert(num_part_pixels[i] > 0);
+
+ const int n = num_part_pixels[i];
+ const int r = n >> 1;
+
+ part_means[i][0] = (part_means[i][0] + r) / n;
+ part_means[i][1] = (part_means[i][1] + r) / n;
+ part_means[i][2] = (part_means[i][2] + r) / n;
+ part_means[i][3] = (part_means[i][3] + r) / n;
+ } // i
+ }
+
+ int part_icov4[astc_helpers::MAX_PARTITIONS][10];
+ clear_obj(part_icov4);
+
+ for (uint32_t p = 0; p < num_subsets; p++)
+ {
+ const uint32_t np = num_part_pixels[p];
+
+ const int mean_r = part_means[p][0];
+ const int mean_g = part_means[p][1];
+ const int mean_b = part_means[p][2];
+ const int mean_a = part_means[p][3];
+
+ int* pCov = &part_icov4[p][0];
+
+ for (uint32_t i = 0; i < np; i++)
+ {
+ const color_rgba& px = part_pixels[p][i];
+
+ const int r = (int)px.r - mean_r;
+ const int g = (int)px.g - mean_g;
+ const int b = (int)px.b - mean_b;
+ const int a = (int)px.a - mean_a;
+
+ pCov[0] += r * r; pCov[1] += r * g; pCov[2] += r * b; pCov[3] += r * a;
+ pCov[4] += g * g; pCov[5] += g * b; pCov[6] += g * a;
+ pCov[7] += b * b; pCov[8] += b * a;
+ pCov[9] += a * a;
+
+ } // i
+
+ } // p
+
+ float slam_to_line_sse_est = 0.0f;
+
+ for (uint32_t s = 0; s < num_subsets; s++)
+ {
+ const int block_max_var4 = basisu::maximum(part_icov4[s][0], part_icov4[s][4], part_icov4[s][7], part_icov4[s][9]);
+
+ float cov4[10];
+ for (uint32_t i = 0; i < 10; i++)
+ cov4[i] = (float)part_icov4[s][i];
+
+ const float sc4 = block_max_var4 ? (1.0f / (float)block_max_var4) : 0;
+ const float wx = sc4 * cov4[0], wy = sc4 * cov4[4], wz = sc4 * cov4[7], wa = sc4 * cov4[9];
+
+ const float x0 = cov4[0] * wx + cov4[1] * wy + cov4[2] * wz + cov4[3] * wa;
+ const float y0 = cov4[1] * wx + cov4[4] * wy + cov4[5] * wz + cov4[6] * wa;
+ const float z0 = cov4[2] * wx + cov4[5] * wy + cov4[7] * wz + cov4[8] * wa;
+ const float w0 = cov4[3] * wx + cov4[6] * wy + cov4[8] * wz + cov4[9] * wa;
+
+ const float x1 = cov4[0] * x0 + cov4[1] * y0 + cov4[2] * z0 + cov4[3] * w0;
+ const float y1 = cov4[1] * x0 + cov4[4] * y0 + cov4[5] * z0 + cov4[6] * w0;
+ const float z1 = cov4[2] * x0 + cov4[5] * y0 + cov4[7] * z0 + cov4[8] * w0;
+ const float w1 = cov4[3] * x0 + cov4[6] * y0 + cov4[8] * z0 + cov4[9] * w0;
+
+ slam_to_line_sse_est += estimate_slam_to_line_sse_4D(cov4, x1, y1, z1, w1);
+
+ } // s
+
+ return slam_to_line_sse_est;
+}
+
+static inline float estimate_partition_sse(uint32_t cem_index,
+ const astc_ldr::pixel_stats_t& pixel_stats,
+ uint32_t block_width, uint32_t block_height,
+ uint32_t num_subsets,
+ const astc_ldr::partition_pattern_vec* pPat)
+{
+ switch (cem_index)
+ {
+ // RGB
+ case astc_helpers::CEM_LDR_RGB_DIRECT:
+ return estimate_partition_rgb_sse(pixel_stats, false, block_width, block_height, num_subsets, pPat);
+ case astc_helpers::CEM_LDR_RGB_BASE_SCALE:
+ return estimate_partition_rgb_sse(pixel_stats, true, block_width, block_height, num_subsets, pPat);
+
+ // RGBA
+ case astc_helpers::CEM_LDR_RGBA_DIRECT:
+ return estimate_partition_rgba_sse(pixel_stats, false, block_width, block_height, num_subsets, pPat);
+ case astc_helpers::CEM_LDR_RGB_BASE_SCALE_PLUS_TWO_A:
+ return estimate_partition_rgba_sse(pixel_stats, true, block_width, block_height, num_subsets, pPat);
+
+ default:
+ assert(0);
+ break;
+ }
+
+ return 0;
+}
+
+#define BASISU_USE_LSH2 (1)
+#define BASISU_USE_LSH3 (1)
+#define BASISU_LSH_FILTERING true
+
static bool estimate_partition2(
uint32_t block_width, uint32_t block_height,
const astc_ldr::pixel_stats_t& pixels,
- int* pBest_parts, uint32_t num_best_parts, // unique indices, not ASTC seeds
- const astc_ldr::partitions_data* pPart_data, bool brute_force_flag)
+ int* pBest_parts, // unique indices, not ASTC seeds
+ int &num_best_parts, // will be modified with the actual number of results
+ const astc_ldr::partitions_data* pPart_data,
+ bool brute_force_flag)
{
- assert(num_best_parts && (num_best_parts <= pPart_data->m_total_unique_patterns));
+ assert(num_best_parts && (num_best_parts <= (int)pPart_data->m_total_unique_patterns));
const uint32_t num_block_pixels = block_width * block_height;
@@ -2201,9 +3088,55 @@ static bool estimate_partition2(
std::sort(part_similarity, part_similarity + pPart_data->m_total_unique_patterns);
- for (uint32_t i = 0; i < num_best_parts; i++)
+ for (int i = 0; i < num_best_parts; i++)
pBest_parts[i] = part_similarity[(pPart_data->m_total_unique_patterns - 1) - i] & 0xFFFF;
}
+ else if (BASISU_USE_LSH2)
+ {
+ astc_ldr::partition_pattern_vec desired_part(block_width, block_height);
+ astc_ldr::partition_pattern_vec desired_part_alt(block_width, block_height);
+
+ for (uint32_t i = 0; i < num_block_pixels; i++)
+ {
+ float proj = (pixels.m_pixels_f[i] - pixels.m_mean_f).dot(pixels.m_mean_rel_axis4);
+
+ desired_part.m_parts[i] = proj < 0.0f;
+ desired_part_alt.m_parts[i] = 1 - desired_part.m_parts[i];
+ }
+
+ uint32_t results[astc_helpers::NUM_PARTITION_PATTERNS];
+ uint32_t total_results = pPart_data->m_part_lhs_map.find(desired_part, results, astc_helpers::NUM_PARTITION_PATTERNS, BASISU_LSH_FILTERING);
+
+ if (!total_results)
+ {
+ num_best_parts = 0;
+ return false;
+ }
+
+ uint32_t part_similarity[astc_helpers::NUM_PARTITION_PATTERNS];
+
+ for (uint32_t res_index = 0; res_index < total_results; res_index++)
+ {
+ const uint32_t part_index = results[res_index];
+
+ const astc_ldr::partition_pattern_vec& pat_vec = pPart_data->m_partition_pats[part_index];
+
+ const int dist2_a = desired_part.get_squared_distance_2subsets(pat_vec);
+ const int dist2_b = desired_part_alt.get_squared_distance_2subsets(pat_vec);
+
+ const int dist2 = minimum(dist2_a, dist2_b);
+
+ part_similarity[res_index] = (dist2 << 16) | part_index;
+
+ } // part_index;
+
+ std::sort(part_similarity, part_similarity + total_results);
+
+ num_best_parts = minimum(num_best_parts, total_results);
+
+ for (int i = 0; i < num_best_parts; i++)
+ pBest_parts[i] = part_similarity[i] & 0xFFFF;
+ }
else
{
astc_ldr::partition_pattern_vec desired_part(block_width, block_height);
@@ -2220,7 +3153,7 @@ static bool estimate_partition2(
pPart_data->m_part_vp_tree.find_nearest(2, desired_part, results, num_best_parts);
- assert(results.get_size() == num_best_parts);
+ assert((int)results.get_size() == num_best_parts);
const auto& elements = results.get_elements();
@@ -2234,16 +3167,18 @@ static bool estimate_partition2(
static bool estimate_partition3(
uint32_t block_width, uint32_t block_height,
const astc_ldr::pixel_stats_t& pixels,
- int* pBest_parts, uint32_t num_best_parts,
+ int* pBest_parts,
+ int &num_best_parts, // will be modified with the actual number of results
const astc_ldr::partitions_data* pPart_data, bool brute_force_flag)
{
- assert(num_best_parts && (num_best_parts <= pPart_data->m_total_unique_patterns));
+ assert(num_best_parts && (num_best_parts <= (int)pPart_data->m_total_unique_patterns));
vec4F training_vecs[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS], mean(0.0f);
const uint32_t num_block_pixels = block_width * block_height, NUM_SUBSETS = 3;
- float brightest_inten = 0.0f, darkest_inten = BIG_FLOAT_VAL;
+ float brightest_inten = -BIG_FLOAT_VAL, darkest_inten = BIG_FLOAT_VAL;
+
vec4F cluster_centroids[NUM_SUBSETS];
clear_obj(cluster_centroids);
@@ -2252,8 +3187,8 @@ static bool estimate_partition3(
vec4F& v = training_vecs[i];
v = pixels.m_pixels_f[i];
-
- float inten = v.dot(vec4F(1.0f));
+
+ float inten = (v - pixels.m_mean_f).dot(pixels.m_mean_rel_axis4);
if (inten < darkest_inten)
{
darkest_inten = inten;
@@ -2387,7 +3322,49 @@ static bool estimate_partition3(
std::sort(part_similarity, part_similarity + pPart_data->m_total_unique_patterns);
- for (uint32_t i = 0; i < num_best_parts; i++)
+ for (int i = 0; i < num_best_parts; i++)
+ pBest_parts[i] = part_similarity[i] & 0xFFFF;
+ }
+ else if (BASISU_USE_LSH3)
+ {
+ astc_ldr::partition_pattern_vec desired_parts[astc_ldr::NUM_PART3_MAPPINGS];
+ for (uint32_t j = 0; j < astc_ldr::NUM_PART3_MAPPINGS; j++)
+ desired_parts[j] = desired_part.get_permuted3(j);
+
+ uint32_t results[astc_helpers::NUM_PARTITION_PATTERNS];
+ uint32_t total_results = pPart_data->m_part_lhs_map.find(desired_part, results, astc_helpers::NUM_PARTITION_PATTERNS, BASISU_LSH_FILTERING);
+
+ if (!total_results)
+ {
+ num_best_parts = 0;
+ return false;
+ }
+
+ uint32_t part_similarity[astc_helpers::NUM_PARTITION_PATTERNS];
+
+ for (uint32_t res_index = 0; res_index < total_results; res_index++)
+ {
+ const uint32_t part_index = results[res_index];
+
+ const astc_ldr::partition_pattern_vec& pat = pPart_data->m_partition_pats[part_index];
+
+ uint32_t lowest_pat_dist = UINT32_MAX;
+ for (uint32_t p = 0; p < astc_ldr::NUM_PART3_MAPPINGS; p++)
+ {
+ uint32_t dist = pat.get_squared_distance(desired_parts[p]);
+ if (dist < lowest_pat_dist)
+ lowest_pat_dist = dist;
+ }
+
+ part_similarity[res_index] = (lowest_pat_dist << 16) | part_index;
+
+ } // part_index;
+
+ std::sort(part_similarity, part_similarity + total_results);
+
+ num_best_parts = minimum(num_best_parts, total_results);
+
+ for (int i = 0; i < num_best_parts; i++)
pBest_parts[i] = part_similarity[i] & 0xFFFF;
}
else
@@ -2397,7 +3374,7 @@ static bool estimate_partition3(
pPart_data->m_part_vp_tree.find_nearest(3, desired_part, results, num_best_parts);
- assert(results.get_size() == num_best_parts);
+ assert((int)results.get_size() == num_best_parts);
const auto& elements = results.get_elements();
@@ -2424,7 +3401,7 @@ static const float g_sobel_y[3][3] = // [y][x]
{ 1.0f, 2.0f, 1.0f }
};
-void compute_sobel(const image& orig, image& dest, const float* pMatrix_3x3)
+static void compute_sobel(const image& orig, image& dest, const float* pMatrix_3x3)
{
const uint32_t width = orig.get_width();
const uint32_t height = orig.get_height();
@@ -2460,18 +3437,25 @@ void compute_sobel(const image& orig, image& dest, const float* pMatrix_3x3)
} // y
}
-void compute_energy_from_dct(uint32_t block_width, uint32_t block_height, float* pDCT)
+// Returns total energy excluding DC
+static float compute_ac_energy_from_dct(uint32_t block_width, uint32_t block_height, float* pDCT)
{
const uint32_t num_texels = block_width * block_height;
+ float total_energy = 0.0f;
for (uint32_t i = 1; i < num_texels; i++)
- pDCT[i] = square(pDCT[i]);
+ {
+ const float v = square(pDCT[i]);
+ pDCT[i] = v;
+ total_energy += v;
+ }
pDCT[0] = 0.0f;
+ return total_energy;
}
// Results scaled by # block texels (block-SSE in weight space)
-float compute_preserved_dct_energy(uint32_t block_width, uint32_t block_height, const float* pEnergy, uint32_t grid_w, uint32_t grid_h)
+static float compute_preserved_dct_energy(uint32_t block_width, uint32_t block_height, const float* pEnergy, uint32_t grid_w, uint32_t grid_h)
{
float tot = 0.0f;
@@ -2488,7 +3472,7 @@ float compute_preserved_dct_energy(uint32_t block_width, uint32_t block_height,
}
// Results scaled by # block texels (block-SSE in weight space)
-inline float compute_lost_dct_energy(uint32_t block_width, uint32_t block_height, const float* pEnergy, uint32_t grid_w, uint32_t grid_h)
+[[maybe_unused]] static inline float compute_lost_dct_energy_orig(uint32_t block_width, uint32_t block_height, const float* pEnergy, uint32_t grid_w, uint32_t grid_h)
{
float tot = 0.0f;
@@ -2506,6 +3490,73 @@ inline float compute_lost_dct_energy(uint32_t block_width, uint32_t block_height
return tot;
}
+static inline float compute_lost_dct_energy(uint32_t block_width, uint32_t block_height, const float* pEnergy, uint32_t grid_w, uint32_t grid_h)
+{
+ float tot0 = 0.0f, tot1 = 0.0f, tot2 = 0.0f, tot3 = 0.0f;
+
+ const float* pSrc_row = pEnergy;
+ for (uint32_t y = 0; y < block_height; y++, pSrc_row += block_width)
+ {
+ uint32_t x = (y < grid_h) ? grid_w : 0;
+
+ while ((x + 4) <= block_width)
+ {
+ tot0 += pSrc_row[x + 0];
+ tot1 += pSrc_row[x + 1];
+ tot2 += pSrc_row[x + 2];
+ tot3 += pSrc_row[x + 3];
+ x += 4;
+ }
+
+ while (x < block_width)
+ {
+ tot0 += pSrc_row[x];
+ ++x;
+ }
+ } // y
+
+ return tot0 + tot1 + tot2 + tot3;
+}
+
+// Build 2D prefix sums over a row-major block of energies.
+// prefix[x + y * block_width] contains the sum over [0..x] x [0..y], inclusive.
+static inline void prepare_dct_energy_prefix_table(uint32_t block_width, uint32_t block_height, const float* pEnergy, float* pPrefix)
+{
+ for (uint32_t y = 0; y < block_height; y++)
+ {
+ float row_sum = 0.0f;
+
+ for (uint32_t x = 0; x < block_width; x++)
+ {
+ row_sum += pEnergy[x + y * block_width];
+
+ float above = 0.0f;
+ if (y > 0)
+ above = pPrefix[x + (y - 1) * block_width];
+
+ pPrefix[x + y * block_width] = row_sum + above;
+ }
+ }
+}
+
+// Sum of the origin-anchored rectangle [0, grid_w) x [0, grid_h).
+static inline float query_dct_energy_prefix_sum(uint32_t block_width, uint32_t block_height, const float* pPrefix, uint32_t grid_w, uint32_t grid_h)
+{
+ (void)block_height;
+
+ assert((grid_w >= 1) && (grid_w <= block_width));
+ assert((grid_h >= 1) && (grid_h <= block_height));
+
+ return pPrefix[(grid_w - 1) + (grid_h - 1) * block_width];
+}
+
+static inline float compute_lost_dct_energy_prefix_sum(uint32_t block_width, uint32_t block_height, const float* pEnergy_prefix_sum, uint32_t grid_w, uint32_t grid_h, float total_ac_energy)
+{
+ const float kept_energy = query_dct_energy_prefix_sum(block_width, block_height, pEnergy_prefix_sum, grid_w, grid_h);
+
+ return maximum(total_ac_energy - kept_energy, 0.0f);
+}
+
struct ldr_astc_lowlevel_block_encoder_params
{
ldr_astc_lowlevel_block_encoder_params()
@@ -2552,20 +3603,32 @@ struct ldr_astc_lowlevel_block_encoder_params
m_gradient_descent_flag = true;
m_polish_weights_flag = true;
m_qcd_enabled_flag = true;
+ m_encode_trial_early_out_thresh = .1f;
+ m_encode_trial_subsets_early_out_thresh = .1f;
m_final_encode_try_base_ofs = true;
m_final_encode_always_try_rgb_direct = false; // if true, even if base_ofs succeeds, we try RGB/RGBA direct too
+#if 0
m_use_parts_std_dev_thresh = (8.0f / 255.0f);
m_use_parts_std_dev_thresh2 = (40.0f / 255.0f);
m_sobel_energy_thresh1 = 3200.0f;
m_sobel_energy_thresh2 = 30000.0f;
m_sobel_energy_thresh3 = 50000.0f;
+#else
+ const float s = .2f; // exp
+ m_use_parts_std_dev_thresh = (8.0f / 255.0f) * s;
+ m_use_parts_std_dev_thresh2 = (40.0f / 255.0f) * s;
+ m_sobel_energy_thresh1 = 3200.0f * s;
+ m_sobel_energy_thresh2 = 30000.0f * s;
+ m_sobel_energy_thresh3 = 50000.0f * s;
+#endif
m_part2_fraction_to_keep = 2;
m_part3_fraction_to_keep = 2;
m_base_parts2 = 32;
m_base_parts3 = 32;
+ m_use_fast_part_est_stage2 = true;
// TODO: Prehaps expose this at a higher level.
m_use_blue_contraction = true;
@@ -2625,12 +3688,16 @@ struct ldr_astc_lowlevel_block_encoder_params
bool m_gradient_descent_flag;
bool m_polish_weights_flag;
bool m_qcd_enabled_flag;
+
+ float m_encode_trial_early_out_thresh;
+ float m_encode_trial_subsets_early_out_thresh;
bool m_final_encode_try_base_ofs;
bool m_final_encode_always_try_rgb_direct;
bool m_brute_force_est_parts;
bool m_disable_part_est_stage2; // only use single stage partition estimation
+ bool m_use_fast_part_est_stage2;
bool m_use_blue_contraction; // currently global enable/disable
@@ -2648,7 +3715,7 @@ struct ldr_astc_lowlevel_block_encoder_params
float m_early_stop_wpsnr;
float m_early_stop2_wpsnr;
- basist::astc_ldr_t::dct2f* m_pDCT2F; // at block size
+ const basist::astc_ldr_t::dct2f* m_pDCT2F; // at block size
};
struct trial_surrogate
@@ -2674,7 +3741,7 @@ struct trial_surrogate
struct encode_block_output
{
int16_t m_trial_mode_index; // -1 = solid, no trial mode
- uint16_t m_blur_id; // blur index
+ uint16_t m_blur_id; // blur index, or codec identifier
astc_helpers::log_astc_block m_log_blk;
@@ -2741,8 +3808,9 @@ struct weight_terms
weight_var += squaref(pWeights[i] - m_mean);
m_var = weight_var / (float)n;
- // drops below 2/3 on smooth blocks and tends to 2/3 when weights are well spread
- m_endpoint_factor = (1.0f + 2.0f * m_var + 2.0f * m_mean * m_mean - 2.0f * m_mean) / (2.0f / 3.0f);
+ // drops below 2/3 on smooth blocks and tends to 2/3 when weights are well spread (so normalized by (2.0f / 3.0f))
+ //m_endpoint_factor = (1.0f + 2.0f * m_var + 2.0f * m_mean * m_mean - 2.0f * m_mean) / (2.0f / 3.0f);
+ m_endpoint_factor = (1.0f + 2.0f * m_var + 2.0f * m_mean * m_mean - 2.0f * m_mean) * (3.0f / 2.0f);
m_endpoint_factor = clamp(m_endpoint_factor, .25f, 1.50f);
const float UNIFORM_VAR = 1.0f / 12.0f;
@@ -2754,13 +3822,18 @@ struct weight_terms
};
// weight_gamma is block size/grid size specific factor (0,1] (the amount of MSE quant error remaining taking into account bilinear smoothing)
-inline chan_mse_est compute_quantized_channel_mse_estimates(uint32_t num_endpoint_levels, uint32_t num_weight_levels, float span_size, float weight_gamma, const weight_terms* pWeight_terms = nullptr)
+static inline chan_mse_est compute_quantized_channel_mse_estimates(uint32_t num_endpoint_levels, uint32_t num_weight_levels, float span_size, float weight_gamma, const weight_terms* pWeight_terms = nullptr)
{
assert(num_endpoint_levels >= 2);
assert(num_weight_levels >= 2);
const float Dep = 1.0f / (float)(num_endpoint_levels - 1); // endpoint quant step
+
+#if BASISU_MODIFIED_WEIGHT_QUANT_MSE_ESTIMATE
+ const float Dw = 1.0f / (float)(num_weight_levels); // weight quant step
+#else
const float Dw = 1.0f / (float)(num_weight_levels - 1); // weight quant step
+#endif
// Endpoint quant MSE estimate is not span dependent
float ep_lower = (Dep * Dep) / 12.0f * (2.0f / 3.0f);
@@ -2777,14 +3850,14 @@ inline chan_mse_est compute_quantized_channel_mse_estimates(uint32_t num_endpoin
return chan_mse_est(ep_lower, wq_lower);
}
-inline float compute_quantized_channel_endpoint_mse_estimate(uint32_t num_endpoint_levels, const weight_terms* pWeight_terms = nullptr)
+static inline float compute_quantized_channel_endpoint_mse_estimate(uint32_t num_endpoint_levels, const weight_terms* pWeight_terms = nullptr)
{
assert(num_endpoint_levels >= 2);
const float Dep = 1.0f / (float)(num_endpoint_levels - 1); // endpoint quant step
// Endpoint quant MSE estimate is not span dependent
- float ep_lower = (Dep * Dep) / 12.0f * (2.0f / 3.0f);
+ float ep_lower = (Dep * Dep) * (1.0f / 12.0f) * (2.0f / 3.0f);
if (pWeight_terms)
ep_lower *= pWeight_terms->m_endpoint_factor;
@@ -2792,14 +3865,18 @@ inline float compute_quantized_channel_endpoint_mse_estimate(uint32_t num_endpoi
return ep_lower;
}
-inline float compute_quantized_channel_weight_mse_estimate(uint32_t num_weight_levels, float span_size, float weight_gamma, const weight_terms* pWeight_terms = nullptr)
+static inline float compute_quantized_channel_weight_mse_estimate(uint32_t num_weight_levels, float span_size, float weight_gamma, const weight_terms* pWeight_terms = nullptr)
{
assert(num_weight_levels >= 2);
-
+
+#if BASISU_MODIFIED_WEIGHT_QUANT_MSE_ESTIMATE
+ const float Dw = 1.0f / (float)(num_weight_levels); // weight quant step
+#else
const float Dw = 1.0f / (float)(num_weight_levels - 1); // weight quant step
+#endif
// Weight quant MSE estimate is span dependent
- float wq_lower = (Dw * Dw) / 12.0f * weight_gamma * (span_size * span_size);
+ float wq_lower = (Dw * Dw) * (1.0f / 12.0f) * weight_gamma * (span_size * span_size);
if (pWeight_terms)
wq_lower *= pWeight_terms->m_weight_spread_scale;
@@ -2868,6 +3945,7 @@ typedef basisu::hash_map shortlist_buck
struct trial_mode_estimate_superbucket_key
{
// All member vars from beginning to m_last will be hashed. Be careful of alignment.
+ // Total size must be sizeof(uint64_t). Unused members MUST be set to 0.
uint8_t m_cem_index;
int8_t m_ccs_index;
uint16_t m_subset_unique_index;
@@ -2876,23 +3954,48 @@ struct trial_mode_estimate_superbucket_key
uint8_t m_last;
uint8_t m_unused[2];
- trial_mode_estimate_superbucket_key()
+ inline trial_mode_estimate_superbucket_key()
{
static_assert((sizeof(*this) % 4) == 0, "struct size must be divisible by 4");
}
- void clear()
+ inline void clear()
{
clear_obj(*this);
}
- operator size_t() const
+ inline operator size_t() const
{
+#if 0
return basist::hash_hsieh((const uint8_t*)this, BASISU_OFFSETOF(trial_mode_estimate_superbucket_key, m_last));
+#elif 1
+ static_assert(sizeof(*this) == sizeof(uint64_t), "struct size must be sizeof(uint64_t)");
+
+ uint64_t x = *reinterpret_cast(this);
+
+ x ^= (x >> 33);
+ x *= 0xff51afd7ed558ccdULL; // Murmur finalizer constant
+ x ^= (x >> 33);
+
+ return (size_t)x;
+#else
+ uint64_t x =
+ (uint64_t)m_cem_index |
+ (((uint64_t)(uint8_t)m_ccs_index) << 8) |
+ (((uint64_t)m_subset_unique_index) << 16) |
+ (((uint64_t)m_num_subsets) << 32);
+
+ x ^= (x >> 33);
+ x *= 0xff51afd7ed558ccdULL; // Murmur finalizer constant
+ x ^= (x >> 33);
+
+ return (size_t)x;
+#endif
}
- bool operator== (const trial_mode_estimate_superbucket_key& rhs) const
+ inline bool operator== (const trial_mode_estimate_superbucket_key& rhs) const
{
+#if 0
#define COMP(e) if (e != rhs.e) return false;
COMP(m_cem_index);
COMP(m_ccs_index);
@@ -2900,6 +4003,16 @@ struct trial_mode_estimate_superbucket_key
COMP(m_num_subsets);
#undef COMP
return true;
+#endif
+
+ static_assert(sizeof(*this) == sizeof(uint64_t), "struct size must be sizeof(uint64_t)");
+
+ return *reinterpret_cast(this) == *reinterpret_cast(&rhs);
+ }
+
+ inline bool operator!= (const trial_mode_estimate_superbucket_key& rhs) const
+ {
+ return !(*this == rhs);
}
};
#pragma pack(pop)
@@ -3077,7 +4190,7 @@ struct ldr_astc_lowlevel_block_encoder
assert(total_parts2 <= (int)std::size(best_parts2_temp));
// Stage 1: kmeans+vptree
- const bool has_est_parts2 = estimate_partition2(
+ bool has_est_parts2 = estimate_partition2(
p.m_block_width, p.m_block_height,
pixel_stats,
best_parts2_temp, total_parts2,
@@ -3115,17 +4228,25 @@ struct ldr_astc_lowlevel_block_encoder
const astc_ldr::partition_pattern_vec* pPat = &pPart_data->m_partition_pats[unique_seed_index];
log_surrogate_astc_blk surrogate_log_blk;
- float sse = encode_surrogate_trial_subsets(
- p.m_block_width, p.m_block_height,
- pixel_stats,
- cem_to_surrogate_encode, 2, part_seed_index, pPat,
- astc_helpers::BISE_256_LEVELS, astc_helpers::BISE_64_LEVELS,
- p.m_block_width, p.m_block_height,
- surrogate_log_blk,
- *p.m_pEnc_params, surrogate_encode_flags);
-
- stats.m_total_surrogate_encodes++;
-
+ float sse;
+ if (p.m_use_fast_part_est_stage2)
+ {
+ sse = estimate_partition_sse(cem_to_surrogate_encode, pixel_stats, p.m_block_width, p.m_block_height, 2, pPat);
+ }
+ else
+ {
+ sse = encode_surrogate_trial_subsets(
+ p.m_block_width, p.m_block_height,
+ pixel_stats,
+ cem_to_surrogate_encode, 2, part_seed_index, pPat,
+ astc_helpers::BISE_256_LEVELS, astc_helpers::BISE_64_LEVELS,
+ p.m_block_width, p.m_block_height,
+ surrogate_log_blk,
+ *p.m_pEnc_params, surrogate_encode_flags);
+
+ stats.m_total_surrogate_encodes++;
+ }
+
part_sses[i] = sse;
} // i
@@ -3196,17 +4317,25 @@ struct ldr_astc_lowlevel_block_encoder
const astc_ldr::partition_pattern_vec* pPat = &pPart_data->m_partition_pats[unique_seed_index];
log_surrogate_astc_blk surrogate_log_blk;
- float sse = encode_surrogate_trial_subsets(
- p.m_block_width, p.m_block_height,
- pixel_stats,
- cem_to_surrogate_encode, 3, part_seed_index, pPat,
- astc_helpers::BISE_256_LEVELS, astc_helpers::BISE_64_LEVELS,
- p.m_block_width, p.m_block_height,
- surrogate_log_blk,
- *p.m_pEnc_params, surrogate_encode_flags);
-
- stats.m_total_surrogate_encodes++;
-
+ float sse;
+ if (p.m_use_fast_part_est_stage2)
+ {
+ sse = estimate_partition_sse(cem_to_surrogate_encode, pixel_stats, p.m_block_width, p.m_block_height, 3, pPat);
+ }
+ else
+ {
+ sse = encode_surrogate_trial_subsets(
+ p.m_block_width, p.m_block_height,
+ pixel_stats,
+ cem_to_surrogate_encode, 3, part_seed_index, pPat,
+ astc_helpers::BISE_256_LEVELS, astc_helpers::BISE_64_LEVELS,
+ p.m_block_width, p.m_block_height,
+ surrogate_log_blk,
+ *p.m_pEnc_params, surrogate_encode_flags);
+
+ stats.m_total_surrogate_encodes++;
+ }
+
part_sses[i] = sse;
} // i
@@ -3313,8 +4442,9 @@ struct ldr_astc_lowlevel_block_encoder
for (uint32_t grid_size_index = 0; grid_size_index < basist::astc_ldr_t::OTM_NUM_GRID_SIZES; grid_size_index++)
{
- if (grid_size_index) // if large grid
+ if (grid_size_index)
{
+ // if "large" grid (gw>=(bw-1)) and (gh>=(bh-1)) - quite conservative filter
if (p.m_use_small_grids_only)
continue;
}
@@ -3325,20 +4455,20 @@ struct ldr_astc_lowlevel_block_encoder
{
if (grid_anisos_index == 1)
{
- // W>=H
+ // W_fract >= H_fract
if (p.m_filter_horizontally_flag)
continue;
}
else if (grid_anisos_index == 2)
{
- // Wm_tm_groups[cem_index][subsets_index][ccs_index][grid_size_index][grid_anisos_index]);
-
+
} // grid_aniso_index
} // grid_size_index
@@ -3416,6 +4546,10 @@ struct ldr_astc_lowlevel_block_encoder
// Create superbuckets
uint32_t max_superbucket_tm_indices = 0;
+
+ trial_mode_estimate_superbucket_hash::insert_result superbucket_ins_res;
+ bool superbucket_ins_res_is_valid = false;
+
for (uint32_t j = 0; j < m_trial_modes_to_estimate.size(); j++)
{
const uint32_t trial_mode_iter = m_trial_modes_to_estimate[j];
@@ -3431,14 +4565,19 @@ struct ldr_astc_lowlevel_block_encoder
if (tm.m_num_parts == 1)
{
- auto ins_res = m_superbucket_hash.insert(new_key, new_val);
- const bool created_flag = ins_res.second;
+ bool created_flag = false;
+ if ((!superbucket_ins_res_is_valid) || (new_key != (superbucket_ins_res.first)->first))
+ {
+ superbucket_ins_res = m_superbucket_hash.insert(new_key, new_val);
+ created_flag = superbucket_ins_res.second;
+ superbucket_ins_res_is_valid = true;
+ }
- assert(ins_res.first->first.m_cem_index == tm.m_cem);
- assert(ins_res.first->first.m_ccs_index == tm.m_ccs_index);
- assert(ins_res.first->first.m_num_subsets == tm.m_num_parts);
+ assert(superbucket_ins_res.first->first.m_cem_index == tm.m_cem);
+ assert(superbucket_ins_res.first->first.m_ccs_index == tm.m_ccs_index);
+ assert(superbucket_ins_res.first->first.m_num_subsets == tm.m_num_parts);
- trial_mode_estimate_superbucket_value& v = (ins_res.first)->second;
+ trial_mode_estimate_superbucket_value& v = (superbucket_ins_res.first)->second;
if (created_flag)
v.m_trial_mode_list.reserve(256);
@@ -3460,14 +4599,19 @@ struct ldr_astc_lowlevel_block_encoder
new_key.m_subset_unique_index = safe_cast_uint16(part_unique_index);
- auto ins_res = m_superbucket_hash.insert(new_key, new_val);
- const bool created_flag = ins_res.second;
+ bool created_flag = false;
+ if ((!superbucket_ins_res_is_valid) || (new_key != (superbucket_ins_res.first)->first))
+ {
+ superbucket_ins_res = m_superbucket_hash.insert(new_key, new_val);
+ created_flag = superbucket_ins_res.second;
+ superbucket_ins_res_is_valid = true;
+ }
- assert(ins_res.first->first.m_cem_index == tm.m_cem);
- assert(ins_res.first->first.m_ccs_index == tm.m_ccs_index);
- assert(ins_res.first->first.m_num_subsets == tm.m_num_parts);
+ assert(superbucket_ins_res.first->first.m_cem_index == tm.m_cem);
+ assert(superbucket_ins_res.first->first.m_ccs_index == tm.m_ccs_index);
+ assert(superbucket_ins_res.first->first.m_num_subsets == tm.m_num_parts);
- trial_mode_estimate_superbucket_value& v = (ins_res.first)->second;
+ trial_mode_estimate_superbucket_value& v = (superbucket_ins_res.first)->second;
if (created_flag)
v.m_trial_mode_list.reserve(256);
@@ -3491,17 +4635,14 @@ struct ldr_astc_lowlevel_block_encoder
const uint32_t max_priority_queue_size = p.m_superbucket_max_to_retain[m_block_complexity_index];
- // purposely downscale lost scale energy relative to the other error sources
- // this biased the encoder towards smaller grids
const float SLAM_TO_LINE_WEIGHT = 1.5f; // upweight STL relative to other errors to give the estimator more of a signal especially for dual plane
- const float QUANT_ERROR_WEIGHT = 1.0f; // quant error is naturally quite pessimistic
+ const float QUANT_ERROR_WEIGHT = 1.0f; // endpoint/weight quant error
const float SCALE_ERROR_WEIGHT = 3.0f; // weight grid downsample (scale) error
-
+
// Discount for blue contraction encoding and base+offset CEM's.
const float BLUE_CONTRACTION_ENDPOINT_QUANT_DISCOUNT = .5f;
// Iterate over all superbuckets, surrogate encode to compute slam to line error, DCT of weight grid(s) to estimate energy lost during weight grid downsampling.
- // TODO: priority queue and aggressive early outs
for (auto superbucket_iter = m_superbucket_hash.begin(); superbucket_iter != m_superbucket_hash.end(); ++superbucket_iter)
{
const trial_mode_estimate_superbucket_key& key = superbucket_iter->first;
@@ -3583,21 +4724,30 @@ struct ldr_astc_lowlevel_block_encoder
float weight0_energy[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
float weight1_energy[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+ float weight0_energy_prefix_sum[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+ float weight1_energy_prefix_sum[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+
basist::astc_ldr_t::fvec& dct_work = m_dct_work;
// Forward DCT in normalized weight (surrogate) space
p.m_pDCT2F->forward(log_blk.m_weights0, weight0_energy, dct_work);
- compute_energy_from_dct(p.m_block_width, p.m_block_height, weight0_energy);
+ const float total_weight0_ac_energy = compute_ac_energy_from_dct(p.m_block_width, p.m_block_height, weight0_energy);
+ prepare_dct_energy_prefix_table(p.m_block_width, p.m_block_height, weight0_energy, weight0_energy_prefix_sum);
+
+ float total_weight1_ac_energy = 0.0f;
if (key.m_ccs_index >= 0)
{
p.m_pDCT2F->forward(log_blk.m_weights1, weight1_energy, dct_work);
- compute_energy_from_dct(p.m_block_width, p.m_block_height, weight1_energy);
+
+ total_weight1_ac_energy = compute_ac_energy_from_dct(p.m_block_width, p.m_block_height, weight1_energy);
+ prepare_dct_energy_prefix_table(p.m_block_width, p.m_block_height, weight1_energy, weight1_energy_prefix_sum);
}
weight_terms weight0_terms, weight1_terms;
weight_terms* pWeight0_terms = &weight0_terms;
weight_terms* pWeight1_terms = nullptr;
+ // TODO: These correction factors are not per-subset, but per-block.
weight0_terms.calc(total_block_texels, log_blk.m_weights0);
if (key.m_ccs_index >= 0)
{
@@ -3620,7 +4770,7 @@ struct ldr_astc_lowlevel_block_encoder
subset_pixels[subset_index] = total_subset_pixels;
}
- // Loop through all trial modes in this sueprbucket. TODO: Sort by endpoint levels?
+ // Loop through all trial modes in this superbucket. TODO: Sort by endpoint levels?
for (uint32_t k = 0; k < val.m_trial_mode_list.size(); k++)
{
const uint32_t trial_mode_index = val.m_trial_mode_list[k];
@@ -3664,6 +4814,7 @@ struct ldr_astc_lowlevel_block_encoder
} // chan_index
}
+ // TODO: perhaps we can rapidly predict when blue contraction can actually be applied based off each subset's endpoints.
if ((tm.m_cem == astc_helpers::CEM_LDR_RGB_DIRECT) || (tm.m_cem == astc_helpers::CEM_LDR_RGBA_DIRECT))
total_e_quant_wsse *= BLUE_CONTRACTION_ENDPOINT_QUANT_DISCOUNT;
@@ -3671,52 +4822,68 @@ struct ldr_astc_lowlevel_block_encoder
if (total_wsse_so_far >= worst_wsse_found_so_far)
continue;
- float lost_weight_energy0 = compute_lost_dct_energy(p.m_block_width, p.m_block_height, weight0_energy, tm.m_grid_width, tm.m_grid_height) * inv_total_block_texels;
+ //const float lost_weight_energy0 = compute_lost_dct_energy(p.m_block_width, p.m_block_height, weight0_energy, tm.m_grid_width, tm.m_grid_height) * inv_total_block_texels;
+ //assert(basisu::equal_tol(lost_weight_energy0, compute_lost_dct_energy_orig(p.m_block_width, p.m_block_height, weight0_energy, tm.m_grid_width, tm.m_grid_height) * inv_total_block_texels, .0000125f));
+
+ const float lost_weight_energy0_prefix_sum = compute_lost_dct_energy_prefix_sum(p.m_block_width, p.m_block_height, weight0_energy_prefix_sum, tm.m_grid_width, tm.m_grid_height, total_weight0_ac_energy) * inv_total_block_texels;
+ //assert(basisu::equal_tol(lost_weight_energy0, lost_weight_energy0_prefix_sum, .00125f));
+ const float lost_weight_energy0 = lost_weight_energy0_prefix_sum;
float lost_weight_energy1 = 0;
if (key.m_ccs_index >= 0)
- lost_weight_energy1 = compute_lost_dct_energy(p.m_block_width, p.m_block_height, weight1_energy, tm.m_grid_width, tm.m_grid_height) * inv_total_block_texels;
+ {
+ //lost_weight_energy1 = compute_lost_dct_energy(p.m_block_width, p.m_block_height, weight1_energy, tm.m_grid_width, tm.m_grid_height) * inv_total_block_texels;
+ //assert(basisu::equal_tol(lost_weight_energy1, compute_lost_dct_energy_orig(p.m_block_width, p.m_block_height, weight1_energy, tm.m_grid_width, tm.m_grid_height) * inv_total_block_texels, .0000125f));
+
+ const float lost_weight_energy1_prefix_sum = compute_lost_dct_energy_prefix_sum(p.m_block_width, p.m_block_height, weight1_energy_prefix_sum, tm.m_grid_width, tm.m_grid_height, total_weight1_ac_energy) * inv_total_block_texels;
+ //assert(basisu::equal_tol(lost_weight_energy1, lost_weight_energy1_prefix_sum, .00125f));
+
+ lost_weight_energy1 = lost_weight_energy1_prefix_sum;
+ }
// Add up:
// slam to line error WSSE (weighted sum of squared errors)
// weight quant error WSSE
// endpoint quant error WSSE
// weight grid rescale error WSSE (scaled by span^2)
- float total_scale_wsse = 0.0f;
-
- for (uint32_t subset_index = 0; subset_index < key.m_num_subsets; subset_index++)
+ if ((lost_weight_energy0 != 0.0f) || (lost_weight_energy1 != 0.0f))
{
- const vec4F& subset_chan_spans = subset_spans[subset_index];
- const uint32_t total_subset_pixels = subset_pixels[subset_index];
+ float total_scale_wsse = 0.0f;
- for (uint32_t c = 0; c < 4; c++)
+ for (uint32_t subset_index = 0; subset_index < key.m_num_subsets; subset_index++)
{
- float span_size = fabs(subset_chan_spans[c]);
+ const vec4F& subset_chan_spans = subset_spans[subset_index];
+ const uint32_t total_subset_pixels = subset_pixels[subset_index];
- if ((span_size == 0.0f) && ((log_blk.m_endpoints[subset_index][1][c] == 0.0f) || (log_blk.m_endpoints[subset_index][1][c] == 1.0f)))
+ for (uint32_t c = 0; c < 4; c++)
{
- // Won't have any E/W quant err at extremes (0.0 or 1.0 are always perfectly represented), no weight downsample error either.
- //chan_mse.m_ep = 0.0f;
- //chan_mse.m_wp = 0.0f;
- }
- else
- {
- // Scale channel MSE by chan weight and the # of subset pixels to get weighted SSE
- const float chan_N = (float)p.m_pEnc_params->m_comp_weights[c] * (float)total_subset_pixels;
-
- // sum in the plane's lost weight energy, scaled by span_size^2 * chan_weight * num_texels_covered
- if (key.m_ccs_index == (int)c)
- total_scale_wsse += lost_weight_energy1 * square(span_size) * chan_N;
+ float span_size = fabs(subset_chan_spans[c]);
+
+ if ((span_size == 0.0f) && ((log_blk.m_endpoints[subset_index][1][c] == 0.0f) || (log_blk.m_endpoints[subset_index][1][c] == 1.0f)))
+ {
+ // Won't have any E/W quant err at extremes (0.0 or 1.0 are always perfectly represented), no weight downsample error either.
+ //chan_mse.m_ep = 0.0f;
+ //chan_mse.m_wp = 0.0f;
+ }
else
- total_scale_wsse += lost_weight_energy0 * square(span_size) * chan_N;
- }
+ {
+ // Scale channel MSE by chan weight and the # of subset pixels to get weighted SSE
+ const float chan_N = (float)p.m_pEnc_params->m_comp_weights[c] * (float)total_subset_pixels;
- } // chan_index
+ // sum in the plane's lost weight energy, scaled by span_size^2 * chan_weight * num_texels_covered
+ if (key.m_ccs_index == (int)c)
+ total_scale_wsse += lost_weight_energy1 * square(span_size) * chan_N;
+ else
+ total_scale_wsse += lost_weight_energy0 * square(span_size) * chan_N;
+ }
+
+ } // chan_index
+ }
+
+ total_wsse_so_far += (SCALE_ERROR_WEIGHT * total_scale_wsse);
+ if (total_wsse_so_far >= worst_wsse_found_so_far)
+ continue;
}
-
- total_wsse_so_far += (SCALE_ERROR_WEIGHT * total_scale_wsse);
- if (total_wsse_so_far >= worst_wsse_found_so_far)
- continue;
float total_w_quant_wsse = 0.0f;
for (uint32_t subset_index = 0; subset_index < key.m_num_subsets; subset_index++)
@@ -4428,7 +5595,7 @@ struct ldr_astc_lowlevel_block_encoder
} // if (num_modes_in_bucket_to_shortlist < num_modes_in_bucket)
- // Surrogate encode the best looking buckets after factoring in estimate SSE errors.
+ // Surrogate encode the best looking modes in the bucket after factoring in estimate SSE errors.
for (uint32_t q = 0; q < num_modes_in_bucket_to_shortlist; q++)
{
@@ -4525,14 +5692,16 @@ struct ldr_astc_lowlevel_block_encoder
bool base_ofs_succeeded_flag = false;
- if ((p.m_final_encode_try_base_ofs) && ((tm.m_cem == astc_helpers::CEM_LDR_RGB_DIRECT) || (tm.m_cem == astc_helpers::CEM_LDR_RGBA_DIRECT)))
+ if ((p.m_final_encode_try_base_ofs) &&
+ ((tm.m_cem == astc_helpers::CEM_LDR_RGB_DIRECT) || (tm.m_cem == astc_helpers::CEM_LDR_RGBA_DIRECT)))
+ //(tm.m_endpoint_ise_range < astc_helpers::BISE_256_LEVELS)) // although this check makes sense for quality, it may not make sense for rate-distortion performance
{
// Add RGB/RGBA BASE PLUS OFFSET variant.
astc_helpers::log_astc_block log_astc_blk_alt;
const uint32_t base_ofs_cem_index = (tm.m_cem == astc_helpers::CEM_LDR_RGB_DIRECT) ? astc_helpers::CEM_LDR_RGB_BASE_PLUS_OFFSET : astc_helpers::CEM_LDR_RGBA_BASE_PLUS_OFFSET;
- bool base_ofs_clamped_flag = false;
+ bool try_direct_encoding_flag = false;
bool alt_enc_trial_status;
if (tm.m_num_parts > 1)
@@ -4547,9 +5716,13 @@ struct ldr_astc_lowlevel_block_encoder
p.m_block_width, p.m_block_height, pixel_stats, base_ofs_cem_index, tm.m_num_parts,
part_seed_index, pPat,
tm.m_endpoint_ise_range, tm.m_weight_ise_range,
- tm.m_grid_width, tm.m_grid_height, log_astc_blk_alt, *p.m_pEnc_params, false,
+ tm.m_grid_width, tm.m_grid_height,
+ p.m_encode_trial_subsets_early_out_thresh,
+ log_astc_blk_alt, *p.m_pEnc_params, false,
p.m_gradient_descent_flag, p.m_polish_weights_flag, p.m_qcd_enabled_flag,
- p.m_use_blue_contraction, &base_ofs_clamped_flag);
+ p.m_use_blue_contraction, &try_direct_encoding_flag);
+
+ stats.m_total_full_encodes++;
}
else
{
@@ -4557,17 +5730,18 @@ struct ldr_astc_lowlevel_block_encoder
p.m_block_width, p.m_block_height, pixel_stats, base_ofs_cem_index,
tm.m_ccs_index != -1, tm.m_ccs_index,
tm.m_endpoint_ise_range, tm.m_weight_ise_range,
- tm.m_grid_width, tm.m_grid_height, log_astc_blk_alt, *p.m_pEnc_params,
+ tm.m_grid_width, tm.m_grid_height, p.m_encode_trial_early_out_thresh,
+ log_astc_blk_alt, *p.m_pEnc_params,
p.m_gradient_descent_flag, p.m_polish_weights_flag, p.m_qcd_enabled_flag,
- p.m_use_blue_contraction, &base_ofs_clamped_flag);
+ p.m_use_blue_contraction, &try_direct_encoding_flag);
+
+ stats.m_total_full_encodes++;
}
assert(alt_enc_trial_status);
if (alt_enc_trial_status)
{
- stats.m_total_full_encodes++;
-
encode_block_output* pOut_block2 = out_blocks.enlarge(1);
pOut_block2->clear();
pOut_block2->m_trial_mode_index = safe_cast_int16(trial_mode_index);
@@ -4589,7 +5763,7 @@ struct ldr_astc_lowlevel_block_encoder
}
}
- base_ofs_succeeded_flag = !base_ofs_clamped_flag;
+ base_ofs_succeeded_flag = !try_direct_encoding_flag;
}
} // (p.m_final_encode_try_base_ofs)
@@ -4611,9 +5785,13 @@ struct ldr_astc_lowlevel_block_encoder
p.m_block_width, p.m_block_height, pixel_stats, tm.m_cem, tm.m_num_parts,
part_seed_index, pPat,
tm.m_endpoint_ise_range, tm.m_weight_ise_range,
- tm.m_grid_width, tm.m_grid_height, log_astc_blk, *p.m_pEnc_params, false,
+ tm.m_grid_width, tm.m_grid_height,
+ p.m_encode_trial_subsets_early_out_thresh,
+ log_astc_blk, *p.m_pEnc_params, false,
p.m_gradient_descent_flag, p.m_polish_weights_flag, p.m_qcd_enabled_flag,
p.m_use_blue_contraction);
+
+ stats.m_total_full_encodes++;
}
else
{
@@ -4621,18 +5799,19 @@ struct ldr_astc_lowlevel_block_encoder
p.m_block_width, p.m_block_height, pixel_stats, tm.m_cem,
tm.m_ccs_index != -1, tm.m_ccs_index,
tm.m_endpoint_ise_range, tm.m_weight_ise_range,
- tm.m_grid_width, tm.m_grid_height, log_astc_blk, *p.m_pEnc_params,
+ tm.m_grid_width, tm.m_grid_height, p.m_encode_trial_early_out_thresh,
+ log_astc_blk, *p.m_pEnc_params,
p.m_gradient_descent_flag, p.m_polish_weights_flag, p.m_qcd_enabled_flag,
p.m_use_blue_contraction);
+
+ stats.m_total_full_encodes++;
}
assert(enc_trial_status);
if (!enc_trial_status)
return false;
-
- stats.m_total_full_encodes++;
-
+
{
encode_block_output* pOut_block1 = out_blocks.enlarge(1);
pOut_block1->clear();
@@ -4831,7 +6010,7 @@ struct trial_mode_desc
};
#pragma pack(pop)
-static const int s_astc_cem_to_unique_ldr_index[16] =
+[[maybe_unused]] static const int s_astc_cem_to_unique_ldr_index[16] =
{
0, // CEM_LDR_LUM_DIRECT
-1, // CEM_LDR_LUM_BASE_PLUS_OFS
@@ -4863,6 +6042,7 @@ static const int s_unique_ldr_index_to_astc_cem[6] =
};
#endif
+#if 0
static uint32_t pack_tm_desc(
uint32_t grid_width, uint32_t grid_height,
uint32_t cem_index, uint32_t ccs_index, uint32_t num_subsets,
@@ -4906,8 +6086,10 @@ static uint32_t pack_tm_desc(
return packed_id;
}
+#endif
-void create_encoder_trial_modes_full_eval(uint32_t block_width, uint32_t block_height,
+#if 0
+static void create_encoder_trial_modes_full_eval(uint32_t block_width, uint32_t block_height,
basisu::vector& encoder_trial_modes, basist::astc_ldr_t::grouped_trial_modes& grouped_encoder_trial_modes,
bool print_debug_info = true, bool print_modes = false)
{
@@ -5128,6 +6310,7 @@ void create_encoder_trial_modes_full_eval(uint32_t block_width, uint32_t block_h
// sanity check
assert(encoder_trial_modes.size() < 11000);
}
+#endif
const uint32_t TOTAL_RGBA_CHAN_PAIRS = 6;
//const uint32_t TOTAL_RGB_CHAN_PAIRS = 3;
@@ -5141,7 +6324,8 @@ static const uint8_t g_rgba_chan_pairs[TOTAL_RGBA_CHAN_PAIRS][2] =
{ 2, 3 }
};
-bool encoder_trial_mode_test()
+#if 0
+static bool encoder_trial_mode_test()
{
for (uint32_t w = 4; w <= 12; w++)
{
@@ -5198,6 +6382,7 @@ bool encoder_trial_mode_test()
fmt_debug_printf("trial mode test succeeded\n");
return true;
}
+#endif
//----------------------------------------------------------------------------------
@@ -5218,9 +6403,10 @@ struct ldr_astc_block_encode_image_high_level_config
bool m_subsets_edge_filtering = true;
bool m_filter_by_pca_angles_flag = true;
- float m_use_direct_angle_thresh = 2.0f;
+
+ float m_use_direct_angle_thresh = .25f;
float m_use_base_scale_angle_thresh = 7.0f;
-
+
bool m_force_all_dual_plane_chan_evals = false; // much slower, test on base
bool m_disable_rgb_dual_plane = false; // DP can be on alpha only, if block has alpha
float m_strong_dp_decorr_thresh_rgb = .998f;
@@ -5245,13 +6431,15 @@ struct ldr_astc_block_encode_image_high_level_config
float m_early_stop_wpsnr = 0.0f;
float m_early_stop2_wpsnr = 0.0f;
- bool m_blurring_enabled = false;
+ bool m_blurring_enabled_p1 = false;
bool m_blurring_enabled_p2 = false;
bool m_gradient_descent_flag = true;
bool m_polish_weights_flag = true;
bool m_qcd_enabled_flag = true; // gradient descent must be enabled too
bool m_bucket_pruning_passes = true;
+ float m_encode_trial_early_out_thresh = .1f;
+ float m_encode_trial_subsets_early_out_thresh = .1f;
// 2nd superpass options
uint32_t m_base_parts2_p2 = 64;
@@ -5263,19 +6451,17 @@ struct ldr_astc_block_encode_image_high_level_config
bool m_force_all_dp_chans_p2 = false;
bool m_final_encode_always_try_rgb_direct = false;
bool m_filter_by_pca_angles_flag_p2 = true;
-
- // only store the single best result per block
- //bool m_save_single_result = false;
+
+ bool m_try_simplified_latent_configs = false;
bool m_debug_images = false;
bool m_debug_output = false;
+ bool m_debug_output_image_metrics = true;
std::string m_debug_file_prefix;
job_pool* m_pJob_pool;
-
- //saliency_map m_saliency_map;
-
+
astc_ldr::cem_encode_params m_cem_enc_params;
};
@@ -5324,8 +6510,9 @@ struct ldr_astc_block_encode_image_output
basisu::vector m_out_blocks;
- uint32_t m_packed_out_block_index = 0; // index of best out block by WSSE
-
+ uint32_t m_packed_out_block_index = 0; // index of chosen block (typically best out block by WSSE, but not necessarily)
+
+ // TODO: Remove, lossy supercompression uses it, but not all compressors write them.
bool m_low_freq_block_flag = false;
bool m_super_strong_edges = false;
bool m_very_strong_edges = false;
@@ -5353,10 +6540,1126 @@ private:
constexpr bool selective_blurring = true;
-bool ldr_astc_block_encode_image(
+struct encoder_config_manager
+{
+ float m_max_std_dev;
+ float m_sobel_energy;
+ bool m_is_lum_only;
+ basisu::vector m_block_dct_energy;
+ bool m_filter_horizontally_flag;
+ bool m_low_freq_block_flag;
+ uint32_t m_total_active_chans;
+ basisu::comparative_stats m_cross_chan_stats[TOTAL_RGBA_CHAN_PAIRS];
+ float m_chan_pair_correlations[6];
+ bool m_active_chan_flags[4];
+ float m_min_corr, m_max_corr;
+ bool m_used_alpha_encoder_modes;
+ astc_ldr::cem_encode_params m_temp_cem_enc_params;
+
+ encoder_config_manager()
+ {
+ clear();
+ }
+
+ void clear()
+ {
+ m_max_std_dev = 0.0f;
+ m_sobel_energy = 0.0f;
+ m_is_lum_only = false;
+ m_block_dct_energy.clear();
+ m_filter_horizontally_flag = false;
+ m_low_freq_block_flag = false;
+ m_total_active_chans = 0;
+ for (uint32_t i = 0; i < TOTAL_RGBA_CHAN_PAIRS; i++)
+ m_cross_chan_stats[i].clear();
+ clear_obj(m_chan_pair_correlations);
+ clear_obj(m_active_chan_flags);
+ m_min_corr = 0, m_max_corr = 0;
+ m_used_alpha_encoder_modes = false;
+ m_sobel_energy = 0.0f;
+ }
+
+ void init(
+ uint32_t bx, uint32_t by,
+ uint32_t block_width, uint32_t block_height, uint32_t total_block_pixels,
+ const astc_ldr::pixel_stats_t& pixel_stats,
+ const basist::astc_ldr_t::dct2f& dct,
+ const ldr_astc_block_encode_image_high_level_config& enc_cfg,
+ const image& sobel_xy,
+ image& vis_dct_low_freq_block,
+ uint32_t blur_id)
+ {
+ m_max_std_dev = 0.0f;
+ for (uint32_t i = 0; i < 4; i++)
+ m_max_std_dev = maximum(m_max_std_dev, pixel_stats.m_rgba_stats[i].m_std_dev);
+
+ m_is_lum_only = true;
+
+ for (uint32_t y = 0; y < block_height; y++)
+ {
+ for (uint32_t x = 0; x < block_width; x++)
+ {
+ const color_rgba& c = pixel_stats.m_pixels[x + y * block_width];
+ bool is_lum_texel = (c.r == c.g) && (c.r == c.b);
+ if (!is_lum_texel)
+ {
+ m_is_lum_only = false;
+ break;
+ }
+ }
+ if (m_is_lum_only)
+ break;
+ }
+
+ // TODO: allocation
+ m_block_dct_energy.resize(total_block_pixels);
+ m_block_dct_energy.set_all(0);
+
+ m_filter_horizontally_flag = false;
+ m_low_freq_block_flag = false;
+
+ {
+ // TODO: allocations
+ basisu::vector block_floats(total_block_pixels);
+ basisu::vector block_dct(total_block_pixels);
+ basist::astc_ldr_t::fvec work;
+
+ for (uint32_t c = 0; c < 4; c++)
+ {
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ block_floats[i] = pixel_stats.m_pixels_f[i][c];
+
+ dct.forward(block_floats.data(), block_dct.data(), work);
+
+ for (uint32_t y = 0; y < block_height; y++)
+ for (uint32_t x = 0; x < block_width; x++)
+ m_block_dct_energy[x + y * block_width] += (float)enc_cfg.m_cem_enc_params.m_comp_weights[c] * squaref(block_dct[x + y * block_width]);
+
+ } // c
+
+ // Wipe DC
+ m_block_dct_energy[0] = 0.0f;
+
+ float tot_energy = compute_preserved_dct_energy(block_width, block_height, m_block_dct_energy.get_ptr(), block_width, block_height);
+
+ float h_energy_lost = compute_lost_dct_energy(block_width, block_height, m_block_dct_energy.get_ptr(), block_width / 2, block_height);
+ float v_energy_lost = compute_lost_dct_energy(block_width, block_height, m_block_dct_energy.get_ptr(), block_width, block_height / 2);
+
+ m_filter_horizontally_flag = h_energy_lost < v_energy_lost;
+
+ float hv2_lost_energy_fract = compute_lost_dct_energy(block_width, block_height, m_block_dct_energy.get_ptr(), 2, 2);
+ if (tot_energy)
+ hv2_lost_energy_fract /= tot_energy;
+
+ const float LOW_FREQ_BLOCK_LOST_ENERGY_FRACT_THRESH = .03f;
+ const float LOW_FREQ_BLOCK_MAX_STD_DEV_TRESH = 1.0f / 255.0f;
+ // Ultra-smooth block determination: Only look at small grids if the block is mostly low frequency energy OR it has a very low max standard deviation.
+ if ((hv2_lost_energy_fract < LOW_FREQ_BLOCK_LOST_ENERGY_FRACT_THRESH) || (m_max_std_dev < LOW_FREQ_BLOCK_MAX_STD_DEV_TRESH))
+ m_low_freq_block_flag = true;
+ }
+
+ if ((enc_cfg.m_debug_images) && (blur_id == 0))
+ vis_dct_low_freq_block.fill_box(bx * block_width, by * block_height, block_width, block_height, m_low_freq_block_flag ? color_rgba(255, 0, 0, 255) : g_black_color);
+
+ for (uint32_t i = 0; i < 4; i++)
+ m_active_chan_flags[i] = false;
+
+ // The number of channels with non-zero spans
+ m_total_active_chans = 0;
+
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ if (pixel_stats.m_rgba_stats[i].m_range > 0.0f)
+ {
+ assert(pixel_stats.m_max[i] != pixel_stats.m_min[i]);
+
+ m_active_chan_flags[i] = true;
+
+ m_total_active_chans++;
+ }
+ else
+ {
+ assert(pixel_stats.m_max[i] == pixel_stats.m_min[i]);
+ }
+ }
+
+ for (uint32_t i = 0; i < TOTAL_RGBA_CHAN_PAIRS; i++)
+ {
+ m_cross_chan_stats[i].clear();
+
+ // def=max correlation for each channel pair (or 1 if one of the channels is inactive)
+ m_chan_pair_correlations[i] = 1.0f;
+ }
+
+ // 0=0, 1
+ // 1=0, 2
+ // 2=1, 2
+ // 3=0, 3
+ // 4=1, 3
+ // 5=2, 3
+
+ m_min_corr = 1.0f;
+ m_max_corr = 0.0f;
+
+ for (uint32_t pair_index = 0; pair_index < TOTAL_RGBA_CHAN_PAIRS; pair_index++)
+ {
+ const uint32_t chanA = g_rgba_chan_pairs[pair_index][0];
+ const uint32_t chanB = g_rgba_chan_pairs[pair_index][1];
+
+ // If both channels were active, we've got usable correlation statistics.
+ if (m_active_chan_flags[chanA] && m_active_chan_flags[chanB])
+ {
+ // TODO: This can be directly derived from the 3D/4D covariance matrix entries.
+ m_cross_chan_stats[pair_index].calc_pearson(total_block_pixels,
+ &pixel_stats.m_pixels_f[0][chanA],
+ &pixel_stats.m_pixels_f[0][chanB],
+ 4, 4,
+ &pixel_stats.m_rgba_stats[chanA],
+ &pixel_stats.m_rgba_stats[chanB]);
+
+ m_chan_pair_correlations[pair_index] = fabsf(m_cross_chan_stats[pair_index].m_pearson);
+
+ const float c = fabsf((float)m_cross_chan_stats[pair_index].m_pearson);
+ m_min_corr = minimum(m_min_corr, c);
+ m_max_corr = maximum(m_max_corr, c);
+ }
+ }
+
+ // min_cor will be 1.0f if all channels inactive (solid)
+
+ m_used_alpha_encoder_modes = pixel_stats.m_has_alpha;
+
+ m_sobel_energy = 0.0f;
+ for (uint32_t y = 0; y < block_height; y++)
+ {
+ for (uint32_t x = 0; x < block_width; x++)
+ {
+ const color_rgba& s = sobel_xy.get_clamped(bx * block_width + x, by * block_height + y);
+ m_sobel_energy += s[0] * s[0] + s[1] * s[1] + s[2] * s[2] + s[3] * s[3];
+ } // x
+ } // y
+
+ m_sobel_energy /= (float)total_block_pixels;
+ }
+
+ void select(
+ ldr_astc_lowlevel_block_encoder_params& enc_blk_params,
+ uint32_t superpass_index,
+ uint32_t bx, uint32_t by,
+ uint32_t block_width, uint32_t block_height, uint32_t total_block_pixels,
+ const image& orig_img_sobel_xy,
+ const basisu::vector& encoder_trial_modes,
+ basist::astc_ldr_t::grouped_trial_modes& grouped_encoder_trial_modes,
+ astc_ldr::partitions_data* pPart_data_p2, astc_ldr::partitions_data* pPart_data_p3,
+ const astc_ldr::pixel_stats_t& pixel_stats,
+ const ldr_astc_block_encode_image_high_level_config& enc_cfg,
+ const basist::astc_ldr_t::dct2f& dct,
+ uint32_t blur_id)
+ {
+ BASISU_NOTE_UNUSED(blur_id);
+
+ enc_blk_params.m_block_width = block_width;
+ enc_blk_params.m_block_height = block_height;
+ enc_blk_params.m_total_block_pixels = total_block_pixels;
+ enc_blk_params.m_bx = bx;
+ enc_blk_params.m_by = by;
+
+ enc_blk_params.m_pOrig_img_sobel_xy_t = &orig_img_sobel_xy;
+
+ enc_blk_params.m_num_trial_modes = encoder_trial_modes.size_u32();
+ enc_blk_params.m_pTrial_modes = encoder_trial_modes.get_ptr();
+ enc_blk_params.m_pGrouped_trial_modes = &grouped_encoder_trial_modes;
+
+ enc_blk_params.m_pPart_data_p2 = pPart_data_p2;
+ enc_blk_params.m_pPart_data_p3 = pPart_data_p3;
+ enc_blk_params.m_pEnc_params = &enc_cfg.m_cem_enc_params;
+
+ float ang_dot = saturate(pixel_stats.m_zero_rel_axis3.dot3(pixel_stats.m_mean_rel_axis3));
+ const float pca_axis_angles = acosf(ang_dot) * (180.0f / (float)cPiD);
+
+ enc_blk_params.m_use_alpha_or_opaque_modes = m_used_alpha_encoder_modes;
+ enc_blk_params.m_use_lum_direct_modes = m_is_lum_only;
+
+ const bool filter_by_pca_angles_flag = (superpass_index == 1) ? enc_cfg.m_filter_by_pca_angles_flag_p2 : enc_cfg.m_filter_by_pca_angles_flag;
+ if (!filter_by_pca_angles_flag)
+ {
+ enc_blk_params.m_use_direct_modes = true;
+ enc_blk_params.m_use_base_scale_modes = true;
+ }
+ else
+ {
+ // TODO: Make selective based off edge blocks?
+ enc_blk_params.m_use_direct_modes = (!m_total_active_chans) || (pca_axis_angles > enc_cfg.m_use_direct_angle_thresh);
+ enc_blk_params.m_use_base_scale_modes = (pca_axis_angles <= enc_cfg.m_use_base_scale_angle_thresh);
+ }
+
+ enc_blk_params.m_grid_hv_filtering = enc_cfg.m_grid_hv_filtering;
+ enc_blk_params.m_filter_horizontally_flag = m_filter_horizontally_flag;
+
+ enc_blk_params.m_use_small_grids_only = m_low_freq_block_flag && enc_cfg.m_low_freq_block_filtering;
+
+ enc_blk_params.m_subsets_enabled = enc_cfg.m_subsets_enabled && (!m_low_freq_block_flag || !enc_cfg.m_subsets_edge_filtering);
+
+ enc_blk_params.m_subsets_edge_filtering = enc_cfg.m_subsets_edge_filtering;
+
+ enc_blk_params.m_use_blue_contraction = enc_cfg.m_use_blue_contraction;
+ enc_blk_params.m_final_encode_try_base_ofs = enc_cfg.m_use_base_ofs;
+
+ memcpy(enc_blk_params.m_superbucket_max_to_retain, enc_cfg.m_superbucket_max_to_retain, sizeof(enc_cfg.m_superbucket_max_to_retain));
+
+ memcpy(enc_blk_params.m_final_shortlist_fraction, enc_cfg.m_final_shortlist_fraction, sizeof(enc_blk_params.m_final_shortlist_fraction));
+ memcpy(enc_blk_params.m_final_shortlist_min_size, enc_cfg.m_final_shortlist_min_size, sizeof(enc_cfg.m_final_shortlist_min_size));
+ memcpy(enc_blk_params.m_final_shortlist_max_size, enc_cfg.m_final_shortlist_max_size, sizeof(enc_blk_params.m_final_shortlist_max_size));
+
+ enc_blk_params.m_part2_fraction_to_keep = enc_cfg.m_part2_fraction_to_keep;
+ enc_blk_params.m_part3_fraction_to_keep = enc_cfg.m_part3_fraction_to_keep;
+ enc_blk_params.m_base_parts2 = enc_cfg.m_base_parts2;
+ enc_blk_params.m_base_parts3 = enc_cfg.m_base_parts3;
+ enc_blk_params.m_gradient_descent_flag = enc_cfg.m_gradient_descent_flag;
+ enc_blk_params.m_polish_weights_flag = enc_cfg.m_polish_weights_flag;
+ enc_blk_params.m_qcd_enabled_flag = enc_cfg.m_qcd_enabled_flag;
+ enc_blk_params.m_encode_trial_early_out_thresh = enc_cfg.m_encode_trial_early_out_thresh;
+ enc_blk_params.m_encode_trial_subsets_early_out_thresh = enc_cfg.m_encode_trial_subsets_early_out_thresh;
+ enc_blk_params.m_bucket_pruning_passes = enc_cfg.m_bucket_pruning_passes;
+
+ enc_blk_params.m_alpha_cems = m_used_alpha_encoder_modes;
+
+ enc_blk_params.m_early_stop_wpsnr = enc_cfg.m_early_stop_wpsnr;
+ enc_blk_params.m_early_stop2_wpsnr = enc_cfg.m_early_stop2_wpsnr;
+
+ enc_blk_params.m_final_encode_always_try_rgb_direct = enc_cfg.m_final_encode_always_try_rgb_direct;
+
+ enc_blk_params.m_pDCT2F = &dct;
+
+ // Determine DP usage
+ if (enc_cfg.m_force_all_dual_plane_chan_evals)
+ {
+ for (uint32_t i = 0; i < 4; i++)
+ enc_blk_params.m_dp_active_chans[i] = m_active_chan_flags[i];
+ }
+ else
+ {
+ for (uint32_t i = 0; i < 3; i++)
+ enc_blk_params.m_dp_active_chans[i] = false;
+
+ // Being very conservative with alpha here - always let the analytical evaluator consider it.
+ enc_blk_params.m_dp_active_chans[3] = pixel_stats.m_has_alpha;
+
+ if (!enc_cfg.m_disable_rgb_dual_plane)
+ {
+ const float rg_corr = m_chan_pair_correlations[0];
+ const float rb_corr = m_chan_pair_correlations[1];
+ const float gb_corr = m_chan_pair_correlations[2];
+
+ int desired_dp_chan_rgb = -1;
+
+ float min_p = minimum(rg_corr, rb_corr, gb_corr);
+
+ if (min_p < enc_cfg.m_strong_dp_decorr_thresh_rgb)
+ {
+ const bool has_r = m_active_chan_flags[0], has_g = m_active_chan_flags[1];
+ //const bool has_b = active_chan_flags[2];
+
+ uint32_t total_active_chans_rgb = 0;
+ for (uint32_t i = 0; i < 3; i++)
+ total_active_chans_rgb += m_active_chan_flags[i];
+
+ if (total_active_chans_rgb == 2)
+ {
+ if (!has_r)
+ desired_dp_chan_rgb = 1;
+ else if (!has_g)
+ desired_dp_chan_rgb = 0;
+ else
+ desired_dp_chan_rgb = 0;
+ }
+ else if (total_active_chans_rgb == 3)
+ {
+ // see if rg/rb is weakly correlated vs. gb
+ if ((rg_corr < gb_corr) && (rb_corr < gb_corr))
+ desired_dp_chan_rgb = 0;
+ // see if gr/gb is weakly correlated vs. rb
+ else if ((rg_corr < rb_corr) && (gb_corr < rb_corr))
+ desired_dp_chan_rgb = 1;
+ // assume b is weakest
+ else
+ desired_dp_chan_rgb = 2;
+ }
+ }
+
+ if (desired_dp_chan_rgb != -1)
+ {
+ assert(m_active_chan_flags[desired_dp_chan_rgb]);
+ enc_blk_params.m_dp_active_chans[desired_dp_chan_rgb] = true;
+ }
+ }
+ }
+
+ if (!enc_blk_params.m_dp_active_chans[0] && !enc_blk_params.m_dp_active_chans[1] && !enc_blk_params.m_dp_active_chans[2] && !enc_blk_params.m_dp_active_chans[3])
+ {
+ enc_blk_params.m_use_dual_planes = false;
+ }
+
+ if (superpass_index == 1)
+ {
+ enc_blk_params.m_base_parts2 = enc_cfg.m_base_parts2_p2;
+ enc_blk_params.m_base_parts3 = enc_cfg.m_base_parts3_p2;
+ enc_blk_params.m_part2_fraction_to_keep = 1;
+ enc_blk_params.m_part3_fraction_to_keep = 1;
+
+ memcpy(enc_blk_params.m_superbucket_max_to_retain, enc_cfg.m_superbucket_max_to_retain_p2, sizeof(enc_cfg.m_superbucket_max_to_retain_p2));
+ memcpy(enc_blk_params.m_final_shortlist_max_size, enc_cfg.m_final_shortlist_max_size_p2, sizeof(enc_cfg.m_final_shortlist_max_size_p2));
+
+ if (enc_cfg.m_second_pass_force_subsets_enabled)
+ enc_blk_params.m_subsets_enabled = true;
+ enc_blk_params.m_subsets_edge_filtering = false;
+
+ if (enc_cfg.m_force_all_dp_chans_p2)
+ {
+ enc_blk_params.m_dp_active_chans[0] = m_active_chan_flags[0];
+ enc_blk_params.m_dp_active_chans[1] = m_active_chan_flags[1];
+ enc_blk_params.m_dp_active_chans[2] = m_active_chan_flags[2];
+ enc_blk_params.m_dp_active_chans[3] = m_active_chan_flags[3];
+ enc_blk_params.m_use_dual_planes = true;
+
+ if (!enc_blk_params.m_dp_active_chans[0] && !enc_blk_params.m_dp_active_chans[1] && !enc_blk_params.m_dp_active_chans[2] && !enc_blk_params.m_dp_active_chans[3])
+ {
+ enc_blk_params.m_use_dual_planes = false;
+ }
+ }
+
+ enc_blk_params.m_gradient_descent_flag = true;
+ enc_blk_params.m_polish_weights_flag = true;
+
+ enc_blk_params.m_use_direct_modes = true;
+ //enc_blk_params.m_use_base_scale_modes = true; // just leaving this alone now from the first pass, it will be disabled for good reasons now
+
+ enc_blk_params.m_early_stop_wpsnr = enc_cfg.m_early_stop_wpsnr + 2.0f;
+ enc_blk_params.m_early_stop2_wpsnr = enc_cfg.m_early_stop2_wpsnr + 2.0f;
+
+ if (enc_cfg.m_second_pass_total_weight_refine_passes)
+ {
+ m_temp_cem_enc_params = enc_cfg.m_cem_enc_params;
+ enc_blk_params.m_pEnc_params = &m_temp_cem_enc_params;
+
+ m_temp_cem_enc_params.m_total_weight_refine_passes = enc_cfg.m_second_pass_total_weight_refine_passes;
+ m_temp_cem_enc_params.m_worst_weight_nudging_flag = true;
+ m_temp_cem_enc_params.m_endpoint_refinement_flag = true;
+ }
+ }
+ }
+};
+
+static bool apply_weight_grid_dct(
+ uint32_t block_width, uint32_t block_height,
+ basist::astc_ldr_t::grid_weight_dct &grid_coder,
+ encode_block_output& out_block,
+ const astc_ldr::pixel_stats_t& pixel_stats, bool recalc_block_wsse,
+ const ldr_astc_block_encode_image_high_level_config& enc_cfg,
+ astc_ldr::partitions_data* pPart_data_p2, astc_ldr::partitions_data* pPart_data_p3,
+ bool try_refining_endpoints)
+{
+ if (out_block.m_trial_mode_index < 0)
+ return true;
+
+ astc_helpers::log_astc_block& log_astc_blk = out_block.m_log_blk;
+
+ assert(!log_astc_blk.m_solid_color_flag_ldr);
+
+ const uint32_t num_planes = (log_astc_blk.m_dual_plane ? 2 : 1);
+ const uint32_t total_grid_weights = log_astc_blk.m_grid_width * log_astc_blk.m_grid_height;
+
+ basist::astc_ldr_t::fvec dct_temp;
+
+ for (uint32_t plane_index = 0; plane_index < num_planes; plane_index++)
+ {
+ basist::astc_ldr_t::dct_syms& syms = out_block.m_packed_dct_plane_data[plane_index];
+
+ code_block_weights(grid_coder, enc_cfg.m_base_q, plane_index, log_astc_blk, syms, dct_temp);
+
+ // ensure existing weights get blown away
+ if (num_planes == 1)
+ {
+ memset(log_astc_blk.m_weights, 0, total_grid_weights);
+ }
+ else
+ {
+ for (uint32_t i = 0; i < total_grid_weights; i++)
+ log_astc_blk.m_weights[i * num_planes + plane_index] = 0;
+ }
+
+ // decode the actual post-DCT/quant weights
+ const bool status = grid_coder.decode_block_weights(enc_cfg.m_base_q, plane_index, log_astc_blk, nullptr, nullptr, dct_temp, &syms);
+
+ assert(status);
+ if (!status)
+ {
+ error_printf("grid_coder.decode_block_weights() failed!\n");
+ return false;
+ }
+ }
+
+ if (!try_refining_endpoints)
+ {
+ if (recalc_block_wsse)
+ {
+ out_block.m_sse = eval_error(block_width, block_height, log_astc_blk, pixel_stats, enc_cfg.m_cem_enc_params);
+ }
+ return true;
+ }
+
+ uint64_t cur_err = eval_error(block_width, block_height, log_astc_blk, pixel_stats, enc_cfg.m_cem_enc_params);
+
+ astc_helpers::log_astc_block new_log_astc_blk(log_astc_blk);
+
+ bool status;
+ if (log_astc_blk.m_num_partitions == 1)
+ {
+ status = encode_trial_refine_only(
+ block_width, block_height,
+ pixel_stats,
+ new_log_astc_blk,
+ enc_cfg.m_cem_enc_params);
+ }
+ else
+ {
+ astc_ldr::partitions_data* pPart_data = (new_log_astc_blk.m_num_partitions == 2) ? pPart_data_p2 : pPart_data_p3;
+
+ const uint32_t part_seed_index = new_log_astc_blk.m_partition_id;
+ const uint32_t part_unique_index = pPart_data->m_part_seed_to_unique_index[part_seed_index];
+ const astc_ldr::partition_pattern_vec* pPat = &pPart_data->m_partition_pats[part_unique_index];
+
+ status = encode_trial_subsets(
+ block_width, block_height,
+ pixel_stats,
+ new_log_astc_blk.m_color_endpoint_modes[0], new_log_astc_blk.m_num_partitions,
+ new_log_astc_blk.m_partition_id, pPat,
+ new_log_astc_blk.m_endpoint_ise_range, new_log_astc_blk.m_weight_ise_range,
+ new_log_astc_blk.m_grid_width, new_log_astc_blk.m_grid_height,
+ enc_cfg.m_encode_trial_subsets_early_out_thresh,
+ new_log_astc_blk,
+ enc_cfg.m_cem_enc_params,
+ true,
+ enc_cfg.m_gradient_descent_flag, enc_cfg.m_polish_weights_flag, enc_cfg.m_qcd_enabled_flag,
+ enc_cfg.m_use_blue_contraction);
+ }
+
+ if (status)
+ {
+ const uint32_t num_cem_endpoint_vals = astc_helpers::get_num_cem_values(new_log_astc_blk.m_color_endpoint_modes[0]);
+ const uint32_t total_endpoint_vals = num_cem_endpoint_vals * new_log_astc_blk.m_num_partitions;
+
+ const bool endpoints_differ = memcmp(log_astc_blk.m_endpoints, new_log_astc_blk.m_endpoints, total_endpoint_vals) != 0;
+
+ if (endpoints_differ)
+ {
+ for (uint32_t plane_index = 0; plane_index < num_planes; plane_index++)
+ {
+ basist::astc_ldr_t::dct_syms& syms = out_block.m_packed_dct_plane_data[plane_index];
+
+ // decode the actual post-DCT/quant weights
+ status = grid_coder.decode_block_weights(enc_cfg.m_base_q, plane_index, new_log_astc_blk, nullptr, nullptr, dct_temp, &syms);
+ assert(status);
+
+ if (!status)
+ {
+ error_printf("grid_coder.decode_block_weights() failed!\n");
+ return false;
+ }
+ }
+
+ uint64_t new_err = eval_error(block_width, block_height, new_log_astc_blk, pixel_stats, enc_cfg.m_cem_enc_params);
+
+ if (new_err < cur_err)
+ {
+ cur_err = new_err;
+ log_astc_blk = new_log_astc_blk;
+ }
+ }
+ }
+
+ if (recalc_block_wsse)
+ out_block.m_sse = cur_err;
+
+ return true;
+}
+
+static void filter_block(
+ uint32_t block_width, uint32_t block_height,
+ uint32_t grid_width, uint32_t grid_height,
+ const astc_ldr::pixel_stats_t& pixel_stats,
+ color_rgba *pUpsampled_block)
+{
+ const uint32_t num_block_samples = block_width * block_height;
+ const uint32_t num_grid_samples = grid_width * grid_height;
+
+ const basist::astc_ldr_t::astc_block_grid_data* pGrid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, grid_width, grid_height);
+
+ const basisu::vector& d = pGrid_data->m_downsample_matrix; // rows=output num_grid_sampless, cols=input num_block_samples (texels)
+ const basisu::vector2D& u = pGrid_data->m_upsample_matrix; // rows=output num_block_samples (texels), cols=input num_grid_samples
+
+ for (uint32_t c = 0; c < 4; c++)
+ {
+ float downsampled_block[astc_helpers::MAX_BLOCK_PIXELS]; // num_grid_samples
+
+ if ((c == 3) && (!pixel_stats.m_has_alpha))
+ {
+ for (uint32_t i = 0; i < num_block_samples; i++)
+ pUpsampled_block[i].a = 255;
+ break;
+ }
+
+ for (uint32_t g = 0; g < num_grid_samples; g++)
+ {
+ float sum = 0;
+ for (uint32_t i = 0; i < num_block_samples; i++)
+ sum += (float)pixel_stats.m_pixels[i][c] * d[g * num_block_samples + i];
+
+ downsampled_block[g] = sum;
+ }
+
+ for (uint32_t k = 0; k < num_block_samples; k++)
+ {
+ float sum = 0;
+ for (uint32_t i = 0; i < num_grid_samples; i++)
+ sum += (float)downsampled_block[i] * u.at_row_col(k, i);
+
+ pUpsampled_block[k][c] = (uint8_t)clamp(basisu::fast_roundf_pos_int(sum), 0, 255);
+ }
+ } // c
+}
+
+// very slow
+[[maybe_unused]] static int find_tm_index(const basisu::vector& encoder_trial_modes, const astc_helpers::log_astc_block& log_blk)
+{
+ assert(astc_helpers::is_block_xuastc_ldr(log_blk));
+
+ for (uint32_t i = 0; i < encoder_trial_modes.size(); i++)
+ {
+ const basist::astc_ldr_t::trial_mode& tm = encoder_trial_modes[i];
+
+ if ((tm.m_cem == log_blk.m_color_endpoint_modes[0]) &&
+ (tm.m_grid_width == log_blk.m_grid_width) && (tm.m_grid_height == log_blk.m_grid_height) &&
+ (tm.m_num_parts == log_blk.m_num_partitions) &&
+ (tm.m_weight_ise_range == log_blk.m_weight_ise_range) &&
+ (tm.m_endpoint_ise_range == log_blk.m_endpoint_ise_range))
+ {
+ const bool tm_dp_flag = (tm.m_ccs_index >= 0);
+
+ if (tm_dp_flag != log_blk.m_dual_plane)
+ continue;
+
+ if (tm_dp_flag)
+ {
+ if (tm.m_ccs_index != log_blk.m_color_component_selector)
+ continue;
+ }
+
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static bool encode_block_to_dc_latent(
+ uint32_t block_width, uint32_t block_height,
+ const astc_ldr::pixel_stats_t& pixel_stats,
+ basisu::vector& out_blocks,
+ const ldr_astc_block_encode_image_high_level_config& enc_cfg,
+ astc_ldr::partitions_data* pPart_data_p2, astc_ldr::partitions_data* pPart_data_p3,
+ const basisu::vector& encoder_trial_modes,
+ uint64_vec &best_2subset_seed_ids, // seed ID's in low 10 bits
+ uint64_vec& best_3subset_seed_ids) // seed ID's in low 10 bits
+{
+ BASISU_NOTE_UNUSED(encoder_trial_modes);
+
+ best_2subset_seed_ids.resize(0);
+ best_2subset_seed_ids.reserve(pPart_data_p2->m_total_unique_patterns);
+
+ best_3subset_seed_ids.resize(0);
+ best_3subset_seed_ids.reserve(pPart_data_p3->m_total_unique_patterns);
+
+ const uint32_t block_size_index = astc_helpers::get_block_size_index(block_width, block_height);
+
+ const uint32_t total_block_pixels = block_width * block_height;
+
+ astc_helpers::log_astc_block log_blk;
+ log_blk.clear();
+ log_blk.m_grid_width = 3;
+ log_blk.m_grid_height = 2;
+ log_blk.m_color_endpoint_modes[0] = 6;
+ log_blk.m_color_endpoint_modes[1] = 6;
+ log_blk.m_weight_ise_range = astc_helpers::BISE_16_LEVELS;
+
+ for (uint32_t subsets = 2; subsets <= 3; subsets++)
+ {
+ encode_block_output& new_output_blk = *out_blocks.enlarge(1);
+ new_output_blk.clear();
+
+ astc_helpers::log_astc_block &best_log_blk = new_output_blk.m_log_blk;
+ uint64_t best_err = UINT64_MAX;
+
+ const astc_ldr::partitions_data* pPat_data = (subsets == 3) ? pPart_data_p3 : pPart_data_p2;
+
+ log_blk.m_num_partitions = (uint8_t)subsets;
+ log_blk.m_endpoint_ise_range = (uint8_t)((subsets == 3) ? astc_helpers::BISE_64_LEVELS : astc_helpers::BISE_256_LEVELS);
+ log_blk.m_color_endpoint_modes[2] = (subsets == 3) ? 6 : 0;
+
+ //const auto& weight_quant_tab = astc_helpers::g_dequant_tables.get_weight_tab(log_blk.m_weight_ise_range).m_val_to_ise;
+ const auto& endpoint_quant_tab = astc_helpers::g_dequant_tables.get_endpoint_tab(log_blk.m_endpoint_ise_range).m_val_to_ise;
+
+ for (uint32_t unique_part_iter = 0; unique_part_iter < pPat_data->m_total_unique_patterns; unique_part_iter++)
+ {
+ const uint32_t part_seed = pPat_data->m_unique_index_to_part_seed[unique_part_iter];
+
+ log_blk.m_partition_id = basisu::safe_cast_uint16(part_seed);
+
+ vec4F sums[3] = { vec4F(0.0f), vec4F(0.0f), vec4F(0.0f) };
+ uint32_t num[3] = { 0, 0, 0 };
+
+ [[maybe_unused]] uint32_t p_hist[3] = { 0, 0, 0 };
+
+ for (uint32_t y = 0; y < block_height; y++)
+ {
+ for (uint32_t x = 0; x < block_width; x++)
+ {
+ uint32_t p = astc_helpers::get_precomputed_texel_partition(block_width, block_height, part_seed, x, y, log_blk.m_num_partitions);
+
+ sums[p][0] += pixel_stats.m_pixels_f[x + y * block_width][0];
+ sums[p][1] += pixel_stats.m_pixels_f[x + y * block_width][1];
+ sums[p][2] += pixel_stats.m_pixels_f[x + y * block_width][2];
+
+ num[p]++;
+ } // x
+ } // y
+
+ for (uint32_t p = 0; p < subsets; p++)
+ {
+ assert(num[p]);
+
+ sums[p] /= (float)num[p];
+
+ for (uint32_t c = 0; c < 3; c++)
+ log_blk.m_endpoints[p * astc_helpers::NUM_MODE6_ENDPOINTS + c] = endpoint_quant_tab[clamp((int)std::round(255.0f * sums[p][c]), 0, 255)];
+
+ log_blk.m_endpoints[p * astc_helpers::NUM_MODE6_ENDPOINTS + 3] = endpoint_quant_tab[(subsets == 3) ? 248 : 255];
+ }
+
+ uint64_t best_k_err = UINT64_MAX;
+
+ for (uint32_t k = (subsets == 3) ? 13 : 15; k < 16; k++)
+ {
+ for (uint32_t i = 0; i < 6; i++)
+ log_blk.m_weights[i] = (uint8_t)k;
+
+ color_rgba unpacked_pixels_astc[astc_helpers::MAX_BLOCK_PIXELS];
+ bool astc_status = astc_helpers::decode_block_xuastc_ldr(log_blk, unpacked_pixels_astc, block_width, block_height, enc_cfg.m_cem_enc_params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8);
+ assert(astc_status);
+ if (!astc_status)
+ return false;
+
+ uint64_t total_err = 0;
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ total_err += weighted_color_error(pixel_stats.m_pixels[i], unpacked_pixels_astc[i], enc_cfg.m_cem_enc_params);
+
+ best_k_err = minimum(best_k_err, total_err);
+
+ if (total_err < best_err)
+ {
+ best_err = total_err;
+ best_log_blk = log_blk;
+ }
+ } // k
+
+ if (subsets == 2)
+ {
+ best_2subset_seed_ids.push_back((best_k_err << 10) | log_blk.m_partition_id);
+ }
+ else
+ {
+ best_3subset_seed_ids.push_back((best_k_err << 10) | log_blk.m_partition_id);
+ }
+
+ } // part_seed
+
+ best_2subset_seed_ids.sort();
+ best_3subset_seed_ids.sort();
+
+ // XUASTC LDR trial mode indices
+ static const uint32_t s_tm_index2[14] = { 675, 675, 974, 974, 1263, 974, 1263, 974, 1263, 1747, 1747, 2131, 2131, 2425 };
+ static const uint32_t s_tm_index3[14] = { 679, 679, 978, 978, 1267, 978, 1267, 978, 1267, 1751, 1751, 2135, 2135, 2429 };
+
+ new_output_blk.m_trial_mode_index = basisu::safe_cast_int16((best_log_blk.m_num_partitions == 2) ? s_tm_index2[block_size_index] : s_tm_index3[block_size_index]);
+
+ assert(new_output_blk.m_trial_mode_index == find_tm_index(encoder_trial_modes, best_log_blk));
+
+ new_output_blk.m_sse = best_err;
+ new_output_blk.m_blur_id = BLUR_ID_DC_LATENT_BASE + best_log_blk.m_num_partitions - 2;
+
+ } // subsets
+
+ return true;
+}
+
+static bool encode_block_to_linear_latent(
+ uint32_t num_patterns_to_try,
+ uint32_t block_width, uint32_t block_height,
+ const astc_ldr::pixel_stats_t& pixel_stats,
+ basisu::vector& out_blocks,
+ const ldr_astc_block_encode_image_high_level_config& enc_cfg,
+ astc_ldr::partitions_data* pPart_data_p2, astc_ldr::partitions_data* pPart_data_p3,
+ const basisu::vector& encoder_trial_modes,
+ uint64_vec& best_2subset_seed_ids,
+ uint64_vec& best_3subset_seed_ids)
+{
+ BASISU_NOTE_UNUSED(encoder_trial_modes);
+ BASISU_NOTE_UNUSED(pPart_data_p2);
+ BASISU_NOTE_UNUSED(pPart_data_p3);
+
+ static const uint32_t s_tm2_index_02[14] = { 717, 717, 1016, 1016, 1305, 1016, 1305, 1016, 1305, 1789, 1789, 2173, 2173, 2467 };
+ static const uint32_t s_tm2_index_13[14] = { 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215 };
+
+ static const uint32_t s_tm3_index_02[14] = { 721, 721, 1020, 1020, 1309, 1020, 1309, 1020, 1309, 1793, 1793, 2177, 2177, 2471 };
+ static const uint32_t s_tm3_index_13[14] = { 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219 };
+
+ const uint32_t block_size_index = astc_helpers::get_block_size_index(block_width, block_height);
+
+ const uint32_t total_grid_samples = 6;
+
+ const uint32_t weight_ise_range = astc_helpers::BISE_16_LEVELS;
+
+ //const auto& weight_quant_tab = astc_helpers::g_dequant_tables.get_weight_tab(weight_ise_range).m_val_to_ise;
+ const auto& weight_dequant_tab = astc_helpers::g_dequant_tables.get_weight_tab(weight_ise_range).m_ISE_to_val;
+
+ const uint32_t NUM_WEIGHT_GRIDS = 4 * 4;
+
+ static const uint32_t grid_width[NUM_WEIGHT_GRIDS] = { 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2 }, grid_height[NUM_WEIGHT_GRIDS] = { 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3 };
+
+ static const uint8_t weight_grids[NUM_WEIGHT_GRIDS][3 * 2] =
+ {
+ {
+ 0, 0, 0,
+ 15, 15, 15
+ },
+
+ {
+ 0, 15,
+ 0, 15,
+ 0, 15
+ },
+
+ {
+ 15, 15, 15,
+ 0, 0, 0
+ },
+
+ {
+ 15, 0,
+ 15, 0,
+ 15, 0
+ },
+
+ // set 2
+ {
+ 0, 0, 0,
+ 4, 8, 15
+ },
+
+ {
+ 0, 4,
+ 0, 8,
+ 0, 15
+ },
+
+ {
+ 15, 8, 4,
+ 0, 0, 0
+ },
+
+ {
+ 15, 0,
+ 8, 0,
+ 4, 0
+ },
+
+ // set 3
+ {
+ 2, 4, 6,
+ 9, 11, 13
+ },
+
+ {
+ 2, 9,
+ 4, 11,
+ 6, 13
+ },
+
+ {
+ 9, 11, 13,
+ 2, 4, 6
+ },
+
+ {
+ 9, 2,
+ 11, 4,
+ 13, 6
+ },
+
+ // set 4
+ {
+ 0, 7, 0,
+ 7, 15, 7
+ },
+
+ {
+ 0, 7,
+ 7, 15,
+ 0, 7
+ },
+
+ {
+ 15, 7, 15,
+ 7, 0, 7
+ },
+
+ {
+ 7, 0,
+ 15, 7,
+ 7, 0
+ }
+ };
+
+ // TODO
+ uint8_t dequantized_grid_weights[NUM_WEIGHT_GRIDS][astc_helpers::MAX_BLOCK_PIXELS];
+ uint8_t dequantized_block_weights[NUM_WEIGHT_GRIDS][astc_helpers::MAX_BLOCK_PIXELS];
+
+ for (uint32_t g = 0; g < NUM_WEIGHT_GRIDS; g++)
+ {
+ for (uint32_t i = 0; i < total_grid_samples; i++)
+ dequantized_grid_weights[g][i] = weight_dequant_tab[weight_grids[g][i]];
+
+ astc_helpers::upsample_weight_grid(
+ block_width, block_height, // destination/to dimension
+ grid_width[g], grid_height[g], // source/from dimension
+ dequantized_grid_weights[g], // these are dequantized [0,64] weights, NOT ISE symbols, [wy][wx]
+ dequantized_block_weights[g]); // [by][bx]
+ }
+
+ astc_ldr::cem_encode_params cem_enc_params;
+ cem_enc_params.init();
+ cem_enc_params.m_decode_mode_srgb = enc_cfg.m_cem_enc_params.m_decode_mode_srgb;
+ cem_enc_params.m_max_ls_passes = 1;
+
+ for (uint32_t subsets = 2; subsets <= 3; subsets++)
+ //const uint32_t subsets = 2;
+ {
+ //const astc_ldr::partitions_data* pPat_data = (subsets == 3) ? pPart_data_p3 : pPart_data_p2;
+
+ for (uint32_t grid_index = 0; grid_index < NUM_WEIGHT_GRIDS; grid_index++)
+ {
+ astc_helpers::log_astc_block log_blk;
+ log_blk.clear();
+
+ log_blk.m_grid_width = (uint8_t)grid_width[grid_index];
+ log_blk.m_grid_height = (uint8_t)grid_height[grid_index];
+ log_blk.m_num_partitions = (uint8_t)subsets;
+ log_blk.m_color_endpoint_modes[0] = 8;
+ log_blk.m_color_endpoint_modes[1] = 8;
+ if (subsets == 3)
+ log_blk.m_color_endpoint_modes[2] = 8;
+ log_blk.m_endpoint_ise_range = (uint8_t)((subsets == 3) ? astc_helpers::BISE_16_LEVELS : astc_helpers::BISE_64_LEVELS);
+ log_blk.m_weight_ise_range = (uint8_t)weight_ise_range;
+ memcpy(log_blk.m_weights, weight_grids[grid_index], total_grid_samples);
+
+ //const auto& endpoint_quant_tab = astc_helpers::g_dequant_tables.get_endpoint_tab(log_blk.m_endpoint_ise_range).m_val_to_ise;
+
+ uint64_t best_err = UINT64_MAX;
+ astc_helpers::log_astc_block best_log_blk;
+ best_log_blk.clear();
+
+ //for (uint32_t unique_part_iter = 0; unique_part_iter < pPat_data->m_total_unique_patterns; unique_part_iter++)
+ for (uint32_t pat_index = 0; pat_index < num_patterns_to_try; pat_index++)
+ {
+ //const uint32_t part_seed = pPat_data->m_unique_index_to_part_seed[unique_part_iter];
+ const uint32_t part_seed = ((subsets == 2) ? best_2subset_seed_ids[pat_index] : best_3subset_seed_ids[pat_index]) & 1023;
+
+ log_blk.m_partition_id = basisu::safe_cast_uint16(part_seed);
+
+ color_rgba subset_pixels[3][astc_helpers::MAX_BLOCK_PIXELS];
+ uint8_t subset_weights[3][astc_helpers::MAX_BLOCK_PIXELS];
+ uint32_t num_subset_pixels[3] = { 0, 0, 0 };
+
+ astc_ldr::pixel_stats_t stats[3];
+
+ for (uint32_t y = 0; y < block_height; y++)
+ {
+ for (uint32_t x = 0; x < block_width; x++)
+ {
+ uint32_t s = astc_helpers::get_precomputed_texel_partition(block_width, block_height, part_seed, x, y, log_blk.m_num_partitions);
+
+ const uint32_t n = num_subset_pixels[s];
+
+ subset_pixels[s][n] = pixel_stats.m_pixels[y * block_width + x];
+ subset_weights[s][n] = dequantized_block_weights[grid_index][y * block_width + x];
+
+ num_subset_pixels[s] = n + 1;
+
+ } // x
+ } // y
+
+ uint64_t total_err = 0;
+ for (uint32_t s = 0; s < subsets; s++)
+ {
+ stats[s].init(num_subset_pixels[s], subset_pixels[s]);
+
+ cem_enc_params.m_pForced_weight_vals0 = subset_weights[s];
+
+ uint8_t temp_weights[astc_helpers::MAX_BLOCK_PIXELS];
+
+ uint64_t subset_err = astc_ldr::cem_encode_pixels(8, -1, stats[s], cem_enc_params, log_blk.m_endpoint_ise_range, astc_helpers::BISE_64_LEVELS,
+ &log_blk.m_endpoints[s * astc_helpers::NUM_MODE8_ENDPOINTS], temp_weights, nullptr, UINT64_MAX, true, nullptr);
+
+ if (subset_err == UINT64_MAX)
+ {
+ total_err = UINT64_MAX;
+ break;
+ }
+
+ total_err += subset_err;
+
+ if (total_err > best_err)
+ break;
+ }
+
+ if (total_err < best_err)
+ {
+ best_err = total_err;
+ best_log_blk = log_blk;
+ }
+
+ } // part_seed
+
+ if (best_err != UINT64_MAX)
+ {
+ encode_block_output& new_output_blk = *out_blocks.enlarge(1);
+
+ new_output_blk.m_log_blk = best_log_blk;
+
+ uint32_t tm_index;
+ if (subsets == 3)
+ {
+ if (((grid_index & 3) == 0) || ((grid_index & 3) == 2))
+ tm_index = s_tm3_index_02[block_size_index];
+ else
+ tm_index = s_tm3_index_13[block_size_index];
+ }
+ else
+ {
+ if (((grid_index & 3) == 0) || ((grid_index & 3) == 2))
+ tm_index = s_tm2_index_02[block_size_index];
+ else
+ tm_index = s_tm2_index_13[block_size_index];
+ }
+
+ //fmt_printf("block_size_index: {}, grid_index: {}, tm: {}\n", block_size_index, grid_index & 3, find_tm_index(encoder_trial_modes, best_log_blk));
+ assert((int)tm_index == find_tm_index(encoder_trial_modes, best_log_blk));
+
+ new_output_blk.m_trial_mode_index = basisu::safe_cast_int16(tm_index);
+ new_output_blk.m_sse = best_err;
+ new_output_blk.m_blur_id = basisu::safe_cast_uint16(BLUR_ID_AC_LATENT_BASE + grid_index + (subsets - 2) * NUM_WEIGHT_GRIDS);// pat_index;
+ }
+
+ } // grid
+
+ } // subsets
+
+ return true;
+}
+
+static void enforce_max_candidate_limit(ldr_astc_block_encode_image_output::block_info& block_info_out, uint32_t max_candidates)
+{
+ assert(max_candidates >= 1);
+
+ if (block_info_out.m_out_blocks.size() <= max_candidates)
+ return;
+
+ uint_vec sorted_indices(block_info_out.m_out_blocks.size());
+ for (uint32_t i = 0; i < block_info_out.m_out_blocks.size(); i++)
+ sorted_indices[i] = i;
+
+ std::partial_sort(sorted_indices.begin(), sorted_indices.begin() + max_candidates, sorted_indices.end(),
+ [&sorted_indices, &block_info_out](const uint32_t a, const uint32_t b)
+ {
+ BASISU_NOTE_UNUSED(sorted_indices); // comparator only reads block_info_out
+ if (block_info_out.m_out_blocks[a].m_sse < block_info_out.m_out_blocks[b].m_sse)
+ {
+ return true;
+ }
+ else if (block_info_out.m_out_blocks[a].m_sse == block_info_out.m_out_blocks[b].m_sse)
+ {
+ if (a < b)
+ return true;
+ }
+
+ return false;
+ }
+ );
+
+ basisu::vector new_blocks;
+
+ int new_packed_out_block_index = -1;
+
+ for (uint32_t i = 0; i < max_candidates; i++)
+ if (sorted_indices[i] == block_info_out.m_packed_out_block_index)
+ new_packed_out_block_index = i;
+
+ if (new_packed_out_block_index < 0)
+ {
+ new_packed_out_block_index = 0;
+
+ new_blocks.resize(max_candidates + 1); // one more than requested, not the end of the world
+
+ new_blocks[0] = block_info_out.m_out_blocks[block_info_out.m_packed_out_block_index];
+
+ for (uint32_t i = 0; i < max_candidates; i++)
+ new_blocks[1 + i] = block_info_out.m_out_blocks[sorted_indices[i]];
+ }
+ else
+ {
+ new_blocks.resize(max_candidates);
+
+ for (uint32_t i = 0; i < max_candidates; i++)
+ new_blocks[i] = block_info_out.m_out_blocks[sorted_indices[i]];
+ }
+
+ block_info_out.m_out_blocks.swap(new_blocks);
+ block_info_out.m_packed_out_block_index = new_packed_out_block_index;
+}
+
+static void display_candidate_statistics(const ldr_astc_block_encode_image_output &enc_out)
+{
+ const auto& block_info = enc_out.m_image_block_info;
+
+ running_stat rs;
+
+ for (uint32_t by = 0; by < block_info.get_height(); by++)
+ {
+ for (uint32_t bx = 0; bx < block_info.get_width(); bx++)
+ {
+ rs.push((double)block_info(bx, by).m_out_blocks.size());
+
+ } // bx
+ } // by
+
+ fmt_debug_printf("Encoder output candidate stats: total: {}, min: {}, max: {}, avg: {3.3}\n", rs.get_total(), rs.get_min(), rs.get_max(), rs.get_mean());
+}
+
+static bool ldr_astc_block_encode_image(
const image& orig_img,
const ldr_astc_block_encode_image_high_level_config& enc_cfg,
- ldr_astc_block_encode_image_output& enc_out)
+ ldr_astc_block_encode_image_output& enc_out,
+ uint32_t max_candidate_limit)
{
if (enc_cfg.m_debug_output)
fmt_debug_printf("ldr_astc_block_encode_image:\n");
@@ -5371,51 +7674,96 @@ bool ldr_astc_block_encode_image(
if (enc_cfg.m_debug_output)
{
- fmt_debug_printf("ASTC base bitrate: {3.3} bpp\n", 128.0f / (float)(enc_cfg.m_block_width * enc_cfg.m_block_height));
+ fmt_debug_printf("\nASTC base bitrate: {3.3} bpp\n", 128.0f / (float)(enc_cfg.m_block_width * enc_cfg.m_block_height));
fmt_debug_printf("ASTC block size: {}x{}\n", enc_cfg.m_block_width, enc_cfg.m_block_height);
+
+ fmt_debug_printf("Image has alpha: {}\n", orig_img.has_alpha());
+
+ fmt_debug_printf("max_candidate_limit: {}\n", max_candidate_limit);;
}
- if (enc_cfg.m_debug_output)
- fmt_debug_printf("Image has alpha: {}\n", orig_img.has_alpha());
-
+ // TODO: The transcoder already creates all this stuff for each block size.
astc_ldr::partitions_data* pPart_data_p2 = &enc_out.m_part_data_p2;
- pPart_data_p2->init(2, enc_cfg.m_block_width, enc_cfg.m_block_height);
+ pPart_data_p2->init(2, enc_cfg.m_block_width, enc_cfg.m_block_height, BASISU_USE_LSH2 == 0, BASISU_USE_LSH2 != 0);
astc_ldr::partitions_data* pPart_data_p3 = &enc_out.m_part_data_p3;
- pPart_data_p3->init(3, enc_cfg.m_block_width, enc_cfg.m_block_height);
+ pPart_data_p3->init(3, enc_cfg.m_block_width, enc_cfg.m_block_height, BASISU_USE_LSH3 == 0, BASISU_USE_LSH3 != 0);
- // blurring coefficients
- const float bw0 = 1.15f;
- const float bw1 = 1.25f, bw1_a = 1.0f;
- const float bw2 = 1.25f;
-
// TODO: Make this optional/tune this, add only 2 level blurring support
- image orig_img_blurred2, orig_img_blurred3, orig_img_blurred4, orig_img_blurred5;
+ // TODO: Make configurable
+ // (TOTAL_BLURRED_IMAGES is defined at module scope near the top of this file.)
+
+ image orig_img_blurred[TOTAL_BLURRED_IMAGES];
+ uint32_t total_blur_encodes_p1 = 0;
+ uint32_t total_blurred_blocks_p1[2048] = { };
+ uint32_t total_blur_encodes_p2 = 0;
+ uint32_t total_blurred_blocks_p2[2048] = { };
- if ((enc_cfg.m_blurring_enabled) || (enc_cfg.m_blurring_enabled_p2))
+ if ((enc_cfg.m_blurring_enabled_p1) || (enc_cfg.m_blurring_enabled_p2))
{
- orig_img_blurred2.resize(orig_img.get_width(), orig_img.get_height());
- orig_img_blurred3.resize(orig_img.get_width(), orig_img.get_height());
- orig_img_blurred4.resize(orig_img.get_width(), orig_img.get_height());
- orig_img_blurred5.resize(orig_img.get_width(), orig_img.get_height());
+ for (uint32_t i = 0; i < TOTAL_BLURRED_IMAGES; i++)
+ orig_img_blurred[i].resize(orig_img.get_width(), orig_img.get_height());
- image_resample(orig_img, orig_img_blurred2, true, "gaussian", bw0);
- image_resample(orig_img, orig_img_blurred3, true, "gaussian", bw1, false, 0, 4, bw1_a);
- image_resample(orig_img, orig_img_blurred4, true, "gaussian", bw1_a, false, 0, 4, bw1);
- image_resample(orig_img, orig_img_blurred5, true, "gaussian", bw2, false);
+ const bool srgb_flag = false;
+
+ //if (TOTAL_BLURRED_IMAGES > 3)
+ if(0)
+ {
+ for (uint32_t k = 0; k < TOTAL_BLURRED_IMAGES; k++)
+ {
+ int i = k / 3;
+ int j = k % 3;
+
+ const float f = (float)i / (float)basisu::maximum(1, ((TOTAL_BLURRED_IMAGES - 1) / 3));
+
+ float w;
+ if (TOTAL_BLURRED_IMAGES <= 6)
+ w = lerp(1.1f, 2.0f, f);
+ else
+ w = lerp(1.01f, 2.2f, f);
+
+ float sx = 1.0f, sy = 1.0f;
+
+ switch (j)
+ {
+ case 0:
+ sx = sy = w; break;
+ case 1:
+ sx = w; break;
+ case 2:
+ default:
+ sy = w; break;
+ }
+
+ image_resample(orig_img, orig_img_blurred[k], srgb_flag, "gaussian", sx, false, 0, 4, sy);
+ }
+ }
+ else
+ {
+ for (uint32_t i = 0; i < TOTAL_BLURRED_IMAGES; i++)
+ {
+ const float f = (float)i / (float)(TOTAL_BLURRED_IMAGES - 1);
+
+ float w;
+
+ w = lerp(1.1f, 2.0f, f);
+
+ float sx = w, sy = w;
+
+ image_resample(orig_img, orig_img_blurred[i], srgb_flag, "gaussian", sx, false, 0, 4, sy);
+ }
+ }
}
if (enc_cfg.m_debug_images)
{
save_png(enc_cfg.m_debug_file_prefix + "dbg_astc_ldr_orig_img.png", orig_img);
- if ((enc_cfg.m_blurring_enabled) || (enc_cfg.m_blurring_enabled_p2))
+ if ((enc_cfg.m_blurring_enabled_p1) || (enc_cfg.m_blurring_enabled_p2))
{
- save_png(enc_cfg.m_debug_file_prefix + "vis_orig_blurred2.png", orig_img_blurred2);
- save_png(enc_cfg.m_debug_file_prefix + "vis_orig_blurred3.png", orig_img_blurred3);
- save_png(enc_cfg.m_debug_file_prefix + "vis_orig_blurred4.png", orig_img_blurred4);
- save_png(enc_cfg.m_debug_file_prefix + "vis_orig_blurred5.png", orig_img_blurred5);
+ for (uint32_t i = 0; i < TOTAL_BLURRED_IMAGES; i++)
+ save_png(enc_cfg.m_debug_file_prefix + fmt_string("vis_orig_blurred{}.png", i), orig_img_blurred[i]);
}
}
@@ -5510,13 +7858,7 @@ bool ldr_astc_block_encode_image(
uint32_t total_shortlist_candidates = 0;
uint32_t total_full_encodes_pass1 = 0;
uint32_t total_full_encodes_pass2 = 0;
-
- uint32_t total_blur_encodes = 0;
- uint32_t total_blurred_blocks1 = 0;
- uint32_t total_blurred_blocks2 = 0;
- uint32_t total_blurred_blocks3 = 0;
- uint32_t total_blurred_blocks4 = 0;
-
+
basist::astc_ldr_t::dct2f dct;
dct.init(enc_cfg.m_block_height, enc_cfg.m_block_width);
@@ -5594,6 +7936,10 @@ bool ldr_astc_block_encode_image(
uint32_t total_blocks_to_recompress = 0;
+ //uint32_t max_candidates = 64;
+ //if (total_blocks >= (512 * 512))
+ // max_candidates = 16; // save memory
+
for (uint32_t superpass_index = 0; superpass_index < total_superpasses; superpass_index++)
{
if (superpass_index == 1)
@@ -5619,16 +7965,19 @@ bool ldr_astc_block_encode_image(
bx, by,
//num_blocks_x, num_blocks_y,
total_blocks, block_width, block_height, total_block_pixels, &packed_blocks, &global_mutex,
- &orig_img, &orig_img_sobel_xy, &orig_img_blurred2, &orig_img_blurred3, &orig_img_blurred4, &orig_img_blurred5,
+ &orig_img, &orig_img_sobel_xy, &orig_img_blurred, max_candidate_limit,
&enc_cfg, &encoder_failed_flag, pPart_data_p2, pPart_data_p3,
&total_blocks_done, &total_superbuckets_created, &total_buckets_created, &total_surrogate_encodes, &total_full_encodes, &total_shortlist_candidates,
&encoder_trial_modes,
- &total_blur_encodes, &total_blurred_blocks1,
+ &total_blur_encodes_p1, &total_blurred_blocks_p1, &total_blur_encodes_p2, &total_blurred_blocks_p2,
&total_full_encodes_pass1, &total_full_encodes_pass2,
&dct, &vis_dct_low_freq_block,
&encoder_pool, &grid_coder, &grouped_encoder_trial_modes,
&enc_out, &output_block_devel_info, &total_void_extent_blocks_skipped, &superpass2_recompress_block_flags, &total_blocks_to_recompress, &last_printed_progress_val]
{
+ //if ((bx == 0x35) && (by == 0xec))
+ // printf(".");
+
if (encoder_failed_flag)
return;
@@ -5743,6 +8092,7 @@ bool ldr_astc_block_encode_image(
neighbor_log_blk.m_color_endpoint_modes[0], neighbor_log_blk.m_num_partitions, neighbor_log_blk.m_partition_id, pPat,
neighbor_log_blk.m_endpoint_ise_range, neighbor_log_blk.m_weight_ise_range,
neighbor_log_blk.m_grid_width, neighbor_log_blk.m_grid_height,
+ enc_cfg.m_encode_trial_subsets_early_out_thresh,
new_log_block,
enc_cfg.m_cem_enc_params,
refine_only_flag,
@@ -5758,6 +8108,7 @@ bool ldr_astc_block_encode_image(
neighbor_log_blk.m_dual_plane, neighbor_log_blk.m_dual_plane ? neighbor_log_blk.m_color_component_selector : -1,
neighbor_log_blk.m_endpoint_ise_range, neighbor_log_blk.m_weight_ise_range,
neighbor_log_blk.m_grid_width, neighbor_log_blk.m_grid_height,
+ enc_cfg.m_encode_trial_early_out_thresh,
new_log_block,
enc_cfg.m_cem_enc_params,
enc_cfg.m_gradient_descent_flag, enc_cfg.m_polish_weights_flag, enc_cfg.m_qcd_enabled_flag,
@@ -5780,30 +8131,24 @@ bool ldr_astc_block_encode_image(
if (enc_cfg.m_use_dct)
{
- const basist::astc_ldr_t::astc_block_grid_data* pGrid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, new_log_block.m_grid_width, new_log_block.m_grid_height);
+ //const basist::astc_ldr_t::astc_block_grid_data* pGrid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, new_log_block.m_grid_width, new_log_block.m_grid_height);
const uint32_t num_planes = (new_log_block.m_dual_plane ? 2 : 1);
+ basist::astc_ldr_t::fvec dct_temp;
+
for (uint32_t plane_index = 0; plane_index < num_planes; plane_index++)
{
- bitwise_coder c;
- basist::astc_ldr_t::dct_syms syms;
- code_block_weights(grid_coder, enc_cfg.m_base_q, plane_index, new_log_block, pGrid_data, c, syms);
-
- new_output_blk.m_packed_dct_plane_data[plane_index] = syms;
-
- c.flush();
-
- basist::bitwise_decoder d;
- d.init(c.get_bytes().data(), c.get_bytes().size_u32());
-
+ basist::astc_ldr_t::dct_syms &syms = new_output_blk.m_packed_dct_plane_data[plane_index];
+
+ code_block_weights(grid_coder, enc_cfg.m_base_q, plane_index, new_log_block, syms, dct_temp);
+
// ensure existing weights get blown away
for (uint32_t i = 0; i < (uint32_t)(new_log_block.m_grid_width * new_log_block.m_grid_height); i++)
new_log_block.m_weights[i * num_planes + plane_index] = 0;
-
- basist::astc_ldr_t::fvec dct_temp;
- bool dec_status = grid_coder.decode_block_weights(enc_cfg.m_base_q, plane_index, new_log_block, &d, pGrid_data, nullptr, dct_temp, nullptr);
-
+
+ bool dec_status = grid_coder.decode_block_weights(enc_cfg.m_base_q, plane_index, new_log_block, nullptr, nullptr, dct_temp, &syms);
+
assert(dec_status);
if (!dec_status)
{
@@ -5871,29 +8216,23 @@ bool ldr_astc_block_encode_image(
if (enc_cfg.m_use_dct)
{
- const basist::astc_ldr_t::astc_block_grid_data* pGrid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, new_log_block.m_grid_width, new_log_block.m_grid_height);
+ //const basist::astc_ldr_t::astc_block_grid_data* pGrid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, new_log_block.m_grid_width, new_log_block.m_grid_height);
const uint32_t num_planes = (new_log_block.m_dual_plane ? 2 : 1);
+ basist::astc_ldr_t::fvec dct_temp;
+
for (uint32_t plane_index = 0; plane_index < num_planes; plane_index++)
{
- bitwise_coder c;
- basist::astc_ldr_t::dct_syms syms;
- code_block_weights(grid_coder, enc_cfg.m_base_q, plane_index, new_log_block, pGrid_data, c, syms);
+ basist::astc_ldr_t::dct_syms &syms = new_output_blk.m_packed_dct_plane_data[plane_index];
+
+ code_block_weights(grid_coder, enc_cfg.m_base_q, plane_index, new_log_block, syms, dct_temp);
- new_output_blk.m_packed_dct_plane_data[plane_index] = syms;
-
- c.flush();
-
- basist::bitwise_decoder d;
- d.init(c.get_bytes().data(), c.get_bytes().size_u32());
-
- // ensure existing weights get blown away
+ // ensure existing weights get blown away
for (uint32_t i = 0; i < (uint32_t)(new_log_block.m_grid_width * new_log_block.m_grid_height); i++)
new_log_block.m_weights[i * num_planes + plane_index] = 0;
-
- basist::astc_ldr_t::fvec dct_temp;
- bool dec_status = grid_coder.decode_block_weights(enc_cfg.m_base_q, plane_index, new_log_block, &d, pGrid_data, nullptr, dct_temp, nullptr);
+
+ bool dec_status = grid_coder.decode_block_weights(enc_cfg.m_base_q, plane_index, new_log_block, nullptr, nullptr, dct_temp, &syms);
assert(dec_status);
if (!dec_status)
@@ -5993,343 +8332,23 @@ bool ldr_astc_block_encode_image(
return;
}
-
- float max_std_dev = 0.0f;
- for (uint32_t i = 0; i < 4; i++)
- max_std_dev = maximum(max_std_dev, pixel_stats.m_rgba_stats[i].m_std_dev);
-
- bool is_lum_only = true;
-
- for (uint32_t y = 0; y < block_height; y++)
- {
- for (uint32_t x = 0; x < block_width; x++)
- {
- const color_rgba& c = pixel_stats.m_pixels[x + y * block_width];
- bool is_lum_texel = (c.r == c.g) && (c.r == c.b);
- if (!is_lum_texel)
- {
- is_lum_only = false;
- break;
- }
- }
- if (is_lum_only)
- break;
- }
-
- basisu::vector block_dct_energy(total_block_pixels);
-
- bool filter_horizontally_flag = false;
- bool low_freq_block_flag = 0;
-
- {
- basisu::vector block_floats(total_block_pixels);
- basisu::vector block_dct(total_block_pixels);
- basist::astc_ldr_t::fvec work;
-
- for (uint32_t c = 0; c < 4; c++)
- {
- for (uint32_t i = 0; i < total_block_pixels; i++)
- block_floats[i] = pixel_stats.m_pixels_f[i][c];
-
- dct.forward(block_floats.data(), block_dct.data(), work);
-
- for (uint32_t y = 0; y < block_height; y++)
- for (uint32_t x = 0; x < block_width; x++)
- block_dct_energy[x + y * block_width] += (float)enc_cfg.m_cem_enc_params.m_comp_weights[c] * squaref(block_dct[x + y * block_width]);
-
- } // c
-
- // Wipe DC
- block_dct_energy[0] = 0.0f;
-
- float tot_energy = compute_preserved_dct_energy(block_width, block_height, block_dct_energy.get_ptr(), block_width, block_height);
-
- float h_energy_lost = compute_lost_dct_energy(block_width, block_height, block_dct_energy.get_ptr(), block_width / 2, block_height);
- float v_energy_lost = compute_lost_dct_energy(block_width, block_height, block_dct_energy.get_ptr(), block_width, block_height / 2);
-
- filter_horizontally_flag = h_energy_lost < v_energy_lost;
-
- float hv2_lost_energy_fract = compute_lost_dct_energy(block_width, block_height, block_dct_energy.get_ptr(), 2, 2);
- if (tot_energy)
- hv2_lost_energy_fract /= tot_energy;
-
- if ((hv2_lost_energy_fract < .03f) || (max_std_dev < (1.0f / 255.0f)))
- low_freq_block_flag = true;
- }
-
- if (enc_cfg.m_debug_images)
- vis_dct_low_freq_block.fill_box(bx * block_width, by * block_height, block_width, block_height, low_freq_block_flag ? color_rgba(255, 0, 0, 255) : g_black_color);
-
- bool active_chan_flags[4] = { };
-
- // The number of channels with non-zero spans
- uint32_t total_active_chans = 0;
- // The indices of the channels with non-zero spans.
- //uint32_t active_chan_list[4] = { 0 };
-
- for (uint32_t i = 0; i < 4; i++)
- {
- if (pixel_stats.m_rgba_stats[i].m_range > 0.0f)
- {
- assert(pixel_stats.m_max[i] != pixel_stats.m_min[i]);
-
- active_chan_flags[i] = true;
-
- //active_chan_list[total_active_chans] = i;
- total_active_chans++;
- }
- else
- {
- assert(pixel_stats.m_max[i] == pixel_stats.m_min[i]);
- }
- }
-
- basisu::comparative_stats cross_chan_stats[TOTAL_RGBA_CHAN_PAIRS];
-
- // def=max correlation for each channel pair (or 1 if one of the channels is inactive)
- float chan_pair_correlations[6] = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
- // 0=0, 1
- // 1=0, 2
- // 2=1, 2
- // 3=0, 3
- // 4=1, 3
- // 5=2, 3
-
- float min_corr = 1.0f, max_corr = 0.0f;
-
- for (uint32_t pair_index = 0; pair_index < TOTAL_RGBA_CHAN_PAIRS; pair_index++)
- {
- const uint32_t chanA = g_rgba_chan_pairs[pair_index][0];
- const uint32_t chanB = g_rgba_chan_pairs[pair_index][1];
-
- // If both channels were active, we've got usable correlation statistics.
- if (active_chan_flags[chanA] && active_chan_flags[chanB])
- {
- // TODO: This can be directly derived from the 3D/4D covariance matrix entries.
- cross_chan_stats[pair_index].calc_pearson(total_block_pixels,
- &pixel_stats.m_pixels_f[0][chanA],
- &pixel_stats.m_pixels_f[0][chanB],
- 4, 4,
- &pixel_stats.m_rgba_stats[chanA],
- &pixel_stats.m_rgba_stats[chanB]);
-
- chan_pair_correlations[pair_index] = fabsf(cross_chan_stats[pair_index].m_pearson);
-
- const float c = fabsf((float)cross_chan_stats[pair_index].m_pearson);
- min_corr = minimum(min_corr, c);
- max_corr = maximum(max_corr, c);
- }
- }
-
- // min_cor will be 1.0f if all channels inactive (solid)
-
- // Pixel the trial modes the encoder will use: RGB or RGBA (we don't currently support trying both)
-
- const bool used_alpha_encoder_modes = pixel_stats.m_has_alpha;
-
- float sobel_energy = 0.0f;
- for (uint32_t y = 0; y < block_height; y++)
- {
- for (uint32_t x = 0; x < block_width; x++)
- {
- const color_rgba& s = orig_img_sobel_xy.get_clamped(bx * block_width + x, by * block_height + y);
- sobel_energy += s[0] * s[0] + s[1] * s[1] + s[2] * s[2] + s[3] * s[3];
- } // x
- } // y
-
- sobel_energy /= (float)total_block_pixels;
-
+
// Configure low-level block encoder.
ldr_astc_lowlevel_block_encoder_params enc_blk_params;
- enc_blk_params.m_block_width = block_width;
- enc_blk_params.m_block_height = block_height;
- enc_blk_params.m_total_block_pixels = total_block_pixels;
- enc_blk_params.m_bx = bx;
- enc_blk_params.m_by = by;
-
- enc_blk_params.m_pOrig_img_sobel_xy_t = &orig_img_sobel_xy;
-
- enc_blk_params.m_num_trial_modes = encoder_trial_modes.size_u32();
- enc_blk_params.m_pTrial_modes = encoder_trial_modes.get_ptr();
- enc_blk_params.m_pGrouped_trial_modes = &grouped_encoder_trial_modes;
-
- enc_blk_params.m_pPart_data_p2 = pPart_data_p2;
- enc_blk_params.m_pPart_data_p3 = pPart_data_p3;
- enc_blk_params.m_pEnc_params = &enc_cfg.m_cem_enc_params;
-
- float ang_dot = saturate(pixel_stats.m_zero_rel_axis3.dot3(pixel_stats.m_mean_rel_axis3));
- const float pca_axis_angles = acosf(ang_dot) * (180.0f / (float)cPiD);
-
- enc_blk_params.m_use_alpha_or_opaque_modes = used_alpha_encoder_modes;
- enc_blk_params.m_use_lum_direct_modes = is_lum_only;
-
- const bool filter_by_pca_angles_flag = (superpass_index == 1) ? enc_cfg.m_filter_by_pca_angles_flag_p2 : enc_cfg.m_filter_by_pca_angles_flag;
- if (!filter_by_pca_angles_flag)
- {
- enc_blk_params.m_use_direct_modes = true;
- enc_blk_params.m_use_base_scale_modes = true;
- }
- else
- {
- // TODO: Make selective based off edge blocks?
- enc_blk_params.m_use_direct_modes = (!total_active_chans) || (pca_axis_angles > enc_cfg.m_use_direct_angle_thresh);
- enc_blk_params.m_use_base_scale_modes = (pca_axis_angles <= enc_cfg.m_use_base_scale_angle_thresh);
- }
-
- enc_blk_params.m_grid_hv_filtering = enc_cfg.m_grid_hv_filtering;
- enc_blk_params.m_filter_horizontally_flag = filter_horizontally_flag;
-
- enc_blk_params.m_use_small_grids_only = low_freq_block_flag && enc_cfg.m_low_freq_block_filtering;
-
- enc_blk_params.m_subsets_enabled = enc_cfg.m_subsets_enabled && (!low_freq_block_flag || !enc_cfg.m_subsets_edge_filtering);
-
- enc_blk_params.m_subsets_edge_filtering = enc_cfg.m_subsets_edge_filtering;
-
- enc_blk_params.m_use_blue_contraction = enc_cfg.m_use_blue_contraction;
- enc_blk_params.m_final_encode_try_base_ofs = enc_cfg.m_use_base_ofs;
-
- memcpy(enc_blk_params.m_superbucket_max_to_retain, enc_cfg.m_superbucket_max_to_retain, sizeof(enc_cfg.m_superbucket_max_to_retain));
-
- memcpy(enc_blk_params.m_final_shortlist_fraction, enc_cfg.m_final_shortlist_fraction, sizeof(enc_blk_params.m_final_shortlist_fraction));
- memcpy(enc_blk_params.m_final_shortlist_min_size, enc_cfg.m_final_shortlist_min_size, sizeof(enc_cfg.m_final_shortlist_min_size));
- memcpy(enc_blk_params.m_final_shortlist_max_size, enc_cfg.m_final_shortlist_max_size, sizeof(enc_blk_params.m_final_shortlist_max_size));
-
- enc_blk_params.m_part2_fraction_to_keep = enc_cfg.m_part2_fraction_to_keep;
- enc_blk_params.m_part3_fraction_to_keep = enc_cfg.m_part3_fraction_to_keep;
- enc_blk_params.m_base_parts2 = enc_cfg.m_base_parts2;
- enc_blk_params.m_base_parts3 = enc_cfg.m_base_parts3;
- enc_blk_params.m_gradient_descent_flag = enc_cfg.m_gradient_descent_flag;
- enc_blk_params.m_polish_weights_flag = enc_cfg.m_polish_weights_flag;
- enc_blk_params.m_qcd_enabled_flag = enc_cfg.m_qcd_enabled_flag;
- enc_blk_params.m_bucket_pruning_passes = enc_cfg.m_bucket_pruning_passes;
-
- enc_blk_params.m_alpha_cems = used_alpha_encoder_modes;
-
- enc_blk_params.m_early_stop_wpsnr = enc_cfg.m_early_stop_wpsnr;
- enc_blk_params.m_early_stop2_wpsnr = enc_cfg.m_early_stop2_wpsnr;
-
- enc_blk_params.m_final_encode_always_try_rgb_direct = enc_cfg.m_final_encode_always_try_rgb_direct;
-
- enc_blk_params.m_pDCT2F = &dct;
-
- // Determine DP usage
- if (enc_cfg.m_force_all_dual_plane_chan_evals)
- {
- for (uint32_t i = 0; i < 4; i++)
- enc_blk_params.m_dp_active_chans[i] = active_chan_flags[i];
- }
- else
- {
- for (uint32_t i = 0; i < 3; i++)
- enc_blk_params.m_dp_active_chans[i] = false;
-
- // Being very conservative with alpha here - always let the analytical evaluator consider it.
- enc_blk_params.m_dp_active_chans[3] = pixel_stats.m_has_alpha;
-
- if (!enc_cfg.m_disable_rgb_dual_plane)
- {
- const float rg_corr = chan_pair_correlations[0];
- const float rb_corr = chan_pair_correlations[1];
- const float gb_corr = chan_pair_correlations[2];
-
- int desired_dp_chan_rgb = -1;
-
- float min_p = minimum(rg_corr, rb_corr, gb_corr);
-
- if (min_p < enc_cfg.m_strong_dp_decorr_thresh_rgb)
- {
- const bool has_r = active_chan_flags[0], has_g = active_chan_flags[1];
- //const bool has_b = active_chan_flags[2];
-
- uint32_t total_active_chans_rgb = 0;
- for (uint32_t i = 0; i < 3; i++)
- total_active_chans_rgb += active_chan_flags[i];
-
- if (total_active_chans_rgb == 2)
- {
- if (!has_r)
- desired_dp_chan_rgb = 1;
- else if (!has_g)
- desired_dp_chan_rgb = 0;
- else
- desired_dp_chan_rgb = 0;
- }
- else if (total_active_chans_rgb == 3)
- {
- // see if rg/rb is weakly correlated vs. gb
- if ((rg_corr < gb_corr) && (rb_corr < gb_corr))
- desired_dp_chan_rgb = 0;
- // see if gr/gb is weakly correlated vs. rb
- else if ((rg_corr < rb_corr) && (gb_corr < rb_corr))
- desired_dp_chan_rgb = 1;
- // assume b is weakest
- else
- desired_dp_chan_rgb = 2;
- }
- }
-
- if (desired_dp_chan_rgb != -1)
- {
- assert(active_chan_flags[desired_dp_chan_rgb]);
- enc_blk_params.m_dp_active_chans[desired_dp_chan_rgb] = true;
- }
- }
- }
-
- if (!enc_blk_params.m_dp_active_chans[0] && !enc_blk_params.m_dp_active_chans[1] && !enc_blk_params.m_dp_active_chans[2] && !enc_blk_params.m_dp_active_chans[3])
- {
- enc_blk_params.m_use_dual_planes = false;
- }
-
- astc_ldr::cem_encode_params temp_cem_enc_params;
- if (superpass_index == 1)
- {
- enc_blk_params.m_base_parts2 = enc_cfg.m_base_parts2_p2;
- enc_blk_params.m_base_parts3 = enc_cfg.m_base_parts3_p2;
- enc_blk_params.m_part2_fraction_to_keep = 1;
- enc_blk_params.m_part3_fraction_to_keep = 1;
-
- memcpy(enc_blk_params.m_superbucket_max_to_retain, enc_cfg.m_superbucket_max_to_retain_p2, sizeof(enc_cfg.m_superbucket_max_to_retain_p2));
- memcpy(enc_blk_params.m_final_shortlist_max_size, enc_cfg.m_final_shortlist_max_size_p2, sizeof(enc_cfg.m_final_shortlist_max_size_p2));
-
- if (enc_cfg.m_second_pass_force_subsets_enabled)
- enc_blk_params.m_subsets_enabled = true;
- enc_blk_params.m_subsets_edge_filtering = false;
-
- if (enc_cfg.m_force_all_dp_chans_p2)
- {
- enc_blk_params.m_dp_active_chans[0] = active_chan_flags[0];
- enc_blk_params.m_dp_active_chans[1] = active_chan_flags[1];
- enc_blk_params.m_dp_active_chans[2] = active_chan_flags[2];
- enc_blk_params.m_dp_active_chans[3] = active_chan_flags[3];
- enc_blk_params.m_use_dual_planes = true;
-
- if (!enc_blk_params.m_dp_active_chans[0] && !enc_blk_params.m_dp_active_chans[1] && !enc_blk_params.m_dp_active_chans[2] && !enc_blk_params.m_dp_active_chans[3])
- {
- enc_blk_params.m_use_dual_planes = false;
- }
- }
-
- enc_blk_params.m_gradient_descent_flag = true;
- enc_blk_params.m_polish_weights_flag = true;
-
- enc_blk_params.m_use_direct_modes = true;
- enc_blk_params.m_use_base_scale_modes = true;
-
- enc_blk_params.m_early_stop_wpsnr = enc_cfg.m_early_stop_wpsnr + 2.0f;
- enc_blk_params.m_early_stop2_wpsnr = enc_cfg.m_early_stop2_wpsnr + 2.0f;
-
- if (enc_cfg.m_second_pass_total_weight_refine_passes)
- {
- temp_cem_enc_params = enc_cfg.m_cem_enc_params;
- enc_blk_params.m_pEnc_params = &temp_cem_enc_params;
-
- temp_cem_enc_params.m_total_weight_refine_passes = enc_cfg.m_second_pass_total_weight_refine_passes;
- temp_cem_enc_params.m_worst_weight_nudging_flag = true;
- temp_cem_enc_params.m_endpoint_refinement_flag = true;
- }
- }
+ encoder_config_manager cfg_man;
+ cfg_man.init(bx, by, block_width, block_height, total_block_pixels,
+ pixel_stats,
+ dct, enc_cfg, orig_img_sobel_xy, vis_dct_low_freq_block,
+ 0);
+
+ cfg_man.select(enc_blk_params, superpass_index,
+ bx, by, block_width, block_height, total_block_pixels,
+ orig_img_sobel_xy, encoder_trial_modes, grouped_encoder_trial_modes,
+ pPart_data_p2, pPart_data_p3,
+ pixel_stats, enc_cfg,
+ dct,
+ 0);
scoped_ldr_astc_lowlevel_block_encoder scoped_block_encoder(encoder_pool);
if (scoped_block_encoder.get_ptr() == nullptr)
@@ -6339,7 +8358,7 @@ bool ldr_astc_block_encode_image(
return;
}
- // solid color
+ // solid color - must be first
{
encode_block_output* pOut = out_blocks.enlarge(1);
pOut->clear();
@@ -6360,164 +8379,184 @@ bool ldr_astc_block_encode_image(
}
encode_block_stats enc_block_stats;
-
- bool enc_status = scoped_block_encoder.get_ptr()->full_encode(enc_blk_params, pixel_stats, out_blocks, 0, enc_block_stats);
- if (!enc_status)
+ bool enc_status;
+
{
- encoder_failed_flag.store(true);
- return;
+ enc_status = scoped_block_encoder.get_ptr()->full_encode(enc_blk_params, pixel_stats, out_blocks, 0, enc_block_stats);
+ if (!enc_status)
+ {
+ encoder_failed_flag.store(true);
+ return;
+ }
}
-#if 1
- // --------------------- BLOCK BLURRING
- // TODO - very slow, needs more configuration and tuning, experimental
- const float BLUR_STD_DEV_THRESH = (15.0f / 255.0f);
- const float BLUR_SOBEL_ENERGY_THRESH = 15000.0f;
-
- const bool use_blurs = (enc_cfg.m_blurring_enabled && (!selective_blurring || ((max_std_dev > BLUR_STD_DEV_THRESH) && (sobel_energy > BLUR_SOBEL_ENERGY_THRESH)))) ||
- (enc_cfg.m_blurring_enabled_p2 && (superpass_index == 1));
-
- if (use_blurs)
+#if 0
+ if (enc_cfg.m_debug_images)
{
- {
- assert(orig_img_blurred2.get_width());
-
- color_rgba block_pixels_blurred2[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
- orig_img_blurred2.extract_block_clamped(block_pixels_blurred2, bx * block_width, by * block_height, block_width, block_height);
-
- astc_ldr::pixel_stats_t pixel_stats_blurred2;
- pixel_stats_blurred2.init(total_block_pixels, block_pixels_blurred2);
-
- enc_status = scoped_block_encoder.get_ptr()->full_encode(enc_blk_params, pixel_stats_blurred2, out_blocks, 1, enc_block_stats);
- if (!enc_status)
- {
- encoder_failed_flag.store(true);
- return;
- }
- }
-
- {
- assert(orig_img_blurred3.get_width());
-
- color_rgba block_pixels_blurred3[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
- orig_img_blurred3.extract_block_clamped(block_pixels_blurred3, bx * block_width, by * block_height, block_width, block_height);
-
- astc_ldr::pixel_stats_t pixel_stats_blurred3;
- pixel_stats_blurred3.init(total_block_pixels, block_pixels_blurred3);
-
- enc_status = scoped_block_encoder.get_ptr()->full_encode(enc_blk_params, pixel_stats_blurred3, out_blocks, 2, enc_block_stats);
- if (!enc_status)
- {
- encoder_failed_flag.store(true);
- return;
- }
- }
-
- {
- assert(orig_img_blurred4.get_width());
-
- color_rgba block_pixels_blurred4[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
- orig_img_blurred4.extract_block_clamped(block_pixels_blurred4, bx * block_width, by * block_height, block_width, block_height);
-
- astc_ldr::pixel_stats_t pixel_stats_blurred4;
- pixel_stats_blurred4.init(total_block_pixels, block_pixels_blurred4);
-
- enc_status = scoped_block_encoder.get_ptr()->full_encode(enc_blk_params, pixel_stats_blurred4, out_blocks, 3, enc_block_stats);
- if (!enc_status)
- {
- encoder_failed_flag.store(true);
- return;
- }
- }
-
- {
- assert(orig_img_blurred5.get_width());
-
- color_rgba block_pixels_blurred5[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
- orig_img_blurred5.extract_block_clamped(block_pixels_blurred5, bx * block_width, by * block_height, block_width, block_height);
-
- astc_ldr::pixel_stats_t pixel_stats_blurred5;
- pixel_stats_blurred5.init(total_block_pixels, block_pixels_blurred5);
-
- enc_status = scoped_block_encoder.get_ptr()->full_encode(enc_blk_params, pixel_stats_blurred5, out_blocks, 4, enc_block_stats);
- if (!enc_status)
- {
- encoder_failed_flag.store(true);
- return;
- }
- }
+ const bool vis_flag = (scoped_block_encoder.get_ptr()->m_block_complexity_index == 0) && (max_std_dev < (6.0f / 255.0f));
+ vis_dct_low_freq_block.fill_box(bx * block_width, by * block_height, block_width, block_height, vis_flag ? color_rgba(255, 0, 255, 255) : g_black_color);
}
#endif
+ // --------------------- BLOCK BLURRING
+ //const float BLUR_STD_DEV_THRESH = (15.0f / 255.0f);
+ //const float BLUR_SOBEL_ENERGY_THRESH = 15000.0f;
+
+ //const float BLUR_STD_DEV_THRESH = (5.0f / 255.0f);
+ //const float BLUR_SOBEL_ENERGY_THRESH = 5000.0f;
+
+ const float BLUR_STD_DEV_THRESH = (10.0f / 255.0f);
+ const float BLUR_SOBEL_ENERGY_THRESH = 10000.0f;
+ const bool use_blurs = (enc_cfg.m_blurring_enabled_p1 && (!selective_blurring || ((cfg_man.m_max_std_dev > BLUR_STD_DEV_THRESH) && (cfg_man.m_sobel_energy > BLUR_SOBEL_ENERGY_THRESH)))) ||
+ (enc_cfg.m_blurring_enabled_p2 && (superpass_index == 1));
+ if (use_blurs)
+ {
+ for (uint32_t i = 0; i < TOTAL_BLURRED_IMAGES; i++)
+ {
+ const uint32_t blur_id = BLUR_ID_GAUSSIAN_ALTERNATE + i;
+ const image& blurred_img = orig_img_blurred[i];
+ assert(blurred_img.get_width());
+
+ color_rgba block_pixels_blurred[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+ blurred_img.extract_block_clamped(block_pixels_blurred, bx * block_width, by * block_height, block_width, block_height);
+
+ astc_ldr::pixel_stats_t pixel_stats_blurred;
+ pixel_stats_blurred.init(total_block_pixels, block_pixels_blurred);
+
+ cfg_man.init(bx, by, block_width, block_height, total_block_pixels,
+ pixel_stats_blurred,
+ dct, enc_cfg, orig_img_sobel_xy, vis_dct_low_freq_block, blur_id);
+
+ ldr_astc_lowlevel_block_encoder_params enc_blk_params_blurred;
+
+ cfg_man.select(enc_blk_params_blurred,
+ superpass_index,
+ bx, by, block_width, block_height, total_block_pixels,
+ orig_img_sobel_xy, encoder_trial_modes, grouped_encoder_trial_modes,
+ pPart_data_p2, pPart_data_p3,
+ pixel_stats_blurred, enc_cfg,
+ dct,
+ blur_id);
+
+ enc_status = scoped_block_encoder.get_ptr()->full_encode(enc_blk_params_blurred, pixel_stats_blurred, out_blocks, blur_id, enc_block_stats);
+ if (!enc_status)
+ {
+ encoder_failed_flag.store(true);
+ return;
+ }
+ }
+ }
+
+ // Grid dimension prefiltering (experimental)
+ const bool BLOCK_PREFILTER_BY_GRID_DIM = false;
+ if (BLOCK_PREFILTER_BY_GRID_DIM && (enc_cfg.m_blurring_enabled_p1 || enc_cfg.m_blurring_enabled_p2))
+ {
+ uint_vec grid_dims;
+
+ for (uint32_t out_block_iter = 0; out_block_iter < out_blocks.size_u32(); out_block_iter++)
+ {
+ if (out_blocks[out_block_iter].m_trial_mode_index < 0)
+ continue;
+
+ astc_helpers::log_astc_block& log_astc_blk = out_blocks[out_block_iter].m_log_blk;
+ if (log_astc_blk.m_solid_color_flag_ldr)
+ continue;
+
+ if ((log_astc_blk.m_grid_width == block_width) && (log_astc_blk.m_grid_height == block_height))
+ continue;
+
+ grid_dims.push_back(
+ ((uint32_t)log_astc_blk.m_grid_width << 8) | ((uint32_t)log_astc_blk.m_grid_height));
+ }
+
+ if (grid_dims.size())
+ {
+ grid_dims.unique();
+ const uint32_t total_unique = grid_dims.size_u32();
+
+ for (uint32_t b = 0; b < total_unique; b++)
+ {
+ const uint32_t grid_width = grid_dims[b] >> 8;
+ const uint32_t grid_height = grid_dims[b] & 0xFF;
+
+ color_rgba upsampled_block[astc_helpers::MAX_BLOCK_PIXELS]; // num_block_samples
+
+ filter_block(block_width, block_height, grid_width, grid_height, pixel_stats, upsampled_block);
+
+ const uint32_t blur_id = BLUR_ID_GRID_DIM_BASE + b;
+
+ astc_ldr::pixel_stats_t pixel_stats_blurred;
+ pixel_stats_blurred.init(total_block_pixels, upsampled_block);
+
+ cfg_man.init(bx, by, block_width, block_height, total_block_pixels,
+ pixel_stats_blurred,
+ dct, enc_cfg, orig_img_sobel_xy, vis_dct_low_freq_block, blur_id);
+
+ ldr_astc_lowlevel_block_encoder_params enc_blk_params_blurred;
+
+ cfg_man.select(enc_blk_params_blurred,
+ superpass_index,
+ bx, by, block_width, block_height, total_block_pixels,
+ orig_img_sobel_xy, encoder_trial_modes, grouped_encoder_trial_modes,
+ pPart_data_p2, pPart_data_p3,
+ pixel_stats_blurred, enc_cfg,
+ dct,
+ blur_id);
+
+ enc_status = scoped_block_encoder.get_ptr()->full_encode(enc_blk_params_blurred, pixel_stats_blurred, out_blocks, blur_id, enc_block_stats);
+ if (!enc_status)
+ {
+ encoder_failed_flag.store(true);
+ return;
+ }
+
+ } // b
+ }
+ }
+
+ // --------------------- EXPERIMENTAL - DC/LINEAR LATENT CODING
+ if ((enc_cfg.m_try_simplified_latent_configs) &&
+ (!pixel_stats.m_has_alpha) &&
+ (cfg_man.m_max_std_dev > (2.0f / 255.0f)))
+ {
+ uint64_vec best_2subset_seed_ids, best_3subset_seed_ids;
+
+ encode_block_to_dc_latent(
+ block_width, block_height,
+ pixel_stats,
+ out_blocks,
+ enc_cfg,
+ pPart_data_p2, pPart_data_p3, encoder_trial_modes,
+ best_2subset_seed_ids, best_3subset_seed_ids);
+
+ encode_block_to_linear_latent(8,
+ block_width, block_height,
+ pixel_stats,
+ out_blocks,
+ enc_cfg,
+ pPart_data_p2, pPart_data_p3, encoder_trial_modes,
+ best_2subset_seed_ids, best_3subset_seed_ids);
+ }
+
// --------------------- WEIGHT GRID DCT CODING
if (enc_cfg.m_use_dct)
{
// apply DCT to weights
for (uint32_t out_block_iter = 0; out_block_iter < out_blocks.size_u32(); out_block_iter++)
{
- if (out_blocks[out_block_iter].m_trial_mode_index < 0)
- continue;
-
- astc_helpers::log_astc_block& log_astc_blk = out_blocks[out_block_iter].m_log_blk;
-
- const basist::astc_ldr_t::astc_block_grid_data* pGrid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, log_astc_blk.m_grid_width, log_astc_blk.m_grid_height);
-
- const uint32_t num_planes = (log_astc_blk.m_dual_plane ? 2 : 1);
- for (uint32_t plane_index = 0; plane_index < num_planes; plane_index++)
+ if (!apply_weight_grid_dct(block_width, block_height,
+ grid_coder,
+ out_blocks[out_block_iter],
+ pixel_stats, true,
+ enc_cfg, pPart_data_p2, pPart_data_p3, true))
{
- bitwise_coder c;
- basist::astc_ldr_t::dct_syms syms;
- code_block_weights(grid_coder, enc_cfg.m_base_q, plane_index, log_astc_blk, pGrid_data, c, syms);
-
- out_blocks[out_block_iter].m_packed_dct_plane_data[plane_index] = syms;
-
- c.flush();
-
- basist::bitwise_decoder d;
- d.init(c.get_bytes().data(), c.get_bytes().size_u32());
-
- // ensure existing weights get blown away
- for (uint32_t i = 0; i < (uint32_t)(log_astc_blk.m_grid_width * log_astc_blk.m_grid_height); i++)
- log_astc_blk.m_weights[i * num_planes + plane_index] = 0;
-
- basist::astc_ldr_t::fvec dct_temp;
- bool status = grid_coder.decode_block_weights(enc_cfg.m_base_q, plane_index, log_astc_blk, &d, pGrid_data, nullptr, dct_temp, nullptr);
-
- assert(status);
- if (!status)
- {
- error_printf("grid_coder.decode_block_weights() failed!\n");
-
- encoder_failed_flag.store(true);
- return;
- }
-
-#if 0
- {
- astc_helpers::log_astc_block alt_log_astc_blk(log_astc_blk);
-
- for (uint32_t i = 0; i < (uint32_t)(log_astc_blk.m_grid_width * log_astc_blk.m_grid_height); i++)
- alt_log_astc_blk.m_weights[i * num_planes + plane_index] = 0;
-
- status = grid_coder.decode_block_weights(q, plane_index, alt_log_astc_blk, nullptr, pGrid_data, &out_block_dct_stats[out_block_iter], &syms);
- assert(status);
-
- for (uint32_t i = 0; i < (uint32_t)(log_astc_blk.m_grid_width * log_astc_blk.m_grid_height); i++)
- {
- assert(log_astc_blk.m_weights[i * num_planes + plane_index] == alt_log_astc_blk.m_weights[i * num_planes + plane_index]);
- }
-
- }
-#endif
- // TODO: in theory, endpoints can be refined if they don't change the DCT span.
+ encoder_failed_flag.store(true);
+ return;
}
-
- out_blocks[out_block_iter].m_sse = eval_error(block_width, block_height, log_astc_blk, pixel_stats, enc_cfg.m_cem_enc_params);
-
} // for
} // use_dct
-
+
// Find best output block
uint64_t best_out_blocks_err = UINT64_MAX;
uint32_t best_out_blocks_index = 0;
@@ -6527,8 +8566,8 @@ bool ldr_astc_block_encode_image(
{
const astc_helpers::log_astc_block& log_astc_blk = out_blocks[out_block_iter].m_log_blk;
- color_rgba dec_pixels[astc_helpers::MAX_BLOCK_DIM * astc_helpers::MAX_BLOCK_DIM];
- bool dec_status = astc_helpers::decode_block(log_astc_blk, dec_pixels, block_width, block_height, enc_cfg.m_cem_enc_params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8);
+ color_rgba dec_pixels[astc_helpers::MAX_BLOCK_PIXELS];
+ const bool dec_status = astc_helpers::decode_block_xuastc_ldr(log_astc_blk, dec_pixels, block_width, block_height, enc_cfg.m_cem_enc_params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8);
assert(dec_status);
if (!dec_status)
@@ -6564,6 +8603,99 @@ bool ldr_astc_block_encode_image(
}
} // out_block_iter
+ // try blurring best candidate found so far (very experimental)
+ const bool BLOCK_POSTFILTERING = false;
+ if (BLOCK_POSTFILTERING &&
+ ( ((enc_cfg.m_blurring_enabled_p1) || (enc_cfg.m_blurring_enabled_p2)) && (cfg_man.m_max_std_dev > (5.0f / 255.0f)) )
+ )
+ {
+ encode_block_output &best_blk = out_blocks[best_out_blocks_index];
+
+ const astc_helpers::log_astc_block& best_log_blk = best_blk.m_log_blk;
+
+ if ((!best_log_blk.m_solid_color_flag_ldr) &&
+ ((best_log_blk.m_grid_width < block_width) || (best_log_blk.m_grid_height < block_height)))
+ {
+ color_rgba upsampled_block[astc_helpers::MAX_BLOCK_PIXELS]; // num_block_samples
+
+ filter_block(block_width, block_height, best_log_blk.m_grid_width, best_log_blk.m_grid_height, pixel_stats, upsampled_block);
+
+ const uint32_t blur_id = BLUR_ID_BEST_CANDIDATE;
+
+ astc_ldr::pixel_stats_t pixel_stats_blurred;
+ pixel_stats_blurred.init(total_block_pixels, upsampled_block);
+
+ cfg_man.init(bx, by, block_width, block_height, total_block_pixels,
+ pixel_stats_blurred,
+ dct, enc_cfg, orig_img_sobel_xy, vis_dct_low_freq_block, blur_id);
+
+ ldr_astc_lowlevel_block_encoder_params enc_blk_params_blurred;
+
+ cfg_man.select(enc_blk_params_blurred,
+ superpass_index,
+ bx, by, block_width, block_height, total_block_pixels,
+ orig_img_sobel_xy, encoder_trial_modes, grouped_encoder_trial_modes,
+ pPart_data_p2, pPart_data_p3,
+ pixel_stats_blurred, enc_cfg,
+ dct,
+ blur_id);
+
+ const uint32_t cur_num_out_blocks = out_blocks.size_u32();
+
+ enc_status = scoped_block_encoder.get_ptr()->full_encode(enc_blk_params_blurred, pixel_stats_blurred, out_blocks, blur_id, enc_block_stats);
+ if (!enc_status)
+ {
+ encoder_failed_flag.store(true);
+ return;
+ }
+
+ for (uint32_t out_block_iter = cur_num_out_blocks; out_block_iter < out_blocks.size_u32(); out_block_iter++)
+ {
+ if (!apply_weight_grid_dct(block_width, block_height,
+ grid_coder,
+ out_blocks[out_block_iter],
+ pixel_stats, true,
+ enc_cfg, pPart_data_p2, pPart_data_p3, true))
+ {
+ encoder_failed_flag.store(true);
+ return;
+ }
+ }
+
+ for (uint32_t out_block_iter = cur_num_out_blocks; out_block_iter < out_blocks.size_u32(); out_block_iter++)
+ {
+ const astc_helpers::log_astc_block& log_astc_blk = out_blocks[out_block_iter].m_log_blk;
+
+ color_rgba dec_pixels[astc_helpers::MAX_BLOCK_PIXELS];
+ bool dec_status = astc_helpers::decode_block(log_astc_blk, dec_pixels, block_width, block_height, enc_cfg.m_cem_enc_params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8);
+
+ assert(dec_status);
+ if (!dec_status)
+ {
+ encoder_failed_flag.store(true);
+ return;
+ }
+
+ uint64_t total_err = 0;
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ total_err += weighted_color_error(block_pixels[i], dec_pixels[i], enc_cfg.m_cem_enc_params);
+
+ assert(out_blocks[out_block_iter].m_blur_id != 0);
+
+ // Replace m_sse with the actual WSSE vs. the original source block (in case it was blurred)
+ out_blocks[out_block_iter].m_sse = total_err;
+
+ if (total_err < best_out_blocks_err)
+ {
+ best_out_blocks_err = total_err;
+ best_out_blocks_log_astc_blk = log_astc_blk;
+ best_out_blocks_index = out_block_iter;
+ }
+ } // out_block_iter
+
+ }
+ }
+
#if 0
// TODO: Save memory, only minimally tested
if (enc_cfg.m_save_single_result)
@@ -6576,15 +8708,26 @@ bool ldr_astc_block_encode_image(
best_out_blocks_index = 0;
}
#endif
-
+ // TODO: limit max candidates to save memory here
+
ldr_astc_block_encode_image_output::block_info& block_info_out = enc_out.m_image_block_info(bx, by);
- block_info_out.m_low_freq_block_flag = low_freq_block_flag;
+ block_info_out.m_low_freq_block_flag = cfg_man.m_low_freq_block_flag;
block_info_out.m_super_strong_edges = scoped_block_encoder.get_ptr()->m_super_strong_edges;
block_info_out.m_very_strong_edges = scoped_block_encoder.get_ptr()->m_very_strong_edges;
block_info_out.m_strong_edges = scoped_block_encoder.get_ptr()->m_strong_edges;
+
block_info_out.m_packed_out_block_index = best_out_blocks_index;
+ const uint64_t check_wsse = out_blocks[best_out_blocks_index].m_sse;
+
+ enforce_max_candidate_limit(block_info_out, max_candidate_limit);
+
+ best_out_blocks_index = block_info_out.m_packed_out_block_index; // may have changed
+
+ BASISU_NOTE_UNUSED(check_wsse);
+ assert(check_wsse == out_blocks[best_out_blocks_index].m_sse);
+
// Create packed ASTC block
astc_helpers::astc_block& best_phys_block = packed_blocks(bx, by);
bool pack_success = astc_helpers::pack_astc_block(best_phys_block, best_out_blocks_log_astc_blk);
@@ -6595,7 +8738,7 @@ bool ldr_astc_block_encode_image(
}
output_block_devel_desc& out_devel_desc = output_block_devel_info(bx, by);
- out_devel_desc.m_low_freq_block_flag = low_freq_block_flag;
+ out_devel_desc.m_low_freq_block_flag = cfg_man.m_low_freq_block_flag;
out_devel_desc.m_super_strong_edges = scoped_block_encoder.get_ptr()->m_super_strong_edges;
out_devel_desc.m_very_strong_edges = scoped_block_encoder.get_ptr()->m_very_strong_edges;
out_devel_desc.m_strong_edges = scoped_block_encoder.get_ptr()->m_strong_edges;
@@ -6603,12 +8746,31 @@ bool ldr_astc_block_encode_image(
// Critical Section
{
std::lock_guard g(global_mutex);
+
+ if (superpass_index == 0)
+ {
+ if (use_blurs)
+ total_blur_encodes_p1++;
- if (use_blurs)
- total_blur_encodes++;
+ if (out_blocks[best_out_blocks_index].m_blur_id >= 1)
+ {
+ const int blur_id = out_blocks[best_out_blocks_index].m_blur_id - 1;
+ if (blur_id < (int)std::size(total_blurred_blocks_p1))
+ total_blurred_blocks_p1[blur_id]++;
+ }
+ }
+ else if (superpass_index == 1)
+ {
+ if (use_blurs)
+ total_blur_encodes_p2++;
- if (out_blocks[best_out_blocks_index].m_blur_id)
- total_blurred_blocks1++;
+ if (out_blocks[best_out_blocks_index].m_blur_id >= 1)
+ {
+ const int blur_id = out_blocks[best_out_blocks_index].m_blur_id - 1;
+ if (blur_id < (int)std::size(total_blurred_blocks_p2))
+ total_blurred_blocks_p2[blur_id]++;
+ }
+ }
if (superpass_index == 0)
{
@@ -6822,6 +8984,8 @@ bool ldr_astc_block_encode_image(
} // j
+ enforce_max_candidate_limit(out_block_info, max_candidate_limit);
+
} // bx
} // by
@@ -6845,6 +9009,13 @@ bool ldr_astc_block_encode_image(
{
const ldr_astc_block_encode_image_output::block_info& out_block_info = enc_out.m_image_block_info(bx, by);
+ if (out_block_info.m_packed_out_block_index >= out_block_info.m_out_blocks.size())
+ {
+ fmt_error_printf("consistency check failed\n");
+ assert(0);
+ return false;
+ }
+
#if BU_CHECK_NEIGHBOR_BEST
uint64_t best_sse = UINT64_MAX;
uint32_t best_out_block_index = 0;
@@ -6901,11 +9072,13 @@ bool ldr_astc_block_encode_image(
return false;
}
- const basist::astc_ldr_t::astc_block_grid_data* pGrid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, best_log_blk.m_grid_width, best_log_blk.m_grid_height);
+ //const basist::astc_ldr_t::astc_block_grid_data* pGrid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, best_log_blk.m_grid_width, best_log_blk.m_grid_height);
const uint32_t total_planes = best_log_blk.m_num_partitions ? (best_log_blk.m_dual_plane ? 2 : 1) : 0;
astc_helpers::log_astc_block verify_log_blk(best_log_blk);
+ basist::astc_ldr_t::fvec dct_temp;
+
for (uint32_t plane_index = 0; plane_index < total_planes; plane_index++)
{
if (!out_block_info.m_out_blocks[out_block_info.m_packed_out_block_index].m_packed_dct_plane_data[plane_index].m_coeffs.size())
@@ -6914,9 +9087,8 @@ bool ldr_astc_block_encode_image(
assert(0);
return false;
}
-
- basist::astc_ldr_t::fvec dct_temp;
- bool dec_status = grid_coder.decode_block_weights(enc_cfg.m_base_q, plane_index, verify_log_blk, nullptr, pGrid_data, nullptr, dct_temp,
+
+ bool dec_status = grid_coder.decode_block_weights(enc_cfg.m_base_q, plane_index, verify_log_blk, nullptr, nullptr, dct_temp,
&out_block_info.m_out_blocks[out_block_info.m_packed_out_block_index].m_packed_dct_plane_data[plane_index]);
if (!dec_status)
@@ -7174,6 +9346,8 @@ bool ldr_astc_block_encode_image(
if (enc_cfg.m_debug_output)
{
+ display_candidate_statistics(enc_out);
+
uint32_t cem_used_hist[16] = { 0 };
uint32_t cem_used_bc[16] = { 0 };
uint32_t cem_used_subsets[16] = { 0 };
@@ -7195,10 +9369,15 @@ bool ldr_astc_block_encode_image(
uint32_t total_used_bc = 0;
+ uint32_t total_candidates = 0;
+
for (uint32_t by = 0; by < num_blocks_y; by++)
{
for (uint32_t bx = 0; bx < num_blocks_x; bx++)
{
+ const ldr_astc_block_encode_image_output::block_info& out_block_info = enc_out.m_image_block_info(bx, by);
+ total_candidates += out_block_info.m_out_blocks.size_u32();
+
const output_block_devel_desc& desc = output_block_devel_info(bx, by);
const astc_helpers::astc_block* pPhys_block = &packed_blocks(bx, by);
@@ -7289,6 +9468,8 @@ bool ldr_astc_block_encode_image(
} // bx
} // by
+ fmt_debug_printf("Avg candidates per block: {}\n", (float)total_candidates / (float)total_blocks);
+
uint32_t total_used_modes = 0;
fmt_debug_printf("--------------------- Trial Modes:\n");
@@ -7347,15 +9528,37 @@ bool ldr_astc_block_encode_image(
fmt_debug_printf("\nEncoder stats:\n");
fmt_debug_printf("Total utilized encoder trial modes: {} {3.2}%\n", total_used_modes, (float)total_used_modes * 100.0f / (float)encoder_trial_modes.size());
- const uint32_t total_blurred_blocks = total_blurred_blocks1 + total_blurred_blocks2 + total_blurred_blocks3 + total_blurred_blocks4;
+ uint32_t overall_blurred_blocks_p1 = 0, overall_blurred_blocks_p2 = 0;
+ for (uint32_t i = 0; i < std::size(total_blurred_blocks_p1); i++)
+ {
+ overall_blurred_blocks_p1 += total_blurred_blocks_p1[i];
+ overall_blurred_blocks_p2 += total_blurred_blocks_p2[i];
+ }
- fmt_debug_printf("\nTotal blur encodes: {} ({3.2}%)\n", total_blur_encodes, (float)total_blur_encodes * 100.0f / (float)total_blocks);
- fmt_debug_printf("Total blurred blocks: {} ({3.2}%)\n", total_blurred_blocks, (float)total_blurred_blocks * 100.0f / (float)total_blocks);
- fmt_debug_printf("Total blurred1 blocks: {} ({3.2}%)\n", total_blurred_blocks1, (float)total_blurred_blocks1 * 100.0f / (float)total_blocks);
- fmt_debug_printf("Total blurred2 blocks: {} ({3.2}%)\n", total_blurred_blocks2, (float)total_blurred_blocks2 * 100.0f / (float)total_blocks);
- fmt_debug_printf("Total blurred3 blocks: {} ({3.2}%)\n", total_blurred_blocks3, (float)total_blurred_blocks3 * 100.0f / (float)total_blocks);
- fmt_debug_printf("Total blurred4 blocks: {} ({3.2}%)\n", total_blurred_blocks4, (float)total_blurred_blocks4 * 100.0f / (float)total_blocks);
+ fmt_debug_printf("\nTotal blur encodes p1: {} ({3.2}%)\n", total_blur_encodes_p1, (float)total_blur_encodes_p1 * 100.0f / (float)total_blocks);
+ fmt_debug_printf("Total blurred blocks p1: {} ({3.2}%)\n", overall_blurred_blocks_p1, (float)overall_blurred_blocks_p1 * 100.0f / (float)total_blocks);
+
+ if (overall_blurred_blocks_p1)
+ {
+ for (uint32_t i = 0; i < std::size(total_blurred_blocks_p1); i++)
+ {
+ if (total_blurred_blocks_p1[i])
+ fmt_debug_printf("Total blurred{} blocks p1: {} ({3.2}%)\n", i, total_blurred_blocks_p1[i], (float)total_blurred_blocks_p1[i] * 100.0f / (float)total_blocks);
+ }
+ }
+ fmt_debug_printf("\nTotal blur encodes p2: {} ({3.2}%)\n", total_blur_encodes_p2, (float)total_blur_encodes_p2 * 100.0f / (float)total_blocks);
+ fmt_debug_printf("Total blurred blocks p2: {} ({3.2}%)\n", overall_blurred_blocks_p2, (float)overall_blurred_blocks_p2 * 100.0f / (float)total_blocks);
+
+ if (overall_blurred_blocks_p2)
+ {
+ for (uint32_t i = 0; i < std::size(total_blurred_blocks_p2); i++)
+ {
+ if (total_blurred_blocks_p2[i])
+ fmt_debug_printf("Total blurred{} blocks p2: {} ({3.2}%)\n", i, total_blurred_blocks_p2[i], (float)total_blurred_blocks_p2[i] * 100.0f / (float)total_blocks);
+ }
+ }
+
fmt_debug_printf("\nTotal superbuckets created: {} ({4.1} per block)\n", total_superbuckets_created, (float)total_superbuckets_created / (float)total_blocks);
fmt_debug_printf("Total shortlist buckets created: {} ({4.1} per block)\n", total_buckets_created, (float)total_buckets_created / (float)total_blocks);
fmt_debug_printf("Total surrogate encodes: {} ({4.1} per block)\n", total_surrogate_encodes, (float)total_surrogate_encodes / (float)total_blocks);
@@ -7403,8 +9606,11 @@ bool ldr_astc_block_encode_image(
}
debug_printf("\n");
- fmt_debug_printf("orig vs. ASTC compressed:\n");
- print_image_metrics(orig_img, unpacked_img);
+ if (enc_cfg.m_debug_output_image_metrics)
+ {
+ fmt_debug_printf("orig vs. ASTC compressed:\n");
+ print_image_metrics(orig_img, unpacked_img);
+ }
fmt_debug_printf("Total encode time: {.3} secs, {.3} ms per block, {.1} blocks/sec\n", total_enc_time, total_enc_time * 1000.0f / total_blocks, total_blocks / total_enc_time);
@@ -7414,7 +9620,1365 @@ bool ldr_astc_block_encode_image(
return true;
}
-//const uint32_t rice_zero_run_m = 3, rice_dct_coeff_m = 2;
+bool ldr_astc_block_encode_image_fast_4x4(
+ const image& orig_img,
+ const ldr_astc_block_encode_image_high_level_config& enc_cfg,
+ const astc_ldr_encode_config& global_cfg,
+ ldr_astc_block_encode_image_output& enc_out,
+ uint32_t max_candidate_limit)
+{
+ BASISU_NOTE_UNUSED(max_candidate_limit);
+
+ if (enc_cfg.m_debug_output)
+ fmt_debug_printf("ldr_astc_block_encode_image_fast_4x4:\n");
+
+ const uint32_t block_width = enc_cfg.m_block_width, block_height = enc_cfg.m_block_height;
+ if ((block_width != 4) || (block_height != 4))
+ {
+ assert(0);
+ return false;
+ }
+
+ //const uint32_t width = orig_img.get_width(), height = orig_img.get_height();
+ //const uint32_t total_pixels = width * height;
+ const uint32_t total_block_pixels = enc_cfg.m_block_width * enc_cfg.m_block_height;
+ const uint32_t num_blocks_x = orig_img.get_block_width(enc_cfg.m_block_width);
+ const uint32_t num_blocks_y = orig_img.get_block_height(enc_cfg.m_block_height);
+ const uint32_t total_blocks = num_blocks_x * num_blocks_y;
+
+ if (enc_cfg.m_debug_output)
+ {
+ fmt_debug_printf("\nASTC base bitrate: {3.3} bpp\n", 128.0f / (float)(enc_cfg.m_block_width * enc_cfg.m_block_height));
+
+ fmt_debug_printf("ASTC block size: {}x{}\n", enc_cfg.m_block_width, enc_cfg.m_block_height);
+
+ fmt_debug_printf("Image has alpha: {}\n", orig_img.has_alpha());
+
+ fmt_debug_printf("max_candidate_limit: {}\n", max_candidate_limit);;
+ }
+
+ if ((enc_cfg.m_cem_enc_params.m_comp_weights[0] != 1) || (enc_cfg.m_cem_enc_params.m_comp_weights[1] != 1) ||
+ (enc_cfg.m_cem_enc_params.m_comp_weights[2] != 1) || (enc_cfg.m_cem_enc_params.m_comp_weights[3] != 1))
+ {
+ printf("WARNING: Fast (effort 0) ASTC/XUASTC LDR 4x4 compressor doesn't support non-default channel weights\n");
+ }
+
+ // We don't use this here, but the supercompressors use these tables.
+ // TODO: The transcoder already creates all this stuff for each block size.
+ astc_ldr::partitions_data* pPart_data_p2 = &enc_out.m_part_data_p2;
+ pPart_data_p2->init(2, enc_cfg.m_block_width, enc_cfg.m_block_height, BASISU_USE_LSH2 == 0, BASISU_USE_LSH2 != 0);
+
+ astc_ldr::partitions_data* pPart_data_p3 = &enc_out.m_part_data_p3;
+ pPart_data_p3->init(3, enc_cfg.m_block_width, enc_cfg.m_block_height, BASISU_USE_LSH3 == 0, BASISU_USE_LSH3 != 0);
+
+ basisu::vector& encoder_trial_modes = enc_out.m_encoder_trial_modes;
+ encoder_trial_modes.reserve(4096);
+
+ basist::astc_ldr_t::grouped_trial_modes& grouped_encoder_trial_modes = enc_out.m_grouped_encoder_trial_modes;
+ basist::astc_ldr_t::create_encoder_trial_modes_table(block_width, block_height, encoder_trial_modes, grouped_encoder_trial_modes, enc_cfg.m_debug_output, false);
+
+ uint32_t bc7f_override_flags = basist::bc7f::cPackBC7FlagPBitOpt | basist::bc7f::cPackBC7FlagPBitOptMode6 | basist::bc7f::cPackBC7FlagUseTrivialMode6 |
+ basist::bc7f::cPackBC7FlagUse2SubsetsRGB | basist::bc7f::cPackBC7FlagASTCCompatible |
+ basist::bc7f::cPackBC7FlagUseDualPlaneRGB | basist::bc7f::cPackBC7FlagUseDualPlaneRGBA |
+ basist::bc7f::cPackBC7FlagUse3SubsetsRGB | basist::bc7f::cPackBC7FlagUse2SubsetsRGBA;
+
+ if (global_cfg.m_force_disable_subsets)
+ {
+ bc7f_override_flags &= ~(basist::bc7f::cPackBC7FlagUse2SubsetsRGB | basist::bc7f::cPackBC7FlagUse3SubsetsRGB | basist::bc7f::cPackBC7FlagUse2SubsetsRGBA);
+ }
+
+ if (global_cfg.m_force_disable_rgb_dual_plane)
+ {
+ bc7f_override_flags &= ~basist::bc7f::cPackBC7FlagUseDualPlaneRGB;
+ bc7f_override_flags |= basist::bc7f::cPackBC7FlagDisableRGBDualPlane;
+ }
+
+ enc_out.m_image_block_info.resize(0, 0);
+ enc_out.m_image_block_info.resize(num_blocks_x, num_blocks_y);
+
+ enc_out.m_packed_phys_blocks.resize(num_blocks_x, num_blocks_y);
+
+ basist::astc_ldr_t::grid_weight_dct grid_coder;
+ if (enc_cfg.m_use_dct)
+ grid_coder.init(block_width, block_height);
+
+ assert(enc_cfg.m_pJob_pool);
+ job_pool& job_pool = *enc_cfg.m_pJob_pool;
+
+ const uint32_t num_threads = (uint32_t)job_pool.get_total_threads();
+ assert(num_threads);
+
+ std::atomic cur_row;
+ cur_row.store(0);
+
+ std::atomic encoder_failed_flag;
+ encoder_failed_flag.store(false);
+
+ for (uint32_t job_index = 0; job_index < num_threads; job_index++)
+ {
+ job_pool.add_job([job_index, num_threads, num_blocks_x, num_blocks_y, block_width, block_height, total_blocks, total_block_pixels, bc7f_override_flags,
+ &cur_row, &encoder_failed_flag,
+ &orig_img, &enc_cfg, &encoder_trial_modes, &grid_coder, &grouped_encoder_trial_modes, &enc_out]
+ {
+ BASISU_NOTE_UNUSED(job_index); BASISU_NOTE_UNUSED(num_threads); BASISU_NOTE_UNUSED(total_blocks); BASISU_NOTE_UNUSED(total_block_pixels); BASISU_NOTE_UNUSED(encoder_trial_modes); BASISU_NOTE_UNUSED(grouped_encoder_trial_modes);
+ basist::astc_ldr_t::fvec dct_temp;
+
+ for ( ; ; )
+ {
+ if (encoder_failed_flag)
+ return;
+
+ const uint32_t by = cur_row.fetch_add(1);
+ if (by >= num_blocks_y)
+ break;
+
+ for (uint32_t bx = 0; bx < num_blocks_x; bx++)
+ {
+ if (encoder_failed_flag)
+ return;
+
+ color_rgba block_pixels[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+ orig_img.extract_block_clamped(block_pixels, bx * block_width, by * block_height, block_width, block_height);
+
+ ldr_astc_block_encode_image_output::block_info& blk_info = enc_out.m_image_block_info(bx, by);
+
+ encode_block_output* pEnc_block_output = blk_info.m_out_blocks.enlarge(1);
+ pEnc_block_output->clear();
+
+ astc_helpers::log_astc_block& log_blk = pEnc_block_output->m_log_blk;
+
+ if (!basist::bc7f::fast_pack_astc(log_blk, (basist::color_rgba*)block_pixels, bc7f_override_flags))
+ {
+ assert(0);
+ encoder_failed_flag.store(true);
+ return;
+ }
+
+ if (log_blk.m_solid_color_flag_ldr)
+ {
+ // weighted SSE should be zero, the block should always be solid here
+ pEnc_block_output->m_trial_mode_index = -1;
+ pEnc_block_output->m_sse = 0;
+ }
+ else
+ {
+ uint32_t cem_to_find = log_blk.m_color_endpoint_modes[0];
+ if (cem_to_find == astc_helpers::CEM_LDR_RGB_BASE_PLUS_OFFSET)
+ cem_to_find = astc_helpers::CEM_LDR_RGB_DIRECT;
+ else if (cem_to_find == astc_helpers::CEM_LDR_RGBA_BASE_PLUS_OFFSET)
+ cem_to_find = astc_helpers::CEM_LDR_RGBA_DIRECT;
+
+ const int ccs_to_find = log_blk.m_dual_plane ? log_blk.m_color_component_selector : -1;
+ BASISU_NOTE_UNUSED(ccs_to_find);
+
+ const basisu::uint_vec& tms = basist::astc_ldr_t::get_tm_candidates(basist::astc_ldr_t::g_grouped_encoder_trial_modes[0],
+ cem_to_find, log_blk.m_num_partitions - 1, log_blk.m_dual_plane ? log_blk.m_color_component_selector + 1 : 0,
+ 1, basist::astc_ldr_t::calc_grid_aniso_val(log_blk.m_grid_width, log_blk.m_grid_height, 4, 4));
+
+ uint32_t tm_index = 0, tms_index = 0;
+ for (tms_index = 0; tms_index < tms.size(); tms_index++)
+ {
+ tm_index = tms[tms_index];
+
+ const auto& tm = basist::astc_ldr_t::g_encoder_trial_modes[0][tm_index];
+
+ assert(tm.m_cem == cem_to_find);
+ assert(tm.m_num_parts == log_blk.m_num_partitions);
+ assert(tm.m_ccs_index == ccs_to_find);
+
+ if ((tm.m_endpoint_ise_range == log_blk.m_endpoint_ise_range) && (tm.m_weight_ise_range == log_blk.m_weight_ise_range))
+ {
+ if ((tm.m_grid_width == log_blk.m_grid_width) && (tm.m_grid_height == log_blk.m_grid_height))
+ {
+ break;
+ }
+ }
+ } // tms_index
+
+ if (tms_index == tms.size())
+ {
+ assert(0);
+ encoder_failed_flag.store(true);
+ return;
+ }
+
+ pEnc_block_output->m_trial_mode_index = basisu::safe_cast_int16(tm_index);
+
+ if (enc_cfg.m_use_dct)
+ {
+ //const basist::astc_ldr_t::astc_block_grid_data* pGrid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, new_log_block.m_grid_width, new_log_block.m_grid_height);
+
+ const uint32_t num_planes = (log_blk.m_dual_plane ? 2 : 1);
+
+ uint32_t total_empty_planes = 0;
+
+ for (uint32_t plane_index = 0; plane_index < num_planes; plane_index++)
+ {
+ basist::astc_ldr_t::dct_syms& syms = pEnc_block_output->m_packed_dct_plane_data[plane_index];
+
+ code_block_weights(grid_coder, enc_cfg.m_base_q, plane_index, log_blk, syms, dct_temp);
+
+ // ensure existing weights get blown away
+ for (uint32_t i = 0; i < (uint32_t)(log_blk.m_grid_width * log_blk.m_grid_height); i++)
+ log_blk.m_weights[i * num_planes + plane_index] = 0;
+
+ bool dec_status = grid_coder.decode_block_weights(enc_cfg.m_base_q, plane_index, log_blk, nullptr, nullptr, dct_temp, &syms);
+
+ assert(dec_status);
+ if (!dec_status)
+ {
+ error_printf("grid_coder.decode_block_weights() failed!\n");
+ encoder_failed_flag.store(true);
+ return;
+ }
+
+ // check for all-zero AC's
+ if (syms.m_coeffs.size() == 1)
+ {
+ if ((1 + syms.m_coeffs[0].m_num_zeros) == (log_blk.m_grid_width * log_blk.m_grid_height))
+ {
+ total_empty_planes++;
+ }
+ }
+ }
+
+ if ((log_blk.m_num_partitions == 1) && (total_empty_planes == num_planes))
+ {
+ // entire block post-quantization is DC only (no non-zero AC), switch to void-extent
+ uint32_t sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0;
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ sum_r += block_pixels[i].r;
+ sum_g += block_pixels[i].g;
+ sum_b += block_pixels[i].b;
+ sum_a += block_pixels[i].a;
+ }
+
+ sum_r = (sum_r + 8) >> 4;
+ sum_g = (sum_g + 8) >> 4;
+ sum_b = (sum_b + 8) >> 4;
+ sum_a = (sum_a + 8) >> 4;
+
+ astc_helpers::set_ldr_solid_block(log_blk, sum_r, sum_g, sum_b, sum_a);
+
+ pEnc_block_output->m_trial_mode_index = -1;
+ }
+
+ } // if (enc_cfg.m_use_dct)
+
+ {
+ color_rgba dec_block_pixels[16];
+
+ bool dec_status = astc_helpers::decode_block_xuastc_ldr(log_blk, dec_block_pixels, 4, 4,
+ enc_cfg.m_cem_enc_params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8);
+
+ if (!dec_status)
+ {
+ // Shouldn't ever happen
+ assert(0);
+ encoder_failed_flag.store(true);
+ return;
+ }
+
+ uint64_t total_err = 0;
+
+ for (uint32_t i = 0; i < 16; i++)
+ total_err += weighted_color_error(dec_block_pixels[i], block_pixels[i], enc_cfg.m_cem_enc_params);
+
+ pEnc_block_output->m_sse = total_err;
+ }
+ }
+
+ bool pack_status = astc_helpers::pack_astc_block(enc_out.m_packed_phys_blocks(bx, by), log_blk);
+ if (!pack_status)
+ {
+ assert(0);
+ encoder_failed_flag.store(true);
+ return;
+ }
+
+ } // bx
+
+ } // for
+
+ } );
+
+ } // job_index
+
+ job_pool.wait_for_all();
+
+ if (encoder_failed_flag)
+ {
+ fmt_error_printf("ldr_astc_block_encode_image_fast_4x4: Main compressor block loop failed!\n");
+ return false;
+ }
+
+ if (enc_cfg.m_debug_output)
+ {
+ display_candidate_statistics(enc_out);
+
+ fmt_debug_printf("ldr_astc_block_encode_image_fast_4x4: done\n");
+ }
+
+ return true;
+}
+
+#if BASISU_SUPPORT_ASTCENC
+static astcenc_context* comp_astc_init_ldr(
+ uint32_t block_w,
+ uint32_t block_h,
+ bool srgb,
+ float quality, // ASTCENC_PRE_MEDIUM etc.
+ uint32_t thread_count,
+ uint32_t max_partitions,
+ bool xuastc_ldr_flag,
+ const uint32_t chan_weights[4],
+ bool disable_rgb_dual_planes,
+ uint32_t candidate_row_pitch, std::vector< std::vector > *pCandidates)
+{
+ astcenc_config config{};
+
+ const astcenc_profile profile = srgb ? ASTCENC_PRF_LDR_SRGB : ASTCENC_PRF_LDR;
+
+ uint32_t flags = ASTCENC_FLG_USE_DECODE_UNORM8;
+
+ // flags = 0 for ordinary LDR color compression
+ astcenc_error status = astcenc_config_init(
+ profile,
+ block_w,
+ block_h,
+ 1,
+ quality,
+ flags,
+ &config);
+
+ if (status != ASTCENC_SUCCESS)
+ {
+ std::printf("astcenc_config_init() failed\n");
+ return nullptr;
+ }
+
+ config.cw_r_weight = (float)chan_weights[0];
+ config.cw_g_weight = (float)chan_weights[1];
+ config.cw_b_weight = (float)chan_weights[2];
+ config.cw_a_weight = (float)chan_weights[3];
+
+ config.m_xuastc_ldr_flag = xuastc_ldr_flag;
+
+ config.tune_partition_count_limit = maximum(1u, max_partitions);
+ config.m_disable_rgb_dual_planes = disable_rgb_dual_planes;
+
+ if (pCandidates)
+ {
+ config.candidate_row_pitch = candidate_row_pitch;
+ config.m_pCandidates = pCandidates;
+ }
+
+ astcenc_context* pContext = nullptr;
+
+ status = astcenc_context_alloc(&config, thread_count, &pContext);
+
+ if (status != ASTCENC_SUCCESS)
+ {
+ std::printf("astcenc_context_alloc() failed\n");
+ return nullptr;
+ }
+
+ return pContext;
+}
+
+static void comp_astc_deinit(astcenc_context* pContext)
+{
+ astcenc_context_free(pContext);
+}
+
+static uint32_t get_astc_block_count(uint32_t dim, uint32_t block_dim)
+{
+ return (dim + block_dim - 1) / block_dim;
+}
+
+// Compress from tightly packed RGBA8 input.
+// src_rgba must point to w * h * 4 bytes.
+static bool comp_astc_image_u8(
+ astcenc_context* pContext,
+ const uint8_t* src_rgba,
+ uint32_t w,
+ uint32_t h,
+ uint32_t block_w,
+ uint32_t block_h,
+ basisu::vector2D& out_blocks,
+ job_pool &job_pool)
+{
+ static const astcenc_swizzle swizzle{
+ ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A
+ };
+
+ astcenc_image image{};
+ image.dim_x = w;
+ image.dim_y = h;
+ image.dim_z = 1;
+ image.data_type = ASTCENC_TYPE_U8;
+
+ // astcenc_image::data is an array of pointers to 2D slices.
+ // For a 2D image we provide one slice pointer.
+ void* slices[1];
+ slices[0] = const_cast(src_rgba);
+ image.data = slices;
+
+ out_blocks.resize(get_astc_block_count(w, block_w), get_astc_block_count(h, block_h));
+
+ const uint32_t num_threads = (uint32_t)job_pool.get_total_threads();
+
+ if (num_threads == 1)
+ {
+ astcenc_error status = astcenc_compress_image(
+ pContext,
+ &image,
+ &swizzle,
+ reinterpret_cast(out_blocks.get_ptr()),
+ out_blocks.size_in_bytes(),
+ 0);
+
+ return (status == ASTCENC_SUCCESS);
+ }
+
+ std::atomic any_failures;
+ any_failures.store(false);
+
+ for (uint32_t thread_index = 0; thread_index < num_threads; thread_index++)
+ {
+ job_pool.add_job([thread_index, pContext, &image, &out_blocks, &any_failures]
+ {
+ if (any_failures)
+ return;
+
+ astcenc_error status = astcenc_compress_image(
+ pContext,
+ &image,
+ &swizzle,
+ reinterpret_cast(out_blocks.get_ptr()),
+ out_blocks.size_in_bytes(),
+ thread_index);
+
+ if (status != ASTCENC_SUCCESS)
+ {
+ any_failures.store(true);
+ }
+ }
+ );
+ }
+
+ job_pool.wait_for_all();
+
+ if (any_failures)
+ return false;
+
+ astcenc_compress_reset(pContext);
+
+ return true;
+}
+
+static bool ldr_astc_block_encode_image_astcenc(
+ const image& orig_img,
+ const ldr_astc_block_encode_image_high_level_config& enc_cfg,
+ const astc_ldr_encode_config& global_cfg,
+ ldr_astc_block_encode_image_output& enc_out,
+ uint32_t max_candidate_limit)
+{
+ if (enc_cfg.m_debug_output)
+ fmt_debug_printf("ldr_astc_block_encode_image_astcenc:\n");
+
+ const uint32_t block_width = enc_cfg.m_block_width, block_height = enc_cfg.m_block_height;
+
+ //const uint32_t width = orig_img.get_width(), height = orig_img.get_height();
+ //const uint32_t total_pixels = width * height;
+ const uint32_t total_block_pixels = enc_cfg.m_block_width * enc_cfg.m_block_height;
+ const uint32_t num_blocks_x = orig_img.get_block_width(enc_cfg.m_block_width);
+ const uint32_t num_blocks_y = orig_img.get_block_height(enc_cfg.m_block_height);
+ const uint32_t total_blocks = num_blocks_x * num_blocks_y;
+
+ if (enc_cfg.m_debug_output)
+ {
+ fmt_debug_printf("\nASTC base bitrate: {3.3} bpp\n", 128.0f / (float)(enc_cfg.m_block_width * enc_cfg.m_block_height));
+
+ fmt_debug_printf("ASTC block size: {}x{}\n", enc_cfg.m_block_width, enc_cfg.m_block_height);
+
+ fmt_debug_printf("Image has alpha: {}\n", orig_img.has_alpha());
+
+ fmt_debug_printf("max_candidate_limit: {}\n", max_candidate_limit);
+ }
+
+ // We don't use this here, but the supercompressors use these tables.
+ // TODO: The transcoder already creates all this stuff for each block size.
+ astc_ldr::partitions_data* pPart_data_p2 = &enc_out.m_part_data_p2;
+ pPart_data_p2->init(2, enc_cfg.m_block_width, enc_cfg.m_block_height, BASISU_USE_LSH2 == 0, BASISU_USE_LSH2 != 0);
+
+ astc_ldr::partitions_data* pPart_data_p3 = &enc_out.m_part_data_p3;
+ pPart_data_p3->init(3, enc_cfg.m_block_width, enc_cfg.m_block_height, BASISU_USE_LSH3 == 0, BASISU_USE_LSH3 != 0);
+
+ basisu::vector& encoder_trial_modes = enc_out.m_encoder_trial_modes;
+ encoder_trial_modes.reserve(4096);
+
+ basist::astc_ldr_t::grouped_trial_modes& grouped_encoder_trial_modes = enc_out.m_grouped_encoder_trial_modes;
+ basist::astc_ldr_t::create_encoder_trial_modes_table(block_width, block_height, encoder_trial_modes, grouped_encoder_trial_modes, enc_cfg.m_debug_output, false);
+
+ const uint32_t block_size_index = astc_helpers::get_block_size_index(block_width, block_height);
+
+ assert(enc_cfg.m_pJob_pool);
+ job_pool& job_pool = *enc_cfg.m_pJob_pool;
+ const uint32_t num_threads = (uint32_t)job_pool.get_total_threads();
+
+ float astcenc_quality = ASTCENC_PRE_FASTEST;
+
+ switch (global_cfg.m_effort_level)
+ {
+ case 0:
+ astcenc_quality = ASTCENC_PRE_FASTEST;
+ break;
+ case 1:
+ astcenc_quality = lerp(ASTCENC_PRE_FASTEST, ASTCENC_PRE_FAST, 1.0f / 3.0f);
+ break;
+ case 2:
+ astcenc_quality = lerp(ASTCENC_PRE_FASTEST, ASTCENC_PRE_FAST, 2.0f / 3.0f);
+ break;
+ case 3:
+ astcenc_quality = ASTCENC_PRE_FAST;
+ break;
+ case 4:
+ astcenc_quality = (ASTCENC_PRE_FAST + ASTCENC_PRE_MEDIUM) * .5f;
+ break;
+ case 5:
+ astcenc_quality = ASTCENC_PRE_MEDIUM;
+ break;
+ case 6:
+ astcenc_quality = (ASTCENC_PRE_MEDIUM + ASTCENC_PRE_THOROUGH) * .5f;
+ break;
+ case 7:
+ astcenc_quality = ASTCENC_PRE_THOROUGH;
+ break;
+ case 8:
+ astcenc_quality = (ASTCENC_PRE_THOROUGH + ASTCENC_PRE_VERYTHOROUGH) * .5f;
+ break;
+ case 9:
+ astcenc_quality = ASTCENC_PRE_VERYTHOROUGH;
+ break;
+ case 10:
+ default:
+ astcenc_quality = ASTCENC_PRE_EXHAUSTIVE;
+ break;
+ }
+
+ std::vector< std::vector > block_candidates(total_blocks);
+
+ astcenc_context* pContext = comp_astc_init_ldr(
+ block_width,
+ block_height,
+ enc_cfg.m_cem_enc_params.m_decode_mode_srgb,
+ astcenc_quality,
+ num_threads,
+ global_cfg.m_force_disable_subsets ? 1 : 3,
+ true, enc_cfg.m_cem_enc_params.m_comp_weights,
+ global_cfg.m_force_disable_rgb_dual_plane,
+ num_blocks_x, &block_candidates);
+
+ if (!pContext)
+ {
+ assert(0);
+ return false;
+ }
+
+ if (enc_cfg.m_debug_output)
+ fmt_debug_printf("Compressing with astcenc\n");
+
+ interval_timer itm;
+ itm.start();
+
+ const bool comp_status = comp_astc_image_u8(
+ pContext,
+ (const uint8_t*)orig_img.get_ptr(),
+ orig_img.get_width(),
+ orig_img.get_height(),
+ block_width,
+ block_height,
+ enc_out.m_packed_phys_blocks,
+ job_pool);
+
+ if (enc_cfg.m_debug_output)
+ fmt_debug_printf("Total time: {} seconds\n", itm.get_elapsed_secs());
+
+ comp_astc_deinit(pContext);
+ pContext = nullptr;
+
+ if (!comp_status)
+ {
+ fmt_error_printf("comp_astc_image_u8() failed!\n");
+
+ assert(0);
+ return false;
+ }
+
+ if (enc_cfg.m_debug_output)
+ fmt_debug_printf("Compressing with astcenc: OK\n");
+
+ enc_out.m_image_block_info.resize(0, 0);
+ enc_out.m_image_block_info.resize(num_blocks_x, num_blocks_y);
+
+ basist::astc_ldr_t::grid_weight_dct grid_coder;
+ if (enc_cfg.m_use_dct)
+ grid_coder.init(block_width, block_height);
+
+ basist::astc_ldr_t::fvec dct_temp;
+
+ uint32_t total_suboptimal_cem_encodings = 0;
+
+ uint32_t min_candidates = UINT32_MAX, max_candidates = 0, total_candidates = 0;
+
+ uint32_t total_bad_candidates = 0;
+
+ for (uint32_t by = 0; by < num_blocks_y; by++)
+ {
+ for (uint32_t bx = 0; bx < num_blocks_x; bx++)
+ {
+ color_rgba block_pixels[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+ orig_img.extract_block_clamped(block_pixels, bx * block_width, by * block_height, block_width, block_height);
+
+ const std::vector& cand_blocks = block_candidates[bx + by * num_blocks_x];
+
+ if (!cand_blocks.size())
+ {
+ assert(0);
+ fmt_error_printf("astcenc gave us no candidates for a block.\n");
+ return false;
+ }
+
+ min_candidates = minimum((uint32_t)cand_blocks.size(), min_candidates);
+ max_candidates = maximum((uint32_t)cand_blocks.size(), max_candidates);
+ total_candidates += (uint32_t)cand_blocks.size();
+
+ uint64_t best_sse = UINT64_MAX;
+ uint32_t best_cand_index = 0;
+
+ ldr_astc_block_encode_image_output::block_info& blk_info = enc_out.m_image_block_info(bx, by);
+
+ for (uint32_t cand_index = 0; cand_index < cand_blocks.size(); cand_index++)
+ {
+ astc_helpers::astc_block* pOrig_packed_block = (astc_helpers::astc_block * )cand_blocks[cand_index].m_bytes;
+
+ const uint32_t cur_out_block_index = blk_info.m_out_blocks.size_u32();
+
+ encode_block_output* pEnc_block_output = blk_info.m_out_blocks.enlarge(1);
+ pEnc_block_output->clear();
+
+ pEnc_block_output->m_blur_id = BLUR_ID_ASTCENC;
+
+ astc_helpers::log_astc_block& log_blk = pEnc_block_output->m_log_blk;
+
+ if (!astc_helpers::unpack_block(pOrig_packed_block, log_blk, block_width, block_height, true))
+ {
+ fmt_error_printf("astcenc produced an invalid physical ASTC block!\n");
+
+ assert(0);
+ return false;
+ }
+
+ if (!astc_helpers::is_block_xuastc_ldr(log_blk))
+ {
+ // Ideally this doesn't happen if we've modified astcenc correctly to ignore non-XUASTC configs.
+ if (!total_bad_candidates)
+ fmt_printf("WARNING: astcenc produced a non-XUASTC LDR compliant candidate! (1). Ignoring it.\n");
+
+ blk_info.m_out_blocks.pop_back();
+
+ total_bad_candidates++;
+ continue;
+ }
+
+ // We can slam this to disabled because if its otherwise valid XUASTC LDR it doesn't matter. (astcenc does use them, but most of the time they don't change the BISE endpoint/weight levels.)
+ // Because we map to valid XUASTC LDR trial mode configs, if slamming this disabled results in an invalid non-suboptimal CEM config, we'll not be able to find the config.
+ if (log_blk.m_uses_suboptimal_cem_encoding)
+ {
+ total_suboptimal_cem_encodings++;
+ log_blk.m_uses_suboptimal_cem_encoding = false;
+ }
+
+ if (log_blk.m_solid_color_flag_ldr)
+ {
+ pEnc_block_output->m_trial_mode_index = -1;
+ }
+ else
+ {
+ uint32_t cem_to_find = log_blk.m_color_endpoint_modes[0];
+ if (cem_to_find == astc_helpers::CEM_LDR_RGB_BASE_PLUS_OFFSET)
+ cem_to_find = astc_helpers::CEM_LDR_RGB_DIRECT;
+ else if (cem_to_find == astc_helpers::CEM_LDR_RGBA_BASE_PLUS_OFFSET)
+ cem_to_find = astc_helpers::CEM_LDR_RGBA_DIRECT;
+
+ const int ccs_to_find = log_blk.m_dual_plane ? log_blk.m_color_component_selector : -1;
+ BASISU_NOTE_UNUSED(ccs_to_find);
+
+ const basisu::uint_vec& tms = basist::astc_ldr_t::get_tm_candidates(
+ basist::astc_ldr_t::g_grouped_encoder_trial_modes[block_size_index],
+ cem_to_find, log_blk.m_num_partitions - 1, log_blk.m_dual_plane ? log_blk.m_color_component_selector + 1 : 0,
+ basist::astc_ldr_t::calc_grid_size_val(log_blk.m_grid_width, log_blk.m_grid_height, block_width, block_height),
+ basist::astc_ldr_t::calc_grid_aniso_val(log_blk.m_grid_width, log_blk.m_grid_height, block_width, block_height));
+
+ uint32_t tm_index = 0, tms_index = 0;
+ for (tms_index = 0; tms_index < tms.size(); tms_index++)
+ {
+ tm_index = tms[tms_index];
+
+ const auto& tm = basist::astc_ldr_t::g_encoder_trial_modes[block_size_index][tm_index];
+
+ assert(tm.m_cem == cem_to_find);
+ assert(tm.m_num_parts == log_blk.m_num_partitions);
+ assert(tm.m_ccs_index == ccs_to_find);
+
+ if ((tm.m_endpoint_ise_range == log_blk.m_endpoint_ise_range) && (tm.m_weight_ise_range == log_blk.m_weight_ise_range))
+ {
+ if ((tm.m_grid_width == log_blk.m_grid_width) && (tm.m_grid_height == log_blk.m_grid_height))
+ {
+ break;
+ }
+ }
+ } // tms_index
+
+ if (tms_index == tms.size())
+ {
+ // Shouldn't happen if we've properly modified astcenc correctly to use the XUASTC LDR compliant configs.
+ if (!total_bad_candidates)
+ fmt_printf("WARNING: astcenc produced a non-XUASTC LDR compliant candidate - but we couldn't find this block's config! (2) Ignoring it.\n");
+
+ blk_info.m_out_blocks.pop_back();
+
+ total_bad_candidates++;
+ continue;
+ }
+
+ pEnc_block_output->m_trial_mode_index = basisu::safe_cast_int16(tm_index);
+
+ if (enc_cfg.m_use_dct)
+ {
+ //const basist::astc_ldr_t::astc_block_grid_data* pGrid_data = basist::astc_ldr_t::find_astc_block_grid_data(block_width, block_height, new_log_block.m_grid_width, new_log_block.m_grid_height);
+
+ const uint32_t num_planes = (log_blk.m_dual_plane ? 2 : 1);
+
+ uint32_t total_empty_planes = 0;
+
+ for (uint32_t plane_index = 0; plane_index < num_planes; plane_index++)
+ {
+ basist::astc_ldr_t::dct_syms& syms = pEnc_block_output->m_packed_dct_plane_data[plane_index];
+
+ code_block_weights(grid_coder, enc_cfg.m_base_q, plane_index, log_blk, syms, dct_temp);
+
+ // ensure existing weights get blown away
+ for (uint32_t i = 0; i < (uint32_t)(log_blk.m_grid_width * log_blk.m_grid_height); i++)
+ log_blk.m_weights[i * num_planes + plane_index] = 0;
+
+ bool dec_status = grid_coder.decode_block_weights(enc_cfg.m_base_q, plane_index, log_blk, nullptr, nullptr, dct_temp, &syms);
+
+ assert(dec_status);
+ if (!dec_status)
+ {
+ error_printf("grid_coder.decode_block_weights() failed!\n");
+ return false;
+ }
+
+ // check for all-zero AC's
+ if (syms.m_coeffs.size() == 1)
+ {
+ if ((1 + syms.m_coeffs[0].m_num_zeros) == (log_blk.m_grid_width * log_blk.m_grid_height))
+ {
+ total_empty_planes++;
+ }
+ }
+ }
+
+ if ((log_blk.m_num_partitions == 1) && (total_empty_planes == num_planes))
+ {
+ // entire block post-quantization is DC only (no non-zero AC), switch to void-extent
+ uint32_t sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0;
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ {
+ sum_r += block_pixels[i].r;
+ sum_g += block_pixels[i].g;
+ sum_b += block_pixels[i].b;
+ sum_a += block_pixels[i].a;
+ }
+
+ sum_r = (sum_r + (total_block_pixels >> 1)) / total_block_pixels;
+ sum_g = (sum_g + (total_block_pixels >> 1)) / total_block_pixels;
+ sum_b = (sum_b + (total_block_pixels >> 1)) / total_block_pixels;
+ sum_a = (sum_a + (total_block_pixels >> 1)) / total_block_pixels;
+
+ astc_helpers::set_ldr_solid_block(log_blk, sum_r, sum_g, sum_b, sum_a);
+
+ pEnc_block_output->m_trial_mode_index = -1;
+ }
+ }
+ }
+
+ uint64_t total_err = 0;
+
+ {
+ color_rgba dec_block_pixels[astc_ldr::ASTC_LDR_MAX_BLOCK_PIXELS];
+
+ bool dec_status = astc_helpers::decode_block_xuastc_ldr(log_blk, dec_block_pixels, block_width, block_height,
+ enc_cfg.m_cem_enc_params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8);
+
+ if (!dec_status)
+ {
+ // Shouldn't ever happen
+ assert(0);
+ return false;
+ }
+
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ total_err += weighted_color_error(dec_block_pixels[i], block_pixels[i], enc_cfg.m_cem_enc_params);
+
+ pEnc_block_output->m_sse = total_err;
+ }
+
+ if (total_err < best_sse)
+ {
+ best_sse = total_err;
+ best_cand_index = cur_out_block_index;
+ }
+
+ } // cand_index
+
+ if (!blk_info.m_out_blocks.size())
+ {
+ //assert(0);
+ error_printf("Couldn't find any compliant astcenc candidates at block {}x{}\n", bx, by);
+ return false;
+ }
+
+ astc_helpers::astc_block* pFinal_packed_block = &enc_out.m_packed_phys_blocks(bx, by);
+
+ bool pack_status = astc_helpers::pack_astc_block(*pFinal_packed_block, blk_info.m_out_blocks[best_cand_index].m_log_blk);
+ if (!pack_status)
+ {
+ //assert(0);
+ error_printf("Failed packing final physical ASTC block at {}x{}\n", bx, by);
+ return false;
+ }
+
+ blk_info.m_packed_out_block_index = best_cand_index;
+
+ const uint64_t check_wsse = blk_info.m_out_blocks[best_cand_index].m_sse;
+ BASISU_NOTE_UNUSED(check_wsse);
+
+ enforce_max_candidate_limit(blk_info, max_candidate_limit);
+
+ best_cand_index = blk_info.m_packed_out_block_index; // may have changed
+
+ assert(check_wsse == blk_info.m_out_blocks[best_cand_index].m_sse);
+
+ } // bx
+
+ } // by
+
+ if (total_bad_candidates)
+ fmt_printf("WARNING: ldr_astc_block_encode_image_astcenc ignored {} invalid (non-XUASTC) candidates.\n", total_bad_candidates);
+
+ if (enc_cfg.m_debug_output)
+ {
+ display_candidate_statistics(enc_out);
+
+ fmt_debug_printf("Total candidates: {}, Avg candidates per block: {}, Min candidates: {}, Max candidates: {}\n",
+ total_candidates, (float)total_candidates / (float)total_blocks, min_candidates, max_candidates);
+
+ if (total_suboptimal_cem_encodings)
+ fmt_debug_printf("Total blocks using suboptimal CEM encodings: {}\n", total_suboptimal_cem_encodings);
+
+ if (enc_cfg.m_debug_output)
+ fmt_debug_printf("ldr_astc_block_encode_image_astcenc: done\n");
+ }
+
+ return true;
+}
+#endif // BASISU_SUPPORT_ASTCENC
+
+// -1 for solid, -2 for error, >= 0 if found
+static int find_tm_index(uint32_t block_width, uint32_t block_height, uint32_t block_size_index, const astc_helpers::log_astc_block &log_blk)
+{
+ assert(astc_helpers::is_block_xuastc_ldr(log_blk));
+
+ if (log_blk.m_solid_color_flag_ldr)
+ {
+ // weighted SSE should be zero, the block should always be solid here
+ return -1;
+ }
+
+ uint32_t cem_to_find = log_blk.m_color_endpoint_modes[0];
+ if (cem_to_find == astc_helpers::CEM_LDR_RGB_BASE_PLUS_OFFSET)
+ cem_to_find = astc_helpers::CEM_LDR_RGB_DIRECT;
+ else if (cem_to_find == astc_helpers::CEM_LDR_RGBA_BASE_PLUS_OFFSET)
+ cem_to_find = astc_helpers::CEM_LDR_RGBA_DIRECT;
+
+ const int ccs_to_find = log_blk.m_dual_plane ? log_blk.m_color_component_selector : -1;
+ BASISU_NOTE_UNUSED(ccs_to_find);
+
+ const basisu::uint_vec& tms = basist::astc_ldr_t::get_tm_candidates(
+ basist::astc_ldr_t::g_grouped_encoder_trial_modes[block_size_index],
+ cem_to_find, log_blk.m_num_partitions - 1, log_blk.m_dual_plane ? log_blk.m_color_component_selector + 1 : 0,
+ basist::astc_ldr_t::calc_grid_size_val(log_blk.m_grid_width, log_blk.m_grid_height, block_width, block_height),
+ basist::astc_ldr_t::calc_grid_aniso_val(log_blk.m_grid_width, log_blk.m_grid_height, block_width, block_height));
+
+ uint32_t tm_index = 0, tms_index = 0;
+ for (tms_index = 0; tms_index < tms.size(); tms_index++)
+ {
+ tm_index = tms[tms_index];
+
+ const auto& tm = basist::astc_ldr_t::g_encoder_trial_modes[block_size_index][tm_index];
+
+ assert(tm.m_cem == cem_to_find);
+ assert(tm.m_num_parts == log_blk.m_num_partitions);
+ assert(tm.m_ccs_index == ccs_to_find);
+
+ if ((tm.m_endpoint_ise_range == log_blk.m_endpoint_ise_range) && (tm.m_weight_ise_range == log_blk.m_weight_ise_range))
+ {
+ if ((tm.m_grid_width == log_blk.m_grid_width) && (tm.m_grid_height == log_blk.m_grid_height))
+ {
+ break;
+ }
+ }
+ } // tms_index
+
+ if (tms_index == tms.size())
+ {
+ assert(0);
+ return -2;
+ }
+
+ return tm_index;
+}
+
+static bool ldr_astc_block_encode_image_astcf(
+ const image& orig_img,
+ const ldr_astc_block_encode_image_high_level_config& enc_cfg,
+ const astc_ldr_encode_config& global_cfg,
+ ldr_astc_block_encode_image_output& enc_out,
+ uint32_t max_candidate_limit)
+{
+ if (enc_cfg.m_debug_output)
+ fmt_debug_printf("ldr_astc_block_encode_image_astcf:\n");
+
+ const uint32_t block_width = enc_cfg.m_block_width, block_height = enc_cfg.m_block_height;
+
+ const int block_dim_index = astc_helpers::find_astc_block_size_index(block_width, block_height);
+ assert((block_dim_index >= 0) && (block_dim_index < (int)astc_helpers::NUM_ASTC_BLOCK_SIZES));
+
+ const uint32_t width = orig_img.get_width(), height = orig_img.get_height();
+ const uint32_t total_pixels = width * height;
+ const uint32_t total_block_pixels = enc_cfg.m_block_width * enc_cfg.m_block_height;
+ const uint32_t num_blocks_x = orig_img.get_block_width(enc_cfg.m_block_width);
+ const uint32_t num_blocks_y = orig_img.get_block_height(enc_cfg.m_block_height);
+ const uint32_t total_blocks = num_blocks_x * num_blocks_y;
+ const bool has_alpha = orig_img.has_alpha();
+
+ if (enc_cfg.m_debug_output)
+ {
+ fmt_debug_printf("\nASTC base bitrate: {3.3} bpp\n", 128.0f / (float)(enc_cfg.m_block_width * enc_cfg.m_block_height));
+
+ fmt_debug_printf("ASTC block size: {}x{}\n", enc_cfg.m_block_width, enc_cfg.m_block_height);
+
+ fmt_debug_printf("Image has alpha: {}\n", has_alpha);
+
+ fmt_debug_printf("max_candidate_limit: {}\n", max_candidate_limit);;
+ }
+
+ // We don't use this here, but the supercompressors use these tables.
+ // TODO: The transcoder already creates all this stuff for each block size.
+ astc_ldr::partitions_data* pPart_data_p2 = &enc_out.m_part_data_p2;
+ pPart_data_p2->init(2, enc_cfg.m_block_width, enc_cfg.m_block_height, BASISU_USE_LSH2 == 0, BASISU_USE_LSH2 != 0);
+
+ astc_ldr::partitions_data* pPart_data_p3 = &enc_out.m_part_data_p3;
+ pPart_data_p3->init(3, enc_cfg.m_block_width, enc_cfg.m_block_height, BASISU_USE_LSH3 == 0, BASISU_USE_LSH3 != 0);
+
+ basisu::vector& encoder_trial_modes = enc_out.m_encoder_trial_modes;
+ encoder_trial_modes.reserve(4096);
+
+ basist::astc_ldr_t::grouped_trial_modes& grouped_encoder_trial_modes = enc_out.m_grouped_encoder_trial_modes;
+ basist::astc_ldr_t::create_encoder_trial_modes_table(block_width, block_height, encoder_trial_modes, grouped_encoder_trial_modes, enc_cfg.m_debug_output, false);
+
+ vector2D& packed_blocks = enc_out.m_packed_phys_blocks;
+ packed_blocks.resize(num_blocks_x, num_blocks_y);
+ memset(packed_blocks.get_ptr(), 0, packed_blocks.size_in_bytes());
+
+ enc_out.m_image_block_info.resize(0, 0);
+ enc_out.m_image_block_info.resize(num_blocks_x, num_blocks_y);
+
+ uint32_t max_subsets = 1;
+ bool use_subsets = false;
+ float var_thresh_2subsets = squaref(6.0f);
+ float var_thresh_3subsets = squaref(6.0f);
+ uint32_t num_subset_carriers = 1, num_subset_pats = 1;
+ uint32_t dot_thresh_fract_index_2subsets = 0;
+
+ bool weight_polishing = (global_cfg.m_effort_level >= 2);
+ uint32_t num_candidates = 1;
+
+ const float feffort_level = global_cfg.m_effort_level * (1.0f / 10.0f);
+
+#if 0
+ if (block_width * block_height <= 25)
+ {
+ // tiny blocks make the DCT related candidate eval quite slow
+ if (has_alpha)
+ num_candidates = clamp((int)std::round(lerp(8.0f, 24.0f, feffort_level)), 1, 64);
+ else
+ num_candidates = clamp((int)std::round(lerp(1.0f, 16.0f, feffort_level)), 1, 64);
+ }
+ else
+#endif
+ {
+ if (has_alpha)
+ num_candidates = clamp((int)std::round(lerp(16.0f, 48.0f, feffort_level)), 1, 64);
+ else
+ num_candidates = clamp((int)std::round(lerp(1.0f, 48.0f, feffort_level)), 1, 64);
+ }
+
+ if (global_cfg.m_effort_level)
+ {
+ if (block_width * block_height <= 25)
+ {
+ num_subset_carriers = clamp((int)std::round(lerp(1.0f, 2.0f, feffort_level)), 1, 3);
+ num_subset_pats = clamp((int)std::round(lerp(1.0f, 6.0f, feffort_level)), 1, 16);
+ }
+ else
+ {
+ num_subset_carriers = clamp((int)std::round(lerp(1.0f, 3.0f, feffort_level)), 1, 3);
+ num_subset_pats = clamp((int)std::round(lerp(1.0f, 16.0f, feffort_level)), 1, 16);
+ }
+
+ if (global_cfg.m_effort_level == 8)
+ dot_thresh_fract_index_2subsets = 1;
+ else if (global_cfg.m_effort_level == 9)
+ dot_thresh_fract_index_2subsets = 2;
+ else if (global_cfg.m_effort_level == 10)
+ dot_thresh_fract_index_2subsets = 3;
+
+ use_subsets = (num_subset_carriers > 0) && (num_subset_pats > 0);
+ if (use_subsets)
+ {
+ max_subsets = (global_cfg.m_effort_level > 1) ? 3 : 2;
+ }
+ }
+
+ if (global_cfg.m_force_disable_subsets)
+ use_subsets = false;
+
+ basist::astc_ldr_t::grid_weight_dct grid_coder;
+ if (enc_cfg.m_use_dct)
+ grid_coder.init(block_width, block_height);
+
+ assert(enc_cfg.m_pJob_pool);
+ job_pool& job_pool = *enc_cfg.m_pJob_pool;
+ const uint32_t num_threads = (uint32_t)job_pool.get_total_threads();
+
+ std::atomic cur_row;
+ cur_row.store(0);
+
+ std::atomic encoder_failed_flag;
+ encoder_failed_flag.store(false);
+
+ astc_ldrf::subset_enc_context ctx;
+
+ bool status = astc_ldrf::init_single_subset_context(
+ ctx,
+ block_width, block_height,
+ enc_cfg.m_cem_enc_params.m_decode_mode_srgb ? astc_helpers::decode_mode::cDecodeModeSRGB8 : astc_helpers::decode_mode::cDecodeModeLDR8,
+ enc_cfg.m_cem_enc_params.m_comp_weights,
+ num_candidates, 2, global_cfg.m_force_disable_rgb_dual_plane, has_alpha, weight_polishing);
+
+ if (global_cfg.m_effort_level <= 2)
+ {
+ // faster/weaker subset encoding
+ ctx.m_use_method2 = false;
+ }
+
+ if (global_cfg.m_effort_level <= 6)
+ {
+ // both very rarely worth the effort
+ ctx.m_higher_effort_bc = false;
+ ctx.m_try_base_ofs = false;
+ }
+
+ if ((status) && (use_subsets))
+ {
+ status = astc_ldrf::init_multi_subset_context(ctx, max_subsets, num_subset_carriers, num_subset_pats,
+ var_thresh_2subsets, dot_thresh_fract_index_2subsets, var_thresh_3subsets,
+ pPart_data_p2, pPart_data_p3);
+ }
+
+ if (!status)
+ {
+ fmt_error_printf("astc_ldrf::init_single_subset_context() failed!\n");
+ return false;
+ }
+
+ if (enc_cfg.m_debug_output)
+ {
+ fmt_debug_printf("num_candidates: {}, use subsets: {}, num_2subset_carriers: {}, num_2subset_pats: {}, 2 subsets threshold var: {}, dot_thresh_fract_index: {}, 3 subsets threshold var: {}, use subsets method1: {}, use subsets method2: {}, higher effort BC: {}, try base ofs: {}, max subsets: {}:\n",
+ num_candidates, use_subsets, num_subset_carriers, num_subset_pats,
+ var_thresh_2subsets, dot_thresh_fract_index_2subsets, var_thresh_3subsets,
+ ctx.m_use_method1, ctx.m_use_method2, ctx.m_higher_effort_bc, ctx.m_try_base_ofs, ctx.m_max_subsets);
+ }
+
+ for (uint32_t job_index = 0; job_index < num_threads; job_index++)
+ {
+ job_pool.add_job([job_index, num_threads, has_alpha, width, height, total_pixels, num_blocks_x, num_blocks_y, block_width, block_height, block_dim_index, total_blocks, total_block_pixels,
+ num_candidates, use_subsets, weight_polishing,
+ &cur_row, &encoder_failed_flag, &ctx, max_candidate_limit,
+ &orig_img, &enc_cfg, &encoder_trial_modes, &grid_coder, &grouped_encoder_trial_modes, &enc_out]
+ {
+ BASISU_NOTE_UNUSED(job_index); BASISU_NOTE_UNUSED(num_threads); BASISU_NOTE_UNUSED(has_alpha); BASISU_NOTE_UNUSED(width); BASISU_NOTE_UNUSED(height); BASISU_NOTE_UNUSED(total_pixels); BASISU_NOTE_UNUSED(total_blocks); BASISU_NOTE_UNUSED(weight_polishing); BASISU_NOTE_UNUSED(encoder_trial_modes); BASISU_NOTE_UNUSED(grouped_encoder_trial_modes);
+ if (encoder_failed_flag)
+ return;
+
+ basist::astc_ldr_t::fvec dct_temp;
+
+ astc_ldrf::astc_lblock_vec all_candidates;
+ all_candidates.reserve(num_candidates);
+
+ astc_ldrf::subset_enc_thread_context thread_ctx;
+
+ uint_vec sorted_indices;
+ sorted_indices.reserve(max_candidate_limit);
+
+ for (; ; )
+ {
+ if (encoder_failed_flag)
+ return;
+
+ const uint32_t by = cur_row.fetch_add(1);
+ if (by >= num_blocks_y)
+ break;
+
+ for (uint32_t bx = 0; bx < num_blocks_x; bx++)
+ {
+ if (encoder_failed_flag)
+ return;
+
+ ldr_astc_block_encode_image_output::block_info& blk_info = enc_out.m_image_block_info(bx, by);
+
+ color_rgba block_pixels[astc_helpers::MAX_BLOCK_PIXELS];
+ orig_img.extract_block_clamped(block_pixels, bx * block_width, by * block_height, block_width, block_height);
+
+ all_candidates.resize(0);
+
+ astc_helpers::log_astc_block best_lblock;
+ if (use_subsets)
+ {
+ astc_ldrf::compress_block_subsets(ctx, thread_ctx, (const uint8_t*)block_pixels, best_lblock, &all_candidates);
+ }
+ else
+ {
+ astc_ldrf::compress_single_subset(ctx, (const uint8_t*)block_pixels, best_lblock, &all_candidates, false);
+ }
+
+ if (encoder_failed_flag)
+ return;
+
+ if (!all_candidates.size())
+ {
+ fmt_error_printf("compress_block: returned no candidates!\n");
+
+ encoder_failed_flag.store(true);
+ return;
+ }
+
+ uint64_t best_cand_err = UINT64_MAX;
+ uint32_t best_cand_index = 0;
+
+ for (uint32_t cand_index = 0; cand_index < all_candidates.size(); cand_index++)
+ {
+ const astc_helpers::log_astc_block& candidate_log_blk = all_candidates[cand_index];
+
+ encode_block_output* pEnc_block_output = blk_info.m_out_blocks.enlarge(1);
+ pEnc_block_output->clear();
+
+ pEnc_block_output->m_blur_id = BLUR_ID_ASTCF;
+
+ astc_helpers::log_astc_block& log_blk = pEnc_block_output->m_log_blk;
+
+ log_blk = candidate_log_blk;
+ astc_ldrf::convert_rank_lblock_to_ise(log_blk);
+
+ int tm_index = find_tm_index(block_width, block_height, block_dim_index, log_blk);
+ if (tm_index == -2)
+ {
+ fmt_error_printf("compress_block: invalid candidate!\n");
+
+ encoder_failed_flag.store(true);
+ return;
+ }
+
+ if ((tm_index >= 0) && (enc_cfg.m_use_dct))
+ {
+ const uint32_t num_planes = (log_blk.m_dual_plane ? 2 : 1);
+
+ uint32_t total_empty_planes = 0;
+
+ for (uint32_t plane_index = 0; plane_index < num_planes; plane_index++)
+ {
+ basist::astc_ldr_t::dct_syms& syms = pEnc_block_output->m_packed_dct_plane_data[plane_index];
+
+ code_block_weights(grid_coder, enc_cfg.m_base_q, plane_index, log_blk, syms, dct_temp);
+
+ // ensure existing weights get blown away
+ for (uint32_t i = 0; i < (uint32_t)(log_blk.m_grid_width * log_blk.m_grid_height); i++)
+ log_blk.m_weights[i * num_planes + plane_index] = 0;
+
+ bool dec_status = grid_coder.decode_block_weights(enc_cfg.m_base_q, plane_index, log_blk, nullptr, nullptr, dct_temp, &syms);
+
+ assert(dec_status);
+ if (!dec_status)
+ {
+ error_printf("grid_coder.decode_block_weights() failed!\n");
+ encoder_failed_flag.store(true);
+ return;
+ }
+
+ // check for all-zero AC's
+ if (syms.m_coeffs.size() == 1)
+ {
+ if ((1 + syms.m_coeffs[0].m_num_zeros) == (log_blk.m_grid_width * log_blk.m_grid_height))
+ {
+ total_empty_planes++;
+ }
+ }
+ }
+
+ if ((log_blk.m_num_partitions == 1) && (total_empty_planes == num_planes))
+ {
+ // entire block post-quantization is DC only (no non-zero AC), switch to void-extent
+ uint32_t sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0;
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ {
+ sum_r += block_pixels[i].r;
+ sum_g += block_pixels[i].g;
+ sum_b += block_pixels[i].b;
+ sum_a += block_pixels[i].a;
+ }
+
+ const uint32_t round = total_block_pixels >> 1;
+ sum_r = (sum_r + round) / total_block_pixels;
+ sum_g = (sum_g + round) / total_block_pixels;
+ sum_b = (sum_b + round) / total_block_pixels;
+ sum_a = (sum_a + round) / total_block_pixels;
+
+ astc_helpers::set_ldr_solid_block(log_blk, sum_r, sum_g, sum_b, sum_a);
+
+ tm_index = -1;
+ }
+
+ } // if (enc_cfg.m_use_dct)
+
+ pEnc_block_output->m_trial_mode_index = basisu::safe_cast_int16(tm_index);
+
+ // unpack the block and compute actual WSSE
+ {
+ color_rgba dec_block_pixels[astc_helpers::MAX_BLOCK_PIXELS];
+
+ bool dec_status = astc_helpers::decode_block_xuastc_ldr(log_blk, dec_block_pixels, block_width, block_height,
+ enc_cfg.m_cem_enc_params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8);
+
+ if (!dec_status)
+ {
+ // Shouldn't ever happen
+ assert(0);
+
+ error_printf("decode_block_xuastc_ldr() failed!\n");
+
+ encoder_failed_flag.store(true);
+ return;
+ }
+
+ uint64_t total_err = 0;
+
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ total_err += weighted_color_error(dec_block_pixels[i], block_pixels[i], enc_cfg.m_cem_enc_params);
+
+ pEnc_block_output->m_sse = total_err;
+
+ if (total_err < best_cand_err)
+ {
+ best_cand_err = total_err;
+ best_cand_index = cand_index;
+ }
+ }
+
+ } // cand_index
+
+ if (blk_info.m_out_blocks.size() > max_candidate_limit)
+ {
+ const uint64_t check_wsse = blk_info.m_out_blocks[best_cand_index].m_sse;
+ BASISU_NOTE_UNUSED(check_wsse);
+
+ // There were just too many candidates returned - we'll risk running out of RAM in WASM. So sort them and just keep the top X.
+ sorted_indices.resize(blk_info.m_out_blocks.size_u32());
+
+ for (uint32_t i = 0; i < sorted_indices.size(); i++)
+ sorted_indices[i] = i;
+
+ std::sort(sorted_indices.begin(), sorted_indices.end(),
+ [&blk_info](const uint32_t a, const uint32_t b)
+ {
+ if (blk_info.m_out_blocks[a].m_sse < blk_info.m_out_blocks[b].m_sse)
+ return true;
+ return false;
+ }
+ );
+
+ basisu::vector shrunk_out_blocks(max_candidate_limit);
+ for (uint32_t i = 0; i < max_candidate_limit; i++)
+ shrunk_out_blocks[i] = blk_info.m_out_blocks[sorted_indices[i]];
+
+ blk_info.m_out_blocks.swap(shrunk_out_blocks);
+ best_cand_index = 0;
+
+ assert(check_wsse == blk_info.m_out_blocks[best_cand_index].m_sse);
+ }
+
+ blk_info.m_packed_out_block_index = best_cand_index;
+
+ const astc_helpers::log_astc_block& best_log_blk = blk_info.m_out_blocks[best_cand_index].m_log_blk;
+
+ bool pack_status = astc_helpers::pack_astc_block(enc_out.m_packed_phys_blocks(bx, by), best_log_blk);
+ if (!pack_status)
+ {
+ assert(0);
+ encoder_failed_flag.store(true);
+ return;
+ }
+ } // bx
+
+ } // for ( ; ; )
+
+ } // lambda function
+ );
+
+ } // job_index
+
+ job_pool.wait_for_all();
+
+ if (encoder_failed_flag)
+ {
+ fmt_error_printf("ldr_astc_block_encode_image_astcf: Main compressor block loop failed!\n");
+ return false;
+ }
+
+ if (enc_cfg.m_debug_output)
+ {
+ display_candidate_statistics(enc_out);
+
+ fmt_debug_printf("ldr_astc_block_encode_image_astcf: OK\n");
+ }
+
+ return true;
+}
const uint_vec& separate_tm_index(uint32_t block_width, uint32_t block_height, const basist::astc_ldr_t::grouped_trial_modes& grouped_enc_trial_modes, const basist::astc_ldr_t::trial_mode& tm,
uint32_t& cem_index, uint32_t& subset_index, uint32_t& ccs_index, uint32_t& grid_size, uint32_t& grid_aniso)
@@ -7503,7 +11067,7 @@ static bool compare_log_blocks_for_equality(const astc_helpers::log_astc_block&
return false;
}
-void configure_encoder_effort_level(int level, ldr_astc_block_encode_image_high_level_config& cfg)
+static void configure_encoder_effort_level(int level, ldr_astc_block_encode_image_high_level_config& cfg)
{
switch (level)
{
@@ -7516,6 +11080,9 @@ void configure_encoder_effort_level(int level, ldr_astc_block_encode_image_high_
cfg.m_use_blue_contraction = true;
cfg.m_use_base_ofs = true;
+ cfg.m_encode_trial_early_out_thresh = 0.01f;
+ cfg.m_encode_trial_subsets_early_out_thresh = 0.01f;
+
cfg.m_force_all_dual_plane_chan_evals = true;
cfg.m_filter_by_pca_angles_flag = false;
@@ -7556,6 +11123,8 @@ void configure_encoder_effort_level(int level, ldr_astc_block_encode_image_high_
cfg.m_grid_hv_filtering = false;
cfg.m_low_freq_block_filtering = false;
+ cfg.m_cem_enc_params.m_use_exhaustive_weight_eval = true;
+
break;
}
case 9:
@@ -7567,12 +11136,15 @@ void configure_encoder_effort_level(int level, ldr_astc_block_encode_image_high_
cfg.m_use_blue_contraction = true;
cfg.m_use_base_ofs = true;
+ cfg.m_encode_trial_early_out_thresh = 0.01f;
+ cfg.m_encode_trial_subsets_early_out_thresh = 0.01f;
+
cfg.m_force_all_dual_plane_chan_evals = false;
cfg.m_filter_by_pca_angles_flag = true;
- cfg.m_superbucket_max_to_retain[0] = 8;
- cfg.m_superbucket_max_to_retain[1] = 16;
- cfg.m_superbucket_max_to_retain[2] = 32;
+ cfg.m_superbucket_max_to_retain[0] = 16;
+ cfg.m_superbucket_max_to_retain[1] = 32;
+ cfg.m_superbucket_max_to_retain[2] = 64;
cfg.m_base_parts2 = 32;
cfg.m_base_parts3 = 32;
@@ -7583,18 +11155,18 @@ void configure_encoder_effort_level(int level, ldr_astc_block_encode_image_high_
cfg.m_final_shortlist_fraction[1] = 1.0f;
cfg.m_final_shortlist_fraction[2] = 1.0f;
- cfg.m_final_shortlist_max_size[0] = 4;
- cfg.m_final_shortlist_max_size[1] = 12;
- cfg.m_final_shortlist_max_size[2] = 24;
+ cfg.m_final_shortlist_max_size[0] = 16;
+ cfg.m_final_shortlist_max_size[1] = 32;
+ cfg.m_final_shortlist_max_size[2] = 64;
// Second superpass
- cfg.m_second_superpass_fract_to_recompress = .075f;
+ cfg.m_second_superpass_fract_to_recompress = .15f;
cfg.m_superbucket_max_to_retain_p2[0] = 16;
cfg.m_superbucket_max_to_retain_p2[1] = 64;
cfg.m_superbucket_max_to_retain_p2[2] = 256;
- cfg.m_final_shortlist_max_size_p2[0] = 8;
- cfg.m_final_shortlist_max_size_p2[1] = 16;
- cfg.m_final_shortlist_max_size_p2[2] = 32;
+ cfg.m_final_shortlist_max_size_p2[0] = 32;
+ cfg.m_final_shortlist_max_size_p2[1] = 64;
+ cfg.m_final_shortlist_max_size_p2[2] = 128;
cfg.m_base_parts2_p2 = 64;
cfg.m_base_parts3_p2 = 64;
cfg.m_force_all_dp_chans_p2 = false;
@@ -7605,10 +11177,13 @@ void configure_encoder_effort_level(int level, ldr_astc_block_encode_image_high_
cfg.m_early_stop_wpsnr = 75.0f;
cfg.m_early_stop2_wpsnr = 70.0f;
+ cfg.m_cem_enc_params.m_use_exhaustive_weight_eval = true;
+
break;
}
case 8:
{
+#if 0
cfg.m_second_superpass_refinement = true;
cfg.m_third_superpass_try_neighbors = true;
@@ -7653,6 +11228,61 @@ void configure_encoder_effort_level(int level, ldr_astc_block_encode_image_high_
cfg.m_early_stop_wpsnr = 75.0f;
cfg.m_early_stop2_wpsnr = 70.0f;
+#endif
+
+ // old 9
+ cfg.m_second_superpass_refinement = true;
+ cfg.m_third_superpass_try_neighbors = true;
+
+ cfg.m_subsets_enabled = true;
+ cfg.m_use_blue_contraction = true;
+ cfg.m_use_base_ofs = true;
+
+ cfg.m_encode_trial_early_out_thresh = 0.01f;
+ cfg.m_encode_trial_subsets_early_out_thresh = 0.01f;
+
+ cfg.m_force_all_dual_plane_chan_evals = false;
+ cfg.m_filter_by_pca_angles_flag = true;
+
+ cfg.m_superbucket_max_to_retain[0] = 8;
+ cfg.m_superbucket_max_to_retain[1] = 16;
+ cfg.m_superbucket_max_to_retain[2] = 32;
+
+ cfg.m_base_parts2 = 32;
+ cfg.m_base_parts3 = 32;
+ cfg.m_part2_fraction_to_keep = 2;
+ cfg.m_part3_fraction_to_keep = 2;
+
+ cfg.m_final_shortlist_fraction[0] = 1.0f;
+ cfg.m_final_shortlist_fraction[1] = 1.0f;
+ cfg.m_final_shortlist_fraction[2] = 1.0f;
+
+ cfg.m_final_shortlist_max_size[0] = 4;
+ cfg.m_final_shortlist_max_size[1] = 12;
+ cfg.m_final_shortlist_max_size[2] = 24;
+
+ // Second superpass
+ cfg.m_second_superpass_fract_to_recompress = .15f;
+ cfg.m_superbucket_max_to_retain_p2[0] = 16;
+ cfg.m_superbucket_max_to_retain_p2[1] = 64;
+ cfg.m_superbucket_max_to_retain_p2[2] = 256;
+ cfg.m_final_shortlist_max_size_p2[0] = 8;
+ cfg.m_final_shortlist_max_size_p2[1] = 16;
+ cfg.m_final_shortlist_max_size_p2[2] = 32;
+ cfg.m_base_parts2_p2 = 64;
+ cfg.m_base_parts3_p2 = 64;
+ cfg.m_force_all_dp_chans_p2 = false;
+ cfg.m_filter_by_pca_angles_flag_p2 = false;
+
+ cfg.m_final_encode_always_try_rgb_direct = false;
+
+ cfg.m_early_stop_wpsnr = 75.0f;
+ cfg.m_early_stop2_wpsnr = 70.0f;
+
+ cfg.m_cem_enc_params.m_use_exhaustive_weight_eval = true;
+
+ //cfg.m_second_pass_total_weight_refine_passes = 0;
+
break;
}
case 7:
@@ -8298,6 +11928,16 @@ static bool compress_image_full_zstd(
for (uint32_t y = 0; y < num_blocks_y; y++)
for (uint32_t x = 0; x < num_blocks_x; x++)
coded_blocks(x, y).clear();
+
+ vector2D input_blocks;
+ if (global_cfg.m_debug_images)
+ {
+ input_blocks.resize(num_blocks_x, num_blocks_y);
+
+ for (uint32_t y = 0; y < num_blocks_y; y++)
+ for (uint32_t x = 0; x < num_blocks_x; x++)
+ input_blocks(x, y).clear();
+ }
vector2D prev_block_states(num_blocks_x, num_blocks_y);
@@ -8372,6 +12012,11 @@ static bool compress_image_full_zstd(
uint32_t best_packed_out_block_index = blk_info.m_packed_out_block_index;
+ if (global_cfg.m_debug_images)
+ {
+ input_blocks(bx, by) = blk_info.m_out_blocks[best_packed_out_block_index].m_log_blk;
+ }
+
// check for run
if ((use_run_commands_global_enable) && (bx || by))
{
@@ -8858,14 +12503,14 @@ static bool compress_image_full_zstd(
for (part_iter = 0; part_iter < tm.m_num_parts; part_iter++)
{
const bool always_repack_flag = false;
- bool blue_contraction_clamped_flag = false, base_ofs_clamped_flag = false;
+ bool blue_contraction_clamped_flag = false, try_direct_encoding_flag = false;
bool conv_status = basist::astc_ldr_t::convert_endpoints_across_cems(
pTrial_log_blk->m_color_endpoint_modes[0], pTrial_log_blk->m_endpoint_ise_range, pTrial_log_blk->m_endpoints,
cur_actual_cem, cur_log_blk.m_endpoint_ise_range, trial_predicted_endpoints[part_iter],
always_repack_flag,
endpoints_use_bc[part_iter], false,
- blue_contraction_clamped_flag, base_ofs_clamped_flag);
+ blue_contraction_clamped_flag, try_direct_encoding_flag);
if (!conv_status)
break;
@@ -8939,14 +12584,14 @@ static bool compress_image_full_zstd(
for (uint32_t part_iter = 0; part_iter < tm.m_num_parts; part_iter++)
{
const bool always_repack_flag = false;
- bool blue_contraction_clamped_flag = false, base_ofs_clamped_flag = false;
+ bool blue_contraction_clamped_flag = false, try_direct_encoding_flag = false;
bool conv_status = basist::astc_ldr_t::convert_endpoints_across_cems(
pEndpoint_pred_log_blk->m_color_endpoint_modes[0], pEndpoint_pred_log_blk->m_endpoint_ise_range, pEndpoint_pred_log_blk->m_endpoints,
cur_actual_cem, cur_log_blk.m_endpoint_ise_range, predicted_endpoints[part_iter],
always_repack_flag,
endpoints_use_bc[part_iter], false,
- blue_contraction_clamped_flag, base_ofs_clamped_flag);
+ blue_contraction_clamped_flag, try_direct_encoding_flag);
if (!conv_status)
{
@@ -9030,6 +12675,13 @@ static bool compress_image_full_zstd(
for (uint32_t plane_iter = 0; plane_iter < total_planes; plane_iter++)
{
const basist::astc_ldr_t::dct_syms& syms = blk_out.m_packed_dct_plane_data[plane_iter];
+
+ if (!syms.m_coeffs.size())
+ {
+ fmt_error_printf("compress_image_full_zstd: internal error - no DCT coeffs\n");
+ return false;
+ }
+
if (syms.m_max_coeff_mag > basist::astc_ldr_t::DCT_MAX_ARITH_COEFF_MAG)
{
use_dct = false;
@@ -9153,8 +12805,9 @@ static bool compress_image_full_zstd(
weight3_bits.flush();
weight4_bits.flush();
- const uint32_t zstd_level = 9;
-
+ // TODO: Make this configurable
+ const uint32_t zstd_level = (global_cfg.m_effort_level >= 3) ? 19 : 9;
+
uint8_vec comp_mode, comp_solid_dpcm, comp_endpoint_dpcm_reuse_indices;
uint8_vec comp_use_bc_bits, comp_endpoint_dpcm_3bit, comp_endpoint_dpcm_4bit, comp_endpoint_dpcm_5bit, comp_endpoint_dpcm_6bit, comp_endpoint_dpcm_7bit, comp_endpoint_dpcm_8bit;
@@ -9245,6 +12898,7 @@ static bool compress_image_full_zstd(
if ((global_cfg.m_debug_images) || (global_cfg.m_debug_output))
{
+ image input_img(width, height);
image coded_img(width, height);
vector2D phys_blocks(num_blocks_x, num_blocks_y);
@@ -9260,7 +12914,7 @@ static bool compress_image_full_zstd(
bool status = astc_helpers::decode_block(log_blk, block_pixels, block_width, block_height, enc_cfg.m_cem_enc_params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8);
if (!status)
{
- fmt_error_printf("astc_helpers::decode_block() failed\n");
+ fmt_error_printf("astc_helpers::decode_block() failed (1)\n");
return false;
}
@@ -9280,18 +12934,41 @@ static bool compress_image_full_zstd(
}
coded_img.set_block_clipped(block_pixels, bx * block_width, by * block_height, block_width, block_height);
+
+ if (global_cfg.m_debug_images)
+ {
+ // input image
+
+ status = astc_helpers::decode_block(input_blocks(bx, by), block_pixels, block_width, block_height, enc_cfg.m_cem_enc_params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8);
+ if (!status)
+ {
+ fmt_error_printf("astc_helpers::decode_block() failed (2)\n");
+ return false;
+ }
+
+ input_img.set_block_clipped(block_pixels, bx * block_width, by * block_height, block_width, block_height);
+ }
} // bx
} //by
if (global_cfg.m_debug_images)
- save_png(global_cfg.m_debug_file_prefix + "coded_img.png", coded_img);
+ {
+ save_png(global_cfg.m_debug_file_prefix + "input_img.png", input_img);
+ debug_printf("Wrote input_img.png\n");
- if (global_cfg.m_debug_output)
+ save_png(global_cfg.m_debug_file_prefix + "coded_img.png", coded_img);
+ debug_printf("Wrote coded_img.png\n");
+ }
+
+ if ((global_cfg.m_debug_output) && (global_cfg.m_debug_output_image_metrics))
{
debug_printf("Orig image vs. coded img:\n");
print_image_metrics(orig_img, coded_img);
+
+ debug_printf("display_astc_statistics:\n");
+ display_astc_statistics(coded_blocks, block_width, block_height, orig_img.get_width(), orig_img.get_height(), false);
}
}
@@ -9342,13 +13019,1877 @@ static bool compress_image_full_zstd(
}
#endif
-bool compress_image(
- const image& orig_img, uint8_vec& comp_data, vector2D& coded_blocks,
+static uint64_t calc_block_wsse(
+ const image& orig_img, int sx, int sy, int block_width, int block_height,
+ const image& tile_img, int tx, int ty,
+ const astc_ldr::cem_encode_params& p)
+{
+ assert(((int)tile_img.get_width() == block_width) && ((int)tile_img.get_height() == block_height));
+
+ uint64_t total_err = 0;
+
+ for (int y = 0; y < block_height; y++)
+ {
+ const int oy = sy + y;
+ if ((oy < 0) || (oy >= (int)orig_img.get_height()))
+ continue;
+
+ for (int x = 0; x < block_width; x++)
+ {
+ const int ox = sx + x;
+ if ((ox < 0) || (ox >= (int)orig_img.get_width()))
+ continue;
+
+ total_err += weighted_color_error(orig_img(ox, oy), tile_img(tx + x, ty + y), p);
+ } // x
+
+ } // y
+
+ return total_err;
+}
+
+static inline vec4F calc_color_delta(const color_rgba& a, const color_rgba& b)
+{
+ return vec4F(
+ (float)(a.r - b.r),
+ (float)(a.g - b.g),
+ (float)(a.b - b.b),
+ (float)(a.a - b.a));
+}
+
+static inline double calc_penalty(const vec4F& orig, const vec4F& cand, const vec4F &comp_weights)
+{
+ vec4F delta(orig - cand);
+ delta = vec4F::component_mul(delta, delta);
+ delta = vec4F::component_mul(delta, comp_weights);
+ return delta[0] + delta[1] + delta[2] + delta[3];
+}
+
+static convar g_astc_refine_cross_block_penalty_weight("astc_refine_cross_block_penalty_weight", 2.5f, 0.0f, 1000.0f);
+
+static uint64_t calc_cross_block_boundary_delta_mismatch(const image& orig_img, const image& candidate_img, uint32_t block_width, uint32_t block_height, uint32_t bx, uint32_t by, const astc_ldr::cem_encode_params& p)
+{
+ const int ofs_x = block_width * bx, ofs_y = block_height * by;
+
+ const vec4F comp_weights((float)p.m_comp_weights[0], (float)p.m_comp_weights[1], (float)p.m_comp_weights[2], (float)p.m_comp_weights[3]);
+
+ double penalty = 0.0f;
+
+ // TODO: Compute in integer
+ for (int x = 0; x < (int)block_width; x++)
+ {
+ vec4F orig_delta_top(calc_color_delta(orig_img.get_clamped(ofs_x + x, ofs_y), orig_img.get_clamped(ofs_x + x, ofs_y - 1)));
+ vec4F cand_delta_top(calc_color_delta(candidate_img.get_clamped(ofs_x + x, ofs_y), candidate_img.get_clamped(ofs_x + x, ofs_y - 1)));
+ penalty += calc_penalty(orig_delta_top, cand_delta_top, comp_weights);
+
+ vec4F orig_delta_bot = calc_color_delta(orig_img.get_clamped(ofs_x + x, ofs_y + block_height - 1), orig_img.get_clamped(ofs_x + x, ofs_y + block_height - 1 + 1));
+ vec4F cand_delta_bot = calc_color_delta(candidate_img.get_clamped(ofs_x + x, ofs_y + block_height - 1), candidate_img.get_clamped(ofs_x + x, ofs_y + block_height - 1 + 1));
+ penalty += calc_penalty(orig_delta_bot, cand_delta_bot, comp_weights);
+ } // x
+
+ for (int y = 0; y < (int)block_height; y++)
+ {
+ vec4F orig_delta_left(calc_color_delta(orig_img.get_clamped(ofs_x, ofs_y + y), orig_img.get_clamped(ofs_x - 1, ofs_y + y)));
+ vec4F cand_delta_left(calc_color_delta(candidate_img.get_clamped(ofs_x, ofs_y + y), candidate_img.get_clamped(ofs_x - 1, ofs_y + y)));
+ penalty += calc_penalty(orig_delta_left, cand_delta_left, comp_weights);
+
+ vec4F orig_delta_right(calc_color_delta(orig_img.get_clamped(ofs_x + block_width - 1, ofs_y + y), orig_img.get_clamped(ofs_x + block_width - 1 + 1, ofs_y + y)));
+ vec4F cand_delta_right(calc_color_delta(candidate_img.get_clamped(ofs_x + block_width - 1, ofs_y + y), candidate_img.get_clamped(ofs_x + block_width - 1 + 1, ofs_y + y)));
+ penalty += calc_penalty(orig_delta_right, cand_delta_right, comp_weights);
+ } // x
+
+ //const float PENALTY_WEIGHT = 2.5f;
+ const float PENALTY_WEIGHT = g_astc_refine_cross_block_penalty_weight.get_float();
+ return (uint64_t)std::round(penalty * PENALTY_WEIGHT);
+}
+
+static inline vec3F calc_ycbcr(const vec3F& c)
+{
+ const float r = c[0], g = c[1], b = c[2];
+
+ float Y = r * 0.212600f + g * 0.715200f + b * 0.072200f;
+ float Cb = r * -0.114572f + g * -0.385428f + b * 0.500000f;
+ float Cr = r * 0.500000f + g * -0.454153f + b * -0.045847f;
+
+ return vec3F(Y, Cb, Cr);
+}
+
+static uint64_t calc_chroma_loss_penalty(const image& orig_img, const image& candidate_img, uint32_t block_width, uint32_t block_height, uint32_t bx, uint32_t by, const astc_ldr::cem_encode_params& p)
+{
+ color_rgba orig_block[astc_helpers::MAX_BLOCK_PIXELS];
+ color_rgba cand_block[astc_helpers::MAX_BLOCK_PIXELS];
+
+ orig_img.extract_block_clamped(orig_block, bx * block_width, by * block_height, block_width, block_height);
+ candidate_img.extract_block_clamped(cand_block, bx * block_width, by * block_height, block_width, block_height);
+
+ const uint32_t total_block_pixels = block_width * block_height;
+
+ vec4F avg_orig(0.0f), avg_cand(0.0f);
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ {
+ avg_orig += orig_block[i].get_vec4F();
+ avg_cand += cand_block[i].get_vec4F();
+ }
+
+ avg_orig *= (1.0f / (float)total_block_pixels);
+ avg_cand *= (1.0f / (float)total_block_pixels);
+
+ vec3F o(calc_ycbcr(avg_orig));
+ vec3F c(calc_ycbcr(avg_cand));
+
+ vec3F delta(o - c);
+
+ float penalty = (delta[1] * delta[1]) + (delta[2] * delta[2]);
+
+ const float PENALTY_WEIGHT = ((float)total_block_pixels * .25f) * (float)p.m_comp_weights[1] * (14.0f * 14.0f);
+ penalty *= PENALTY_WEIGHT;
+
+ return (uint64_t)std::round(penalty);
+}
+
+static uint64_t calc_block_sse(
+ uint32_t block_width, uint32_t block_height,
+ const color_rgba *pBlock_pixels,
+ const astc_helpers::log_astc_block& log_astc_blk,
+ const ldr_astc_block_encode_image_high_level_config& enc_cfg)
+{
+ color_rgba dec_pixels[astc_helpers::MAX_BLOCK_PIXELS];
+ bool dec_status = astc_helpers::decode_block(log_astc_blk, dec_pixels, block_width, block_height, enc_cfg.m_cem_enc_params.m_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8);
+
+ assert(dec_status);
+ if (!dec_status)
+ return UINT64_MAX;
+
+ const uint32_t total_block_pixels = block_width * block_height;
+
+ uint64_t total_err = 0;
+ for (uint32_t i = 0; i < total_block_pixels; i++)
+ total_err += weighted_color_error(pBlock_pixels[i], dec_pixels[i], enc_cfg.m_cem_enc_params);
+
+ return total_err;
+}
+
+struct deblocking_thread_state
+{
+ image m_deblock_orig; // bw*3 x by*3
+ image m_deblock_staging; // bw*3 x by*3
+ image m_deblock_temp; // (bw+2) x (by+2)
+};
+
+// true on success
+static bool deblocking_find_best_candidate(
+ uint32_t block_width, uint32_t block_height, uint32_t num_blocks_x, uint32_t num_blocks_y,
+ uint32_t pass, uint32_t bx, uint32_t by,
+ deblocking_thread_state &thread_state,
+ const image& candidate_img,
+ const image& orig_img,
const astc_ldr_encode_config& global_cfg,
+ const ldr_astc_block_encode_image_high_level_config& enc_cfg,
+ ldr_astc_block_encode_image_output& enc_out,
+ uint32_t &new_best_packed_block_index, uint64_t &best_err)
+{
+ BASISU_NOTE_UNUSED(pass);
+
+ best_err = UINT64_MAX;
+
+ const astc_helpers::decode_mode dec_mode = global_cfg.m_astc_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8;
+
+ ldr_astc_block_encode_image_output::block_info& blk_info = enc_out.m_image_block_info(bx, by);
+
+ const basisu::vector& out_blocks = blk_info.m_out_blocks;
+
+ new_best_packed_block_index = blk_info.m_packed_out_block_index;
+
+ const uint64_t cur_sse = out_blocks[blk_info.m_packed_out_block_index].m_sse;
+ if (!cur_sse)
+ {
+ best_err = 0;
+ return true;
+ }
+
+ // if a candidate has a WSSE worse than this, ignore it
+ //const uint64_t skip_sse_thresh = (cur_sse * 4) / 3;
+ const uint64_t skip_sse_thresh = UINT64_MAX;
+
+ uint32_t best_cand_index = 0;
+
+ // always 3x3 blocks centered around our current block
+ image& deblock_orig_img = thread_state.m_deblock_orig;
+ image& deblock_staging_img = thread_state.m_deblock_staging;
+ image& deblock_temp_img = thread_state.m_deblock_temp;
+
+ // grab 3x3 block region around block from the original image and the current output, place into thread local temporary buffer
+ // this makes candidate evaluation simpler
+ deblock_orig_img.blit_clamped(
+ orig_img,
+ ((int)bx - 1) * (int)block_width, ((int)by - 1) * (int)block_height,
+ block_width * 3, block_height * 3,
+ 0, 0);
+
+ deblock_staging_img.blit_clamped(
+ candidate_img,
+ ((int)bx - 1) * (int)block_width, ((int)by - 1) * (int)block_height,
+ block_width * 3, block_height * 3,
+ 0, 0);
+
+ uint32_t total_neighbors_base_ofs = 0, total_neighbors_examined = 0;
+ for (int k = 0; k < 4; k++)
+ {
+ int nbx = 0, nby = 0;
+
+ if (k < 2)
+ nbx = (int)bx + ((k & 1) ? -1 : 1);
+ else
+ nby = (int)by + (((k - 2) & 1) ? -1 : 1);
+
+ if ((nbx < 0) || (nbx >= (int)num_blocks_x))
+ continue;
+ if ((nby < 0) || (nby >= (int)num_blocks_y))
+ continue;
+
+ ldr_astc_block_encode_image_output::block_info& neighbor_blk_info = enc_out.m_image_block_info(nbx, nby);
+
+ const astc_helpers::log_astc_block& neighbor_block = neighbor_blk_info.m_out_blocks[neighbor_blk_info.m_packed_out_block_index].m_log_blk;
+
+ if (neighbor_block.m_solid_color_flag_ldr)
+ continue;
+
+ if (astc_helpers::cem_is_ldr_base_scale(neighbor_block.m_color_endpoint_modes[0]))
+ total_neighbors_base_ofs++;
+
+ total_neighbors_examined++;
+ }
+
+ bool penalize_isolated_base_ofs_blocks = false;
+ if (total_neighbors_examined)
+ {
+ const uint32_t neighbors_base_ofs_fract = (total_neighbors_base_ofs << 4) / total_neighbors_examined;
+
+ const uint32_t PENALIZE_ISOLATED_BASE_OFS_FRACT = 8; // div 16
+
+ penalize_isolated_base_ofs_blocks = neighbors_base_ofs_fract < PENALIZE_ISOLATED_BASE_OFS_FRACT;
+ }
+
+ [[maybe_unused]] bool applied_base_ofs_penalty = false;
+
+ color_rgba block_pixels[astc_helpers::MAX_BLOCK_PIXELS];
+
+ for (uint32_t cand_index = 0; cand_index < out_blocks.size(); cand_index++)
+ {
+ const encode_block_output& out_blk = out_blocks[cand_index];
+
+ const uint64_t cand_sse = out_blocks[cand_index].m_sse;
+
+ uint64_t block_wsse = UINT64_MAX, block_artifact_penalty_wsse = 0, chroma_loss_penalty_wsse = 0;
+ uint64_t cand_err = UINT64_MAX;
+
+ // skip if the pre-deblock error would just increase too much, likely not worth it
+ if (cand_sse > skip_sse_thresh)
+ continue;
+
+ if (!astc_helpers::decode_block_xuastc_ldr(out_blk.m_log_blk, block_pixels, block_width, block_height, dec_mode))
+ {
+ assert(0);
+ return false;
+ }
+
+ // insert candidate block into the center of our 3x3 block staging image
+ deblock_staging_img.set_block_clipped(block_pixels, 1 * block_width, 1 * block_height, block_width, block_height);
+
+ if (global_cfg.m_scd_will_postfilter)
+ {
+ // deblocks a region of (block_width+2)x(block_height+2), with source pixel offset of (-1,-1), output into deblock_temp_img
+ deblock_block_region(block_width, block_height, deblock_staging_img, 1 * block_width, 1 * block_height, deblock_temp_img);
+
+ block_wsse = calc_block_wsse(
+ deblock_orig_img, 1 * block_width - 1, 1 * block_height - 1, block_width + 2, block_height + 2,
+ deblock_temp_img, 0, 0,
+ enc_cfg.m_cem_enc_params);
+ }
+ else
+ {
+ block_wsse = 0;
+
+ for (int y = 0; y < (int)block_height; y++)
+ for (int x = 0; x < (int)block_width; x++)
+ block_wsse += weighted_color_error(deblock_orig_img.get_clamped(1 * block_width + x, 1 * block_height + y), block_pixels[x + y * block_width], enc_cfg.m_cem_enc_params);
+
+ // CPU/GPU postfilter is NOT going to be applied, but they want to deblock anyway. No impact to texels around this block.
+ const uint32_t num_filtered_texels = (block_width + 2) * (block_height + 2);
+ const uint32_t num_unfiltered_texels = block_width * block_height;
+
+ // boost this wsse so the other penalties (tuned with filtering enabled) are roughly relative to it
+ block_wsse = (block_wsse * num_filtered_texels) / num_unfiltered_texels;
+ }
+
+ block_artifact_penalty_wsse = calc_cross_block_boundary_delta_mismatch(deblock_orig_img, deblock_staging_img, block_width, block_height, 1, 1, enc_cfg.m_cem_enc_params);
+
+ chroma_loss_penalty_wsse = global_cfg.m_scd_preserve_chroma ? calc_chroma_loss_penalty(deblock_orig_img, deblock_staging_img, block_width, block_height, 1, 1, enc_cfg.m_cem_enc_params) : 0;
+
+ cand_err = block_wsse;
+
+ if (penalize_isolated_base_ofs_blocks)
+ {
+ if (!out_blk.m_log_blk.m_solid_color_flag_ldr && astc_helpers::cem_is_ldr_base_scale(out_blk.m_log_blk.m_color_endpoint_modes[0]))
+ {
+ // penalize base-ofs candidates if there are not enough base-ofs neighbors, as they will likely be more visible
+ cand_err = (cand_err * 5) / 4;
+ //total_base_ofs_penalties++;
+ applied_base_ofs_penalty = true;
+ }
+ }
+
+ cand_err += block_artifact_penalty_wsse + chroma_loss_penalty_wsse;
+
+ if ((cand_index != blk_info.m_packed_out_block_index) && (out_blk.m_log_blk.m_solid_color_flag_ldr))
+ {
+ // don't switch to solid unless it REALLY seems to help
+ cand_err = cand_err * 8;
+ }
+
+ if (cand_err < best_err)
+ {
+ best_err = cand_err;
+ best_cand_index = cand_index;
+ }
+
+ } // cand_index
+
+ new_best_packed_block_index = best_cand_index;
+
+ return true;
+}
+
+// ------
+
+// true on success
+static bool deblocking_compute_error(
+ uint32_t block_width, uint32_t block_height, uint32_t num_blocks_x, uint32_t num_blocks_y,
+ uint32_t bx, uint32_t by,
+ image& candidate_img,
+ const image& orig_img,
+ const astc_ldr_encode_config& global_cfg,
+ const ldr_astc_block_encode_image_high_level_config& enc_cfg,
+ const ldr_astc_block_encode_image_output& enc_out,
+ uint64_t& cur_err,
+ image &deblock_orig, image &deblock_temp)
+{
+ assert(deblock_orig.get_width() == block_width);
+ assert(deblock_orig.get_height() == block_height);
+
+ assert(deblock_temp.get_width() == (block_width + 2));
+ assert(deblock_temp.get_height() == (block_height + 2));
+
+ const astc_helpers::decode_mode dec_mode = global_cfg.m_astc_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8;
+
+ cur_err = UINT64_MAX;
+
+ const ldr_astc_block_encode_image_output::block_info& blk_info = enc_out.m_image_block_info(bx, by);
+
+ const basisu::vector& out_blocks = blk_info.m_out_blocks;
+
+ deblock_orig.blit(candidate_img,
+ bx * block_width, by * block_height, block_width, block_height,
+ 0, 0,
+ true);
+
+ color_rgba block_pixels[astc_helpers::MAX_BLOCK_PIXELS];
+
+ uint32_t total_neighbors_base_ofs = 0, total_neighbors_examined = 0;
+ for (int k = 0; k < 4; k++)
+ {
+ int nbx = 0, nby = 0;
+
+ if (k < 2)
+ nbx = (int)bx + ((k & 1) ? -1 : 1);
+ else
+ nby = (int)by + (((k - 2) & 1) ? -1 : 1);
+
+ if ((nbx < 0) || (nbx >= (int)num_blocks_x))
+ continue;
+ if ((nby < 0) || (nby >= (int)num_blocks_y))
+ continue;
+
+ const ldr_astc_block_encode_image_output::block_info& neighbor_blk_info = enc_out.m_image_block_info(nbx, nby);
+
+ const astc_helpers::log_astc_block& neighbor_block = neighbor_blk_info.m_out_blocks[neighbor_blk_info.m_packed_out_block_index].m_log_blk;
+
+ if (neighbor_block.m_solid_color_flag_ldr)
+ continue;
+
+ if (astc_helpers::cem_is_ldr_base_scale(neighbor_block.m_color_endpoint_modes[0]))
+ total_neighbors_base_ofs++;
+
+ total_neighbors_examined++;
+ }
+
+ bool penalize_isolated_base_ofs_blocks = false;
+ if (total_neighbors_examined)
+ {
+ const uint32_t neighbors_base_ofs_fract = (total_neighbors_base_ofs << 4) / total_neighbors_examined;
+
+ const uint32_t PENALIZE_ISOLATED_BASE_OFS_FRACT = 8; // div 16
+
+ penalize_isolated_base_ofs_blocks = neighbors_base_ofs_fract < PENALIZE_ISOLATED_BASE_OFS_FRACT;
+ }
+
+ const uint32_t cand_index = blk_info.m_packed_out_block_index;
+
+ {
+ const encode_block_output& out_blk = out_blocks[cand_index];
+
+ //const uint64_t cand_sse = out_blocks[cand_index].m_sse;
+
+ uint64_t block_wsse = 0, block_artifact_penalty_wsse = 0, chroma_loss_penalty_wsse = 0;
+
+ // skip if the pre-deblock error would just increase too much, likely not worth it
+
+ if (!astc_helpers::decode_block_xuastc_ldr(out_blk.m_log_blk, block_pixels, block_width, block_height, dec_mode))
+ {
+ assert(0);
+ return false;
+ }
+
+ candidate_img.set_block_clipped(block_pixels, bx * block_width, by * block_height, block_width, block_height);
+
+ if (global_cfg.m_scd_will_postfilter)
+ {
+ deblock_block_region(block_width, block_height, candidate_img, bx * block_width, by * block_height, deblock_temp);
+
+ block_wsse = calc_block_wsse(orig_img, bx * block_width - 1, by * block_height - 1, block_width + 2, block_height + 2, deblock_temp, 0, 0, enc_cfg.m_cem_enc_params);
+ }
+ else
+ {
+ block_wsse = 0;
+
+ const int sx = bx * block_width, sy = by * block_height;
+ for (int y = 0; y < (int)block_height; y++)
+ for (int x = 0; x < (int)block_width; x++)
+ block_wsse += weighted_color_error(orig_img.get_clamped(sx + x, sy + y), block_pixels[x + y * block_width], enc_cfg.m_cem_enc_params);
+
+ // CPU/GPU postfilter is NOT going to be applied, but they want to deblock anyway. No impact to texels around this block.
+ const uint32_t num_filtered_texels = (block_width + 2) * (block_height + 2);
+ const uint32_t num_unfiltered_texels = block_width * block_height;
+
+ // boost this wsse so the other penalties (tuned with filtering enabled) are roughly relative to it
+ block_wsse = (block_wsse * num_filtered_texels) / num_unfiltered_texels;
+ }
+
+ block_artifact_penalty_wsse = calc_cross_block_boundary_delta_mismatch(orig_img, candidate_img, block_width, block_height, bx, by, enc_cfg.m_cem_enc_params);
+
+ chroma_loss_penalty_wsse = global_cfg.m_scd_preserve_chroma ? calc_chroma_loss_penalty(orig_img, candidate_img, block_width, block_height, bx, by, enc_cfg.m_cem_enc_params) : 0;
+
+ uint64_t cand_err = block_wsse;
+
+ if (penalize_isolated_base_ofs_blocks)
+ {
+ if (!out_blk.m_log_blk.m_solid_color_flag_ldr && astc_helpers::cem_is_ldr_base_scale(out_blk.m_log_blk.m_color_endpoint_modes[0]))
+ {
+ // penalize base-ofs candidates if there are not enough base-ofs neighbors, as they will likely be more visible
+ cand_err = (cand_err * 5) / 4;
+ //total_base_ofs_penalties++;
+ }
+ }
+
+ cand_err += block_artifact_penalty_wsse + chroma_loss_penalty_wsse;
+
+ cur_err = cand_err;
+ }
+
+ candidate_img.blit(deblock_orig,
+ 0, 0, block_width, block_height,
+ bx * block_width, by * block_height,
+ true);
+
+ return true;
+}
+
+static uint64_t deblocking_compute_overall_error(
+ uint32_t block_width, uint32_t block_height, uint32_t num_blocks_x, uint32_t num_blocks_y,
+ const image& orig_img,
+ const astc_ldr_encode_config& global_cfg,
+ const ldr_astc_block_encode_image_high_level_config& enc_cfg,
+ const ldr_astc_block_encode_image_output& enc_out)
+{
+ const uint32_t width = orig_img.get_width(), height = orig_img.get_height();
+
+ image candidate_img;
+ candidate_img.resize(width, height);
+
+ const astc_helpers::decode_mode dec_mode = global_cfg.m_astc_decode_mode_srgb ? astc_helpers::cDecodeModeSRGB8 : astc_helpers::cDecodeModeLDR8;
+
+ for (uint32_t by = 0; by < num_blocks_y; by++)
+ {
+ for (uint32_t bx = 0; bx < num_blocks_x; bx++)
+ {
+ const ldr_astc_block_encode_image_output::block_info& blk_info = enc_out.m_image_block_info(bx, by);
+ const encode_block_output& best_blk = blk_info.m_out_blocks[blk_info.m_packed_out_block_index];
+
+ const astc_helpers::log_astc_block& log_blk = best_blk.m_log_blk;
+
+ color_rgba block_pixels[astc_helpers::MAX_BLOCK_PIXELS];
+
+ if (!astc_helpers::decode_block_xuastc_ldr(log_blk, block_pixels, block_width, block_height, dec_mode))
+ return false;
+
+ candidate_img.set_block_clipped(block_pixels, bx * block_width, by * block_height, block_width, block_height);
+
+ } // bx
+
+ } // by
+
+ const image orig_candidate_img(candidate_img);
+
+ image deblock_orig(block_width, block_height);
+ image deblock_temp(block_width + 2, block_height + 2);
+
+ uint64_t overall_err = 0;
+
+ for (uint32_t by = 0; by < num_blocks_y; by++)
+ {
+ for (uint32_t bx = 0; bx < num_blocks_x; bx++)
+ {
+ uint64_t cur_err = 0;
+
+ if (!deblocking_compute_error(
+ block_width, block_height, num_blocks_x, num_blocks_y,
+ bx, by,
+ candidate_img,
+ orig_img,
+ global_cfg,
+ enc_cfg,
+ enc_out,
+ cur_err,
+ deblock_orig, deblock_temp))
+ {
+ return UINT64_MAX;
+ }
+
+ overall_err += cur_err;
+
+ } // bx
+ } // by
+
+ fmt_printf("Overall image error: {}, {3.3} per block\n", overall_err, (double)overall_err / (double)(num_blocks_x * num_blocks_y));
+
+ uint32_t total_cand_differences = 0;
+
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ if (candidate_img(x, y) != orig_candidate_img(x, y))
+ total_cand_differences++;
+ }
+ }
+
+ fmt_printf("Total cand differences: {}\n", total_cand_differences);
+ return overall_err;
+}
+
+// ------
+
+static convar g_astc_refine_max_new_blocks("astc_refine_max_new_blocks", 16, 1, 256);
+
+static convar g_astc_refine_try_nonprimary_candidates("astc_refine_try_nonprimary_candidates", 1, 0, 1);
+static convar g_astc_refine_mutate_part_id_prob("astc_refine_mutate_part_id_prob", 10, 0, 100);
+static convar g_astc_refine_mutate_endpoint_edge("astc_refine_mutate_endpoint_edge", 1, 0, 1);
+static convar g_astc_refine_mutate_dct("astc_refine_mutate_dct", 1, 0, 1);
+static convar g_astc_refine_mutate_endpoints("astc_refine_mutate_endpoints", 1, 0, 1);
+static convar g_astc_refine_base_seed("astc_refine_base_seed", 0, INT_MIN, INT_MAX);
+
+static bool mutate_candidates(
+ uint32_t pass,
+ uint32_t block_width, uint32_t block_height, uint32_t num_blocks_x, uint32_t num_blocks_y, uint32_t total_blocks,
+ const uint32_t max_new_blocks, const uint_vec* const pJob_block_list, const vector2D &block_std_dev,
+ const uint32_t NUM_SIMILAR_PATS, const vector2D