Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encode astc support #952

Merged
merged 19 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4cddced
Add format option and make it exclusive option with encode
wasimabbas-arm Oct 19, 2024
1aec6ae
Add astc encode support
wasimabbas-arm Oct 19, 2024
15b354b
Rename astc_encode.cpp to astc_codec.cpp, use dfd model to query for …
wasimabbas-arm Nov 13, 2024
e77bca9
Fix bug with transcode swizzle calculation
wasimabbas-arm Nov 13, 2024
37a67b1
Minor fix to wording
wasimabbas-arm Nov 13, 2024
ba1b2ee
Updates CTS submodule, and minor wording fixes
wasimabbas-arm Nov 15, 2024
c6ca7f5
Update to latest CTS tests
wasimabbas-arm Nov 15, 2024
e8c8dbc
Update to latest CTS tests
wasimabbas-arm Nov 15, 2024
03a4c68
Update format help text
wasimabbas-arm Nov 17, 2024
027a384
Update cts submodule to point at latest of https://github.com/Khronos…
wasimabbas-arm Nov 18, 2024
81499f8
Uncomment ktx encode ASTC specific documentation
wasimabbas-arm Nov 18, 2024
8bac12c
Allow comparing ssim and pnsr for ASTC images
wasimabbas-arm Nov 19, 2024
a17d907
Update metrics stderr messages and add metrics to command create enco…
wasimabbas-arm Nov 20, 2024
abf8e65
Update tests submodule
wasimabbas-arm Nov 20, 2024
43fa081
Update tests submodule
wasimabbas-arm Nov 20, 2024
262f6b7
Add check to prevent ASTC encoding from ASTC encded KTX file
wasimabbas-arm Nov 20, 2024
11b3451
Fix accessing basis compressed image for metrics calculation
wasimabbas-arm Nov 26, 2024
c629069
Move option checkes to call site for astc or basis compression paths
wasimabbas-arm Nov 27, 2024
42d73da
Update cts submodule
wasimabbas-arm Nov 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ macro(common_libktx_settings target enable_write library_type)
${target}
PRIVATE
lib/basis_encode.cpp
lib/astc_encode.cpp
lib/astc_codec.cpp
${BASISU_ENCODER_C_SRC}
${BASISU_ENCODER_CXX_SRC}
lib/writer1.c
Expand Down
3 changes: 3 additions & 0 deletions include/ktx.h
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,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
Expand Down
318 changes: 315 additions & 3 deletions lib/astc_encode.cpp → lib/astc_codec.cpp

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/cts
Submodule cts updated 118 files
50 changes: 26 additions & 24 deletions tools/ktx/command_create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -609,8 +609,8 @@ Create a KTX2 file from various input files.
is present in the input. @c SRGB or @c UNORM is chosen depending on the
specified ASTC format. The ASTC-specific and common encoder options listed
@ref ktx_create_options_encoding "below" become valid, otherwise they are ignored.
<!--This matches the functionality of the @ref ktx_encode "ktx encode" command
when an ASTC format is specified.<br /> -->
This matches the functionality of the @ref ktx_encode "ktx encode" command
when an ASTC format is specified.<br />
<br />
When used with @b \--encode it specifies the target format before the encoding step.
In this case it must be one of:
Expand Down Expand Up @@ -874,15 +874,17 @@ void CommandCreate::processOptions(cxxopts::Options& opts, cxxopts::ParseResult&
}
}

const auto canCompare = options.codec == BasisCodec::BasisLZ || options.codec == BasisCodec::UASTC;
const auto basisCodec = options.codec == BasisCodec::BasisLZ || options.codec == BasisCodec::UASTC;
const auto astcCodec = isFormatAstc(options.vkFormat);
const auto canCompare = basisCodec || astcCodec;

if (canCompare)
if (basisCodec)
fillOptionsCodecBasis<decltype(options)>(options);

if (options.compare_ssim && !canCompare)
fatal_usage("--compare-ssim can only be used with BasisLZ or UASTC encoding.");
fatal_usage("--compare-ssim can only be used with BasisLZ, UASTC or ASTC encoding.");
if (options.compare_psnr && !canCompare)
fatal_usage("--compare-psnr can only be used with BasisLZ or UASTC encoding.");
fatal_usage("--compare-psnr can only be used with BasisLZ, UASTC or ASTC encoding.");

if (isFormatAstc(options.vkFormat) && !options.raw) {
options.encodeASTC = true;
Expand Down Expand Up @@ -1186,8 +1188,17 @@ void CommandCreate::executeCreate() {
}

// Encode and apply compression
encodeBasis(texture, options);
encodeASTC(texture, options);

MetricsCalculator metrics;
metrics.saveReferenceImages(texture, options, *this);

if (options.codec != BasisCodec::NONE)
encodeBasis(texture, options);
if (options.encodeASTC)
encodeASTC(texture, options);

metrics.decodeAndCalculateMetrics(texture, options, *this);

compress(texture, options);

// Add KTXwriterScParams metadata if ASTC encoding, BasisU encoding, or other supercompression was used
Expand All @@ -1212,25 +1223,16 @@ void CommandCreate::executeCreate() {
// -------------------------------------------------------------------------------------------------

void CommandCreate::encodeBasis(KTXTexture2& texture, OptionsEncodeBasis<false>& opts) {
MetricsCalculator metrics;
metrics.saveReferenceImages(texture, options, *this);

if (opts.codec != BasisCodec::NONE) {
auto ret = ktxTexture2_CompressBasisEx(texture, &opts);
if (ret != KTX_SUCCESS)
fatal(rc::KTX_FAILURE, "Failed to encode KTX2 file with codec \"{}\". KTX Error: {}",
to_underlying(opts.codec), ktxErrorString(ret));
}

metrics.decodeAndCalculateMetrics(texture, options, *this);
auto ret = ktxTexture2_CompressBasisEx(texture, &opts);
if (ret != KTX_SUCCESS)
fatal(rc::KTX_FAILURE, "Failed to encode KTX2 file with codec \"{}\". KTX Error: {}",
to_underlying(opts.codec), ktxErrorString(ret));
}

void CommandCreate::encodeASTC(KTXTexture2& texture, OptionsEncodeASTC& opts) {
if (opts.encodeASTC) {
const auto ret = ktxTexture2_CompressAstcEx(texture, &opts);
if (ret != KTX_SUCCESS)
fatal(rc::KTX_FAILURE, "Failed to encode KTX2 file with codec ASTC. KTX Error: {}", ktxErrorString(ret));
}
const auto ret = ktxTexture2_CompressAstcEx(texture, &opts);
if (ret != KTX_SUCCESS)
fatal(rc::KTX_FAILURE, "Failed to encode KTX2 file with codec ASTC. KTX Error: {}", ktxErrorString(ret));
}

void CommandCreate::compress(KTXTexture2& texture, const OptionsDeflate& opts) {
Expand Down
78 changes: 64 additions & 14 deletions tools/ktx/command_encode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0

#include "command.h"
#include "encode_utils_astc.h"
#include "encode_utils_common.h"
#include "platform_utils.h"
#include "metrics_utils.h"
Expand Down Expand Up @@ -40,12 +41,12 @@ Encode a KTX2 file.

@section ktx_encode_description DESCRIPTION
@b ktx @b encode can encode the KTX file specified as the @e input-file argument
to a universal format<!-- or one of the ASTC formats,--> optionally supercompress the result,
to a universal format or one of the ASTC formats, 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.

For universal <!-- and ASTC LDR--> formats, the input file must be R8, R8G8, R8G8B8
For universal and ASTC LDR formats, the input file must be R8, R8G8, R8G8B8
or R8G8B8A8 (or their sRGB variants).

<!--For ASTC HDR formats the input file must be TBD (e.g. R16_{,S}FLOAT,
Expand All @@ -67,12 +68,11 @@ Encode a KTX2 file.
they are ignored. Case-insensitive.</dd>

@snippet{doc} ktx/encode_utils_basis.h command options_basis_encoders
<!-- <dt>\--format</dt>
<dt>\--format</dt>
<dd>KTX format enum that specifies the target ASTC format. Non-ASTC
formats are invalid. When specified the ASTC-specific and common
encoder options listed @ref ktx\_encode\_options\_encoding "below"
become valid, otherwise they are ignored.
-->
</dl>
@snippet{doc} ktx/deflate_utils.h command options_deflate
@snippet{doc} ktx/command.h command options_generic
Expand All @@ -81,7 +81,7 @@ Encode a KTX2 file.
The following specific and common encoder options are available. Specific options
become valid only if their encoder has been selected. Common encoder options
become valid when an encoder they apply to has been selected. Otherwise they are ignored.
<!--@snippet{doc} ktx/encode_utils_astc.h command options_encode_astc-->
@snippet{doc} ktx/encode_utils_astc.h command options_encode_astc
@snippet{doc} ktx/encode_utils_basis.h command options_encode_basis
@snippet{doc} ktx/encode_utils_common.h command options_encode_common
@snippet{doc} ktx/metrics_utils.h command options_metrics
Expand All @@ -103,11 +103,16 @@ Encode a KTX2 file.
*/
class CommandEncode : public Command {
struct OptionsEncode {
inline static const char* kFormat = "format";
inline static const char* kCodec = "codec";

VkFormat vkFormat = VK_FORMAT_UNDEFINED;

void init(cxxopts::Options& opts);
void process(cxxopts::Options& opts, cxxopts::ParseResult& args, Reporter& report);
};

Combine<OptionsEncode, OptionsEncodeBasis<true>, OptionsEncodeCommon, OptionsMetrics, OptionsDeflate, OptionsSingleInSingleOut, OptionsGeneric> options;
Combine<OptionsEncode, OptionsEncodeASTC, OptionsEncodeBasis<true>, OptionsEncodeCommon, OptionsMetrics, OptionsDeflate, OptionsSingleInSingleOut, OptionsGeneric> options;

public:
virtual int main(int argc, char* argv[]) override;
Expand Down Expand Up @@ -138,13 +143,35 @@ int CommandEncode::main(int argc, char* argv[]) {

void CommandEncode::OptionsEncode::init(cxxopts::Options& opts) {
opts.add_options()
("codec", "Target codec."
(kFormat, "KTX format enum that specifies the KTX file output format."
" The enum names are matching the VkFormats without the VK_FORMAT_ prefix."
" The VK_FORMAT_ prefix is ignored if present."
"\nIt can't be used with --codec."
"\nThe value must be an ASTC format. When specified the ASTC encoder specific"
" options becomes valid."
" Case insensitive.", cxxopts::value<std::string>(), "<enum>")
(kCodec, "Target codec."
" With each encoding option the encoder specific options become valid,"
" otherwise they are ignored. Case-insensitive."
"\nPossible options are: basis-lz | uastc", cxxopts::value<std::string>(), "<target>");
}

void CommandEncode::OptionsEncode::process(cxxopts::Options&, cxxopts::ParseResult&, Reporter&) {
void CommandEncode::OptionsEncode::process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) {
if (args[kCodec].count() && args[kFormat].count())
report.fatal_usage("Format and codec can't be both specified together.");

if (args[kFormat].count()) {
const auto formatStr = args[kFormat].as<std::string>();
const auto parsedVkFormat = parseVkFormat(formatStr);
if (!parsedVkFormat)
report.fatal_usage("The requested format is invalid or unsupported: \"{}\".", formatStr);

vkFormat = *parsedVkFormat;

if (!isFormatAstc(vkFormat)) {
report.fatal_usage("Optional option 'format' is not an ASTC format.");
}
}
}

void CommandEncode::initOptions(cxxopts::Options& opts) {
Expand All @@ -156,6 +183,10 @@ void CommandEncode::processOptions(cxxopts::Options& opts, cxxopts::ParseResult&

fillOptionsCodecBasis<decltype(options)>(options);

if ((options.codec == BasisCodec::NONE || options.codec == BasisCodec::INVALID) &&
options.vkFormat == VK_FORMAT_UNDEFINED)
fatal_usage("Either codec or format must be specified");

if (options.codec == BasisCodec::BasisLZ) {
if (options.zstd.has_value())
fatal_usage("Cannot encode to BasisLZ and supercompress with Zstd.");
Expand All @@ -164,11 +195,17 @@ void CommandEncode::processOptions(cxxopts::Options& opts, cxxopts::ParseResult&
fatal_usage("Cannot encode to BasisLZ and supercompress with ZLIB.");
}

const auto canCompare = options.codec == BasisCodec::BasisLZ || options.codec == BasisCodec::UASTC;
const auto basisCodec = options.codec == BasisCodec::BasisLZ || options.codec == BasisCodec::UASTC;
const auto astcCodec = isFormatAstc(options.vkFormat);
const auto canCompare = basisCodec || astcCodec;

if (options.compare_ssim && !canCompare)
fatal_usage("--compare-ssim can only be used with BasisLZ or UASTC encoding.");
fatal_usage("--compare-ssim can only be used with BasisLZ, UASTC or ASTC encoding.");
if (options.compare_psnr && !canCompare)
fatal_usage("--compare-psnr can only be used with BasisLZ or UASTC encoding.");
fatal_usage("--compare-psnr can only be used with BasisLZ, UASTC or ASTC encoding.");

if (astcCodec)
options.encodeASTC = true;
}

void CommandEncode::executeEncode() {
Expand All @@ -185,6 +222,10 @@ void CommandEncode::executeEncode() {
fatal(rc::INVALID_FILE, "Cannot encode KTX2 file with {} supercompression.",
toString(ktxSupercmpScheme(texture->supercompressionScheme)));

const auto* bdfd = texture->pDfd + 1;
if (khr_df_model_e(KHR_DFDVAL(bdfd, MODEL)) == KHR_DF_MODEL_ASTC && options.encodeASTC)
fatal_usage("Encoding from ASTC format {} to another ASTC format {} is not supported.", toString(VkFormat(texture->vkFormat)), toString(options.vkFormat));

switch (texture->vkFormat) {
case VK_FORMAT_R8_UNORM:
case VK_FORMAT_R8_SRGB:
Expand Down Expand Up @@ -220,9 +261,18 @@ void CommandEncode::executeEncode() {

MetricsCalculator metrics;
metrics.saveReferenceImages(texture, options, *this);
ret = ktxTexture2_CompressBasisEx(texture, &options);
if (ret != KTX_SUCCESS)
fatal(rc::IO_FAILURE, "Failed to encode KTX2 file with codec \"{}\". KTX Error: {}", ktxErrorString(ret));

if (options.vkFormat != VK_FORMAT_UNDEFINED) {
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 to ASTC. KTX Error: {}", ktxErrorString(ret));
} else {
ret = ktxTexture2_CompressBasisEx(texture, &options);
if (ret != KTX_SUCCESS)
fatal(rc::IO_FAILURE, "Failed to encode KTX2 file with codec \"{}\". KTX Error: {}", options.codecName, ktxErrorString(ret));
}

metrics.decodeAndCalculateMetrics(texture, options, *this);

if (options.zstd) {
Expand Down
52 changes: 19 additions & 33 deletions tools/ktx/encode_utils_basis.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,40 +349,26 @@ struct OptionsEncodeBasis : public ktxBasisParams {
}

void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) {
std::string codec_option{"encode"};

if (ENCODE_CMD) {
// "encode" command - required "codec" argument
codec = validateBasisCodec(args["codec"]);
switch (codec) {
case BasisCodec::NONE:
report.fatal(rc::INVALID_ARGUMENTS, "Missing codec argument.");
break;

case BasisCodec::BasisLZ:
case BasisCodec::UASTC:
codecName = to_lower_copy(args["codec"].as<std::string>());
break;

default:
report.fatal_usage("Invalid encode codec: \"{}\".", args["codec"].as<std::string>());
break;
}
} else {
// "create" command - optional "encode" argument
codec = validateBasisCodec(args["encode"]);
switch (codec) {
case BasisCodec::NONE:
// Not specified
break;

case BasisCodec::BasisLZ:
case BasisCodec::UASTC:
codecName = to_lower_copy(args["encode"].as<std::string>());
break;

default:
report.fatal_usage("Invalid encode codec: \"{}\".", args["encode"].as<std::string>());
break;
}
codec_option = "codec";
}

codec = validateBasisCodec(args[codec_option]);
switch (codec) {
case BasisCodec::NONE:
// Not specified
break;

case BasisCodec::BasisLZ:
case BasisCodec::UASTC:
codecName = to_lower_copy(args[codec_option].as<std::string>());
break;

default:
report.fatal_usage("Invalid encode codec: \"{}\".", args[codec_option].as<std::string>());
break;
}

if (codec == BasisCodec::UASTC) {
Expand Down
25 changes: 19 additions & 6 deletions tools/ktx/metrics_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ namespace ktx {
<dl>
<dt>\--compare-ssim</dt>
<dd>Calculate encoding structural similarity index measure (SSIM) and print it to stdout.
Requires Basis-LZ or UASTC encoding.</dd>
Requires Basis-LZ, UASTC or ASTC encoding.</dd>
<dt>\--compare-psnr</dt>
<dd>Calculate encoding peak signal-to-noise ratio (PSNR) and print it to stdout.
Requires Basis-LZ or UASTC encoding.</dd>
Requires Basis-LZ, UASTC or ASTC encoding.</dd>
</dl>
</dl>
//! [command options_metrics]
Expand All @@ -43,8 +43,8 @@ struct OptionsMetrics {

void init(cxxopts::Options& opts) {
opts.add_options()
("compare-ssim", "Calculate encoding structural similarity index measure (SSIM) and print it to stdout. Requires Basis-LZ or UASTC encoding.")
("compare-psnr", "Calculate encoding peak signal-to-noise ratio (PSNR) and print it to stdout. Requires Basis-LZ or UASTC encoding.");
("compare-ssim", "Calculate encoding structural similarity index measure (SSIM) and print it to stdout. Requires Basis-LZ, UASTC or ASTC encoding.")
("compare-psnr", "Calculate encoding peak signal-to-noise ratio (PSNR) and print it to stdout. Requires Basis-LZ, UASTC or ASTC encoding.");
}

void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter&) {
Expand Down Expand Up @@ -101,11 +101,24 @@ class MetricsCalculator {
KTXTexture2 texture{static_cast<ktxTexture2*>(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);
const auto* bdfd = texture->pDfd + 1;
if (khr_df_model_e(KHR_DFDVAL(bdfd, MODEL)) == KHR_DF_MODEL_ASTC)
{
ec = ktxTexture2_DecodeAstc(texture, VK_FORMAT_R8G8B8A8_UNORM);
}
else
{
tSwizzleInfo = determineTranscodeSwizzle(texture, report);
ec = ktxTexture2_TranscodeBasis(texture, KTX_TTF_RGBA32, 0);
}
if (ec != KTX_SUCCESS)
report.fatal(rc::KTX_FAILURE, "Failed to transcode KTX2 texture to calculate error metrics: {}", ktxErrorString(ec));

Expand Down