From 74a81997f93dcc1f4ca4de69166ba496c55f2a1f Mon Sep 17 00:00:00 2001 From: Wasim Abbas Date: Fri, 1 Dec 2023 16:42:12 +0000 Subject: [PATCH 1/8] Add missing backslahes to build_macos.sh --- ci_scripts/build_macos.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ci_scripts/build_macos.sh b/ci_scripts/build_macos.sh index b8432d7394..8e18969bc1 100755 --- a/ci_scripts/build_macos.sh +++ b/ci_scripts/build_macos.sh @@ -76,8 +76,9 @@ cmake_args=("-G" "Xcode" \ "-D" "KTX_FEATURE_TESTS=$FEATURE_TESTS" \ "-D" "KTX_FEATURE_TOOLS=$FEATURE_TOOLS" \ "-D" "KTX_FEATURE_TOOLS_CTS=$FEATURE_TOOLS_CTS" \ - "-D" "KTX_LOADTEST_APPS_USE_LOCAL_DEPENDENCIES=$LOADTESTS_USE_LOCAL_DEPENDENCIES" - "-D" "KTX_WERROR=$WERROR" + "-D" "KTX_LOADTEST_APPS_USE_LOCAL_DEPENDENCIES=$LOADTESTS_USE_LOCAL_DEPENDENCIES" \ + "-D" "KTX_WERROR=$WERROR" \ + "-D" "CMAKE_EXPORT_COMPILE_COMMANDS=ON" \ "-D" "BASISU_SUPPORT_OPENCL=$SUPPORT_OPENCL" \ "-D" "BASISU_SUPPORT_SSE=$SUPPORT_SSE" \ ) From 9000e0c151f290b9856b34e9bef935e21d1a54b6 Mon Sep 17 00:00:00 2001 From: Wasim Abbas Date: Fri, 1 Dec 2023 16:43:53 +0000 Subject: [PATCH 2/8] Move astc options out of create --- tools/ktx/astc_utils.h | 89 ++++++++++++++++++++++++++++++++++++ tools/ktx/command_create.cpp | 79 +------------------------------- 2 files changed, 90 insertions(+), 78 deletions(-) create mode 100644 tools/ktx/astc_utils.h diff --git a/tools/ktx/astc_utils.h b/tools/ktx/astc_utils.h new file mode 100644 index 0000000000..1654791f2c --- /dev/null +++ b/tools/ktx/astc_utils.h @@ -0,0 +1,89 @@ +// Copyright 2022-2023 The Khronos Group Inc. +// Copyright 2022-2023 RasterGrid Kft. +// SPDX-License-Identifier: Apache-2.0 + +#include "command.h" +#include "utility.h" +#include + +namespace ktx { + +struct OptionsASTC : public ktxAstcParams { + inline static const char* kAstcQuality = "astc-quality"; + inline static const char* kAstcPerceptual = "astc-perceptual"; + + std::string astcOptions{}; + bool encodeASTC = false; + ClampedOption qualityLevel{ktxAstcParams::qualityLevel, 0, KTX_PACK_ASTC_QUALITY_LEVEL_MAX}; + + OptionsASTC() : ktxAstcParams() { + threadCount = std::thread::hardware_concurrency(); + if (threadCount == 0) + threadCount = 1; + structSize = sizeof(ktxAstcParams); + normalMap = false; + for (int i = 0; i < 4; i++) + inputSwizzle[i] = 0; + qualityLevel.clear(); + } + + void init(cxxopts::Options& opts) { + opts.add_options("Encode ASTC") + (kAstcQuality, + "The quality level configures the quality-performance tradeoff for " + "the compressor; more complete searches of the search space " + "improve image quality at the expense of compression time. Default " + "is 'medium'. The quality level can be set between fastest (0) and " + "exhaustive (100) via the following fixed quality presets:\n\n" + " Level | Quality\n" + " ---------- | -----------------------------\n" + " fastest | (equivalent to quality = 0)\n" + " fast | (equivalent to quality = 10)\n" + " medium | (equivalent to quality = 60)\n" + " thorough | (equivalent to quality = 98)\n" + " exhaustive | (equivalent to quality = 100)", + cxxopts::value(), "") + (kAstcPerceptual, + "The codec should optimize for perceptual error, instead of direct " + "RMS error. This aims to improve perceived image quality, but " + "typically lowers the measured PSNR score. Perceptual methods are " + "currently only available for normal maps and RGB color data."); + } + + void captureASTCOption(const char* name) { + astcOptions += fmt::format(" --{}", name); + } + + template + T captureASTCOption(cxxopts::ParseResult& args, const char* name) { + const T value = args[name].as(); + astcOptions += fmt::format(" --{} {}", name, value); + return value; + } + + void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) { + if (args[kAstcQuality].count()) { + static std::unordered_map astc_quality_mapping{ + {"fastest", KTX_PACK_ASTC_QUALITY_LEVEL_FASTEST}, + {"fast", KTX_PACK_ASTC_QUALITY_LEVEL_FAST}, + {"medium", KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM}, + {"thorough", KTX_PACK_ASTC_QUALITY_LEVEL_THOROUGH}, + {"exhaustive", KTX_PACK_ASTC_QUALITY_LEVEL_EXHAUSTIVE} + }; + const auto qualityLevelStr = to_lower_copy(captureASTCOption(args, kAstcQuality)); + const auto it = astc_quality_mapping.find(qualityLevelStr); + if (it == astc_quality_mapping.end()) + report.fatal_usage("Invalid astc-quality: \"{}\"", qualityLevelStr); + qualityLevel = it->second; + } else { + qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM; + } + + if (args[kAstcPerceptual].count()) { + captureASTCOption(kAstcPerceptual); + perceptual = KTX_TRUE; + } + } +}; + +} // namespace ktx diff --git a/tools/ktx/command_create.cpp b/tools/ktx/command_create.cpp index 8a7580ec37..650024271d 100644 --- a/tools/ktx/command_create.cpp +++ b/tools/ktx/command_create.cpp @@ -17,6 +17,7 @@ #include #include #include "ktx.h" +#include "astc_utils.h" #include "image.hpp" #include "imageio.h" @@ -551,84 +552,6 @@ struct OptionsCreate { } }; -struct OptionsASTC : public ktxAstcParams { - inline static const char* kAstcQuality = "astc-quality"; - inline static const char* kAstcPerceptual = "astc-perceptual"; - - std::string astcOptions{}; - bool encodeASTC = false; - ClampedOption qualityLevel{ktxAstcParams::qualityLevel, 0, KTX_PACK_ASTC_QUALITY_LEVEL_MAX}; - - OptionsASTC() : ktxAstcParams() { - threadCount = std::thread::hardware_concurrency(); - if (threadCount == 0) - threadCount = 1; - structSize = sizeof(ktxAstcParams); - normalMap = false; - for (int i = 0; i < 4; i++) - inputSwizzle[i] = 0; - qualityLevel.clear(); - } - - void init(cxxopts::Options& opts) { - opts.add_options("Encode ASTC") - (kAstcQuality, - "The quality level configures the quality-performance tradeoff for " - "the compressor; more complete searches of the search space " - "improve image quality at the expense of compression time. Default " - "is 'medium'. The quality level can be set between fastest (0) and " - "exhaustive (100) via the following fixed quality presets:\n\n" - " Level | Quality\n" - " ---------- | -----------------------------\n" - " fastest | (equivalent to quality = 0)\n" - " fast | (equivalent to quality = 10)\n" - " medium | (equivalent to quality = 60)\n" - " thorough | (equivalent to quality = 98)\n" - " exhaustive | (equivalent to quality = 100)", - cxxopts::value(), "") - (kAstcPerceptual, - "The codec should optimize for perceptual error, instead of direct " - "RMS error. This aims to improve perceived image quality, but " - "typically lowers the measured PSNR score. Perceptual methods are " - "currently only available for normal maps and RGB color data."); - } - - void captureASTCOption(const char* name) { - astcOptions += fmt::format(" --{}", name); - } - - template - T captureASTCOption(cxxopts::ParseResult& args, const char* name) { - const T value = args[name].as(); - astcOptions += fmt::format(" --{} {}", name, value); - return value; - } - - void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) { - if (args[kAstcQuality].count()) { - static std::unordered_map astc_quality_mapping{ - {"fastest", KTX_PACK_ASTC_QUALITY_LEVEL_FASTEST}, - {"fast", KTX_PACK_ASTC_QUALITY_LEVEL_FAST}, - {"medium", KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM}, - {"thorough", KTX_PACK_ASTC_QUALITY_LEVEL_THOROUGH}, - {"exhaustive", KTX_PACK_ASTC_QUALITY_LEVEL_EXHAUSTIVE} - }; - const auto qualityLevelStr = to_lower_copy(captureASTCOption(args, kAstcQuality)); - const auto it = astc_quality_mapping.find(qualityLevelStr); - if (it == astc_quality_mapping.end()) - report.fatal_usage("Invalid astc-quality: \"{}\"", qualityLevelStr); - qualityLevel = it->second; - } else { - qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM; - } - - if (args[kAstcPerceptual].count()) { - captureASTCOption(kAstcPerceptual); - perceptual = KTX_TRUE; - } - } -}; - // ------------------------------------------------------------------------------------------------- /** @page ktx_create ktx create From 4263d61a8197821be807f9d67e954ac74468aff0 Mon Sep 17 00:00:00 2001 From: Wasim Abbas Date: Fri, 1 Dec 2023 17:04:54 +0000 Subject: [PATCH 3/8] Add encode-astc support --- tools/ktx/CMakeLists.txt | 1 + tools/ktx/command_encode_astc.cpp | 205 ++++++++++++++++++++++++++++++ tools/ktx/ktx_main.cpp | 16 ++- 3 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 tools/ktx/command_encode_astc.cpp diff --git a/tools/ktx/CMakeLists.txt b/tools/ktx/CMakeLists.txt index 0b2dff1880..37aa5fb757 100644 --- a/tools/ktx/CMakeLists.txt +++ b/tools/ktx/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(ktxtools command.h command_create.cpp command_encode.cpp + command_encode_astc.cpp command_extract.cpp command_help.cpp command_info.cpp diff --git a/tools/ktx/command_encode_astc.cpp b/tools/ktx/command_encode_astc.cpp new file mode 100644 index 0000000000..b7b29fc035 --- /dev/null +++ b/tools/ktx/command_encode_astc.cpp @@ -0,0 +1,205 @@ +// Copyright 2022-2023 The Khronos Group Inc. +// Copyright 2022-2023 RasterGrid Kft. +// SPDX-License-Identifier: Apache-2.0 + +#include "command.h" +#include "platform_utils.h" +#include "metrics_utils.h" +#include "compress_utils.h" +#include "encode_utils.h" +#include "formats.h" +#include "sbufstream.h" +#include "utility.h" +#include "validate.h" +#include "astc_utils.h" +#include "ktx.h" +#include +#include +#include +#include +#include + +#include +#include +#include + + +namespace ktx { + +/** @page ktx_encode_astc ktx encode_astc +@~English + +Encode a KTX2 file. + +@section ktx_encode_astc_synopsis SYNOPSIS + ktx encode-astc [option...] @e input-file @e output-file + +@section ktx_encode_astc_description DESCRIPTION + @b ktx @b encode can encode the KTX file specified as the @e input-file argument, + optionally supercompress the result, and save it as the @e output-file. + If the @e input-file is '-' the file will be read from the stdin. + If the @e output-path is '-' the output file will be written to the stdout. + The input file must be R8, RG8, RGB8 or RGBA8 (or their sRGB variant). + If the input file is invalid the first encountered validation error is displayed + to the stderr and the command exits with the relevant non-zero status code. + + The following options are available: +
+ @snippet{doc} ktx/encode_utils.h command options_codec + @snippet{doc} ktx/metrics_utils.h command options_metrics +
+ @snippet{doc} ktx/compress_utils.h command options_compress + @snippet{doc} ktx/command.h command options_generic + +@section ktx_encode_astc_exitstatus EXIT STATUS + @snippet{doc} ktx/command.h command exitstatus + +@section ktx_encode_astc_history HISTORY + +@par Version 4.0 + - Initial version + +@section ktx_encode_astc_author AUTHOR + - Wasim Abbas, Arm Ltd. www.arm.com +*/ +class CommandEncodeAstc : public Command { + enum { + all = -1, + }; + + struct OptionsEncode { + void init(cxxopts::Options& opts); + void process(cxxopts::Options& opts, cxxopts::ParseResult& args, Reporter& report); + }; + + Combine options; + +public: + virtual int main(int argc, char* argv[]) override; + virtual void initOptions(cxxopts::Options& opts) override; + virtual void processOptions(cxxopts::Options& opts, cxxopts::ParseResult& args) override; + +private: + void executeEncodeAstc(); +}; + +// ------------------------------------------------------------------------------------------------- + +int CommandEncodeAstc::main(int argc, char* argv[]) { + try { + parseCommandLine("ktx encode-astc", + "ASTC Encode the KTX file specified as the input-file argument,\n" + " optionally supercompress the result, and save it as the output-file.", + argc, argv); + executeEncodeAstc(); + return +rc::SUCCESS; + } catch (const FatalError& error) { + return +error.returnCode; + } catch (const std::exception& e) { + fmt::print(std::cerr, "{} fatal: {}\n", commandName, e.what()); + return +rc::RUNTIME_ERROR; + } +} + +void CommandEncodeAstc::OptionsEncode::init(cxxopts::Options&) { +} + +void CommandEncodeAstc::OptionsEncode::process(cxxopts::Options&, cxxopts::ParseResult&, Reporter&) { +} + +void CommandEncodeAstc::initOptions(cxxopts::Options& opts) { + options.init(opts); +} + +void CommandEncodeAstc::processOptions(cxxopts::Options& opts, cxxopts::ParseResult& args) { + options.process(opts, args, *this); +} + +void CommandEncodeAstc::executeEncodeAstc() { + InputStream inputStream(options.inputFilepath, *this); + validateToolInput(inputStream, fmtInFile(options.inputFilepath), *this); + + KTXTexture2 texture{nullptr}; + StreambufStream ktx2Stream{inputStream->rdbuf(), std::ios::in | std::ios::binary}; + auto ret = ktxTexture2_CreateFromStream(ktx2Stream.stream(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, texture.pHandle()); + if (ret != KTX_SUCCESS) + fatal(rc::INVALID_FILE, "Failed to create KTX2 texture: {}", ktxErrorString(ret)); + + if (texture->supercompressionScheme != KTX_SS_NONE) + fatal(rc::INVALID_FILE, "Cannot encode KTX2 file with {} supercompression.", + toString(ktxSupercmpScheme(texture->supercompressionScheme))); + + switch (texture->vkFormat) { + case VK_FORMAT_R8_UNORM: + case VK_FORMAT_R8_SRGB: + case VK_FORMAT_R8G8_UNORM: + case VK_FORMAT_R8G8_SRGB: + case VK_FORMAT_R8G8B8_UNORM: + case VK_FORMAT_R8G8B8_SRGB: + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_R8G8B8A8_SRGB: + // Allowed formats + break; + default: + fatal_usage("Only R8, RG8, RGB8, or RGBA8 UNORM and SRGB formats can be encoded, " + "but format is {}.", toString(VkFormat(texture->vkFormat))); + break; + } + + // Convert 1D textures to 2D (we could consider 1D as an invalid input) + texture->numDimensions = std::max(2u, texture->numDimensions); + + // Modify KTXwriter metadata + const auto writer = fmt::format("{} {}", commandName, version(options.testrun)); + ktxHashList_DeleteKVPair(&texture->kvDataHead, KTX_WRITER_KEY); + ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_KEY, + static_cast(writer.size() + 1), // +1 to include the \0 + writer.c_str()); + + ktx_uint32_t oetf = ktxTexture2_GetOETF(texture); + if (options.normalMap && oetf != KHR_DF_TRANSFER_LINEAR) + fatal(rc::INVALID_FILE, + "--normal-mode specified but the input file uses non-linear transfer function {}.", + toString(khr_df_transfer_e(oetf))); + + MetricsCalculator metrics; + metrics.saveReferenceImages(texture, options, *this); + ret = ktxTexture2_CompressAstcEx(texture, &options); + if (ret != KTX_SUCCESS) + fatal(rc::IO_FAILURE, "Failed to encode KTX2 file with codec \"{}\". KTX Error: {}", ktxErrorString(ret)); + metrics.decodeAndCalculateMetrics(texture, options, *this); + + if (options.zstd) { + ret = ktxTexture2_DeflateZstd(texture, *options.zstd); + if (ret != KTX_SUCCESS) + fatal(rc::IO_FAILURE, "Zstd deflation failed. KTX Error: {}", ktxErrorString(ret)); + } + + if (options.zlib) { + ret = ktxTexture2_DeflateZLIB(texture, *options.zlib); + if (ret != KTX_SUCCESS) + fatal(rc::IO_FAILURE, "ZLIB deflation failed. KTX Error: {}", ktxErrorString(ret)); + } + + // Add KTXwriterScParams metadata + const auto writerScParams = fmt::format("{}{}", options.astcOptions, options.compressOptions); + if (writerScParams.size() > 0) { + // Options always contain a leading space + assert(writerScParams[0] == ' '); + ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_SCPARAMS_KEY, + static_cast(writerScParams.size()), + writerScParams.c_str() + 1); // +1 to exclude leading space + } + + // Save output file + const auto outputPath = std::filesystem::path(DecodeUTF8Path(options.outputFilepath)); + if (outputPath.has_parent_path()) + std::filesystem::create_directories(outputPath.parent_path()); + + OutputStream outputFile(options.outputFilepath, *this); + outputFile.writeKTX2(texture, *this); +} + +} // namespace ktx + +KTX_COMMAND_ENTRY_POINT(ktxEncodeAstc, ktx::CommandEncodeAstc) diff --git a/tools/ktx/ktx_main.cpp b/tools/ktx/ktx_main.cpp index b4b0b76a56..1ab319d069 100644 --- a/tools/ktx/ktx_main.cpp +++ b/tools/ktx/ktx_main.cpp @@ -160,19 +160,21 @@ void Tools::printUsage(std::ostream& os, const cxxopts::Options& options) { KTX_COMMAND_BUILTIN(ktxCreate) KTX_COMMAND_BUILTIN(ktxExtract) KTX_COMMAND_BUILTIN(ktxEncode) +KTX_COMMAND_BUILTIN(ktxEncodeAstc) KTX_COMMAND_BUILTIN(ktxTranscode) KTX_COMMAND_BUILTIN(ktxInfo) KTX_COMMAND_BUILTIN(ktxValidate) KTX_COMMAND_BUILTIN(ktxHelp) std::unordered_map builtinCommands = { - { "create", ktxCreate }, - { "extract", ktxExtract }, - { "encode", ktxEncode }, - { "transcode", ktxTranscode }, - { "info", ktxInfo }, - { "validate", ktxValidate }, - { "help", ktxHelp } + { "create", ktxCreate }, + { "extract", ktxExtract }, + { "encode", ktxEncode }, + { "encode-astc", ktxEncodeAstc }, + { "transcode", ktxTranscode }, + { "info", ktxInfo }, + { "validate", ktxValidate }, + { "help", ktxHelp } }; int main(int argc, char* argv[]) { From c4cc963647071e45787c7bc84a27468129ec9cde Mon Sep 17 00:00:00 2001 From: Wasim Abbas Date: Sat, 2 Dec 2023 12:37:03 +0000 Subject: [PATCH 4/8] Shuffle around doxygen snippet and remove CMAKE_EXPORT_COMPILE_COMMANDS --- ci_scripts/build_macos.sh | 1 - tools/ktx/astc_utils.h | 28 ++++++++++++++++++++++++++++ tools/ktx/command_create.cpp | 25 +------------------------ tools/ktx/command_encode_astc.cpp | 3 +-- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/ci_scripts/build_macos.sh b/ci_scripts/build_macos.sh index 8e18969bc1..0c129efd6d 100755 --- a/ci_scripts/build_macos.sh +++ b/ci_scripts/build_macos.sh @@ -78,7 +78,6 @@ cmake_args=("-G" "Xcode" \ "-D" "KTX_FEATURE_TOOLS_CTS=$FEATURE_TOOLS_CTS" \ "-D" "KTX_LOADTEST_APPS_USE_LOCAL_DEPENDENCIES=$LOADTESTS_USE_LOCAL_DEPENDENCIES" \ "-D" "KTX_WERROR=$WERROR" \ - "-D" "CMAKE_EXPORT_COMPILE_COMMANDS=ON" \ "-D" "BASISU_SUPPORT_OPENCL=$SUPPORT_OPENCL" \ "-D" "BASISU_SUPPORT_SSE=$SUPPORT_SSE" \ ) diff --git a/tools/ktx/astc_utils.h b/tools/ktx/astc_utils.h index 1654791f2c..0c50993e9c 100644 --- a/tools/ktx/astc_utils.h +++ b/tools/ktx/astc_utils.h @@ -8,6 +8,34 @@ namespace ktx { +/** +//! [command options_astc] +
+
\--astc-quality <level>
+
The quality level configures the quality-performance + tradeoff for the compressor; more complete searches of the + search space improve image quality at the expense of + compression time. Default is 'medium'. The quality level can be + set between fastest (0) and exhaustive (100) via the + following fixed quality presets: + + + + + + + +
Level Quality
fastest (equivalent to quality = 0)
fast (equivalent to quality = 10)
medium (equivalent to quality = 60)
thorough (equivalent to quality = 98)
exhaustive (equivalent to quality = 100)
+
+
\--astc-perceptual
+
The codec should optimize for perceptual error, instead of + direct RMS error. This aims to improve perceived image quality, + but typically lowers the measured PSNR score. Perceptual + methods are currently only available for normal maps and RGB + color data.
+
+//! [command options_astc] +*/ struct OptionsASTC : public ktxAstcParams { inline static const char* kAstcQuality = "astc-quality"; inline static const char* kAstcPerceptual = "astc-perceptual"; diff --git a/tools/ktx/command_create.cpp b/tools/ktx/command_create.cpp index 650024271d..7284b7b9f1 100644 --- a/tools/ktx/command_create.cpp +++ b/tools/ktx/command_create.cpp @@ -601,30 +601,7 @@ Create a KTX2 file from various input files. otherwise they are ignored.
The format will be used to verify and load all input files into a texture before encoding.
Case insensitive. Required. -
-
\--astc-quality <level>
-
The quality level configures the quality-performance - tradeoff for the compressor; more complete searches of the - search space improve image quality at the expense of - compression time. Default is 'medium'. The quality level can be - set between fastest (0) and exhaustive (100) via the - following fixed quality presets: - - - - - - - -
Level Quality
fastest (equivalent to quality = 0)
fast (equivalent to quality = 10)
medium (equivalent to quality = 60)
thorough (equivalent to quality = 98)
exhaustive (equivalent to quality = 100)
-
-
\--astc-perceptual
-
The codec should optimize for perceptual error, instead of - direct RMS error. This aims to improve perceived image quality, - but typically lowers the measured PSNR score. Perceptual - methods are currently only available for normal maps and RGB - color data.
-
+ @snippet{doc} ktx/astc_utils.h command options_astc
\--1d
Create a 1D texture. If not set the texture will be a 2D or 3D texture.
\--cubemap
diff --git a/tools/ktx/command_encode_astc.cpp b/tools/ktx/command_encode_astc.cpp index b7b29fc035..73eb1a4ff2 100644 --- a/tools/ktx/command_encode_astc.cpp +++ b/tools/ktx/command_encode_astc.cpp @@ -23,7 +23,6 @@ #include #include - namespace ktx { /** @page ktx_encode_astc ktx encode_astc @@ -45,7 +44,7 @@ Encode a KTX2 file. The following options are available:
- @snippet{doc} ktx/encode_utils.h command options_codec + @snippet{doc} ktx/astc_utils.h command options_astc @snippet{doc} ktx/metrics_utils.h command options_metrics
@snippet{doc} ktx/compress_utils.h command options_compress From 24d0fee57380ae128f91b24ae54babd3c43171f1 Mon Sep 17 00:00:00 2001 From: Wasim Abbas Date: Tue, 5 Dec 2023 00:44:21 +0000 Subject: [PATCH 5/8] Add decode method for astc textures that is used by psnr and ssim metrics --- include/ktx.h | 3 + lib/astc_encode.cpp | 312 +++++++++++++++++++++++++++++- tools/ktx/command_create.cpp | 6 +- tools/ktx/command_encode_astc.cpp | 11 +- tools/ktx/metrics_utils.h | 15 +- 5 files changed, 334 insertions(+), 13 deletions(-) diff --git a/include/ktx.h b/include/ktx.h index dcf5dae42a..d741f6dde4 100644 --- a/include/ktx.h +++ b/include/ktx.h @@ -1258,6 +1258,9 @@ ktxTexture2_CompressAstcEx(ktxTexture2* This, ktxAstcParams* params); KTX_API KTX_error_code KTX_APIENTRY ktxTexture2_CompressAstc(ktxTexture2* This, ktx_uint32_t quality); +KTX_API KTX_error_code KTX_APIENTRY +ktxTexture2_DecodeAstc(ktxTexture2* This, ktx_uint32_t vkformat); + /** * @memberof ktxTexture2 * @~English diff --git a/lib/astc_encode.cpp b/lib/astc_encode.cpp index 657b518d12..8376cdb4bb 100644 --- a/lib/astc_encode.cpp +++ b/lib/astc_encode.cpp @@ -360,7 +360,7 @@ astcSwizzle(const ktxAstcParams ¶ms) { static void astcBlockDimensions(ktx_uint32_t block_size, - uint32_t& block_x, uint32_t& block_y, uint32_t& block_z) { + uint32_t& block_x, uint32_t& block_y, uint32_t& block_z) { switch (block_size) { case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4 : block_x = 4; block_y = 4; block_z = 1; break; case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4 : block_x = 5; block_y = 4; block_z = 1; break; @@ -391,6 +391,87 @@ astcBlockDimensions(ktx_uint32_t block_size, } } +static void +astcBlockDimensions(VkFormat format, + uint32_t &x, uint32_t &y, uint32_t &z) noexcept { + switch (format) { + case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: x = 4; y = 4; z = 0; break; + case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: x = 4; y = 4; z = 0; break; + case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: x = 5; y = 4; z = 0; break; + case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: x = 5; y = 4; z = 0; break; + case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: x = 5; y = 5; z = 0; break; + case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: x = 5; y = 5; z = 0; break; + case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: x = 6; y = 5; z = 0; break; + case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: x = 6; y = 5; z = 0; break; + case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: x = 6; y = 6; z = 0; break; + case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: x = 6; y = 6; z = 0; break; + case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: x = 8; y = 5; z = 0; break; + case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: x = 8; y = 5; z = 0; break; + case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: x = 8; y = 6; z = 0; break; + case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: x = 8; y = 6; z = 0; break; + case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: x = 8; y = 8; z = 0; break; + case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: x = 8; y = 8; z = 0; break; + case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: x = 10; y = 5; z = 0; break; + case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: x = 10; y = 5; z = 0; break; + case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: x = 10; y = 6; z = 0; break; + case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: x = 10; y = 6; z = 0; break; + case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: x = 10; y = 8; z = 0; break; + case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: x = 10; y = 8; z = 0; break; + case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: x = 10; y = 10; z = 0; break; + case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: x = 10; y = 10; z = 0; break; + case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: x = 12; y = 10; z = 0; break; + case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: x = 12; y = 10; z = 0; break; + case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: x = 12; y = 12; z = 0; break; + case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: x = 12; y = 12; z = 0; break; + case VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK: x = 4; y = 4; z = 0; break; + case VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK: x = 5; y = 4; z = 0; break; + case VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK: x = 5; y = 5; z = 0; break; + case VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK: x = 6; y = 5; z = 0; break; + case VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK: x = 6; y = 6; z = 0; break; + case VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK: x = 8; y = 5; z = 0; break; + case VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK: x = 8; y = 6; z = 0; break; + case VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK: x = 8; y = 8; z = 0; break; + case VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK: x = 10; y = 5; z = 0; break; + case VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK: x = 10; y = 6; z = 0; break; + case VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK: x = 10; y = 8; z = 0; break; + case VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK: x = 10; y = 10; z = 0; break; + case VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK: x = 12; y = 10; z = 0; break; + case VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK: x = 12; y = 12; z = 0; break; + case VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT: x = 3; y = 3; z = 3; break; + case VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT: x = 3; y = 3; z = 3; break; + case VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT: x = 3; y = 3; z = 3; break; + case VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT: x = 4; y = 3; z = 3; break; + case VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT: x = 4; y = 3; z = 3; break; + case VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT: x = 4; y = 3; z = 3; break; + case VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT: x = 4; y = 4; z = 3; break; + case VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT: x = 4; y = 4; z = 3; break; + case VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT: x = 4; y = 4; z = 3; break; + case VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT: x = 4; y = 4; z = 4; break; + case VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT: x = 4; y = 4; z = 4; break; + case VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT: x = 4; y = 4; z = 4; break; + case VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT: x = 5; y = 4; z = 4; break; + case VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT: x = 5; y = 4; z = 4; break; + case VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT: x = 5; y = 4; z = 4; break; + case VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT: x = 5; y = 5; z = 4; break; + case VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT: x = 5; y = 5; z = 4; break; + case VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT: x = 5; y = 5; z = 4; break; + case VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT: x = 5; y = 5; z = 5; break; + case VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT: x = 5; y = 5; z = 5; break; + case VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT: x = 5; y = 5; z = 5; break; + case VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT: x = 6; y = 5; z = 5; break; + case VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT: x = 6; y = 5; z = 5; break; + case VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT: x = 6; y = 5; z = 5; break; + case VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT: x = 6; y = 6; z = 5; break; + case VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT: x = 6; y = 6; z = 5; break; + case VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT: x = 6; y = 6; z = 5; break; + case VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT: x = 6; y = 6; z = 6; break; + case VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT: x = 6; y = 6; z = 6; break; + case VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT: x = 6; y = 6; z = 6; break; + default: + x = 0; y = 0; z = 0; + } +} + static float astcQuality(ktx_uint32_t quality_level) { switch (quality_level) { @@ -803,3 +884,232 @@ ktxTexture2_CompressAstc(ktxTexture2* This, ktx_uint32_t quality) { return ktxTexture2_CompressAstcEx(This, ¶ms); } + +struct decompression_workload +{ + astcenc_context* context; + uint8_t* data; + size_t data_len; + astcenc_image* image_out; + astcenc_swizzle swizzle; + astcenc_error error; +}; + +/** + * @brief Runner callback function for a decompression worker thread. + * + * @param thread_count The number of threads in the worker pool. + * @param thread_id The index of this thread in the worker pool. + * @param payload The parameters for this thread. + */ +static void decompression_workload_runner(int thread_count, int thread_id, void* payload) { + (void)thread_count; + + decompression_workload* work = static_cast(payload); + astcenc_error error = astcenc_decompress_image(work->context, work->data, work->data_len, + work->image_out, &work->swizzle, thread_id); + // This is a racy update, so which error gets returned is a random, but it + // will reliably report an error if an error occurs + if (error != ASTCENC_SUCCESS) + { + work->error = error; + } +} + +/** + * @brief Decodes the provided ktx2 texture if its astc encoded + * + * @param This The texture to decode + * @param vkformat The decoding format to use + */ +// will update "This" with the uncompressed copy +KTX_API KTX_error_code KTX_APIENTRY +ktxTexture2_DecodeAstc(ktxTexture2 *This, ktx_uint32_t vkformat) { + // Decompress This using astc-encoder + uint32_t* BDB = This->pDfd + 1; + khr_df_model_e colorModel = (khr_df_model_e)KHR_DFDVAL(BDB, MODEL); + if (colorModel != KHR_DF_MODEL_ASTC && This->supercompressionScheme != KTX_SS_NONE) // No supercompression supported yet + { + return KTX_INVALID_OPERATION; // Not in valid astc decodable format + } + + DECLARE_PRIVATE(priv, This); + + uint32_t channelId = KHR_DFDSVAL(BDB, 0, CHANNELID); + if (channelId == KHR_DF_CHANNEL_ASTC_DATA) { + // Found astc data + } + else + return KTX_FILE_DATA_ERROR; + + // Create a prototype texture to use for calculating sizes in the target + // format and, as useful side effects, provide us with a properly sized + // data allocation and the DFD for the target format. + ktxTextureCreateInfo createInfo; + createInfo.glInternalformat = 0; + createInfo.vkFormat = vkformat; + createInfo.baseWidth = This->baseWidth; + createInfo.baseHeight = This->baseHeight; + createInfo.baseDepth = This->baseDepth; + createInfo.generateMipmaps = This->generateMipmaps; + createInfo.isArray = This->isArray; + createInfo.numDimensions = This->numDimensions; + createInfo.numFaces = This->numFaces; + createInfo.numLayers = This->numLayers; + createInfo.numLevels = This->numLevels; + createInfo.pDfd = nullptr; + + KTX_error_code result; + ktxTexture2* prototype; + result = ktxTexture2_Create(&createInfo, KTX_TEXTURE_CREATE_ALLOC_STORAGE, &prototype); + + if (result != KTX_SUCCESS) { + assert(result == KTX_OUT_OF_MEMORY); // The only run time error + return result; + } + + if (!This->pData) { + if (ktxTexture_isActiveStream((ktxTexture*)This)) { + // Load pending. Complete it. + result = ktxTexture2_LoadImageData(This, NULL, 0); + if (result != KTX_SUCCESS) + { + ktxTexture2_Destroy(prototype); + return result; + } + } else { + // No data to transcode. + ktxTexture2_Destroy(prototype); + return KTX_INVALID_OPERATION; + } + } + + // This is where I do the decompression from "This" to prototype target + astcenc_profile profile{ASTCENC_PRF_LDR_SRGB}; + astcenc_swizzle swizzle{ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A}; + + uint32_t block_size_x{4}; // Get the right blocks from vkformat + uint32_t block_size_y{4}; + uint32_t block_size_z{1}; + float quality{ASTCENC_PRE_MEDIUM}; + uint32_t flags{0}; // TODO: Use normals mode to reconstruct normals params->normalMap ? ASTCENC_FLG_MAP_NORMAL : 0}; + + astcBlockDimensions((VkFormat)vkformat, block_size_x, block_size_y, block_size_z); + + // quality = astcQuality(params->qualityLevel); + // profile = astcEncoderAction(*params, BDB); + // swizzle = astcSwizzle(*params); + + // if(params->perceptual) flags |= ASTCENC_FLG_USE_PERCEPTUAL; + + uint32_t threadCount{1}; // Decompression isn't the bottlneck and only used when checking for psnr and ssim + astcenc_config astc_config; + astcenc_context *astc_context; + astcenc_error astc_error = astcenc_config_init(profile, + block_size_x, block_size_y, block_size_z, + quality, flags, + &astc_config); + + if (astc_error != ASTCENC_SUCCESS) + return KTX_INVALID_OPERATION; + + astc_error = astcenc_context_alloc(&astc_config, threadCount, &astc_context); + + if (astc_error != ASTCENC_SUCCESS) + return KTX_INVALID_OPERATION; + + decompression_workload work; + work.context = astc_context; + work.swizzle = swizzle; + work.error = ASTCENC_SUCCESS; + + for (uint32_t levelIndex = 0; levelIndex < This->numLevels; ++levelIndex) { + const uint32_t imageWidth = std::max(This->baseWidth >> levelIndex, 1u); + const uint32_t imageHeight = std::max(This->baseHeight >> levelIndex, 1u); + const uint32_t imageDepths = std::max(This->baseDepth >> levelIndex, 1u); + + for (uint32_t layerIndex = 0; layerIndex < This->numLayers; ++layerIndex) { + for (uint32_t faceIndex = 0; faceIndex < This->numFaces; ++faceIndex) { + for (uint32_t depthSliceIndex = 0; depthSliceIndex < imageDepths; ++depthSliceIndex) { + + ktx_size_t levelImageSizeIn = ktxTexture_calcImageSize(ktxTexture(This), levelIndex, KTX_FORMAT_VERSION_TWO); + + ktx_size_t imageOffsetIn; + ktx_size_t imageOffsetOut; + + ktxTexture2_GetImageOffset(This, levelIndex, layerIndex, faceIndex + depthSliceIndex, &imageOffsetIn); + ktxTexture2_GetImageOffset(prototype, levelIndex, layerIndex, faceIndex + depthSliceIndex, &imageOffsetOut); + + auto* imageDataIn = This->pData + imageOffsetIn; + auto* imageDataOut = prototype->pData + imageOffsetOut; + + astcenc_image imageOut; + imageOut.dim_x = imageWidth; + imageOut.dim_y = imageHeight; + imageOut.dim_z = imageDepths; + imageOut.data_type = ASTCENC_TYPE_U8; // TODO: Fix for HDR types + imageOut.data = (void**)&imageDataOut; // TODO: Fix for HDR types + + work.data = imageDataIn; + work.data_len = levelImageSizeIn; + work.image_out = &imageOut; + + // Only launch worker threads for multi-threaded use - it makes basic + // single-threaded profiling and debugging a little less convoluted + if (threadCount > 1) { + launchThreads(threadCount, decompression_workload_runner, &work); + } else { + work.error = astcenc_decompress_image(work.context, work.data, work.data_len, + work.image_out, &work.swizzle, 0); + } + + // Reset ASTC context for next image + astcenc_decompress_reset(astc_context); + + if (work.error != ASTCENC_SUCCESS) { + std::cout << "ASTC decompressor failed\n" << astcenc_get_error_string(work.error) << std::endl; + + astcenc_context_free(astc_context); + return KTX_INVALID_OPERATION; + } + } + } + } + } + + // We are done with astcencoder + astcenc_context_free(astc_context); + + if (result == KTX_SUCCESS) { + // Fix up the current texture + DECLARE_PROTECTED(thisPrtctd, This); + DECLARE_PRIVATE(protoPriv, prototype); + DECLARE_PROTECTED(protoPrtctd, prototype); + memcpy(&thisPrtctd._formatSize, &protoPrtctd._formatSize, + sizeof(ktxFormatSize)); + This->vkFormat = vkformat; + This->isCompressed = prototype->isCompressed; + This->supercompressionScheme = KTX_SS_NONE; + priv._requiredLevelAlignment = protoPriv._requiredLevelAlignment; + // Copy the levelIndex from the prototype to This. + memcpy(priv._levelIndex, protoPriv._levelIndex, + This->numLevels * sizeof(ktxLevelIndexEntry)); + // Move the DFD and data from the prototype to This. + free(This->pDfd); + This->pDfd = prototype->pDfd; + prototype->pDfd = 0; + free(This->pData); + This->pData = prototype->pData; + This->dataSize = prototype->dataSize; + prototype->pData = 0; + prototype->dataSize = 0; + // Free SGD data + This->_private->_sgdByteLength = 0; + if (This->_private->_supercompressionGlobalData) { + free(This->_private->_supercompressionGlobalData); + This->_private->_supercompressionGlobalData = NULL; + } + } + ktxTexture2_Destroy(prototype); + return result; +} diff --git a/tools/ktx/command_create.cpp b/tools/ktx/command_create.cpp index 7284b7b9f1..604bfac6f8 100644 --- a/tools/ktx/command_create.cpp +++ b/tools/ktx/command_create.cpp @@ -95,7 +95,7 @@ struct OptionsCreate { void init(cxxopts::Options& opts) { opts.add_options() - (kFormat, "KTX format enum that specifies the image data format." + (kFormat, "The data format of the images in the output KTX file." " The enum names are matching the VkFormats without the VK_FORMAT_ prefix." " The VK_FORMAT_ prefix is ignored if present." "\nWhen used with --encode it specifies the format of the input files before the encoding step." @@ -582,7 +582,7 @@ Create a KTX2 file from various input files. The following options are available:
\--format <enum>
-
KTX format enum that specifies the image data format. +
The data format of the images in the output KTX file. The enum names are matching the VkFormats without the VK_FORMAT_ prefix. The VK_FORMAT_ prefix is ignored if present.
When used with --encode it specifies the format of the input files before the encoding step. @@ -601,7 +601,7 @@ Create a KTX2 file from various input files. otherwise they are ignored.
The format will be used to verify and load all input files into a texture before encoding.
Case insensitive. Required.
- @snippet{doc} ktx/astc_utils.h command options_astc + @snippet{doc} ktx/astc_utils.h command options_astc
\--1d
Create a 1D texture. If not set the texture will be a 2D or 3D texture.
\--cubemap
diff --git a/tools/ktx/command_encode_astc.cpp b/tools/ktx/command_encode_astc.cpp index 73eb1a4ff2..ee9b86a91e 100644 --- a/tools/ktx/command_encode_astc.cpp +++ b/tools/ktx/command_encode_astc.cpp @@ -62,10 +62,6 @@ Encode a KTX2 file. - Wasim Abbas, Arm Ltd. www.arm.com */ class CommandEncodeAstc : public Command { - enum { - all = -1, - }; - struct OptionsEncode { void init(cxxopts::Options& opts); void process(cxxopts::Options& opts, cxxopts::ParseResult& args, Reporter& report); @@ -82,8 +78,6 @@ class CommandEncodeAstc : public Command { void executeEncodeAstc(); }; -// ------------------------------------------------------------------------------------------------- - int CommandEncodeAstc::main(int argc, char* argv[]) { try { parseCommandLine("ktx encode-astc", @@ -124,7 +118,7 @@ void CommandEncodeAstc::executeEncodeAstc() { if (ret != KTX_SUCCESS) fatal(rc::INVALID_FILE, "Failed to create KTX2 texture: {}", ktxErrorString(ret)); - if (texture->supercompressionScheme != KTX_SS_NONE) + if (texture->supercompressionScheme == KTX_SS_BASIS_LZ) // This also means it supports BEGIN_VENDOR_RANGE - END_VENDOR_RANGE and RESERVED fatal(rc::INVALID_FILE, "Cannot encode KTX2 file with {} supercompression.", toString(ktxSupercmpScheme(texture->supercompressionScheme))); @@ -163,9 +157,12 @@ void CommandEncodeAstc::executeEncodeAstc() { MetricsCalculator metrics; metrics.saveReferenceImages(texture, options, *this); + + options.mode = KTX_PACK_ASTC_ENCODER_MODE_LDR; // TODO: Fix me for HDR textures ret = ktxTexture2_CompressAstcEx(texture, &options); if (ret != KTX_SUCCESS) fatal(rc::IO_FAILURE, "Failed to encode KTX2 file with codec \"{}\". KTX Error: {}", ktxErrorString(ret)); + metrics.decodeAndCalculateMetrics(texture, options, *this); if (options.zstd) { diff --git a/tools/ktx/metrics_utils.h b/tools/ktx/metrics_utils.h index 72cd5bd7a0..c5e5565d3f 100644 --- a/tools/ktx/metrics_utils.h +++ b/tools/ktx/metrics_utils.h @@ -94,11 +94,22 @@ class MetricsCalculator { KTXTexture2 texture{static_cast(malloc(sizeof(ktxTexture2)))}; ktxTexture2_constructCopy(texture, encodedTexture); - const auto tSwizzleInfo = determineTranscodeSwizzle(texture, report); + // Start with a default swizzle + TranscodeSwizzleInfo tSwizzleInfo{}; + tSwizzleInfo.defaultNumComponents = 4; + tSwizzleInfo.swizzle = "rgba"; ktx_error_code_e ec = KTX_SUCCESS; // Decode the encoded texture to observe the compression losses - ec = ktxTexture2_TranscodeBasis(texture, KTX_TTF_RGBA32, 0); + if (isFormatAstc((VkFormat)texture->vkFormat)) + { + ec = ktxTexture2_DecodeAstc(texture, VK_FORMAT_R8G8B8A8_UNORM); + } + else + { + ec = ktxTexture2_TranscodeBasis(texture, KTX_TTF_RGBA32, 0); + tSwizzleInfo = determineTranscodeSwizzle(texture, report); + } if (ec != KTX_SUCCESS) report.fatal(rc::KTX_FAILURE, "Failed to transcode KTX2 texture to calculate error metrics: {}", ktxErrorString(ec)); From a925f3f67f9c9b7a77a32ad80bbf5efc911c21a1 Mon Sep 17 00:00:00 2001 From: Wasim Abbas Date: Sat, 9 Dec 2023 12:51:14 +0000 Subject: [PATCH 6/8] Remove tabs, replacing with 4 spaces each, fix bug with vkformat in astc decoding --- lib/astc_encode.cpp | 178 +++++++++++++++--------------- tools/ktx/command_create.cpp | 10 +- tools/ktx/command_encode_astc.cpp | 2 +- tools/ktx/metrics_utils.h | 24 ++-- 4 files changed, 108 insertions(+), 106 deletions(-) diff --git a/lib/astc_encode.cpp b/lib/astc_encode.cpp index 8376cdb4bb..2a4654007b 100644 --- a/lib/astc_encode.cpp +++ b/lib/astc_encode.cpp @@ -360,7 +360,7 @@ astcSwizzle(const ktxAstcParams ¶ms) { static void astcBlockDimensions(ktx_uint32_t block_size, - uint32_t& block_x, uint32_t& block_y, uint32_t& block_z) { + uint32_t& block_x, uint32_t& block_y, uint32_t& block_z) { switch (block_size) { case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4 : block_x = 4; block_y = 4; block_z = 1; break; case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4 : block_x = 5; block_y = 4; block_z = 1; break; @@ -393,7 +393,7 @@ astcBlockDimensions(ktx_uint32_t block_size, static void astcBlockDimensions(VkFormat format, - uint32_t &x, uint32_t &y, uint32_t &z) noexcept { + uint32_t &x, uint32_t &y, uint32_t &z) noexcept { switch (format) { case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: x = 4; y = 4; z = 0; break; case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: x = 4; y = 4; z = 0; break; @@ -887,12 +887,12 @@ ktxTexture2_CompressAstc(ktxTexture2* This, ktx_uint32_t quality) { struct decompression_workload { - astcenc_context* context; - uint8_t* data; - size_t data_len; - astcenc_image* image_out; - astcenc_swizzle swizzle; - astcenc_error error; + astcenc_context* context; + uint8_t* data; + size_t data_len; + astcenc_image* image_out; + astcenc_swizzle swizzle; + astcenc_error error; }; /** @@ -903,17 +903,17 @@ struct decompression_workload * @param payload The parameters for this thread. */ static void decompression_workload_runner(int thread_count, int thread_id, void* payload) { - (void)thread_count; - - decompression_workload* work = static_cast(payload); - astcenc_error error = astcenc_decompress_image(work->context, work->data, work->data_len, - work->image_out, &work->swizzle, thread_id); - // This is a racy update, so which error gets returned is a random, but it - // will reliably report an error if an error occurs - if (error != ASTCENC_SUCCESS) - { - work->error = error; - } + (void)thread_count; + + decompression_workload* work = static_cast(payload); + astcenc_error error = astcenc_decompress_image(work->context, work->data, work->data_len, + work->image_out, &work->swizzle, thread_id); + // This is a racy update, so which error gets returned is a random, but it + // will reliably report an error if an error occurs + if (error != ASTCENC_SUCCESS) + { + work->error = error; + } } /** @@ -925,7 +925,7 @@ static void decompression_workload_runner(int thread_count, int thread_id, void* // will update "This" with the uncompressed copy KTX_API KTX_error_code KTX_APIENTRY ktxTexture2_DecodeAstc(ktxTexture2 *This, ktx_uint32_t vkformat) { - // Decompress This using astc-encoder + // Decompress This using astc-encoder uint32_t* BDB = This->pDfd + 1; khr_df_model_e colorModel = (khr_df_model_e)KHR_DFDVAL(BDB, MODEL); if (colorModel != KHR_DF_MODEL_ASTC && This->supercompressionScheme != KTX_SS_NONE) // No supercompression supported yet @@ -935,12 +935,12 @@ ktxTexture2_DecodeAstc(ktxTexture2 *This, ktx_uint32_t vkformat) { DECLARE_PRIVATE(priv, This); - uint32_t channelId = KHR_DFDSVAL(BDB, 0, CHANNELID); - if (channelId == KHR_DF_CHANNEL_ASTC_DATA) { - // Found astc data - } - else - return KTX_FILE_DATA_ERROR; + uint32_t channelId = KHR_DFDSVAL(BDB, 0, CHANNELID); + if (channelId == KHR_DF_CHANNEL_ASTC_DATA) { + // Found astc data + } + else + return KTX_FILE_DATA_ERROR; // Create a prototype texture to use for calculating sizes in the target // format and, as useful side effects, provide us with a properly sized @@ -984,7 +984,7 @@ ktxTexture2_DecodeAstc(ktxTexture2 *This, ktx_uint32_t vkformat) { } } - // This is where I do the decompression from "This" to prototype target + // This is where I do the decompression from "This" to prototype target astcenc_profile profile{ASTCENC_PRF_LDR_SRGB}; astcenc_swizzle swizzle{ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A}; @@ -994,7 +994,7 @@ ktxTexture2_DecodeAstc(ktxTexture2 *This, ktx_uint32_t vkformat) { float quality{ASTCENC_PRE_MEDIUM}; uint32_t flags{0}; // TODO: Use normals mode to reconstruct normals params->normalMap ? ASTCENC_FLG_MAP_NORMAL : 0}; - astcBlockDimensions((VkFormat)vkformat, block_size_x, block_size_y, block_size_z); + astcBlockDimensions((VkFormat)This->vkFormat, block_size_x, block_size_y, block_size_z); // quality = astcQuality(params->qualityLevel); // profile = astcEncoderAction(*params, BDB); @@ -1002,7 +1002,7 @@ ktxTexture2_DecodeAstc(ktxTexture2 *This, ktx_uint32_t vkformat) { // if(params->perceptual) flags |= ASTCENC_FLG_USE_PERCEPTUAL; - uint32_t threadCount{1}; // Decompression isn't the bottlneck and only used when checking for psnr and ssim + uint32_t threadCount{1}; // Decompression isn't the bottlneck and only used when checking for psnr and ssim astcenc_config astc_config; astcenc_context *astc_context; astcenc_error astc_error = astcenc_config_init(profile, @@ -1018,69 +1018,69 @@ ktxTexture2_DecodeAstc(ktxTexture2 *This, ktx_uint32_t vkformat) { if (astc_error != ASTCENC_SUCCESS) return KTX_INVALID_OPERATION; - decompression_workload work; - work.context = astc_context; - work.swizzle = swizzle; - work.error = ASTCENC_SUCCESS; - - for (uint32_t levelIndex = 0; levelIndex < This->numLevels; ++levelIndex) { - const uint32_t imageWidth = std::max(This->baseWidth >> levelIndex, 1u); - const uint32_t imageHeight = std::max(This->baseHeight >> levelIndex, 1u); - const uint32_t imageDepths = std::max(This->baseDepth >> levelIndex, 1u); - - for (uint32_t layerIndex = 0; layerIndex < This->numLayers; ++layerIndex) { - for (uint32_t faceIndex = 0; faceIndex < This->numFaces; ++faceIndex) { - for (uint32_t depthSliceIndex = 0; depthSliceIndex < imageDepths; ++depthSliceIndex) { - - ktx_size_t levelImageSizeIn = ktxTexture_calcImageSize(ktxTexture(This), levelIndex, KTX_FORMAT_VERSION_TWO); - - ktx_size_t imageOffsetIn; - ktx_size_t imageOffsetOut; - - ktxTexture2_GetImageOffset(This, levelIndex, layerIndex, faceIndex + depthSliceIndex, &imageOffsetIn); - ktxTexture2_GetImageOffset(prototype, levelIndex, layerIndex, faceIndex + depthSliceIndex, &imageOffsetOut); - - auto* imageDataIn = This->pData + imageOffsetIn; - auto* imageDataOut = prototype->pData + imageOffsetOut; - - astcenc_image imageOut; - imageOut.dim_x = imageWidth; - imageOut.dim_y = imageHeight; - imageOut.dim_z = imageDepths; - imageOut.data_type = ASTCENC_TYPE_U8; // TODO: Fix for HDR types - imageOut.data = (void**)&imageDataOut; // TODO: Fix for HDR types - - work.data = imageDataIn; - work.data_len = levelImageSizeIn; - work.image_out = &imageOut; - - // Only launch worker threads for multi-threaded use - it makes basic - // single-threaded profiling and debugging a little less convoluted - if (threadCount > 1) { - launchThreads(threadCount, decompression_workload_runner, &work); - } else { - work.error = astcenc_decompress_image(work.context, work.data, work.data_len, - work.image_out, &work.swizzle, 0); - } - - // Reset ASTC context for next image - astcenc_decompress_reset(astc_context); - - if (work.error != ASTCENC_SUCCESS) { - std::cout << "ASTC decompressor failed\n" << astcenc_get_error_string(work.error) << std::endl; - - astcenc_context_free(astc_context); - return KTX_INVALID_OPERATION; - } - } - } - } - } + decompression_workload work; + work.context = astc_context; + work.swizzle = swizzle; + work.error = ASTCENC_SUCCESS; + + for (uint32_t levelIndex = 0; levelIndex < This->numLevels; ++levelIndex) { + const uint32_t imageWidth = std::max(This->baseWidth >> levelIndex, 1u); + const uint32_t imageHeight = std::max(This->baseHeight >> levelIndex, 1u); + const uint32_t imageDepths = std::max(This->baseDepth >> levelIndex, 1u); + + for (uint32_t layerIndex = 0; layerIndex < This->numLayers; ++layerIndex) { + for (uint32_t faceIndex = 0; faceIndex < This->numFaces; ++faceIndex) { + for (uint32_t depthSliceIndex = 0; depthSliceIndex < imageDepths; ++depthSliceIndex) { + + ktx_size_t levelImageSizeIn = ktxTexture_calcImageSize(ktxTexture(This), levelIndex, KTX_FORMAT_VERSION_TWO); + + ktx_size_t imageOffsetIn; + ktx_size_t imageOffsetOut; + + ktxTexture2_GetImageOffset(This, levelIndex, layerIndex, faceIndex + depthSliceIndex, &imageOffsetIn); + ktxTexture2_GetImageOffset(prototype, levelIndex, layerIndex, faceIndex + depthSliceIndex, &imageOffsetOut); + + auto* imageDataIn = This->pData + imageOffsetIn; + auto* imageDataOut = prototype->pData + imageOffsetOut; + + astcenc_image imageOut; + imageOut.dim_x = imageWidth; + imageOut.dim_y = imageHeight; + imageOut.dim_z = imageDepths; + imageOut.data_type = ASTCENC_TYPE_U8; // TODO: Fix for HDR types + imageOut.data = (void**)&imageDataOut; // TODO: Fix for HDR types + + work.data = imageDataIn; + work.data_len = levelImageSizeIn; + work.image_out = &imageOut; + + // Only launch worker threads for multi-threaded use - it makes basic + // single-threaded profiling and debugging a little less convoluted + if (threadCount > 1) { + launchThreads(threadCount, decompression_workload_runner, &work); + } else { + work.error = astcenc_decompress_image(work.context, work.data, work.data_len, + work.image_out, &work.swizzle, 0); + } + + // Reset ASTC context for next image + astcenc_decompress_reset(astc_context); + + if (work.error != ASTCENC_SUCCESS) { + std::cout << "ASTC decompressor failed\n" << astcenc_get_error_string(work.error) << std::endl; + + astcenc_context_free(astc_context); + return KTX_INVALID_OPERATION; + } + } + } + } + } - // We are done with astcencoder + // We are done with astcdecoder astcenc_context_free(astc_context); - if (result == KTX_SUCCESS) { + if (result == KTX_SUCCESS) { // Fix up the current texture DECLARE_PROTECTED(thisPrtctd, This); DECLARE_PRIVATE(protoPriv, prototype); diff --git a/tools/ktx/command_create.cpp b/tools/ktx/command_create.cpp index 604bfac6f8..209f2b716f 100644 --- a/tools/ktx/command_create.cpp +++ b/tools/ktx/command_create.cpp @@ -95,10 +95,11 @@ struct OptionsCreate { void init(cxxopts::Options& opts) { opts.add_options() - (kFormat, "The data format of the images in the output KTX file." + (kFormat, "VkFormat enum that specifies the data format of the images in the output KTX file." " The enum names are matching the VkFormats without the VK_FORMAT_ prefix." " The VK_FORMAT_ prefix is ignored if present." - "\nWhen used with --encode it specifies the format of the input files before the encoding step." + "\nWhen used with --encode it specifies the format of the texture object created as input to " + " the encoding step before the encoding step." " In this case it must be one of:" "\n R8_UNORM" "\n R8_SRGB" @@ -582,10 +583,11 @@ Create a KTX2 file from various input files. The following options are available:
\--format <enum>
-
The data format of the images in the output KTX file. +
VkFormat enum that specifies the data format of the images in the output KTX file. The enum names are matching the VkFormats without the VK_FORMAT_ prefix. The VK_FORMAT_ prefix is ignored if present.
- When used with --encode it specifies the format of the input files before the encoding step. + When used with --encode it specifies the format of the texture object created as input + to the encoding step before the encoding step. In this case it must be one of:
  • R8_UNORM
  • diff --git a/tools/ktx/command_encode_astc.cpp b/tools/ktx/command_encode_astc.cpp index ee9b86a91e..f0c16028e7 100644 --- a/tools/ktx/command_encode_astc.cpp +++ b/tools/ktx/command_encode_astc.cpp @@ -158,7 +158,7 @@ void CommandEncodeAstc::executeEncodeAstc() { MetricsCalculator metrics; metrics.saveReferenceImages(texture, options, *this); - options.mode = KTX_PACK_ASTC_ENCODER_MODE_LDR; // TODO: Fix me for HDR textures + options.mode = KTX_PACK_ASTC_ENCODER_MODE_LDR; // TODO: Fix me for HDR textures ret = ktxTexture2_CompressAstcEx(texture, &options); if (ret != KTX_SUCCESS) fatal(rc::IO_FAILURE, "Failed to encode KTX2 file with codec \"{}\". KTX Error: {}", ktxErrorString(ret)); diff --git a/tools/ktx/metrics_utils.h b/tools/ktx/metrics_utils.h index c5e5565d3f..2672bbb499 100644 --- a/tools/ktx/metrics_utils.h +++ b/tools/ktx/metrics_utils.h @@ -94,22 +94,22 @@ class MetricsCalculator { KTXTexture2 texture{static_cast(malloc(sizeof(ktxTexture2)))}; ktxTexture2_constructCopy(texture, encodedTexture); - // Start with a default swizzle + // Start with a default swizzle TranscodeSwizzleInfo tSwizzleInfo{}; - tSwizzleInfo.defaultNumComponents = 4; - tSwizzleInfo.swizzle = "rgba"; + tSwizzleInfo.defaultNumComponents = 4; + tSwizzleInfo.swizzle = "rgba"; ktx_error_code_e ec = KTX_SUCCESS; // Decode the encoded texture to observe the compression losses - if (isFormatAstc((VkFormat)texture->vkFormat)) - { - ec = ktxTexture2_DecodeAstc(texture, VK_FORMAT_R8G8B8A8_UNORM); - } - else - { - ec = ktxTexture2_TranscodeBasis(texture, KTX_TTF_RGBA32, 0); - tSwizzleInfo = determineTranscodeSwizzle(texture, report); - } + if (isFormatAstc((VkFormat)texture->vkFormat)) + { + ec = ktxTexture2_DecodeAstc(texture, VK_FORMAT_R8G8B8A8_UNORM); + } + else + { + ec = ktxTexture2_TranscodeBasis(texture, KTX_TTF_RGBA32, 0); + tSwizzleInfo = determineTranscodeSwizzle(texture, report); + } if (ec != KTX_SUCCESS) report.fatal(rc::KTX_FAILURE, "Failed to transcode KTX2 texture to calculate error metrics: {}", ktxErrorString(ec)); From b8ccb41186e31439a23730b8c85d7fae957b11b1 Mon Sep 17 00:00:00 2001 From: Wasim Abbas Date: Sat, 9 Dec 2023 17:09:07 +0000 Subject: [PATCH 7/8] Minor typo fix --- lib/astc_encode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/astc_encode.cpp b/lib/astc_encode.cpp index 2a4654007b..50d4241811 100644 --- a/lib/astc_encode.cpp +++ b/lib/astc_encode.cpp @@ -925,7 +925,7 @@ static void decompression_workload_runner(int thread_count, int thread_id, void* // will update "This" with the uncompressed copy KTX_API KTX_error_code KTX_APIENTRY ktxTexture2_DecodeAstc(ktxTexture2 *This, ktx_uint32_t vkformat) { - // Decompress This using astc-encoder + // Decompress This using astc-decoder uint32_t* BDB = This->pDfd + 1; khr_df_model_e colorModel = (khr_df_model_e)KHR_DFDVAL(BDB, MODEL); if (colorModel != KHR_DF_MODEL_ASTC && This->supercompressionScheme != KTX_SS_NONE) // No supercompression supported yet From ba5a4250adab33cb33ab17b6dab49b21e9ba2a9d Mon Sep 17 00:00:00 2001 From: Wasim Abbas Date: Sat, 7 Sep 2024 16:31:54 +0100 Subject: [PATCH 8/8] Fix SRGB asserts --- lib/astc_encode.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/astc_encode.cpp b/lib/astc_encode.cpp index 2dc8dfd3b4..9aefa2695e 100644 --- a/lib/astc_encode.cpp +++ b/lib/astc_encode.cpp @@ -797,8 +797,10 @@ ktxTexture2_CompressAstcEx(ktxTexture2* This, ktxAstcParams* params) { assert(KHR_DFDVAL(prototype->pDfd+1, MODEL) == KHR_DF_MODEL_ASTC && "Invalid dfd generated for ASTC image\n"); assert((transfer == KHR_DF_TRANSFER_SRGB - ? KHR_DFDVAL(prototype->pDfd+1, PRIMARIES) == KHR_DF_PRIMARIES_SRGB - : true) && "Not a valid sRGB image\n"); + ? KHR_DFDVAL(prototype->pDfd+1, TRANSFER) == KHR_DF_TRANSFER_SRGB && + KHR_DFDVAL(prototype->pDfd+1, PRIMARIES) == KHR_DF_PRIMARIES_SRGB + : true) && "Not a valid sRGB image\n"); + // Fix up the current (This) texture #undef DECLARE_PRIVATE