Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
KTXwriterScParams support
Browse files Browse the repository at this point in the history
aqnuep committed Oct 4, 2023
1 parent e6a6a3b commit ee528c0
Showing 5 changed files with 86 additions and 24 deletions.
2 changes: 1 addition & 1 deletion tests/cts
Submodule cts updated 643 files
43 changes: 34 additions & 9 deletions tools/ktx/command_create.cpp
Original file line number Diff line number Diff line change
@@ -525,10 +525,11 @@ struct OptionsCreate {
};

struct OptionsASTC : public ktxAstcParams {
bool astc = false;
std::string astcOptions{};
bool encodeASTC = false;
ClampedOption<ktx_uint32_t> qualityLevel{ktxAstcParams::qualityLevel, 0, KTX_PACK_ASTC_QUALITY_LEVEL_MAX};

OptionsASTC() {
OptionsASTC() : ktxAstcParams() {
threadCount = std::thread::hardware_concurrency();
if (threadCount == 0)
threadCount = 1;
@@ -566,9 +567,20 @@ struct OptionsASTC : public ktxAstcParams {
"currently only available for normal maps and RGB color data.");
}

void captureASTCOption(const char* name) {
astcOptions += fmt::format(" --{}", name);
}

template <typename T>
T captureASTCOption(cxxopts::ParseResult& args, const char* name) {
const T value = args[name].as<T>();
astcOptions += fmt::format(" --{} {}", name, value);
return value;
}

void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) {
if (args["astc-mode"].count()) {
const auto modeStr = args["astc-mode"].as<std::string>();
const auto modeStr = to_lower_copy(captureASTCOption<std::string>(args, "astc-mode"));
if (modeStr == "ldr")
mode = KTX_PACK_ASTC_ENCODER_MODE_LDR;
else if (modeStr == "hdr")
@@ -587,7 +599,7 @@ struct OptionsASTC : public ktxAstcParams {
{"thorough", KTX_PACK_ASTC_QUALITY_LEVEL_THOROUGH},
{"exhaustive", KTX_PACK_ASTC_QUALITY_LEVEL_EXHAUSTIVE}
};
const auto qualityLevelStr = to_lower_copy(args["astc-quality"].as<std::string>());
const auto qualityLevelStr = to_lower_copy(captureASTCOption<std::string>(args, "astc-quality"));
const auto it = astc_quality_mapping.find(qualityLevelStr);
if (it == astc_quality_mapping.end())
report.fatal_usage("Invalid astc-quality: \"{}\"", qualityLevelStr);
@@ -596,7 +608,10 @@ struct OptionsASTC : public ktxAstcParams {
qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM;
}

perceptual = args["astc-perceptual"].as<bool>();
if (args["astc-perceptual"].count()) {
captureASTCOption("astc-perceptual");
perceptual = KTX_TRUE;
}
}
};

@@ -911,7 +926,7 @@ void CommandCreate::processOptions(cxxopts::Options& opts, cxxopts::ParseResult&
fatal_usage("--compare-psnr can only be used with BasisLZ or UASTC encoding.");

if (isFormatAstc(options.vkFormat) && !options.raw) {
options.astc = true;
options.encodeASTC = true;

switch (options.vkFormat) {
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: [[fallthrough]];
@@ -976,7 +991,7 @@ void CommandCreate::processOptions(cxxopts::Options& opts, cxxopts::ParseResult&
}
}

if (options._1d && options.astc)
if (options._1d && options.encodeASTC)
fatal_usage("ASTC format {} cannot be used for 1 dimensional textures (indicated by --1d).",
toString(options.vkFormat));
}
@@ -1107,7 +1122,7 @@ void CommandCreate::executeCreate() {
fatal_usage("Requested {} levels is too many. With input image \"{}\" sized {}x{} and depth {} the texture can only have {} levels at most.",
options.levels.value_or(1), fmtInFile(inputFilepath), target.width(), target.height(), numBaseDepths, maxLevels);

if (options.astc)
if (options.encodeASTC)
selectASTCMode(inputImageFile->spec().format().largestChannelBitLength());

firstImageSpec = inputImageFile->spec();
@@ -1202,6 +1217,16 @@ void CommandCreate::executeCreate() {
encodeASTC(texture, options);
compress(texture, options);

// Add KTXwriterScParams metadata if ASTC encoding, BasisU encoding, or other supercompression was used
const auto writerScParams = fmt::format("{}{}{}", options.astcOptions, options.codecOptions, 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<uint32_t>(writerScParams.size()),
writerScParams.c_str() + 1); // +1 to exclude leading space
}

// Save output file
if (std::filesystem::path(options.outputFilepath).has_parent_path())
std::filesystem::create_directories(std::filesystem::path(options.outputFilepath).parent_path());
@@ -1227,7 +1252,7 @@ void CommandCreate::encode(KTXTexture2& texture, OptionsCodec<false>& opts) {
}

void CommandCreate::encodeASTC(KTXTexture2& texture, OptionsASTC& opts) {
if (opts.astc) {
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));
10 changes: 10 additions & 0 deletions tools/ktx/command_encode.cpp
Original file line number Diff line number Diff line change
@@ -208,6 +208,16 @@ void CommandEncode::executeEncode() {
fatal(rc::IO_FAILURE, "ZLIB deflation failed. KTX Error: {}", ktxErrorString(ret));
}

// Add KTXwriterScParams metadata
const auto writerScParams = fmt::format("{}{}", options.codecOptions, 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<uint32_t>(writerScParams.size()),
writerScParams.c_str() + 1); // +1 to exclude leading space
}

// Save output file
if (std::filesystem::path(options.outputFilepath).has_parent_path())
std::filesystem::create_directories(std::filesystem::path(options.outputFilepath).parent_path());
12 changes: 10 additions & 2 deletions tools/ktx/compress_utils.h
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ namespace ktx {
//! [command options_compress]
*/
struct OptionsCompress {
std::string compressOptions{};
std::optional<uint32_t> zstd;
std::optional<uint32_t> zlib;

@@ -51,14 +52,21 @@ struct OptionsCompress {
cxxopts::value<uint32_t>(), "<level>");
}

template <typename T>
T captureCompressOption(cxxopts::ParseResult& args, const char* name) {
const T value = args[name].as<T>();
compressOptions += fmt::format(" --{} {}", name, value);
return value;
}

void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) {
if (args["zstd"].count()) {
zstd = args["zstd"].as<uint32_t>();
zstd = captureCompressOption<uint32_t>(args, "zstd");
if (zstd < 1u || zstd > 22u)
report.fatal_usage("Invalid zstd level: \"{}\". Value must be between 1 and 22 inclusive.", zstd.value());
}
if (args["zlib"].count()) {
zlib = args["zlib"].as<uint32_t>();
zlib = captureCompressOption<uint32_t>(args, "zlib");
if (zlib < 1u || zlib > 9u)
report.fatal_usage("Invalid zlib level: \"{}\". Value must be between 1 and 9 inclusive.", zlib.value());
}
43 changes: 31 additions & 12 deletions tools/ktx/encode_utils.h
Original file line number Diff line number Diff line change
@@ -245,6 +245,7 @@ struct OptionsCodec {
}
};

std::string codecOptions{};
std::string codecName;
EncodeCodec codec;
BasisOptions basisOpts;
@@ -334,6 +335,17 @@ struct OptionsCodec {
}
}

void captureCodecOption(const char* name) {
codecOptions += fmt::format(" --{}", name);
}

template <typename T>
T captureCodecOption(cxxopts::ParseResult& args, const char* name) {
const T value = args[name].as<T>();
codecOptions += fmt::format(" --{} {}", name, value);
return value;
}

void validateCommonEncodeArg(Reporter& report, const char* name) {
if (codec == EncodeCodec::NONE)
report.fatal(rc::INVALID_ARGUMENTS,
@@ -418,101 +430,108 @@ struct OptionsCodec {

if (args["clevel"].count()) {
validateBasisLZArg(report, "clevel");
basisOpts.compressionLevel = args["clevel"].as<uint32_t>();
basisOpts.compressionLevel = captureCodecOption<uint32_t>(args, "clevel");;
}

if (args["qlevel"].count()) {
validateBasisLZArg(report, "qlevel");
basisOpts.qualityLevel = args["qlevel"].as<uint32_t>();
basisOpts.qualityLevel = captureCodecOption<uint32_t>(args, "qlevel");
}

if (args["no-endpoint-rdo"].count()) {
validateBasisLZArg(report, "no-endpoint-rdo");
captureCodecOption("no-endpoint-rdo");
basisOpts.noEndpointRDO = 1;
}

if (args["no-selector-rdo"].count()) {
validateBasisLZArg(report, "no-selector-rdo");
captureCodecOption("no-selector-rdo");
basisOpts.noSelectorRDO = 1;
}

if (args["max-endpoints"].count()) {
validateBasisLZEndpointRDOArg(report, "max-endpoints");
basisOpts.maxEndpoints = args["max-endpoints"].as<uint32_t>();
basisOpts.maxEndpoints = captureCodecOption<uint32_t>(args, "max-endpoints");
}

if (args["endpoint-rdo-threshold"].count()) {
validateBasisLZEndpointRDOArg(report, "endpoint-rdo-threshold");
basisOpts.endpointRDOThreshold = args["endpoint-rdo-threshold"].as<float>();
basisOpts.endpointRDOThreshold = captureCodecOption<float>(args, "endpoint-rdo-threshold");
}

if (args["max-selectors"].count()) {
validateBasisLZSelectorRDOArg(report, "max-selectors");
basisOpts.maxSelectors = args["max-selectors"].as<uint32_t>();
basisOpts.maxSelectors = captureCodecOption<uint32_t>(args, "max-selectors");
}

if (args["selector-rdo-threshold"].count()) {
validateBasisLZSelectorRDOArg(report, "selector-rdo-threshold");
basisOpts.selectorRDOThreshold = args["selector-rdo-threshold"].as<float>();
basisOpts.selectorRDOThreshold = captureCodecOption<float>(args, "selector-rdo-threshold");
}

if (args["uastc-quality"].count()) {
validateUASTCArg(report, "uastc-quality");
uint32_t level = args["uastc-quality"].as<uint32_t>();
uint32_t level = captureCodecOption<uint32_t>(args, "uastc-quality");
level = std::clamp<uint32_t>(level, 0, KTX_PACK_UASTC_MAX_LEVEL);
basisOpts.uastcFlags = (unsigned int)~KTX_PACK_UASTC_LEVEL_MASK;
basisOpts.uastcFlags |= level;
}

if (args["uastc-rdo"].count()) {
validateUASTCArg(report, "uastc-rdo");
captureCodecOption("uastc-rdo");
basisOpts.uastcRDO = 1;
}

if (args["uastc-rdo-l"].count()) {
validateUASTCRDOArg(report, "uastc-rdo-l");
basisOpts.uastcRDOQualityScalar = args["uastc-rdo-l"].as<float>();
basisOpts.uastcRDOQualityScalar = captureCodecOption<float>(args, "uastc-rdo-l");
}

if (args["uastc-rdo-d"].count()) {
validateUASTCRDOArg(report, "uastc-rdo-d");
basisOpts.uastcRDODictSize = args["uastc-rdo-d"].as<uint32_t>();
basisOpts.uastcRDODictSize = captureCodecOption<uint32_t>(args, "uastc-rdo-d");
}

if (args["uastc-rdo-b"].count()) {
validateUASTCRDOArg(report, "uastc-rdo-b");
basisOpts.uastcRDOMaxSmoothBlockErrorScale = args["uastc-rdo-b"].as<float>();
basisOpts.uastcRDOMaxSmoothBlockErrorScale = captureCodecOption<float>(args, "uastc-rdo-b");
}

if (args["uastc-rdo-s"].count()) {
validateUASTCRDOArg(report, "uastc-rdo-s");
basisOpts.uastcRDOMaxSmoothBlockStdDev = args["uastc-rdo-s"].as<float>();
basisOpts.uastcRDOMaxSmoothBlockStdDev = captureCodecOption<float>(args, "uastc-rdo-s");
}

if (args["uastc-rdo-f"].count()) {
validateUASTCRDOArg(report, "uastc-rdo-f");
captureCodecOption("uastc-rdo-f");
basisOpts.uastcRDODontFavorSimplerModes = 1;
}

if (args["uastc-rdo-m"].count()) {
validateUASTCRDOArg(report, "uastc-rdo-m");
captureCodecOption("uastc-rdo-m");
basisOpts.uastcRDONoMultithreading = 1;
}

if (args["normal-mode"].count()) {
validateCommonEncodeArg(report, "normal-mode");
captureCodecOption("normal-mode");
basisOpts.normalMap = true;
}

if (args["threads"].count()) {
validateCommonEncodeArg(report, "threads");
basisOpts.threadCount = args["threads"].as<uint32_t>();
basisOpts.threadCount = captureCodecOption<uint32_t>(args, "threads");
} else {
basisOpts.threadCount = std::thread::hardware_concurrency();
}

if (args["no-sse"].count()) {
validateCommonEncodeArg(report, "no-sse");
captureCodecOption("no-sse");
basisOpts.noSSE = true;
}
}

0 comments on commit ee528c0

Please sign in to comment.