Skip to content

Commit

Permalink
Encode astc support (#952)
Browse files Browse the repository at this point in the history
This PR has:
    Commit to add --format option and make it exclusive with --codec
    Add ASTC support to encode
  • Loading branch information
wasimabbas-arm authored Nov 28, 2024
1 parent 60c3689 commit d1ad5cd
Show file tree
Hide file tree
Showing 8 changed files with 448 additions and 82 deletions.
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

0 comments on commit d1ad5cd

Please sign in to comment.