This commit is contained in:
2026-06-14 19:09:18 +01:00
parent 14bd1a9271
commit 13fa90a0e9
3958 changed files with 999286 additions and 4 deletions
+80
View File
@@ -0,0 +1,80 @@
# Copyright 2017-2020 The Khronos Group Inc.
# SPDX-License-Identifier: Apache-2.0
if(NOT KTX_FEATURE_KTX1)
message(WARNING "KTX_FEATURE_KTX1 is not set -> disabling tools")
return()
endif()
if(NOT KTX_FEATURE_KTX2)
message(WARNING "KTX_FEATURE_KTX2 is not set -> disabling tools")
return()
endif()
function(set_tool_properties tool_target)
if(APPLE)
# The first RPATH entry is so executables run in the build
# directories will work and will find the just built libraries
# instead of whatever may be installed on the system. The
# second RPATH entry is for finding the installed library.
set_target_properties(${tool_target} PROPERTIES
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES"
# Creates an LC_RPATH entry in the Mac-O binary for each
# item in this list. When searching for libraries whose
# install name starts with @rpath, as libktx's does, dyld
# searches each LC_RPATH in the order given here.
#
# Check the LC_RPATH entries with
# - otool -l <file> | grep -A 3 LC_RPATH
INSTALL_RPATH "@executable_path;@executable_path/../${CMAKE_INSTALL_LIBDIR}"
CXX_VISIBILITY_PRESET ${STATIC_APP_LIB_SYMBOL_VISIBILITY}
)
elseif(LINUX)
set_target_properties(${tool_target} PROPERTIES
# With modern tools sets DT_RUNPATH not the deprecated
# DT_RPATH in ELF binaries. ld.so searches for libraries
# as follows:
# - LD_LIBRARY_PATH
# - RUNPATH
# - Directories given in /etc/ld.so.conf.
# /usr/local/lib is listed there.
# - Default path: /lib;/usr/lib.
# $ORIGIN is equivalent to @executable_path.
#
# Use relative path to installed lib so users can change
# installation location. Using CMAKE_INSTALL_FULL_LIBDIR
# would not work when changing the location during package
# install only when changing the installation location
# during `cmake --build` or `cmake --install`. The second
# entry may not be necessary as users installing to an
# alternate location will likely have it set in their
# LD_LIBRARY_PATH or /etc/ld.so.conf.
#
# Check DT_RUNPATH with one of
# - readelf -d <file> | head -20
# - objdump -x <file> | grep 'R.*PATH'
INSTALL_RPATH "\$ORIGIN;\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
)
endif()
endfunction()
add_subdirectory(imageio)
add_subdirectory(ktx)
add_subdirectory(ktx2check)
add_subdirectory(ktx2ktx2)
add_subdirectory(ktxinfo)
add_subdirectory(ktxsc)
add_subdirectory(toktx)
install(TARGETS
ktxtools
ktx2check
ktx2ktx2
ktxinfo
ktxsc
toktx
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT tools
)
+111
View File
@@ -0,0 +1,111 @@
# Copyright 2022 The Khronos Group Inc.
# SPDX-License-Identifier: Apache-2.0
set( PLUGIN_SOURCES
exr.imageio/exrinput.cc
jpg.imageio/jpginput.cc
npbm.imageio/npbminput.cc
png.imageio/pnginput.cc
png.imageio/pngoutput.cc
)
set( PLUGIN_HEADERS
)
set( EXTERNAL
"${PROJECT_SOURCE_DIR}/external"
)
set( EXR_PLUGIN_EXTERNAL_SOURCES
)
set( EXR_PLUGIN_EXTERNAL_HEADERS
${EXTERNAL}/astc-encoder/Source/ThirdParty/tinyexr.h
)
set( JPG_PLUGIN_EXTERNAL_SOURCES
${EXTERNAL}/basisu/encoder/jpgd.cpp
)
set( JPG_PLUGIN_EXTERNAL_HEADERS
${EXTERNAL}/basisu/encoder/jpgd.h
)
set( PNG_PLUGIN_EXTERNAL_SOURCES
${EXTERNAL}/lodepng/lodepng.cpp
)
set( PNG_PLUGIN_EXTERNAL_HEADERS
${EXTERNAL}/lodepng/lodepng.h
)
add_library( imageio STATIC
formatdesc.h
image.hpp
imagecodec.hpp
imagespan.hpp
imageinput.cc
imageio.cc
imageio.h
imageio_utility.h
imageoutput.cc
${PLUGIN_HEADERS}
${PLUGIN_SOURCES}
${EXR_PLUGIN_EXTERNAL_HEADERS}
${EXR_PLUGIN_EXTERNAL_SOURCES}
${JPG_PLUGIN_EXTERNAL_HEADERS}
${JPG_PLUGIN_EXTERNAL_SOURCES}
${PNG_PLUGIN_EXTERNAL_HEADERS}
${PNG_PLUGIN_EXTERNAL_SOURCES}
)
source_group(TREE "${CMAKE_CURRENT_LIST_DIR}" PREFIX "Source Files" FILES ${PLUGIN_SOURCES})
source_group(TREE "${CMAKE_CURRENT_LIST_DIR}" PREFIX "Header Files" FILES ${PLUGIN_HEADERS})
source_group("Header Files/exr.imageio" FILES
${EXR_PLUGIN_EXTERNAL_SOURCES}
)
source_group("Header Files/exr.imageio" FILES
${EXR_PLUGIN_EXTERNAL_HEADERS}
)
source_group("Source Files/jpg.imageio" FILES
${JPG_PLUGIN_EXTERNAL_SOURCES}
)
source_group("Header Files/jpg.imageio" FILES
${JPG_PLUGIN_EXTERNAL_HEADERS}
)
source_group("Source Files/png.imageio" FILES
${PNG_PLUGIN_EXTERNAL_SOURCES}
)
source_group("Header Files/png.imageio" FILES
${PNG_PLUGIN_EXTERNAL_HEADERS}
)
target_include_directories(
imageio
PUBLIC
.
PRIVATE
$<TARGET_PROPERTY:objUtil,INTERFACE_INCLUDE_DIRECTORIES>
)
target_include_directories(
imageio
SYSTEM
PUBLIC
${PROJECT_SOURCE_DIR}/other_include
PRIVATE
"${PROJECT_SOURCE_DIR}/external/astc-encoder/Source/ThirdParty"
"${PROJECT_SOURCE_DIR}/external/basisu"
"${PROJECT_SOURCE_DIR}/external/dfdutils"
"${PROJECT_SOURCE_DIR}/external/lodepng"
)
target_compile_definitions(
imageio
PRIVATE
$<TARGET_PROPERTY:ktx,INTERFACE_COMPILE_DEFINITIONS>
)
target_compile_features(imageio PUBLIC cxx_std_17)
target_link_libraries(imageio fmt::fmt)
set_target_properties(imageio PROPERTIES
CXX_VISIBILITY_PRESET ${STATIC_APP_LIB_SYMBOL_VISIBILITY}
)
+320
View File
@@ -0,0 +1,320 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab:
// Copyright 2022 The Khronos Group Inc.
// SPDX-License-Identifier: Apache-2.0
#include "imageio.h"
#include <array>
#include <cassert>
#include <optional>
#include <string_view>
#include <vector>
// TEXR is not defined in tinyexr.h. Current GitHub tinyexr master uses
// assert. The version in astc-encoder must be old.
#define TEXR_ASSERT(x) assert(x)
#define TINYEXR_IMPLEMENTATION
#include "tinyexr.h"
#include <KHR/khr_df.h>
#include "dfd.h"
#include <fmt/format.h>
//
// Documentation on the OpenEXR format can be found at:
// https://openexr.readthedocs.io/en/latest/
// More information about OpenEXR including sample images can be found at:
// https://www.openexr.com/
//
class ExrInput final : public ImageInput {
public:
ExrInput() : ImageInput("exr") {
// InitEXRVersion(&version); // No need to call, no such function
InitEXRHeader(&header);
InitEXRImage(&image);
}
~ExrInput() {
// FreeEXRVersion(&version); // No need to call, no such function
if (image.width != 0 && image.height != 0) {
FreeEXRImage(&image);
}
FreeEXRHeader(&header);
FreeEXRErrorMessage(err);
}
virtual void open(ImageSpec&) override;
virtual void readImage(void* buffer, size_t bufferByteCount,
uint32_t subimage = 0, uint32_t miplevel = 0,
const FormatDescriptor& targetFormat = FormatDescriptor()) override;
/// Read a single scanline (all channels) of native data into contiguous
/// memory.
virtual void readNativeScanline(void* /*buffer*/, size_t /*bufferByteCount*/,
uint32_t /*y*/, uint32_t /*z*/,
uint32_t /*subimage*/, uint32_t /*miplevel*/) override { };
void slurp();
private:
std::vector<unsigned char> exrBuffer;
private:
EXRVersion version;
EXRHeader header;
EXRImage image;
int ec = 0;
const char* err = nullptr;
};
ImageInput* exrInputCreate() {
return new ExrInput;
}
const char* exrInputExtensions[] = { "exr", nullptr };
void ExrInput::slurp() {
size_t exrByteLength;
isp->seekg(0, isp->end);
exrByteLength = isp->tellg();
isp->seekg(0, isp->beg);
exrBuffer.resize(exrByteLength);
isp->read(reinterpret_cast<char*>(exrBuffer.data()), exrByteLength);
}
void ExrInput::open(ImageSpec& newspec) {
assert(isp != nullptr && "ImageInput not properly opened");
{
unsigned char versionData[tinyexr::kEXRVersionSize];
isp->read(reinterpret_cast<char*>(versionData), tinyexr::kEXRVersionSize);
if (isp->fail())
throwOnReadFailure();
isp->seekg(0);
ec = ParseEXRVersionFromMemory(&version, versionData, tinyexr::kEXRVersionSize);
if (ec == TINYEXR_ERROR_INVALID_MAGIC_NUMBER)
throw different_format();
if (ec != TINYEXR_SUCCESS)
throw std::runtime_error(fmt::format("EXR version decode error: {}.", ec));
}
// It's an EXR file
slurp();
ec = ParseEXRHeaderFromMemory(&header, &version, exrBuffer.data(), exrBuffer.size(), &err);
if (ec != TINYEXR_SUCCESS)
throw std::runtime_error(fmt::format("EXR header decode error: {} - {}.", ec, err));
// Determine file data format
uint32_t bitDepth = 0;
ImageInputFormatType formatType;
int qualifiers = 0;
for (int i = 0; i < header.num_channels; i++) {
const auto type = header.channels[i].pixel_type;
switch (type) {
case TINYEXR_PIXELTYPE_FLOAT:
bitDepth = std::max(bitDepth, 32u);
formatType = ImageInputFormatType::exr_float;
qualifiers = KHR_DF_SAMPLE_DATATYPE_SIGNED | KHR_DF_SAMPLE_DATATYPE_FLOAT;
break;
case TINYEXR_PIXELTYPE_HALF:
bitDepth = std::max(bitDepth, 16u);
formatType = ImageInputFormatType::exr_float;
qualifiers = KHR_DF_SAMPLE_DATATYPE_SIGNED | KHR_DF_SAMPLE_DATATYPE_FLOAT;
break;
case TINYEXR_PIXELTYPE_UINT:
bitDepth = std::max(bitDepth, 32u);
formatType = ImageInputFormatType::exr_uint;
qualifiers = 0;
break;
default:
throw std::runtime_error(fmt::format("EXR header decode error: Not supported pixel type: {}.", type));
}
}
const uint32_t width = header.data_window[2] - header.data_window[0] + 1;
const uint32_t height = header.data_window[3] - header.data_window[1] + 1;
// Use "chromaticities" attribute, if present, to determine color primaries
khr_df_primaries_e colorPrimaries = KHR_DF_PRIMARIES_UNSPECIFIED;
for (int i = 0; i < header.num_custom_attributes; ++i) {
auto attributeName = std::string_view(header.custom_attributes[i].name);
if (attributeName == "chromaticities") {
int expectedSize = 8 * sizeof(float);
if (header.custom_attributes[i].size != expectedSize) {
throw std::runtime_error(fmt::format("EXR chromaticities attribute decode error: Expected size {} but got {}.",
expectedSize, header.custom_attributes[i].size));
}
const float* chromaticities = (const float*)header.custom_attributes[i].value;
Primaries primaries;
primaries.Rx = chromaticities[0];
primaries.Ry = chromaticities[1];
primaries.Gx = chromaticities[2];
primaries.Gy = chromaticities[3];
primaries.Bx = chromaticities[4];
primaries.By = chromaticities[5];
primaries.Wx = chromaticities[6];
primaries.Wy = chromaticities[7];
colorPrimaries = findMapping(&primaries, 0.002f);
}
}
images.emplace_back(ImageSpec(
width,
height,
1,
// We make TinyEXR decode to top-left.
ImageSpec::Origin(ImageSpec::Origin::eLeft, ImageSpec::Origin::eTop),
header.num_channels,
bitDepth,
static_cast<khr_df_sample_datatype_qualifiers_e>(qualifiers),
KHR_DF_TRANSFER_LINEAR,
colorPrimaries,
KHR_DF_MODEL_RGBSDA),
formatType);
newspec = spec();
}
/// @brief Read an entire image into contiguous memory performing conversions
/// to @a requestFormat.
///
/// Supported conversions are half->[half,float,uint], float->float, and uint->uint.
void ExrInput::readImage(void* outputBuffer, size_t bufferByteCount,
uint32_t subimage, uint32_t miplevel,
const FormatDescriptor& requestFormat) {
assert(subimage == 0); (void) subimage;
assert(miplevel == 0); (void) miplevel;
const auto& targetFormat = requestFormat.isUnknown() ? spec().format() : requestFormat;
// Determine and verify requested format conversions
if (!targetFormat.sameUnitAllChannels() || targetFormat.samples.empty())
throw std::runtime_error(fmt::format("EXR load error: "
"Requested format conversion to different channels is not supported."));
const auto targetBitDepth = targetFormat.samples[0].bitLength + 1;
const bool targetL = targetFormat.samples[0].qualifierLinear;
const bool targetE = targetFormat.samples[0].qualifierExponent;
const bool targetS = targetFormat.samples[0].qualifierSigned;
const bool targetF = targetFormat.samples[0].qualifierFloat;
// TinyEXR only supports half->[half,float,uint], float->float, uint->uint conversions
int requestedType = 0;
if (targetBitDepth == 16 && !targetE && !targetL && targetS && targetF)
requestedType = TINYEXR_PIXELTYPE_HALF;
else if (targetBitDepth == 32 && !targetE && !targetL && targetS && targetF)
requestedType = TINYEXR_PIXELTYPE_FLOAT;
else if (targetBitDepth == 32 && !targetE && !targetL && !targetS && !targetF)
requestedType = TINYEXR_PIXELTYPE_UINT;
else
throw std::runtime_error(fmt::format("EXR load error: "
"Requested format conversion to {}-bit{}{}{}{} is not supported.",
targetBitDepth,
targetL ? " Linear" : "",
targetE ? " Exponent" : "",
targetS ? " Signed" : "",
targetF ? " Float" : ""));
for (int i = 0; i < header.num_channels; ++i) {
header.requested_pixel_types[i] = requestedType;
if (header.pixel_types[i] != TINYEXR_PIXELTYPE_HALF && header.pixel_types[i] != requestedType)
throw std::runtime_error(fmt::format("EXR load error: "
"Requested format conversion from the input type is not supported."));
}
// Load image version
EXRVersion exr_version;
ec = ParseEXRVersionFromMemory(&exr_version, exrBuffer.data(), exrBuffer.size());
if (ec != TINYEXR_SUCCESS)
throw std::runtime_error(
fmt::format("EXR load error: {} - {}.", ec, "Failed to parse EXR version"));
if (exr_version.multipart || exr_version.non_image)
throw std::runtime_error(
fmt::format("EXR load error: {}.", "Unsupported EXR version (2.0)"));
// Load image data
// TinyEXR decodes images so that the first bytes in the returned buffer
// are the top-left corner of the image with line_order == 0 "increasing Y"
// and the bottom-left corner otherwise, "decreasing Y". Force a top-left
// origin regardless of the line_order in the file. See
// https://github.com/syoyo/tinyexr/issues/213 for more information.
header.line_order = 0;
ec = LoadEXRImageFromMemory(&image, &header, exrBuffer.data(), exrBuffer.size(), &err);
if (ec != TINYEXR_SUCCESS)
throw std::runtime_error(fmt::format("EXR load error: {} - {}.", ec, err));
const auto height = static_cast<uint32_t>(image.height);
const auto width = static_cast<uint32_t>(image.width);
const auto numSourceChannels = static_cast<uint32_t>(image.num_channels);
const auto numTargetChannels = targetFormat.channelCount();
const auto expectedBufferByteCount = height * width * numTargetChannels * targetBitDepth / 8;
if (bufferByteCount != expectedBufferByteCount)
throw std::runtime_error(fmt::format("EXR load error: "
"Provided target buffer size is {} does not match the expected value: {}.", bufferByteCount, expectedBufferByteCount));
// Find the RGBA channels
std::array<std::optional<uint32_t>, 4> channels;
for (uint32_t i = 0; i < numSourceChannels; ++i) {
if (std::strcmp(header.channels[i].name, "R") == 0)
channels[0] = i;
else if (std::strcmp(header.channels[i].name, "G") == 0)
channels[1] = i;
else if (std::strcmp(header.channels[i].name, "B") == 0)
channels[2] = i;
else if (std::strcmp(header.channels[i].name, "A") == 0)
channels[3] = i;
else
warning(fmt::format("EXR load warning: Unrecognized channel \"{}\" is ignored.", header.channels[i].name));
// TODO: check for 1 channel "Y" and make greyscale texture.
// TODO: check for "Y", "RY" and "BY" (luminance/chroma) and reject as unsupported.
// TODO: check for "AR", "AG", "AB" and make texture with pre-multipled alpha provided there is also an A channel? Or reject?
}
// Copy the data
const auto copyData = [&](unsigned char* ptr, uint32_t dataSize, const void* defaultColor) {
const auto sourcePtr = [&](uint32_t channel, uint32_t x, uint32_t y) {
return reinterpret_cast<const unsigned char*>(image.images[channel] + (y * width + x) * dataSize);
};
for (uint32_t y = 0; y < height; ++y) {
for (uint32_t x = 0; x < width; ++x) {
auto* targetPixel = ptr + (y * width * numTargetChannels + x * numTargetChannels) * dataSize;
for (uint32_t c = 0; c < numTargetChannels; ++c) {
if (channels[c].has_value()) {
std::memcpy(targetPixel + c * dataSize, sourcePtr(*channels[c], x, y), dataSize);
} else {
std::memcpy(targetPixel + c * dataSize, static_cast<const uint8_t*>(defaultColor) + c * dataSize, dataSize);
}
}
}
}
};
switch (requestedType) {
case TINYEXR_PIXELTYPE_HALF: {
uint16_t defaultColor[] = { 0x0000, 0x0000, 0x0000, 0x3C00 }; // { 0.h, 0.h, 0.h,1.h }
copyData(reinterpret_cast<unsigned char*>(outputBuffer), sizeof(defaultColor[0]), &defaultColor[0]);
break;
}
case TINYEXR_PIXELTYPE_FLOAT: {
float defaultColor[] = { 0.f, 0.f, 0.f, 1.f };
copyData(reinterpret_cast<unsigned char*>(outputBuffer), sizeof(defaultColor[0]), &defaultColor[0]);
break;
}
case TINYEXR_PIXELTYPE_UINT: {
uint32_t defaultColor[] = { 0, 0, 0, 1 };
copyData(reinterpret_cast<unsigned char*>(outputBuffer), sizeof(defaultColor[0]), &defaultColor[0]);
break;
}
default:
assert(false && "Internal error");
break;
}
}
+765
View File
@@ -0,0 +1,765 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2023 The Khronos Group Inc.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Data Format Descriptor for imageio.
*
* @author Mark Callow
*/
#include <exception>
#include <iterator>
#include <string>
#include <vector>
#include <KHR/khr_df.h>
/// @brief Image format descriptor
///
/// Based on Khronos Data Format specification. Omits the parts needed for
/// serialization (size, descriptorType, etc.) and, since there is no worry
/// about writing & reading across compilers and platforms, uses a struct
/// instead of the khr\_df.h macros.
///
/// Note that @e samples are not @e channels, a.k.a @e components. @e samples
/// represent a series of contiguous bits in the bitstream representing
/// a pixel of the image. Since the various ImageInput derived classes
/// convert incoming data to local endianness most @e channels need only
/// a single sample. Only formats such as those with a shared exponent
/// need multiple samples per component.
///
/// This descriptor is way more general than is needed by the current set of
/// supported input formats for which there will always be one sample per
/// channel. We use this because it is familiar from use elsewhere in
/// KTX-Software and because of the large number of useful enums provided
/// by khr\_df.h.
///
/// @note This class uses the Data Format Specification nomenclature of
/// @e channel for consistency with thar spec. Elsewhere in KTX-Software
/// @e component is widely used.
///
struct FormatDescriptor {
/// @internal
/// @brief Basic descriptor.
struct basicDescriptor {
uint32_t colorModel: 8;
uint32_t colorPrimaries: 8;
uint32_t transferFunction: 8;
uint32_t dataFlags: 8;
uint32_t texelBlockDimension0: 8;
uint32_t texelBlockDimension1: 8;
uint32_t texelBlockDimension2: 8;
uint32_t texelBlockDimension3: 8;
uint32_t bytesPlane0: 8;
uint32_t bytesPlane1: 8;
uint32_t bytesPlane2: 8;
uint32_t bytesPlane3: 8;
uint32_t bytesPlane4: 8;
uint32_t bytesPlane5: 8;
uint32_t bytesPlane6: 8;
uint32_t bytesPlane7: 8;
/// @brief Default constructor
basicDescriptor() {
memset(this, 0, sizeof(*this));
}
/// @brief Constructor for unpacked, non-compressed textures.
basicDescriptor(uint32_t pixelByteCount,
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT) {
colorModel = m;
colorPrimaries = p;
transferFunction = t;
dataFlags = f;
texelBlockDimension0 = 0; // Uncompressed means only 1x1x1x1 blocks.
texelBlockDimension1 = 0;
texelBlockDimension2 = 0;
texelBlockDimension3 = 0;
bytesPlane0 = pixelByteCount;
bytesPlane1 = bytesPlane2 = bytesPlane3 = 0;
bytesPlane4 = bytesPlane5 = bytesPlane6 = bytesPlane7 = 0;
}
bool operator==(const basicDescriptor& rhs) const {
const uint32_t* a = reinterpret_cast<const uint32_t*>(this);
const uint32_t* b = reinterpret_cast<const uint32_t*>(&rhs);
for (uint32_t i = 0; i < 4; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
bool operator!=(const basicDescriptor& rhs) const {
return !(*this == rhs);
}
khr_df_model_e model() const noexcept {
return static_cast<khr_df_model_e>(colorModel);
}
khr_df_primaries_e primaries() const noexcept {
return static_cast<khr_df_primaries_e>(colorPrimaries);
}
khr_df_transfer_e transfer() const noexcept {
return static_cast<khr_df_transfer_e>(transferFunction);
}
khr_df_flags_e flags() const noexcept {
return static_cast<khr_df_flags_e>(dataFlags);
}
} basic;
/// @internal
/// @brief Extended descriptor.
///
/// In a true DFD this would be an extension descriptor type complete with
/// size, vendorId, descriptorType, etc.
struct extendedDescriptor {
uint32_t channelCount; /// Saved channel count to avoid having to loop
/// over samples to figure out the count.
bool sameUnitAllChannels = false; /// All samples have same types and sizes.
float oeGamma = -1; /// Power function exponent used when the image was
/// encoded, if one was used. -1 otherwise.
/// @internal
/// @brief ICC profile descriptor.
struct iccProfileDescriptor {
std::string name;
std::vector<uint8_t> profile;
iccProfileDescriptor() { }
iccProfileDescriptor(std::string& n, uint8_t* p, size_t ps) {
name = n;
profile.resize(ps);
profile.insert(profile.begin(), p, &p[ps]);
}
bool operator==(const iccProfileDescriptor& rhs) const {
bool result = !this->name.compare(rhs.name);
return result && (this->profile == rhs.profile);
}
bool operator!=(const iccProfileDescriptor& rhs) const {
return !(*this == rhs);
}
} iccProfile;
extendedDescriptor(uint32_t cc = 0) :
channelCount(cc) { }
bool operator==(const extendedDescriptor& rhs) const {
if (this->channelCount != rhs.channelCount) return false;
if (this->sameUnitAllChannels != rhs.sameUnitAllChannels) return false;
if (this->oeGamma != rhs.oeGamma) return false;
return this->iccProfile == rhs.iccProfile;
}
bool operator!=(const extendedDescriptor& rhs) const {
return !(*this == rhs);
}
} extended;
struct sample {
uint32_t bitOffset: 16;
uint32_t bitLength: 8;
// uint32_t channelType: 8;
uint32_t channelType: 4;
uint32_t qualifierLinear: 1;
uint32_t qualifierExponent: 1;
uint32_t qualifierSigned: 1;
uint32_t qualifierFloat: 1;
uint32_t samplePosition0: 8;
uint32_t samplePosition1: 8;
uint32_t samplePosition2: 8;
uint32_t samplePosition3: 8;
uint32_t lower;
uint32_t upper;
bool operator==(const sample& rhs) const {
const uint32_t* a = reinterpret_cast<const uint32_t*>(this);
const uint32_t* b = reinterpret_cast<const uint32_t*>(&rhs);
for (uint32_t i = 0; i < 4; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
/// @brief Constructs an uninitialized sample object
sample() = default;
/// @brief Construct a sample with default sampleUpper and sampleLower.
///
/// For uncompressed formats. Handle integer data as normalized. For
/// unsigned use the full range of the number of bits. For signed set
/// sampleUpper and sampleLower so 0 is representable.
sample(uint32_t chanType,
uint32_t bLength, uint32_t offset,
khr_df_sample_datatype_qualifiers_e dataType
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e tf = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA) {
bitOffset = offset;
bitLength = bLength - 1;
channelType = chanType;
if (channelType == 3 && m != KHR_DF_MODEL_XYZW) {
/// XYZW does not have an alpha chennel. *_ALPHA has the same
/// value for all other 4-channel-capable uncompressed models.
channelType = KHR_DF_CHANNEL_RGBSDA_ALPHA;
}
qualifierFloat = (dataType & KHR_DF_SAMPLE_DATATYPE_FLOAT) != 0;
qualifierSigned = (dataType & KHR_DF_SAMPLE_DATATYPE_SIGNED) != 0;
qualifierExponent = (dataType & KHR_DF_SAMPLE_DATATYPE_EXPONENT) != 0;
qualifierLinear = (dataType & KHR_DF_SAMPLE_DATATYPE_LINEAR) != 0;
if (tf > KHR_DF_TRANSFER_LINEAR
&& channelType == KHR_DF_CHANNEL_RGBSDA_ALPHA) {
qualifierLinear = 1;
}
union {
uint32_t i;
float f;
} uLower, uUpper;
if (qualifierFloat) {
if (qualifierSigned) {
uUpper.f = 1.0f;
uLower.f = -1.0f;
} else {
uUpper.f = 1.0f;
uLower.f = 0.0f;
}
} else {
if (qualifierSigned) {
// signed normalized
if (bitLength > 32) {
uUpper.i = 0x7FFFFFFF;
} else {
uUpper.i = (1U << (bLength - 1)) - 1;
}
uLower.i = ~uUpper.i;
uLower.i += 1;
} else {
// unsigned normalized
if (bitLength > 32) {
uUpper.i = 0xFFFFFFFFU;
} else {
uUpper.i = (uint32_t)((1U << bLength) - 1U);
}
uLower.i = 0U;
}
}
lower = uLower.i;
upper = uUpper.i;
}
/// @brief Construct a sample with custom sampleLower and sampleUpper.
///
/// For uncompressed formats.
sample(uint32_t chanType,
uint32_t bitLength, uint32_t offset,
uint32_t sampleLower, uint32_t sampleUpper,
khr_df_sample_datatype_qualifiers_e dataType
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e tf = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA)
: sample(chanType, bitLength, offset, dataType, tf, m)
{
if (qualifierFloat) {
throw std::runtime_error(
"Invalid use of constructor for float data"
);
}
lower = sampleLower;
upper = sampleUpper;
}
};
std::vector<sample> samples;
/// @brief Default constructor
///
/// Will have zero samples which means format unknown.
FormatDescriptor() { }
/// @brief Constructor for unpacked, non-compressed data.
///
/// All channels have the same number of bits and basic data type.
/// As all wide data types will be in local endianness we need only
/// one sample per channel.
///
/// Data is assumed to be unsigned normalized. @c sampleUpper will be
/// set to the max value representable by @a channelBitLength.
///
/// @c channelType will be set to the standard channel types for @a channelCount
/// and @a m.
FormatDescriptor(uint32_t channelCount, uint32_t channelBitLength,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: basic((channelBitLength * channelCount) / 8, t, p, m, f),
extended(channelCount)
{
for (uint32_t s = 0; s < channelCount; s++) {
samples.push_back(sample(s, channelBitLength,
s * channelBitLength,
dt, t, m));
}
if (m == KHR_DF_MODEL_YUVSDA && channelCount == 2) {
samples[1].channelType = KHR_DF_CHANNEL_YUVSDA_ALPHA;
}
extended.sameUnitAllChannels = true;
}
/// @brief Constructor for unpacked, non-compressed data with custom
/// sampleLower and sampleUpper
///
/// All channels have the same number of bits and basic data type.
/// Use this for unnormalized integer data or normalized data that does not
/// use the full range representable by @a channelBitLength.
FormatDescriptor(uint32_t channelCount, uint32_t channelBitLength,
uint32_t sampleLower, uint32_t sampleUpper,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: basic((channelBitLength * channelCount) / 8, t, p, m, f),
extended(channelCount)
{
for (uint32_t s = 0; s < channelCount; s++) {
samples.push_back(sample(s, channelBitLength,
s * channelBitLength,
sampleLower, sampleUpper,
dt, t, m));
}
if (m == KHR_DF_MODEL_YUVSDA && channelCount == 2) {
samples[1].channelType = KHR_DF_CHANNEL_YUVSDA_ALPHA;
}
extended.sameUnitAllChannels = true;
}
static uint32_t totalBits(uint32_t sampleCount, std::vector<uint32_t>& bits) {
uint32_t totalBits = 0;
for (uint32_t s = 0; s < sampleCount; s++) {
totalBits += bits[s];
}
return totalBits;
}
/// @brief Constructor for non-compressed textures with varying bit lengths or channel types.
///
/// Each channel has the same basic data type.
FormatDescriptor(uint32_t channelCount,
std::vector<uint32_t>& channelBitLengths,
std::vector<khr_df_model_channels_e>& channelTypes,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: basic(totalBits(channelCount, channelBitLengths) >> 3, t, p, m, f),
extended(channelCount)
{
if (channelCount > channelBitLengths.size()
|| channelCount > channelTypes.size()) {
throw std::runtime_error(
"Not enough channelBits or channelType specfications."
);
}
uint32_t bitOffset = 0;
bool bitLengthsEqual = true;
uint32_t firstBitLength = channelBitLengths[0];
for (uint32_t s = 0; s < channelCount; s++) {
samples.push_back(sample(channelTypes[s], channelBitLengths[s],
bitOffset, dt, t, m));
bitOffset += channelBitLengths[s];
if (firstBitLength != channelBitLengths[s]) {
bitLengthsEqual = false;
}
}
if (bitLengthsEqual) {
extended.sameUnitAllChannels = true;
}
if (m == KHR_DF_MODEL_YUVSDA && channelCount == 2) {
samples[1].channelType = KHR_DF_CHANNEL_YUVSDA_ALPHA;
}
}
/// @brief Constructor for non-compressed textures with varying bit lengths
/// or channel types and custom sampleLower and sampleUpper.
///
/// Each channel has the same basic data type. Use this for unnormalized
/// integer data or normalized data that does not use the full bit range.
FormatDescriptor(uint32_t channelCount,
std::vector<uint32_t>& channelBitLengths,
std::vector<khr_df_model_channels_e>& channelTypes,
std::vector<uint32_t>& samplesLower,
std::vector<uint32_t>& samplesUpper,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: basic(totalBits(channelCount, channelBitLengths) >> 3, t, p, m, f),
extended(channelCount)
{
if (channelCount > channelBitLengths.size()
|| channelCount > channelTypes.size()) {
throw std::runtime_error(
"Not enough channelBits or channelType specfications."
);
}
uint32_t bitOffset = 0;
bool bitLengthsEqual = true;
uint32_t firstBitLength = channelBitLengths[0];
for (uint32_t s = 0; s < channelCount; s++) {
samples.push_back(sample(channelTypes[s], channelBitLengths[s],
samplesLower[s], samplesUpper[s],
bitOffset, dt, t, m));
bitOffset += channelBitLengths[s];
if (firstBitLength != channelBitLengths[s]) {
bitLengthsEqual = false;
}
}
if (bitLengthsEqual) {
extended.sameUnitAllChannels = true;
}
if (m == KHR_DF_MODEL_YUVSDA && channelCount == 2) {
samples[1].channelType = KHR_DF_CHANNEL_YUVSDA_ALPHA;
}
}
/// @brief Constructor for non-compressed, shared exponent format.
///
/// Each channel is a floating point. All channels share the same exponent
/// and have the same number of mantissa bits.
// TODO: Handle whether there is an implicit 1 and a sign bit.
FormatDescriptor(uint32_t channelCount,
uint32_t mantissaBitLength,
uint32_t exponentBitLength,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_LINEAR,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: basic((channelCount * mantissaBitLength + exponentBitLength) >> 3,
t, p, m, f),
extended(channelCount)
{
if (dt & KHR_DF_SAMPLE_DATATYPE_FLOAT) {
throw std::runtime_error(
"DATATYPE_FLOAT is set for a shared exponent format");
}
for (uint32_t s = 0; s < channelCount; s++) {
uint32_t sampleLower = 0, sampleUpper;
// sampleUpper and sampleLower values for the mantissa should be
// set to indicate the representation of 1.0 and 0.0 (for unsigned
// formats) or -1.0 (for signed formats) respectively when the
// exponent is in a 0 position after any bias has been corrected.
// If there is an implicit 1 bit, these values for the mantissa
// will exceed what can be represented in the number of available
// mantissa bits.
sampleUpper = 1U << mantissaBitLength;
samples.push_back(sample(s, mantissaBitLength,
s * mantissaBitLength,
sampleLower, sampleUpper,
dt, t, m));
// The sampleLower for the exponent should indicate the exponent
// bias. That is, the mantissa should be scaled by two raised to
// the power of the stored exponent minus this sampleLower value.
//
// The sampleUpper for the exponent indicates the maximum legal
// exponent value. Values above this are used to encode infinities
// and not-a-number (NaN) values. sampleUpper can therefore be used
// to indicate whether or not the format supports these encodings.
//sampleLower = exponentBias;
//sampleUpper = maxLegalExponentValue;
samples.push_back(sample(s, exponentBitLength,
channelCount * mantissaBitLength,
sampleLower, sampleUpper,
static_cast<khr_df_sample_datatype_qualifiers_e>(dt | KHR_DF_SAMPLE_DATATYPE_EXPONENT),
t, m));
}
extended.sameUnitAllChannels = true;
}
/// @brief Constructor from pre-constructed basic and sample descriptors
FormatDescriptor(
FormatDescriptor::basicDescriptor basic,
std::vector<FormatDescriptor::sample> samples_)
: basic(basic),
extended(static_cast<uint32_t>(samples_.size())),
samples(std::move(samples_))
{
extended.sameUnitAllChannels = true;
if (!samples.empty()) {
for (uint32_t i = 1; i < static_cast<uint32_t>(samples.size()); ++i) {
if (samples[0].bitLength != samples[i].bitLength
|| samples[0].qualifierLinear != samples[i].qualifierLinear
|| samples[0].qualifierExponent != samples[i].qualifierExponent
|| samples[0].qualifierSigned != samples[i].qualifierSigned
|| samples[0].qualifierFloat != samples[i].qualifierFloat) {
extended.sameUnitAllChannels = false;
break;
}
}
}
}
bool isUnknown() const noexcept {
return samples.size() == 0;
}
bool sameUnitAllChannels() const noexcept {
return extended.sameUnitAllChannels;
}
bool operator==(const FormatDescriptor& rhs) const {
if (this->basic != rhs.basic) return false;
if (this->extended != rhs.extended) return false;
return this->samples == rhs.samples;
}
bool operator!=(const FormatDescriptor& rhs) const {
return !(*this == rhs);
}
khr_df_model_e model() const noexcept {
return basic.model();
}
khr_df_primaries_e primaries() const noexcept {
return basic.primaries();
}
khr_df_transfer_e transfer() const noexcept {
return basic.transfer();
}
khr_df_flags_e flags() const noexcept {
return basic.flags();
}
float oeGamma() const noexcept {
return extended.oeGamma;
}
const std::string& iccProfileName() const noexcept {
return extended.iccProfile.name;
}
const std::vector<uint8_t>& iccProfile() const noexcept {
return extended.iccProfile.profile;
}
void setModel(khr_df_model_e m) {
basic.colorModel = m;
}
void setPrimaries(khr_df_primaries_e p) {
basic.colorPrimaries = p;
}
void setTransfer(khr_df_transfer_e t) {
khr_df_transfer_e oldOetf = transfer();
basic.transferFunction = t;
if ((oldOetf <= KHR_DF_TRANSFER_LINEAR) != (t <= KHR_DF_TRANSFER_LINEAR))
{
std::vector<sample>::iterator sit = samples.begin();
for (; sit < samples.end(); sit++) {
if (sit->channelType == KHR_DF_CHANNEL_RGBSDA_ALPHA) {
sit->qualifierLinear = t > KHR_DF_TRANSFER_LINEAR;
}
}
}
}
uint32_t pixelByteCount() const noexcept {
return basic.bytesPlane0;
}
uint32_t sampleCount() const noexcept {
return static_cast<uint32_t>(samples.size());
}
uint32_t sampleBitLength(uint32_t s) const noexcept {
return samples[s].bitLength + 1;
}
// TODO: remove?
uint32_t sampleByteCount(uint32_t s) const noexcept {
// Use integer division so 0 is returned when length is < a byte.
return sampleBitLength(s) / 8;
}
uint32_t sampleUpper(uint32_t s) const noexcept {
return samples[s].upper;
}
uint32_t channelCount() const noexcept {
return extended.channelCount;
}
uint32_t channelBitLength(khr_df_model_channels_e c) const {
std::vector<sample>::const_iterator it = samples.begin();
uint32_t bitLength = 0;
for (; it < samples.end(); it++) {
if (it->channelType == static_cast<uint32_t>(c)) {
bitLength += it->bitLength + 1;
}
}
if (bitLength == 0) {
throw std::runtime_error("No such channel.");
}
return bitLength;
}
uint32_t channelBitLength() const {
if (channelCount() == 1)
return samples[0].bitLength + 1;
if (!extended.sameUnitAllChannels) {
throw std::runtime_error(
"Differing size channels. Specify channel to query."
);
}
return channelBitLength(KHR_DF_CHANNEL_RGBSDA_R);
}
uint32_t largestChannelBitLength() const {
uint32_t maxBitLength = 0;
for (uint32_t i = 0; i < 16; ++i) {
uint32_t bitLength = 0;
for (const auto& sample : samples)
if (sample.channelType == i)
bitLength += sample.bitLength + 1;
if (bitLength > maxBitLength)
maxBitLength = bitLength;
}
return maxBitLength;
}
bool anyChannelBitLengthNotEqual(uint32_t bitLength) const {
for (uint32_t i = 0; i < 16; ++i) {
uint32_t channelBitLength = 0;
for (const auto& sample : samples)
if (sample.channelType == i)
channelBitLength += sample.bitLength + 1;
if (bitLength != channelBitLength)
return true;
}
return false;
}
khr_df_sample_datatype_qualifiers_e
channelDataType(khr_df_model_channels_e c) const {
// TODO: Fix for shared exponent case...
std::vector<sample>::const_iterator it = samples.begin();
for (; it < samples.end(); it++) {
if (it->channelType == static_cast<uint32_t>(c)) {
return static_cast<khr_df_sample_datatype_qualifiers_e>
(it->channelType & KHR_DF_SAMPLEMASK_QUALIFIERS);
}
}
throw std::runtime_error("No such channel.");
}
khr_df_sample_datatype_qualifiers_e channelDataType() const {
if (!extended.sameUnitAllChannels) {
throw std::runtime_error(
"Differing size channels. Specify channel to query."
);
}
return channelDataType(KHR_DF_CHANNEL_RGBSDA_R);
}
uint32_t channelUpper() const {
if (extended.channelCount != samples.size()) {
throw std::runtime_error(
"Multiple samples per channel. Call sampleUpper(uint32_t s)."
);
}
if (!extended.sameUnitAllChannels) {
throw std::runtime_error(
"Differing size channels. Call sampleUpper(uint32_t s)."
);
}
return sampleUpper(KHR_DF_CHANNEL_RGBSDA_R);
}
void updateSampleInfo(uint32_t channelCount, uint32_t channelBitLength,
uint32_t sampleLower, uint32_t sampleUpper,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0))
{
samples.clear();
for (uint32_t s = 0; s < channelCount; s++) {
samples.push_back(sample(s, channelBitLength,
s * channelBitLength,
sampleLower, sampleUpper,
dt,
basic.transfer(),
basic.model()));
}
if (basic.model() == KHR_DF_MODEL_YUVSDA && channelCount == 2) {
samples[1].channelType = KHR_DF_CHANNEL_YUVSDA_ALPHA;
}
extended.channelCount = channelCount;
extended.sameUnitAllChannels = true;
}
void updateSampleBitCounts(std::vector<uint32_t>& bits) {
uint32_t b, s;
uint32_t offset = 0;
for (b = 0, s = 0; s < samples.size(); s++) {
samples[s].bitLength = bits[b] - 1;
samples[s].bitOffset = offset;
offset += bits[b];
if (b < bits.size() - 1)
b++;
// else set remaining sample sizes to last available bits value.
}
}
void updateChannelCount(uint32_t newCount) {
if (newCount == extended.channelCount)
return;
if (extended.channelCount != samples.size()) {
// TODO: Either fix error handling or implement
throw std::runtime_error(
"changeChannelCount not supported when # samples != # channels");
}
extended.channelCount = newCount;
if (newCount < samples.size()) {
samples.erase(samples.begin() + newCount, samples.end());
return;
}
uint32_t firstNewIndex = static_cast<uint32_t>(samples.size());
uint32_t offset = samples.back().bitOffset
+ samples.back().bitLength + 1;
samples.resize(newCount, samples.back());
std::vector<sample>::iterator sit = samples.begin() + firstNewIndex;
for (; sit < samples.end(); sit++) {
sit->bitOffset = offset;
offset += (sit->bitLength + 1);
}
}
[[nodiscard]] const sample* find(khr_df_model_channels_e channel) const {
for (const auto& sample : samples)
if (sample.channelType == static_cast<uint32_t>(channel))
return &sample;
return nullptr;
}
void removeLastChannel() {
const auto numChannels = static_cast<uint32_t>(samples.size());
assert(numChannels > 1);
assert(basic.bytesPlane0 % numChannels == 0);
samples.pop_back();
basic.bytesPlane0 = basic.bytesPlane0 / numChannels * (numChannels - 1u);
if (extended.channelCount != 0)
--extended.channelCount;
}
friend std::ostream& operator<< (std::ostream& o, khr_df_sample_datatype_qualifiers_e q) {
if (q & KHR_DF_SAMPLE_DATATYPE_SIGNED)
o << " signed ";
if (q & KHR_DF_SAMPLE_DATATYPE_FLOAT) {
if (!(q & KHR_DF_SAMPLE_DATATYPE_SIGNED))
o << " ";
o << "float ";
}
return o;
}
};
File diff suppressed because it is too large Load Diff
+715
View File
@@ -0,0 +1,715 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab:
// Copyright 2023-2024 The Khronos Group Inc.
// Copyright 2023-2024 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
//!
//! @internal
//! @~English
//! @file
//!
//! @brief Internal Image Codec class
//!
#pragma once
#include <algorithm>
#include <functional>
#include <limits>
#include <type_traits>
#include <map>
#include <vector>
#include <assert.h>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4201)
#endif
#include <glm/gtc/packing.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#include "imageio_utility.h"
#include "vkformat_enum.h"
class ImageCodec {
public:
struct TexelBlockCodec {
};
ImageCodec() {
flags.valid = false;
flags.isBlockCompressed = false;
flags.isPacked = false;
flags.isFloat = false;
flags.isFloatHalf = false;
flags.isSigned = false;
flags.isNormalized = false;
}
ImageCodec(VkFormat vkFormat, uint32_t typeSize, const uint32_t* dfd) {
flags.valid = true;
flags.isBlockCompressed = false;
flags.isPacked = false;
flags.isFloat = false;
flags.isFloatHalf = false;
flags.isSigned = false;
flags.isNormalized = false;
const auto* bdfd = dfd + 1;
const auto model = khr_df_model_e(KHR_DFDVAL(bdfd, MODEL));
texelBlockDimensions[0] = KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION0) + 1;
texelBlockDimensions[1] = KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION1) + 1;
texelBlockDimensions[2] = KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION2) + 1;
texelBlockDimensions[3] = KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION3) + 1;
packedElementByteSize = typeSize;
// Packed element size must always be 1, 2, or 4 currently
// (for block compressed formats the element size is considered 1 by convention)
switch (packedElementByteSize) {
case 1: codec.getPackedElement = getPackedElement<uint8_t>; break;
case 2: codec.getPackedElement = getPackedElement<uint16_t>; break;
case 4: codec.getPackedElement = getPackedElement<uint32_t>; break;
default: flags.valid = false; return;
}
// We initialize the packed element count here based on the first plane's size and the element size
const auto firstPlaneBytes = KHR_DFDVAL(bdfd, BYTESPLANE0);
packedElementCount = firstPlaneBytes / packedElementByteSize;
// We can do the above because we do not currently support multiple planes
if (KHR_DFDVAL(bdfd, BYTESPLANE1) != 0) {
flags.valid = false;
return;
}
// If packedElementCount is zero, then there's something wrong with bytesPlane0 being zero
if (packedElementCount == 0) {
flags.valid = false;
return;
}
// By default we do not have directly accessible channels
// (e.g. for block compressed we can only access packed data)
channels = 0;
switch (model) {
case KHR_DF_MODEL_RGBSDA: [[fallthrough]];
case KHR_DF_MODEL_YUVSDA: [[fallthrough]];
case KHR_DF_MODEL_YIQSDA: [[fallthrough]];
case KHR_DF_MODEL_LABSDA: [[fallthrough]];
case KHR_DF_MODEL_CMYKA: [[fallthrough]];
case KHR_DF_MODEL_XYZW: [[fallthrough]];
case KHR_DF_MODEL_HSVA_ANG: [[fallthrough]];
case KHR_DF_MODEL_HSLA_ANG: [[fallthrough]];
case KHR_DF_MODEL_HSVA_HEX: [[fallthrough]];
case KHR_DF_MODEL_HSLA_HEX: [[fallthrough]];
case KHR_DF_MODEL_YCGCOA: [[fallthrough]];
case KHR_DF_MODEL_YCCBCCRC: [[fallthrough]];
case KHR_DF_MODEL_ICTCP: [[fallthrough]];
case KHR_DF_MODEL_CIEXYZ: [[fallthrough]];
case KHR_DF_MODEL_CIEXYY:
// These color models are handled as simple per-channel texel blocks
switch (vkFormat) {
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
flags.isFloat = true;
channels = 3;
codec.decodeFLOAT = decodeFLOAT_E9B5G5R5;
break;
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
channels = 3;
codec.decodeFLOAT = decodeFLOAT_B10G11R11;
break;
case VK_FORMAT_R16G16_SFIXED5_NV:
channels = 2;
codec.decodeFLOAT = decodeFLOAT_SFIXED5_NV<2>;
break;
case VK_FORMAT_D16_UNORM_S8_UINT:
flags.isNormalized = true;
channels = 2;
codec.decodeUINT = decodeUINT_D16_S8;
codec.decodeFLOAT = decodeFLOAT_D16_S8;
break;
case VK_FORMAT_X8_D24_UNORM_PACK32:
flags.isNormalized = true;
channels = 1;
codec.decodeUINT = decodeUINT_D24;
codec.decodeFLOAT = decodeFLOAT_D24;
break;
case VK_FORMAT_D24_UNORM_S8_UINT:
flags.isNormalized = true;
channels = 2;
codec.decodeUINT = decodeUINT_D24_S8;
codec.decodeFLOAT = decodeFLOAT_D24_S8;
break;
case VK_FORMAT_D32_SFLOAT_S8_UINT:
channels = 2;
codec.decodeFLOAT = decodeFLOAT_D32_S8;
break;
default: {
// For other formats we only support cases where the number formats match across the samples
const auto sampleCount = KHR_DFDSAMPLECOUNT(bdfd);
const auto firstDataType = KHR_DFDSVAL(bdfd, 0, QUALIFIERS) & ~KHR_DF_SAMPLE_DATATYPE_LINEAR;
const auto firstBitLength = KHR_DFDSVAL(bdfd, 0, BITLENGTH) + 1;
const auto sampleUpper = KHR_DFDSVAL(bdfd, 0, SAMPLEUPPER);
flags.isFloat = (firstDataType & KHR_DF_SAMPLE_DATATYPE_FLOAT) != 0;
flags.isFloatHalf = flags.isFloat && (firstBitLength == 16);
flags.isSigned = (firstDataType & KHR_DF_SAMPLE_DATATYPE_SIGNED) != 0;
flags.isNormalized = sampleUpper != (flags.isFloat ? imageio::bit_cast<uint32_t>(1.f) : 1u);
// Channel count matches sample count for these cases
channels = sampleCount;
if (firstDataType & KHR_DF_SAMPLE_DATATYPE_EXPONENT) {
// No other shared exponent formats are supported other than the ones handled explicitly
flags.valid = false;
return;
}
for (uint32_t i = 0; i < sampleCount; ++i) {
const auto dataType = KHR_DFDSVAL(bdfd, i, QUALIFIERS) & ~KHR_DF_SAMPLE_DATATYPE_LINEAR;
const auto bitLength = KHR_DFDSVAL(bdfd, i, BITLENGTH) + 1;
// If not all elements match the packed element byte size then this is a packed format
if (bitLength != firstBitLength || bitLength != packedElementByteSize * 8) {
flags.isPacked = true;
}
// We do not support mixed component types here as all special cases are handled outside
if (dataType != firstDataType) {
flags.valid = false;
return;
}
// For floats we also require either 32-bit or 16-bit float as all other
// special cases are currently handled outside
if (flags.isFloat && (bitLength != 16) && (bitLength != 32)) {
flags.valid = false;
return;
}
}
if (flags.isFloatHalf) {
// Data is a vector of FP16 values
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_FP16Vec<1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_FP16Vec<2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_FP16Vec<3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_FP16Vec<4>; break;
default: flags.valid = false; return;
}
} else if (flags.isFloat) {
// Data is a vector of FP32 values
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_FP32Vec<1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_FP32Vec<2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_FP32Vec<3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_FP32Vec<4>; break;
default: flags.valid = false; return;
}
} else if (flags.isPacked) {
// Data is packed so use the more general decoders
for (uint32_t i = 0; i < sampleCount; ++i) {
const auto bitOffset = KHR_DFDSVAL(bdfd, i, BITOFFSET);
const auto bitLength = KHR_DFDSVAL(bdfd, i, BITLENGTH) + 1;
SampleInfo sampleInfo{};
sampleInfo.elementIndex = (bitOffset >> 3) / packedElementByteSize;
sampleInfo.bitOffset = bitOffset % (packedElementByteSize << 3);
sampleInfo.bitLength = bitLength;
// If any of the samples straddle the packed elements then there's something wrong
if (sampleInfo.bitOffset + sampleInfo.bitLength > (packedElementByteSize << 3)) {
flags.valid = false;
return;
}
packedSampleInfo.emplace_back(sampleInfo);
}
switch (packedElementByteSize) {
case 1:
// 8-bit packed elements
if (flags.isSigned) {
codec.decodeSINT = decodeSINT_SINTPacked<int8_t>;
if (flags.isNormalized)
codec.decodeFLOAT = decodeFLOAT_SINTPacked<int8_t>;
} else {
codec.decodeUINT = decodeUINT_UINTPacked<uint8_t>;
if (flags.isNormalized)
codec.decodeFLOAT = decodeFLOAT_UINTPacked<uint8_t>;
}
break;
case 2:
// 16-bit packed elements
if (flags.isSigned) {
codec.decodeSINT = decodeSINT_SINTPacked<int16_t>;
if (flags.isNormalized)
codec.decodeFLOAT = decodeFLOAT_SINTPacked<int16_t>;
} else {
codec.decodeUINT = decodeUINT_UINTPacked<uint16_t>;
if (flags.isNormalized)
codec.decodeFLOAT = decodeFLOAT_UINTPacked<uint16_t>;
}
break;
case 4:
// 32-bit packed elements
if (flags.isSigned) {
codec.decodeSINT = decodeSINT_SINTPacked<int32_t>;
if (flags.isNormalized)
codec.decodeFLOAT = decodeFLOAT_SINTPacked<int32_t>;
} else {
codec.decodeUINT = decodeUINT_UINTPacked<uint32_t>;
if (flags.isNormalized)
codec.decodeFLOAT = decodeFLOAT_UINTPacked<uint32_t>;
}
break;
default:
flags.valid = false;
return;
}
} else {
// Data is not packed so we can use the optimized decoders
if (flags.isSigned) {
switch (packedElementByteSize) {
case 1:
// 8-bit signed integer
switch (sampleCount) {
case 1: codec.decodeSINT = decodeSINT_SINTVec<int8_t, 1>; break;
case 2: codec.decodeSINT = decodeSINT_SINTVec<int8_t, 2>; break;
case 3: codec.decodeSINT = decodeSINT_SINTVec<int8_t, 3>; break;
case 4: codec.decodeSINT = decodeSINT_SINTVec<int8_t, 4>; break;
default: flags.valid = false; return;
}
if (flags.isNormalized) {
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_SINTVec<int8_t, 1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_SINTVec<int8_t, 2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_SINTVec<int8_t, 3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_SINTVec<int8_t, 4>; break;
default: break;
}
}
break;
case 2:
// 16-bit signed integer
switch (sampleCount) {
case 1: codec.decodeSINT = decodeSINT_SINTVec<int16_t, 1>; break;
case 2: codec.decodeSINT = decodeSINT_SINTVec<int16_t, 2>; break;
case 3: codec.decodeSINT = decodeSINT_SINTVec<int16_t, 3>; break;
case 4: codec.decodeSINT = decodeSINT_SINTVec<int16_t, 4>; break;
default: flags.valid = false; return;
}
if (flags.isNormalized) {
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_SINTVec<int16_t, 1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_SINTVec<int16_t, 2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_SINTVec<int16_t, 3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_SINTVec<int16_t, 4>; break;
default: break;
}
}
break;
case 4:
// 32-bit signed integer
switch (sampleCount) {
case 1: codec.decodeSINT = decodeSINT_SINTVec<int32_t, 1>; break;
case 2: codec.decodeSINT = decodeSINT_SINTVec<int32_t, 2>; break;
case 3: codec.decodeSINT = decodeSINT_SINTVec<int32_t, 3>; break;
case 4: codec.decodeSINT = decodeSINT_SINTVec<int32_t, 4>; break;
default: flags.valid = false; return;
}
if (flags.isNormalized) {
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_SINTVec<int32_t, 1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_SINTVec<int32_t, 2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_SINTVec<int32_t, 3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_SINTVec<int32_t, 4>; break;
default: break;
}
}
break;
default:
flags.valid = false;
return;
}
} else {
switch (packedElementByteSize) {
case 1:
// 8-bit unsigned integer
switch (sampleCount) {
case 1: codec.decodeUINT = decodeUINT_UINTVec<uint8_t, 1>; break;
case 2: codec.decodeUINT = decodeUINT_UINTVec<uint8_t, 2>; break;
case 3: codec.decodeUINT = decodeUINT_UINTVec<uint8_t, 3>; break;
case 4: codec.decodeUINT = decodeUINT_UINTVec<uint8_t, 4>; break;
default: flags.valid = false; return;
}
if (flags.isNormalized) {
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint8_t, 1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint8_t, 2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint8_t, 3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint8_t, 4>; break;
default: break;
}
}
break;
case 2:
// 16-bit unsigned integer
switch (sampleCount) {
case 1: codec.decodeUINT = decodeUINT_UINTVec<uint16_t, 1>; break;
case 2: codec.decodeUINT = decodeUINT_UINTVec<uint16_t, 2>; break;
case 3: codec.decodeUINT = decodeUINT_UINTVec<uint16_t, 3>; break;
case 4: codec.decodeUINT = decodeUINT_UINTVec<uint16_t, 4>; break;
default: flags.valid = false; return;
}
if (flags.isNormalized) {
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint16_t, 1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint16_t, 2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint16_t, 3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint16_t, 4>; break;
default: break;
}
}
break;
case 4:
// 32-bit unsigned integer
switch (sampleCount) {
case 1: codec.decodeUINT = decodeUINT_UINTVec<uint32_t, 1>; break;
case 2: codec.decodeUINT = decodeUINT_UINTVec<uint32_t, 2>; break;
case 3: codec.decodeUINT = decodeUINT_UINTVec<uint32_t, 3>; break;
case 4: codec.decodeUINT = decodeUINT_UINTVec<uint32_t, 4>; break;
default: flags.valid = false; return;
}
if (flags.isNormalized) {
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint32_t, 1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint32_t, 2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint32_t, 3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint32_t, 4>; break;
default: break;
}
}
break;
default:
flags.valid = false;
return;
}
}
}
break;
}
}
break;
case KHR_DF_MODEL_BC1A: [[fallthrough]];
case KHR_DF_MODEL_BC2: [[fallthrough]];
case KHR_DF_MODEL_BC3: [[fallthrough]];
case KHR_DF_MODEL_BC4: [[fallthrough]];
case KHR_DF_MODEL_BC5: [[fallthrough]];
case KHR_DF_MODEL_BC6H: [[fallthrough]];
case KHR_DF_MODEL_BC7: [[fallthrough]];
case KHR_DF_MODEL_ETC1: [[fallthrough]];
case KHR_DF_MODEL_ETC2: [[fallthrough]];
case KHR_DF_MODEL_ASTC: [[fallthrough]];
case KHR_DF_MODEL_PVRTC: [[fallthrough]];
case KHR_DF_MODEL_PVRTC2:
// These color models are handled is raw compressed blocks
flags.isBlockCompressed = true;
break;
case KHR_DF_MODEL_UASTC:
// UASTC needs special handling
packedElementByteSize = 1;
packedElementCount = 16;
flags.isBlockCompressed = true;
break;
case KHR_DF_MODEL_ETC1S:
// ETC1S (as used by BasisLZ) is not supported directly
flags.valid = false;
return;
default:
flags.valid = false;
return;
}
texelBlockByteSize = packedElementByteSize * packedElementCount;
// If we couldn't determine the texel block size then something went wrong.
if (texelBlockByteSize == 0) {
flags.valid = false;
return;
}
}
operator bool() const { return flags.valid; }
constexpr bool isBlockCompressed() const { return flags.isBlockCompressed; }
constexpr bool isPacked() const { return flags.isPacked; }
constexpr bool isFloat() const { return flags.isFloat; }
constexpr bool isFloatHalf() const { return flags.isFloatHalf; }
constexpr bool isSigned() const { return flags.isSigned; }
constexpr bool isNormalized() const { return flags.isNormalized; }
constexpr bool canDecodeUINT() const { return codec.decodeUINT != nullptr; }
constexpr bool canDecodeSINT() const { return codec.decodeSINT != nullptr; }
constexpr bool canDecodeFLOAT() const { return codec.decodeFLOAT != nullptr; }
glm::uvec4 getTexelBlockDimensions() const { return texelBlockDimensions; }
constexpr uint32_t getPackedElementByteSize() const { return packedElementByteSize; }
constexpr uint32_t getPackedElementCount() const { return packedElementCount; }
constexpr uint32_t getTexelBlockByteSize() const { return texelBlockByteSize; }
constexpr uint32_t getChannelCount() const { return channels; }
glm::uvec4 pixelToTexelBlockSize(glm::uvec4 pixelSize) const {
return (pixelSize + texelBlockDimensions - glm::uvec4(1, 1, 1, 1)) / texelBlockDimensions;
}
uint32_t getPackedElement(const void* ptr, uint32_t index) const { return codec.getPackedElement(this, ptr, index); }
glm::uvec4 decodeUINT(const void* ptr) const { return codec.decodeUINT(this, ptr); }
glm::ivec4 decodeSINT(const void* ptr) const { return codec.decodeSINT(this, ptr); }
glm::vec4 decodeFLOAT(const void* ptr) const { return codec.decodeFLOAT(this, ptr); }
private:
struct {
uint32_t valid : 1;
uint32_t isBlockCompressed : 1;
uint32_t isPacked : 1;
uint32_t isFloat : 1;
uint32_t isFloatHalf : 1;
uint32_t isSigned : 1;
uint32_t isNormalized : 1;
} flags;
glm::uvec4 texelBlockDimensions = glm::uvec4(0, 0, 0, 0);
uint32_t packedElementByteSize = 0;
uint32_t packedElementCount = 0;
uint32_t texelBlockByteSize = 0;
uint32_t channels = 0;
struct SampleInfo {
uint32_t elementIndex;
uint32_t bitOffset;
uint32_t bitLength;
};
std::vector<SampleInfo> packedSampleInfo = {};
typedef uint32_t (*GetPackedElement)(const ImageCodec*, const void*, uint32_t);
typedef glm::uvec4 (*DecodeUINT)(const ImageCodec*, const void*);
typedef glm::ivec4 (*DecodeSINT)(const ImageCodec*, const void*);
typedef glm::vec4 (*DecodeFLOAT)(const ImageCodec*, const void*);
struct {
GetPackedElement getPackedElement = nullptr;
DecodeUINT decodeUINT = nullptr;
DecodeSINT decodeSINT = nullptr;
DecodeFLOAT decodeFLOAT = nullptr;
} codec = {};
template <typename TYPE>
static uint32_t getPackedElement(const ImageCodec* codec, const void* ptr, uint32_t index) {
static_assert(std::is_unsigned_v<TYPE>);
(void)codec; // silences unused parameter warnings in release builds
assert(sizeof(TYPE) == codec->getPackedElementByteSize());
auto data = reinterpret_cast<const TYPE*>(ptr);
return data[index];
}
static glm::vec4 decodeFLOAT_E9B5G5R5(const ImageCodec*, const void* ptr) {
auto data = reinterpret_cast<const uint32_t*>(ptr);
return glm::vec4(glm::unpackF3x9_E1x5(data[0]), 1.f);
}
static glm::vec4 decodeFLOAT_B10G11R11(const ImageCodec*, const void* ptr) {
auto data = reinterpret_cast<const uint32_t*>(ptr);
auto value = glm::unpackF2x11_1x10(data[0]);
// Need to handle NaN and infinity as special cases, because GLM "swallows" them
const uint32_t exponentShifts[] = { 6, 11 + 6, 22 + 5 };
const uint32_t exponentMask = 0x1F;
const uint32_t mantissaShifts[] = { 0, 11, 22 };
const uint32_t mantissaMasks[] = { 0x3F, 0x3F, 0x1F };
for (uint32_t channel = 0; channel < 3; ++channel) {
const uint32_t exponent = (data[0] >> exponentShifts[channel]) & exponentMask;
const uint32_t mantissa = (data[0] >> mantissaShifts[channel]) & mantissaMasks[channel];
if (exponent == 31) {
if (mantissa == 0) {
value[channel] = std::numeric_limits<float>::infinity();
} else {
value[channel] = std::numeric_limits<float>::quiet_NaN();
}
}
}
return glm::vec4(value, 1.f);
}
static glm::uvec4 decodeUINT_D16_S8(const ImageCodec*, const void* ptr) {
auto data = reinterpret_cast<const uint16_t*>(ptr);
return glm::uvec4(data[0], data[1] & 0xFF, 0, 0);
}
static glm::vec4 decodeFLOAT_D16_S8(const ImageCodec*, const void* ptr) {
auto data = reinterpret_cast<const uint16_t*>(ptr);
return glm::vec4(imageio::convertUNORMToFloat(data[0], 16), static_cast<float>(data[1] & 0xFF), 0.f, 1.f);
}
static glm::uvec4 decodeUINT_D24(const ImageCodec*, const void* ptr) {
auto data = reinterpret_cast<const uint32_t*>(ptr);
return glm::uvec4(data[0] & 0xFFFFFF, 0, 0, 0);
}
static glm::vec4 decodeFLOAT_D24(const ImageCodec*, const void* ptr) {
auto data = reinterpret_cast<const uint32_t*>(ptr);
return glm::vec4(imageio::convertUNORMToFloat(data[0] & 0xFFFFFF, 24), 0.f, 0.f, 1.f);
}
static glm::uvec4 decodeUINT_D24_S8(const ImageCodec*, const void* ptr) {
auto data = reinterpret_cast<const uint32_t*>(ptr);
return glm::uvec4(data[0] >> 8, data[0] & 0xFF, 0, 0);
}
static glm::vec4 decodeFLOAT_D24_S8(const ImageCodec*, const void* ptr) {
auto data = reinterpret_cast<const uint32_t*>(ptr);
return glm::vec4(imageio::convertUNORMToFloat(data[0] >> 8, 24), static_cast<float>(data[0] & 0xFF), 0.f, 1.f);
}
static glm::vec4 decodeFLOAT_D32_S8(const ImageCodec*, const void* ptr) {
auto data_float = reinterpret_cast<const float*>(ptr);
auto data_uint = reinterpret_cast<const uint32_t*>(ptr);
return glm::vec4(data_float[0], static_cast<float>(data_uint[1] & 0xFF), 0.f, 1.f);
}
template <int COMPONENTS>
static glm::vec4 decodeFLOAT_FP32Vec(const ImageCodec*, const void* ptr) {
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const float*>(ptr);
glm::vec4 result(0.f, 0.f, 0.f, 1.f);
for (int i = 0; i < COMPONENTS; ++i)
result[i] = data[i];
return result;
}
template <int COMPONENTS>
static glm::vec4 decodeFLOAT_FP16Vec(const ImageCodec*, const void* ptr) {
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const uint16_t*>(ptr);
glm::vec4 result(0.f, 0.f, 0.f, 1.f);
for (int i = 0; i < COMPONENTS; ++i)
result[i] = imageio::half_to_float(data[i]);
return result;
}
template <int COMPONENTS>
static glm::vec4 decodeFLOAT_SFIXED5_NV(const ImageCodec*, const void* ptr) {
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const int16_t*>(ptr);
glm::vec4 result(0.f, 0.f, 0.f, 1.f);
for (int i = 0; i < COMPONENTS; ++i)
result[i] = data[i] / 32.f;
return result;
}
template <typename TYPE, int COMPONENTS>
static glm::uvec4 decodeUINT_UINTVec(const ImageCodec*, const void* ptr) {
static_assert(std::is_unsigned_v<TYPE>);
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const TYPE*>(ptr);
glm::uvec4 result(0, 0, 0, 0);
for (int i = 0; i < COMPONENTS; ++i)
result[i] = data[i];
return result;
}
template <typename TYPE, int COMPONENTS>
static glm::vec4 decodeFLOAT_UINTVec(const ImageCodec*, const void* ptr) {
static_assert(std::is_unsigned_v<TYPE>);
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const TYPE*>(ptr);
const auto upper = static_cast<float>((256u << sizeof(TYPE)) - 1u);
glm::vec4 result(0.f, 0.f, 0.f, 1.f);
for (int i = 0; i < COMPONENTS; ++i)
result[i] = static_cast<float>(data[i]) / upper;
return result;
}
template <typename TYPE, int COMPONENTS>
static glm::ivec4 decodeSINT_SINTVec(const ImageCodec*, const void* ptr) {
static_assert(std::is_signed_v<TYPE>);
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const TYPE*>(ptr);
glm::ivec4 result(0, 0, 0, 0);
for (int i = 0; i < COMPONENTS; ++i)
result[i] = data[i];
return result;
}
template <typename TYPE, int COMPONENTS>
static glm::vec4 decodeFLOAT_SINTVec(const ImageCodec*, const void* ptr) {
static_assert(std::is_signed_v<TYPE>);
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const TYPE*>(ptr);
const auto upper = static_cast<float>((128u << sizeof(TYPE)) - 1u);
glm::vec4 result(0.f, 0.f, 0.f, 1.f);
for (int i = 0; i < COMPONENTS; ++i)
result[i] = std::max(static_cast<float>(data[i]) / upper, -1.f);
return result;
}
template <typename TYPE>
static glm::uvec4 decodeUINT_UINTPacked(const ImageCodec* codec, const void* ptr) {
static_assert(std::is_unsigned_v<TYPE>);
auto data = reinterpret_cast<const TYPE*>(ptr);
glm::uvec4 result(0, 0, 0, 0);
for (std::size_t i = 0; i < codec->packedSampleInfo.size(); ++i) {
const auto& info = codec->packedSampleInfo[i];
result[static_cast<glm::length_t>(i)] = (data[info.elementIndex] >> info.bitOffset) & ((1u << info.bitLength) - 1u);
}
return result;
}
template <typename TYPE>
static glm::vec4 decodeFLOAT_UINTPacked(const ImageCodec* codec, const void* ptr) {
static_assert(std::is_unsigned_v<TYPE>);
auto data = reinterpret_cast<const TYPE*>(ptr);
glm::vec4 result(0.f, 0.f, 0.f, 1.f);
for (std::size_t i = 0; i < codec->packedSampleInfo.size(); ++i) {
const auto& info = codec->packedSampleInfo[i];
const auto upper = static_cast<float>((1u << info.bitLength) - 1u);
uint32_t rawValue = (data[info.elementIndex] >> info.bitOffset) & ((1u << info.bitLength) - 1u);
result[static_cast<glm::length_t>(i)] = static_cast<float>(rawValue) / upper;
}
return result;
}
template <typename TYPE>
static glm::ivec4 decodeSINT_SINTPacked(const ImageCodec* codec, const void* ptr) {
static_assert(std::is_signed_v<TYPE>);
auto data = reinterpret_cast<const std::make_unsigned_t<TYPE>*>(ptr);
glm::ivec4 result(0, 0, 0, 0);
for (std::size_t i = 0; i < codec->packedSampleInfo.size(); ++i) {
const auto& info = codec->packedSampleInfo[i];
uint32_t rawValue = (data[info.elementIndex] >> info.bitOffset) & ((1u << info.bitLength) - 1u);
result[static_cast<glm::length_t>(i)] = imageio::sign_extend(rawValue, info.bitLength);
}
return result;
}
template <typename TYPE>
static glm::vec4 decodeFLOAT_SINTPacked(const ImageCodec* codec, const void* ptr) {
static_assert(std::is_signed_v<TYPE>);
auto data = reinterpret_cast<const std::make_unsigned_t<TYPE>*>(ptr);
glm::vec4 result(0.f, 0.f, 0.f, 1.f);
for (std::size_t i = 0; i < codec->packedSampleInfo.size(); ++i) {
const auto& info = codec->packedSampleInfo[i];
const auto upper = static_cast<float>((1u << (info.bitLength - 1)) - 1u);
uint32_t rawValue = (data[info.elementIndex] >> info.bitOffset) & ((1u << info.bitLength) - 1u);
result[static_cast<glm::length_t>(i)] = static_cast<float>(imageio::sign_extend(rawValue, info.bitLength)) / upper;
}
return result;
}
};
+354
View File
@@ -0,0 +1,354 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab:
// Copyright 2022 The Khronos Group Inc.
// SPDX-License-Identifier: Apache-2.0
//!
//! @internal
//! @~English
//! @file
//!
//! @brief ImageInput:open function
//!
#include "imageio.h"
#include "imageio_utility.h"
#include "platform_utils.h"
#include <iomanip>
#include <map>
#include <stdarg.h>
#include <stdexcept>
#include <filesystem>
// Search for and instantiate a plugin that can read the format
// and open the file.
std::unique_ptr<ImageInput>
ImageInput::open(const std::string& filename,
const ImageSpec* /*config*/,
WarningCallbackFunction wcb)
//Filesystem::IOProxy* ioproxy, string_view plugin_searchpath)
{
// Populate inputFormats.
if (Imageio::inputFormats.empty()) {
Imageio::catalogBuiltinPlugins();
assert(!Imageio::inputFormats.empty() && "No image input plugins compiled in.");
}
std::ifstream ifs;
std::unique_ptr<std::stringstream> buffer;
ImageInput::Creator createFunction = nullptr;
const std::string* fn;
const std::string sn("stdin");
bool doBuffer = true;
if (filename.compare("-")) {
// Regular file.
// Check file exists, before looking for a suitable plugin.
// MS's STL has `open` overloads that accept wchar_t to handle
// Window's Unicode file names.
ifs.open(std::filesystem::path(DecodeUTF8Path(filename)), std::ios::binary | std::ios::in);
if (ifs.fail()) {
throw std::runtime_error(
fmt::format("Open of \"{}\" failed. {}",
filename, strerror(errno))
);
}
// Extract the file extension from the filename (without the leading dot)
Imageio::string format = filename.substr(filename.find_last_of('.')+1);
format.tolower();
// Look for a plugin for that extension.
if (!format.empty()) {
// First try the plug-in that matches the extension
Imageio::InputPluginMap::const_iterator found
= Imageio::inputFormats.find(format);
if (found != Imageio::inputFormats.end()) {
createFunction = found->second;
}
}
fn = &filename;
} else {
// cin (stdin)
#if defined(_WIN32)
// Set "stdin" to have binary mode. There is no way to this via cin.
(void)_setmode( _fileno( stdin ), _O_BINARY );
// Windows shells set the FILE_SYNCHRONOUS_IO_NONALERT option when
// creating pipes. Cygwin since 3.4.x does the same thing, a change
// which affects anything dependent on it, e.g. Git for Windows
// (since 2.41.0) and MSYS2. When this option is set, cin.seekg(0)
// erroneously returns success. Always buffer.
doBuffer = true;
#else
// Can we seek in this cin?
std::cin.seekg(0);
doBuffer = std::cin.fail();
#endif
if (doBuffer) {
// Can't seek. Buffer stdin. This is a potentially large buffer.
// Must avoid copy. If use stack variable for ss, it's streambuf
// will also be on the stack and lost after this function exits
// even with std::move.
buffer =
std::unique_ptr<std::stringstream>(new std::stringstream);
*buffer << std::cin.rdbuf();
buffer->seekg(0, std::ios::beg);
}
fn = &sn;
}
// Remember which formats we've already tried, so we don't double dip.
std::vector<ImageInput::Creator> formatsTried;
std::string specificError;
std::unique_ptr<ImageInput> in;
if (createFunction) {
formatsTried.push_back(createFunction);
// Try our guess.
try {
in = std::unique_ptr<ImageInput>(createFunction());
in->connectCallback(wcb);
ImageSpec tmpspec;
in->open(*fn, ifs, buffer, tmpspec);
return in;
} catch (const std::runtime_error& e) {
// Oops, it failed. Apparently, this file can't be
// opened with this II.
if (in) {
specificError = e.what();
in.reset();
}
}
}
// A plugin designated for the requested extension either couldn't be
// found or was unable to open the file or there is no requested extension
// due to stdin. Try every plugin and see if one will open the file.
for (auto&& plugin : Imageio::inputFormats) {
// If we already tried this create function, don't do it again
if (std::find(formatsTried.begin(), formatsTried.end(), plugin.second)
!= formatsTried.end()) {
continue;
}
formatsTried.push_back(plugin.second); // remember
createFunction = plugin.second;
try {
in = std::unique_ptr<ImageInput>(createFunction());
in->connectCallback(wcb);
ImageSpec tmpspec;
in->open(*fn, ifs, buffer, tmpspec);
return in;
} catch (...) {
if (in.get()) in.reset();
continue;
}
}
if (!specificError.empty()) {
// Pass along any specific error message we got from our
// best guess of the format.
throw std::runtime_error(specificError);
} else {
throw std::runtime_error(
fmt::format(
"No image plugin recognized the format of \"{}\". "
"Is it a file format that we don't know about?\n",
filename.compare("-") ? filename : "the data on stdin")
);
}
}
/// @brief Open a file for image input.
///
/// Default implementation for derived classes.
void ImageInput::open(const std::string& filename, ImageSpec& newspec)
{
close(); // previously opened file.
if (filename.compare("-")) {
// Regular file
_filename = filename;
file.open(filename);
if (file.fail()) {
throw std::runtime_error(
fmt::format("Open of \"{}\" failed. {}",
filename, strerror(errno))
);
}
isp = &file;
open(newspec);
} else {
// stdin
_filename = "stdin";
#if defined(_WIN32)
/* Set "stdin" to have binary mode */
(void)_setmode( _fileno( stdin ), _O_BINARY );
#endif
// Can we seek in this cin?
std::cin.seekg(0);
if (std::cin.fail()) {
// Can't seek. Buffer stdin. This is a potentially large buffer.
// The class uses a unique_ptr due to the constraints of supporting
// the static create function. See comment in that function above.
buffer =
std::unique_ptr<std::stringstream>(new std::stringstream);
*buffer << std::cin.rdbuf();
isp = buffer.get();
open(newspec);
} else {
open(std::cin, newspec);
}
}
}
/// @brief Read an entire image into contiguous memory performing conversions
/// to @a format.
///
/// Default implementation for derived classes. Input channel values are scaled
/// from the input's channelUpper value to the max representable value of the
/// output format (unorm8 or unorm16).
///
/// Supported conversions are
/// - bit scaling
/// - unorm8<->unorm16
void
ImageInput::readScanline(void* pBufferOut, size_t bufferByteCount,
uint32_t y, uint32_t z,
uint32_t subimage, uint32_t miplevel,
const FormatDescriptor& format)
{
const auto& targetFormat = format.isUnknown() ? spec().format() : format;
const auto targetBitLength = targetFormat.largestChannelBitLength();
const auto requestBits = std::max(imageio::bit_ceil(targetBitLength), 8u);
if (requestBits != 8 && requestBits != 16)
throw std::runtime_error(fmt::format(
"Requested decode into {}-bit format is not supported.",
requestBits)
);
const bool targetL = targetFormat.samples[0].qualifierLinear;
const bool targetE = targetFormat.samples[0].qualifierExponent;
const bool targetS = targetFormat.samples[0].qualifierSigned;
const bool targetF = targetFormat.samples[0].qualifierFloat;
// Only UNORM requests are supported here.
if (targetE || targetL || targetS || targetF)
throw std::runtime_error(fmt::format(
"Requested format conversion to {}-bit{}{}{}{} is not supported.",
requestBits,
targetL ? " Linear" : "",
targetE ? " Exponent" : "",
targetS ? " Signed" : "",
targetF ? " Float" : "")
);
seekSubimage(subimage, miplevel);
size_t imageByteCount = (requestBits * spec().imageChannelCount()) / 8;
if (imageByteCount < bufferByteCount)
throw buffer_too_small();
uint8_t* pNativeBuffer;
if (targetFormat.channelBitLength() != spec().format().channelBitLength()) {
if (spec().format().channelBitLength() == 16) {
if (nativeBuffer16.size() < spec().imageChannelCount())
nativeBuffer16.resize(spec().imageChannelCount());
pNativeBuffer = reinterpret_cast<uint8_t*>(nativeBuffer16.data());
bufferByteCount = nativeBuffer16.size() * sizeof(uint16_t);
} else {
if (nativeBuffer8.size() < spec().imageChannelCount())
nativeBuffer8.resize(spec().imageChannelCount());
pNativeBuffer = nativeBuffer8.data();
bufferByteCount = nativeBuffer16.size() * sizeof(uint8_t);
}
} else {
pNativeBuffer = static_cast<uint8_t*>(pBufferOut);
}
readNativeScanline(pNativeBuffer, bufferByteCount, y, z, subimage, miplevel);
if (reinterpret_cast<uint16_t*>(pNativeBuffer) == nativeBuffer16.data()) {
rescale(static_cast<uint8_t*>(pBufferOut),
static_cast<uint8_t>(targetFormat.channelUpper()),
nativeBuffer16.data(),
static_cast<uint16_t>(spec().format().channelUpper()),
spec().imageChannelCount());
} else if (pNativeBuffer == nativeBuffer8.data()) {
rescale(static_cast<uint16_t*>(pBufferOut),
static_cast<uint16_t>(targetFormat.channelUpper()),
nativeBuffer8.data(),
static_cast<uint8_t>(spec().format().channelUpper()),
spec().imageChannelCount());
} else if (targetFormat.channelUpper() != spec().format().channelUpper()) {
if (spec().format().channelBitLength() == 16) {
rescale(static_cast<uint16_t*>(pBufferOut),
static_cast<uint16_t>(targetFormat.channelUpper()),
static_cast<uint16_t*>(pBufferOut),
static_cast<uint16_t>(spec().format().channelUpper()),
spec().imageChannelCount());
} else {
rescale(static_cast<uint8_t*>(pBufferOut),
static_cast<uint8_t>(targetFormat.channelUpper()),
static_cast<uint8_t*>(pBufferOut),
static_cast<uint8_t>(spec().format().channelUpper()),
spec().imageChannelCount());
}
}
}
/// @brief Read an entire image into contiguous memory performing conversions
/// to @a format.
///
/// Default implementation for derived classes.
///
/// @sa readScanline() for support conversions.
void
ImageInput::readImage(void* pBuffer, size_t bufferByteCount,
uint32_t subimage, uint32_t miplevel,
const FormatDescriptor& format)
{
const auto& targetFormat = format.isUnknown() ? spec().format() : format;
size_t outScanlineByteCount
= targetFormat.pixelByteCount() * spec().width();
if (bufferByteCount < outScanlineByteCount * spec().height())
throw buffer_too_small();
uint8_t* pDst = static_cast<uint8_t*>(pBuffer);
for (uint32_t y = 0; y < spec().height(); y++) {
readScanline(pDst, bufferByteCount,
y, 0, subimage, miplevel, targetFormat);
pDst += outScanlineByteCount;
bufferByteCount -= outScanlineByteCount;
}
}
void ImageInput::throwOnReadFailure()
{
if (isp->eof()) {
throw std::runtime_error("Unexpected end-of-file.");
} else {
throw std::runtime_error(
fmt::format("I/O error reading file: {}", strerror(errno))
);
}
}
void ImageInput::warning(const std::string& wmsg)
{
if (sendWarning) {
sendWarning(wmsg);
}
}
void ImageInput::fwarning(const std::string& wmsg)
{
std::string fwmsg = _filename + ": ";
fwmsg += wmsg;
warning(fwmsg);
}
+118
View File
@@ -0,0 +1,118 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab:
// Copyright 2022 The Khronos Group Inc.
// SPDX-License-Identifier: Apache-2.0
//!
//! @internal
//! @~English
//! @file
//!
//! @brief Create plugin maps.
//!
#include "imageio.h"
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <iostream>
#include <map>
#include <string>
#include <sstream>
#include <stdexcept>
#include <vector>
#include <stdarg.h>
#define PLUGENTRY(name) \
ImageInput* name##InputCreate(); \
ImageOutput* name##OutputCreate(); \
extern const char* name##InputExtensions[]; \
extern const char* name##OutputExtensions[];
#define PLUGENTRY_RO(name) \
ImageInput* name##InputCreate(); \
extern const char* name##InputExtensions[];
#define PLUGENTRY_WO(name) \
ImageOutput* name##OutputCreate(); \
extern const char* name##OutputExtensions[];
PLUGENTRY_RO(exr);
PLUGENTRY_RO(jpeg);
PLUGENTRY_RO(npbm);
PLUGENTRY(png)
namespace Imageio {
// These combine extensions and format names into a single map.
InputPluginMap inputFormats;
OutputPluginMap outputFormats;
void
declareImageioFormat(const std::string& formatname,
ImageInput::Creator inputCreator,
const char** inputExtensions,
ImageOutput::Creator outputCreator,
const char** outputExtensions)
{
if (inputCreator) {
for (const char** e = inputExtensions; e && *e; ++e) {
string ext(*e);
ext.tolower();
if (inputFormats.find(ext) == inputFormats.end()) {
inputFormats[ext] = inputCreator;
}
}
if (inputFormats.find(formatname) == inputFormats.end())
inputFormats[formatname] = inputCreator;
}
if (outputCreator) {
for (const char** e = outputExtensions; e && *e; ++e) {
string ext(*e);
ext.tolower();
if (outputFormats.find(ext) == outputFormats.end()) {
outputFormats[ext] = outputCreator;
}
}
if (outputFormats.find(formatname) == outputFormats.end())
outputFormats[formatname] = outputCreator;
}
}
void
catalogBuiltinPlugins()
{
#define DECLAREPLUG(name) \
declareImageioFormat( \
#name, (ImageInput::Creator)name##InputCreate, \
name##InputExtensions, \
(ImageOutput::Creator)name##OutputCreate, \
name##OutputExtensions)
#define DECLAREPLUG_RO(name) \
declareImageioFormat( \
#name, (ImageInput::Creator)name##InputCreate, \
name##InputExtensions, \
nullptr, nullptr)
#define DECLAREPLUG_WO(name) \
declareImageioFormat( \
nullptr, nullptr, \
#name, (ImageOutput::Creator)name##OutputCreate, \
name##OutputExtensions)
#if !defined(DISABLE_OPENEXR)
DECLAREPLUG_RO (exr);
#endif
#if !defined(DISABLE_JPEG)
DECLAREPLUG_RO(jpeg);
#endif
#if !defined(DISABLE_NPBM)
DECLAREPLUG_RO (npbm);
#endif
#if !defined(DISABLE_PNG)
DECLAREPLUG (png);
#endif
// 'Raw' format is not in the catalog as an explicit API is a better fit
}
} // namespace Imageio
+693
View File
@@ -0,0 +1,693 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab:
// Copyright 2022 The Khronos Group Inc.
// SPDX-License-Identifier: Apache-2.0
//!
//! @internal
//! @~English
//! @file
//!
//! @brief Base classes for image input and output plugins
//!
//! The API for these classes is inspired by that of OpenImageIO. We don't use
//! OIIO because
//! - the total size, with all its dependencies, is 128 Mb. There is no easy
//! way, i.e. via cmake comnfiguration, to omit plugins that are of no
//! interest.
//! - it takes between 40m and 1hr on the CI services to build it and all
//! its dependencies
//! - I have consistently been unable to build the vcpkg version of it for
//! all the platforms we need. vcpkg is the only package manager whose
//! installed products are redistributable.
//!
#pragma once
#include "stdafx.h"
#include <algorithm>
#include <cctype>
#include <iostream>
#include <fstream>
#include <functional>
#include <limits>
#include <map>
#include <string>
#include <sstream>
#include <vector>
#include <memory>
#include <fmt/format.h>
#include <math.h>
#include "formatdesc.h"
using stride_t = int64_t;
const stride_t AutoStride = std::numeric_limits<stride_t>::min();
typedef bool (*ProgressCallback)(void *opaque_data, float portion_done);
class ImageSpec {
public:
struct Origin {
uint8_t x;
uint8_t y;
uint8_t z;
static const uint8_t eLeft = 0;
static const uint8_t eRight = 1;
static const uint8_t eTop = 0;
static const uint8_t eBottom = 1;
static const uint8_t eFront = 0;
static const uint8_t eBack = 1;
static const uint8_t eUnspecified = 0xff;
// This is the most common origin among image file formats, hence
// the no arg constructor sets it. If unspecified, use the 3 arg ctor.
Origin() : x(eLeft), y(eTop), z(eFront) { };
Origin(uint8_t _x, uint8_t _y) : x(_x), y(_y), z(eFront) { }
Origin(uint8_t _x, uint8_t _y, uint8_t _z) : x(_x), y(_y), z(_z) { }
bool unspecified() {
return x == eUnspecified || y == eUnspecified || z == eUnspecified;
}
};
protected:
FormatDescriptor formatDesc;
uint32_t imageWidth; ///< width of the pixel data
uint32_t imageHeight; ///< height of the pixel data
uint32_t imageDepth; ///< depth of pixel data, >1 indicates a "volume"
Origin imageOrigin; ///< logical corner of image that is the first pixel in the data stream
public:
ImageSpec() : imageWidth(0), imageHeight(0),
imageDepth(0), imageOrigin() { }
ImageSpec(uint32_t w, uint32_t h, uint32_t d, FormatDescriptor& formatDesc)
: ImageSpec(w, h, d, Origin(), formatDesc) { }
ImageSpec(uint32_t w, uint32_t h, uint32_t d, Origin&& o,
FormatDescriptor formatDesc)
: formatDesc(std::move(formatDesc)),
imageWidth(w), imageHeight(h), imageDepth(d), imageOrigin(o) { }
ImageSpec(uint32_t w, uint32_t h, uint32_t d,
uint32_t channelCount, uint32_t channelBitCount,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: ImageSpec(w, h, d, Origin(), channelCount,
channelBitCount, dt, t, p, m, f) { }
ImageSpec(uint32_t w, uint32_t h, uint32_t d, Origin&& o,
uint32_t channelCount, uint32_t channelBitCount,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: formatDesc(channelCount, channelBitCount, dt, t, p, m, f),
imageWidth(w), imageHeight(h), imageDepth(d), imageOrigin(o) { }
ImageSpec(uint32_t w, uint32_t h, uint32_t d,
uint32_t channelCount, uint32_t channelBitCount,
uint32_t channelLower, uint32_t channelUpper,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: ImageSpec(w, h, d, Origin(), channelCount,
channelBitCount,channelLower, channelUpper,
dt, t, p, m, f) { }
ImageSpec(uint32_t w, uint32_t h, uint32_t d, Origin&& o,
uint32_t channelCount, uint32_t channelBitCount,
uint32_t channelLower, uint32_t channelUpper,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: formatDesc(channelCount, channelBitCount,
channelLower, channelUpper,
dt, t, p, m, f),
imageWidth(w), imageHeight(h), imageDepth(d), imageOrigin(o) { }
ImageSpec(uint32_t w, uint32_t h, uint32_t d,
uint32_t channelCount, std::vector<uint32_t>& channelBitLengths,
std::vector<khr_df_model_channels_e>& channelTypes,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: ImageSpec(w, h, d, Origin(), channelCount,
channelBitLengths, channelTypes, dt, t, p, m, f) { }
ImageSpec(uint32_t w, uint32_t h, uint32_t d, Origin&& o,
uint32_t channelCount, std::vector<uint32_t>& channelBitLengths,
std::vector<khr_df_model_channels_e>& channelTypes,
khr_df_sample_datatype_qualifiers_e dt
= static_cast<khr_df_sample_datatype_qualifiers_e>(0),
khr_df_transfer_e t = KHR_DF_TRANSFER_UNSPECIFIED,
khr_df_primaries_e p = KHR_DF_PRIMARIES_BT709,
khr_df_model_e m = KHR_DF_MODEL_RGBSDA,
khr_df_flags_e f = KHR_DF_FLAG_ALPHA_STRAIGHT)
: formatDesc(channelCount, channelBitLengths,
channelTypes, dt, t, p, m, f),
imageWidth(w), imageHeight(h), imageDepth(d), imageOrigin(o) { }
FormatDescriptor& format() { return formatDesc; }
const FormatDescriptor& format() const { return formatDesc; }
uint32_t width() const noexcept { return imageWidth; }
uint32_t height() const noexcept { return imageHeight; }
uint32_t depth() const noexcept { return imageDepth; }
const Origin& origin() const noexcept { return imageOrigin; }
void setWidth(uint32_t w) { imageWidth = w; }
void setHeight(uint32_t h) { imageHeight = h; }
void setDepth(uint32_t d) { imageDepth = d; }
void setOrigin(const Origin& o) { imageOrigin = o; }
size_t imagePixelCount() const noexcept {
return depth() * width() * height();
};
size_t imageChannelCount() const noexcept {
return imagePixelCount() * format().channelCount();
};
size_t imageByteCount() const noexcept {
return imagePixelCount() * format().pixelByteCount();
};
size_t scanlineByteCount() const noexcept {
return width() * format().pixelByteCount();
}
size_t scanlineChannelCount() const noexcept {
return width() * format().channelCount();
}
};
constexpr bool operator==(const ImageSpec::Origin& lhs, const ImageSpec::Origin& rhs) {
return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z;
}
constexpr bool operator!=(const ImageSpec::Origin& lhs, const ImageSpec::Origin& rhs) {
return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z;
}
[[nodiscard]] inline std::string toString(const ImageSpec::Origin& o) noexcept {
std::string str;
switch (o.x) {
case ImageSpec::Origin::eLeft: str = "left"; break;
case ImageSpec::Origin::eRight: str = "right"; break;
case ImageSpec::Origin::eUnspecified: str = "unspecified"; break;
default: assert(false && "Invalid origin.x");
}
str += ",";
switch (o.y) {
case ImageSpec::Origin::eTop: str += "top"; break;
case ImageSpec::Origin::eBottom: str += "bottom"; break;
case ImageSpec::Origin::eUnspecified: str += "unspecified"; break;
default: assert(false && "Invalid origin.y");
}
str += ",";
switch (o.z) {
case ImageSpec::Origin::eFront: str += "front"; break;
case ImageSpec::Origin::eBack: str += "back"; break;
case ImageSpec::Origin::eUnspecified: str += "unspecified"; break;
default: assert(false && "Invalid origin.z");
}
return str;
}
typedef std::function<void(const std::string&)> WarningCallbackFunction;
enum class ImageInputFormatType {
png_l,
png_la,
png_rgb,
png_rgba,
exr_uint,
exr_float,
npbm,
jpg,
};
inline const char* toString(ImageInputFormatType type) {
switch (type) {
case ImageInputFormatType::png_l:
return "png_l";
case ImageInputFormatType::png_la:
return "png_la";
case ImageInputFormatType::png_rgb:
return "png_rgb";
case ImageInputFormatType::png_rgba:
return "png_rgba";
case ImageInputFormatType::exr_uint:
return "exr_uint";
case ImageInputFormatType::exr_float:
return "exr_float";
case ImageInputFormatType::npbm:
return "npbm";
case ImageInputFormatType::jpg:
return "jpg";
}
assert(false && "Invalid ImageInputFormatType enum value");
return "<<invalid>>";
}
class ImageInput {
protected:
std::ifstream file;
std::unique_ptr<std::stringstream> buffer;
std::istream* isp = nullptr;
std::string name;
std::string _filename;
std::vector<uint16_t> nativeBuffer16;
std::vector<uint8_t> nativeBuffer8;
struct imageInfo {
ImageSpec spec;
ImageInputFormatType formatType;
size_t filepos;
imageInfo(ImageSpec&& is, ImageInputFormatType formatType, size_t pos = 0)
: spec(is), formatType(formatType), filepos(pos) { }
};
std::vector<imageInfo> images; ///<
uint32_t curSubimage = std::numeric_limits<uint32_t>::max();
uint32_t curMiplevel = std::numeric_limits<uint32_t>::max();
WarningCallbackFunction sendWarning = nullptr;
public:
using unique_ptr = std::unique_ptr<ImageInput>;
/// @brief Create an ImageInput subclass instance that is able to read the
/// given file and open it.
///
/// The `config`, if not nullptr, points to an ImageSpec giving hints,
/// requests, or special instructions. ImageInput implementations are
/// free to not respond to any such requests, so the default
/// implementation is just to ignore `config`.
///
/// `open()` will first try to make an ImageInput corresponding to
/// the format implied by the file extension (for example, `"foo.tif"`
/// will try the TIFF plugin), but if one is not found or if the
/// inferred one does not open the file, every known ImageInput type
/// will be tried until one is found that will open the file.
///
/// @param[in] filename The name of the file to open.
///
/// @param[in] config Optional pointer to an ImageSpec whose metadata
/// contains "configuration hints."
///
/// @returns
/// A `unique_ptr` that will close and free the ImageInput when
/// it exits scope or is reset. The pointer will be empty if the
/// required writer was not able to be created. If the open fails,
/// the `unique_ptr` will be empty. An error can be retrieved by
/// ImageInput::geterror().
static unique_ptr open(const std::string& filename,
const ImageSpec *config=nullptr,
WarningCallbackFunction wcb=nullptr);
//Filesystem::IOProxy* ioproxy = nullptr,
//string_view plugin_searchpath="");
virtual ~ImageInput() { close(); }
// TODO: is config necessary?
virtual void open (const std::string& filename, ImageSpec& newspec);
virtual void open (const std::string& filename, ImageSpec& newspec,
const ImageSpec& /*config*/) {
return open(filename, newspec);
}
virtual void close() {
if (isp == buffer.get()) {
buffer.reset();
} else if (file.is_open()) {
file.close();
}
isp = nullptr;
}
void connectCallback(WarningCallbackFunction wcb) {
sendWarning = wcb;
}
protected:
ImageInput(std::string&& name) : name(name) { }
virtual void open (std::ifstream&& ifs, ImageSpec& newspec) {
file = std::move(ifs);
isp = &file;
open(newspec);
}
virtual void open (std::ifstream& ifs, ImageSpec& newspec,
const ImageSpec& /*config*/) {
return open(ifs, newspec);
}
virtual void open (std::unique_ptr<std::stringstream>&& iss,
ImageSpec& newspec) {
buffer = std::move(iss);
isp = buffer.get();
open(newspec);
}
virtual void open (std::unique_ptr<std::stringstream>&& iss,
ImageSpec& newspec,
const ImageSpec& /*config*/) {
return open(std::move(iss), newspec);
}
virtual void open (std::istream& cin_, ImageSpec& newspec) {
isp = &cin_;
open(newspec);
}
virtual void open (std::istream& cin_, ImageSpec& newspec,
const ImageSpec& /*config*/) {
return open(cin_, newspec);
}
virtual void open(ImageSpec& newspec) = 0;
//virtual void open(ImageSpec& newspec, const ImageSpec& config) = 0;
std::ifstream& getFile() { return file; }
std::unique_ptr<std::stringstream>& getBuffer() { return buffer; }
void throwOnReadFailure();
void warning(const std::string& wmsg);
void fwarning(const std::string& wmsg);
private:
/** @internal
* @brief Open file or stringstream.
*
* This is solely to support the static open(). Subclasses should have no need
* to override.
*
* std::move is used because if the open is successful the class object needs to retain the
* fstream or stringstream that would otherwise disappear when open() exits. The catch
* clauses std::move the stream info back to the caller so it can keep searching for a plugin.
*/
void open(const std::string& filename, std::ifstream& ifs,
std::unique_ptr<std::stringstream>& bufferIn,
ImageSpec& newspec) {
_filename = filename; // Purely so warnings can include the file name.
if (ifs.is_open()) {
try {
open(std::move(ifs), newspec);
} catch (...) {
ifs = std::move(getFile());
ifs.clear();
ifs.seekg(0);
throw;
}
} else if (bufferIn.get() != nullptr) {
try {
open(std::move(bufferIn), newspec);
} catch (...) {
bufferIn = std::move(getBuffer());
bufferIn->clear();
bufferIn.get()->seekg(0);
throw;
}
} else {
try {
open(std::cin, newspec);
} catch (...) {
std::cin.clear();
std::cin.seekg(0);
throw;
}
}
}
public:
virtual const std::string& formatName(void) const { return name; }
virtual const std::string& filename(void) const { return _filename; }
// Return a reference to the ImageSpec of the current image.
// This default method assumes no subimages.
virtual const ImageSpec& spec (void) const {
return images[0].spec;
}
// Return the FormatType of the current image.
// This default method assumes no subimages.
virtual ImageInputFormatType formatType (void) const {
return images[0].formatType;
}
// Return a full copy of the ImageSpec of the designated subimage & level.
// If there is no such subimage and miplevel it returns an ImageSpec
// whose format returns true for isUnknown().
// This default method assumes no subimages.
virtual ImageSpec spec (uint32_t /*subimage*/, uint32_t /*miplevel=0*/) {
ImageSpec ret;
if (curSubimage < images.size()) {
ret = images[curSubimage].spec;
}
return ret;
}
// Return a copy of the ImageSpec but only the dimension and type fields.
// TODO: Determine if this is necessary.
virtual ImageSpec spec_dimensions (uint32_t /*subimage*/, uint32_t /*miplevel=0*/) {
return spec();
}
virtual uint32_t currentSubimage(void) const { return curSubimage; }
virtual uint32_t currentMiplevel(void) const { return curMiplevel; }
virtual uint32_t subimageCount(void) const { return 1; }
virtual uint32_t miplevelCount(void) const { return 1; }
virtual bool seekSubimage(uint32_t subimage, uint32_t miplevel = 0) {
// Default implementation assumes no support for subimages or
// mipmaps, so there is no work to do.
return subimage == currentSubimage() && miplevel == currentMiplevel();
}
/// Read an entire image into contiguous memory performing conversions to
/// @a requestFormat.
///
/// @TODO @a requestFormat allows callers to request almost unlimited
/// possible conversions compared to the original format. The current
/// plug-ins only provide a handful of conversions and those available
/// vary by plug-in. Plug-ins must throw an exception when an
/// unsupported conversion is requested. As a work in progress this is
/// okay but we need to rationalize all this such as
///
/// 1. a subset of all possible conversions supported by every plug-in
/// 2. conversion-specific exceptions so caller can tell what didn't work.
///
/// Commonly supported transformations are bit scaling and changing the
/// channel count, both adding and removing channels. See the derived
/// classes for the specific coversions supported.
///
virtual void readImage(void* buffer, size_t bufferByteCount,
uint32_t subimage = 0, uint32_t miplevel = 0,
const FormatDescriptor& requestFormat = FormatDescriptor());
/// @brief Read a scanline into contiguous memory performing conversions to
/// @a requestFormat.
///
/// Supported conversions in the default implementation are uint->uint for
/// 8- & 16-bit values.
///
/// @sa See readImage for information about handling of requestFormat.
virtual void readScanline(void* buffer, size_t bufferByteCount,
uint32_t y, uint32_t z,
uint32_t subimage, uint32_t miplevel,
const FormatDescriptor& requestFormat = FormatDescriptor());
/// Read a single scanline (all channels) of native data into contiguous
/// memory.
virtual void readNativeScanline(void* buffer, size_t bufferByteCount,
uint32_t y, uint32_t z = 0,
uint32_t subimage = 0, uint32_t miplevel = 0) = 0;
template<class Tr, class Tw>
inline static void
rescale(Tw* write, const Tr* read, size_t nvals, Tr max)
{
if (max) {
float multiplier = static_cast<float>(std::numeric_limits<Tw>::max()) / max;
for (size_t i = 0; i < nvals; i++) {
write[i] = static_cast<Tw>(roundf(read[i] * multiplier));
}
}
}
template<class Tr, class Tw>
inline static void
rescale(Tw* write, Tw maxw, const Tr* read, Tr maxr, size_t nvals)
{
if (maxr) {
float multiplier = static_cast<float>(maxw) / maxr;
for (size_t i = 0; i < nvals; i++) {
write[i] = static_cast<Tw>(roundf(read[i] * multiplier));
}
}
}
class different_format : public std::runtime_error {
public:
different_format() : std::runtime_error("") { }
};
class invalid_file : public std::runtime_error {
public:
invalid_file(std::string error)
: std::runtime_error("Invalid file: " + error) { }
};
class buffer_too_small : public std::runtime_error {
public:
buffer_too_small() : std::runtime_error("Image buffer too small.") { }
};
typedef ImageInput* (*Creator)();
};
class ImageOutput {
public:
/// unique_ptr to an ImageOutput.
using unique_ptr = std::unique_ptr<ImageOutput>;
static unique_ptr create (const std::string& name);
protected:
ImageOutput(std::string&& name) : name(name) { }
public:
virtual ~ImageOutput () { };
/// Return the name of the format implemented by this class.
virtual const std::string& formatName(void) const { return name; }
/// Query if feature is supported.
virtual int supports (std::string /*feature*/) const {
return false;
}
/// Modes passed to the `open()` call.
enum OpenMode { Create, AppendSubimage, AppendMIPLevel };
///
/// @param name The name of the image file to open.
/// @param newspec The ImageSpec describing the resolution, data
/// types, etc.
/// @param mode Specifies whether the purpose of the `open` is
/// to create/truncate the file (default: `Create`),
/// append another subimage (`AppendSubimage`), or
/// append another MIP level (`AppendMIPLevel`).
/// @returns `true` upon success, or `false` upon failure.
virtual void open (const std::string& name, const ImageSpec& newspec,
OpenMode mode=Create) = 0;
/// Return a reference to the image format specification of the current
/// subimage. Note that the contents of the spec are invalid before
/// `open()` or after `close()`.
const ImageSpec &spec (void) const { return imageSpec; }
/// Closes the currently open file associated with this ImageOutput and
/// frees any memory or resources associated with it.
virtual void close () = 0;
///
/// @param y/z The y & z coordinates of the scanline.
/// @param format A FormatDescriptor describing @a data.
/// @param data Pointer to the pixel data.
/// @param xstride The distance in bytes between successive
/// pixels in @a data (or `AutoStride`).
virtual void writeScanline (int y, int z, const FormatDescriptor& format,
const void *data, stride_t xstride=AutoStride);
///
/// @param format A FormatDescriptor describing @a data.
/// @param data Pointer to the pixel data.
/// @param xstride/ystride/zstride
/// The distance in bytes between successive pixels,
/// scanlines, and image planes (or `AutoStride`).
/// @param progress_callback/progress_callback_data
/// Optional progress callback.
/// @returns `true` upon success, or `false` upon failure.
virtual void writeImage (const FormatDescriptor& format, const void *data,
stride_t xstride=AutoStride,
stride_t ystride=AutoStride,
stride_t zstride=AutoStride,
ProgressCallback progress_callback=nullptr,
void *progress_callback_data=nullptr);
/// Specify a reduced-resolution ("thumbnail") version of the image.
/// Note that many image formats may require the thumbnail to be
/// specified prior to writing the pixels.
///
//virtual void setThumbnail(const Image& thumb) { return false; }
/// Read the pixels of the current subimage of @a in, and write it as the
/// next subimage of `*this`, in a way that is efficient and does not
/// alter pixel values, if at all possible. Both @a in and `this` must
/// be a properly-opened `ImageInput and `ImageOutput`, respectively,
/// and their current images must match in size and number of channels.
///
/// If a particular ImageOutput implementation does not supply a
/// `copy_image` method, it will inherit the default implementation,
/// which is to simply read scanlines from @a in and write them
/// to `*this`.
///
/// @param in A pointer to the open `ImageInput` to read from.
virtual void copyImage (ImageInput *in);
/// Call signature of a function that creates and returns an
/// `ImageOutput*`.
typedef ImageOutput* (*Creator)();
protected:
ImageSpec imageSpec; ///< format spec of the currently open image
std::string name;
};
namespace Imageio {
class string : public std::string {
public:
using std::string::string;
string tolower() {
std::transform(begin(), end(), begin(),
[](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return *this;
}
string (const std::string& s) : std::string(s) {}
};
typedef std::map<std::string, ImageInput::Creator> InputPluginMap;
extern InputPluginMap inputFormats;
typedef std::map<std::string, ImageOutput::Creator> OutputPluginMap;
extern OutputPluginMap outputFormats;
void catalogBuiltinPlugins();
}
+302
View File
@@ -0,0 +1,302 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <array>
#include <algorithm>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4201)
#endif
#include <glm/gtc/packing.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
// -----------------------------------------------------------------------------
namespace imageio {
// C++20 - std::bit_cast
template <class To, class From>
[[nodiscard]] constexpr inline To bit_cast(const From& src) noexcept {
static_assert(sizeof(To) == sizeof(From));
static_assert(std::is_trivially_copyable_v<From>);
static_assert(std::is_trivially_copyable_v<To>);
static_assert(std::is_trivially_constructible_v<To>);
To dst;
std::memcpy(&dst, &src, sizeof(To));
return dst;
}
// C++20 - std::bit_ceil
template <typename T>
[[nodiscard]] constexpr inline T bit_ceil(T x) noexcept {
x -= 1;
for (uint32_t i = 0; i < sizeof(x) * 8; ++i)
if (1u << i > x)
return 1u << i;
return 0;
}
// --- Half utilities ----------------------------------------------------------
// Based on https://gist.github.com/rygorous/eb3a019b99fdaa9c3064
union FP32 {
uint32_t u;
float f;
struct P {
uint32_t Mantissa : 23;
uint32_t Exponent : 8;
uint32_t Sign : 1;
} p;
};
union FP16 {
uint16_t u;
struct P {
uint16_t Mantissa : 10;
uint16_t Exponent : 5;
uint16_t Sign : 1;
} p;
};
inline float half_to_float(uint16_t value) {
FP16 h;
h.u = value;
static const FP32 magic = {113 << 23};
static const uint32_t shifted_exp = 0x7c00 << 13; // exponent mask after shift
FP32 o;
o.u = (h.u & 0x7fff) << 13; // exponent/mantissa bits
uint32_t exp = shifted_exp & o.u; // just the exponent
o.u += (127 - 15) << 23; // exponent adjust
// handle exponent special cases
if (exp == shifted_exp) // Inf/NaN?
o.u += (128 - 16) << 23; // extra exp adjust
else if (exp == 0) { // Zero/Denormal?
o.u += 1 << 23; // extra exp adjust
o.f -= magic.f; // renormalize
}
o.u |= (h.u & 0x8000) << 16; // sign bit
return o.f;
}
inline uint16_t float_to_half(float value) {
FP32 f;
f.f = value;
FP16 o = {0};
// Based on ISPC reference code (with minor modifications)
if (f.p.Exponent == 0) // Signed zero/denormal (which will underflow)
o.p.Exponent = 0;
else if (f.p.Exponent == 255) { // Inf or NaN (all exponent bits set)
o.p.Exponent = 31;
o.p.Mantissa = f.p.Mantissa ? 0x200 : 0; // NaN->qNaN and Inf->Inf
} else { // Normalized number
// Exponent unbias the single, then bias the halfp
int newexp = f.p.Exponent - 127 + 15;
if (newexp >= 31) // Overflow, return signed infinity
o.p.Exponent = 31;
else if (newexp <= 0) { // Underflow
if ((14 - newexp) <= 24) { // Mantissa might be non-zero
uint32_t mant = f.p.Mantissa | 0x800000; // Hidden 1 bit
o.p.Mantissa = mant >> (14 - newexp);
if ((mant >> (13 - newexp)) & 1) // Check for rounding
o.u++; // Round, might overflow into exp bit, but this is OK
}
} else {
o.p.Exponent = newexp;
o.p.Mantissa = f.p.Mantissa >> 13;
if (f.p.Mantissa & 0x1000) // Check for rounding
o.u++; // Round, might overflow to inf, this is OK
}
}
o.p.Sign = f.p.Sign;
return o.u;
}
// -----------------------------------------------------------------------------
inline int32_t sign_extend(uint32_t value, uint32_t numBits) {
assert(numBits <= 32);
uint32_t signBitMask = 1u << (numBits - 1);
if (value & signBitMask) {
uint32_t signExtendedValue = value | ~(signBitMask - 1);
int32_t result;
std::memcpy(&result, &signExtendedValue, sizeof(result));
return result;
} else {
return static_cast<int32_t>(value);
}
}
template <typename T>
[[nodiscard]] constexpr inline T extract_bits(const void* data, uint32_t offset, uint32_t numBits) {
assert(numBits <= sizeof(T) * 8);
const auto* source = static_cast<const uint8_t*>(data);
std::array<uint8_t, sizeof(T)> target{0};
for (uint32_t i = 0; i < numBits; ++i) {
const auto sourceBitIndex = offset + i;
const auto sourceByteIndex = sourceBitIndex / 8;
const auto sourceBitSubByteIndex = sourceBitIndex % 8;
const auto sourceBitSubByteMask = 1u << sourceBitSubByteIndex;
const auto sourceBitValue = (source[sourceByteIndex] & sourceBitSubByteMask) != 0;
const auto targetBitIndex = i;
const auto targetByteIndex = targetBitIndex / 8;
const auto targetBitSubByteIndex = targetBitIndex % 8;
target[targetByteIndex] |= sourceBitValue ? 1u << targetBitSubByteIndex : 0u;
}
return bit_cast<T>(target);
}
[[nodiscard]] inline uint32_t convertFloatToUNORM(float value, uint32_t numBits) {
assert(numBits > 0 && numBits <= 32);
if (std::isnan(value))
return 0;
if (value < 0.f)
return 0;
if (value > 1.f)
return (1u << numBits) - 1u;
return static_cast<uint32_t>(value * static_cast<float>((1u << numBits) - 1u) + 0.5f);
}
[[nodiscard]] inline float convertSFloatToFloat(uint32_t rawBits, uint32_t numBits) {
assert(numBits == 16 || numBits == 32);
if (numBits == 16)
return half_to_float(static_cast<uint16_t>(rawBits));
if (numBits == 32)
return bit_cast<float>(rawBits);
return 0;
}
[[nodiscard]] inline float convertUFloatToFloat(uint32_t rawBits, uint32_t numBits) {
assert(numBits == 10 || numBits == 11);
if (numBits == 10)
return glm::detail::packed10bitToFloat(rawBits);
else if (numBits == 11)
return glm::detail::packed11bitToFloat(rawBits);
return 0;
}
[[nodiscard]] inline float convertSIntToFloat(uint32_t rawBits, uint32_t numBits) {
assert(numBits > 0 && numBits <= 32);
const auto signBit = (rawBits & 1u << (numBits - 1)) != 0;
const auto valueBits = rawBits & ~(1u << (numBits - 1));
const auto signedValue = static_cast<int32_t>(valueBits) * (signBit ? -1 : 1);
return static_cast<float>(signedValue);
}
[[nodiscard]] inline float convertUIntToFloat(uint32_t rawBits, uint32_t numBits) {
assert(numBits > 0 && numBits <= 32); (void) numBits;
return static_cast<float>(rawBits);
}
[[nodiscard]] inline float convertSNORMToFloat(uint32_t rawBits, uint32_t numBits) {
assert(numBits > 0 && numBits <= 32);
const auto upper = static_cast<float>((1u << (numBits - 1u)) - 1u);
return std::max(static_cast<float>(rawBits) / upper, -1.f);
}
[[nodiscard]] inline float convertUNORMToFloat(uint32_t rawBits, uint32_t numBits) {
assert(numBits > 0 && numBits <= 32);
const auto upper = static_cast<float>((1u << numBits) - 1u);
return static_cast<float>(rawBits) / upper;
}
[[nodiscard]] inline uint32_t convertSFloatToUInt(uint32_t rawBits, uint32_t numBits) {
assert(numBits == 16 || numBits == 32);
if (numBits == 16)
return static_cast<uint32_t>(half_to_float(static_cast<uint16_t>(rawBits)));
if (numBits == 32)
return static_cast<uint32_t>(bit_cast<float>(rawBits));
return 0;
}
[[nodiscard]] inline uint32_t convertUFloatToUInt(uint32_t rawBits, uint32_t numBits) {
assert(numBits == 10 || numBits == 11 || numBits == 14);
(void) rawBits;
(void) numBits;
assert(false && "Not yet implemented");
return 0;
}
[[nodiscard]] inline uint32_t convertSIntToUInt(uint32_t rawBits, uint32_t numBits) {
assert(numBits > 0 && numBits <= 32);
(void) rawBits;
(void) numBits;
assert(false && "Not yet implemented");
return 0;
}
[[nodiscard]] inline uint32_t convertUIntToUInt(uint32_t rawBits, uint32_t numBits) {
assert(numBits > 0 && numBits <= 32);
(void) numBits;
return rawBits;
}
[[nodiscard]] constexpr inline uint32_t convertUNORM(uint32_t rawBits, uint32_t sourceBits, uint32_t targetBits) noexcept {
assert(sourceBits > 0 && sourceBits <= 32);
assert(targetBits > 0 && targetBits <= 32);
rawBits &= (1u << sourceBits) - 1u;
if (targetBits == sourceBits) {
return rawBits;
} else if (targetBits >= sourceBits) {
// Upscale with "left bit replication" to fill in the least significant bits
uint64_t result = 0;
for (uint32_t i = 0; i < targetBits; i += sourceBits)
result |= static_cast<uint64_t>(rawBits) << (targetBits - i) >> sourceBits;
return static_cast<uint32_t>(result);
} else {
// Downscale with rounding: Check the most significant bit that was dropped: 1 -> up, 0 -> down
const auto msDroppedBitIndex = sourceBits - targetBits - 1u;
const auto msDroppedBitValue = rawBits & (1u << msDroppedBitIndex);
if (msDroppedBitValue)
// Min stops the 'overflow' if every targetBit is saturated and we would round up
return std::min((rawBits >> (sourceBits - targetBits)) + 1u, (1u << targetBits) - 1u);
else
return rawBits >> (sourceBits - targetBits);
}
}
[[nodiscard]] constexpr inline uint32_t convertUINT(uint32_t rawBits, uint32_t sourceBits, uint32_t targetBits) noexcept {
assert(sourceBits > 0 && sourceBits <= 32);
assert(targetBits > 0 && targetBits <= 32);
const auto targetValueMask = targetBits == 32 ? std::numeric_limits<uint32_t>::max() : (1u << targetBits) - 1u;
const auto sourceValueMask = sourceBits == 32 ? std::numeric_limits<uint32_t>::max() : (1u << sourceBits) - 1u;
rawBits &= sourceValueMask;
if (targetBits < sourceBits)
rawBits &= targetValueMask;
return rawBits;
}
[[nodiscard]] constexpr inline uint32_t convertSINT(uint32_t rawBits, uint32_t sourceBits, uint32_t targetBits) noexcept {
assert(sourceBits > 1 && sourceBits <= 32);
assert(targetBits > 1 && targetBits <= 32);
const auto sourceSignBitIndex = sourceBits - 1u;
const auto sourceSignMask = 1u << sourceSignBitIndex;
const auto sign = (rawBits & sourceSignMask) != 0;
const auto sourceValueBits = sourceBits - 1u;
const auto sourceValueMask = (1u << sourceValueBits) - 1u;
const auto sourceValue = rawBits & sourceValueMask;
const auto targetSignBitIndex = targetBits - 1u;
const auto targetValueBits = targetBits - 1u;
const auto targetValueMask = (1u << targetValueBits) - 1u;
uint32_t result = 0;
result |= (sign ? 1u : 0u) << targetSignBitIndex;
if (targetBits < sourceBits)
result |= sourceValue & targetValueMask;
else
result |= sourceValue;
return result;
}
} // namespace imageio
+97
View File
@@ -0,0 +1,97 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab:
// Copyright 2022 The Khronos Group Inc.
// SPDX-License-Identifier: Apache-2.0
//!
//! @internal
//! @~English
//! @file
//!
//! @brief ImageOutput class implementation
//!
#include "imageio.h"
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <iostream>
#include <map>
#include <string>
#include <sstream>
#include <stdexcept>
#include <vector>
#include <stdarg.h>
std::unique_ptr<ImageOutput>
ImageOutput::create(const std::string& filename)
{
std::unique_ptr<ImageOutput> out;
if (filename.empty()) { // Can't even guess if no name given
throw std::runtime_error(
"ImageOutput::create() called with no filename"
);
}
// Populate outputFormats.
if (Imageio::outputFormats.empty()) {
Imageio::catalogBuiltinPlugins();
assert(!Imageio::outputFormats.empty()
&& "No image output plugins compiled in.");
}
// Extract the file extension from the filename (without the leading dot)
Imageio::string format = filename.substr(filename.find_last_of('.')+1);
if (format.empty()) {
// If the file had no extension, maybe it was itself the format name
format = filename;
}
format.tolower();
ImageOutput::Creator createFunction = nullptr;
// See if it's already in the table. If not, scan all plugins we can
// find to populate the table.
Imageio::OutputPluginMap::const_iterator found
= Imageio::outputFormats.find(format);
if (found != Imageio::outputFormats.end()) {
createFunction = found->second;
} else {
throw std::runtime_error(
fmt::format("Could not find a format writer for \"{}\". "
"Is it a file format that we don't know about?",
filename));
}
assert(createFunction != nullptr);
out = std::unique_ptr<ImageOutput>(createFunction());
return out;
}
void
ImageOutput::writeScanline(int /*y*/, int /*z*/,
const FormatDescriptor& /*format*/,
const void* /*data*/, stride_t /*xstride*/)
{
}
void
ImageOutput::writeImage(const FormatDescriptor& /*format*/,
const void* /*data*/,
stride_t /*xstride*/,
stride_t /*ystride*/,
stride_t /*zstride*/,
ProgressCallback /*progress_callback*/,
void* /*progress_callback_data*/)
{
}
void
ImageOutput::copyImage(ImageInput* /*in*/)
{
}
+186
View File
@@ -0,0 +1,186 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab:
// Copyright 2023-2024 The Khronos Group Inc.
// Copyright 2023-2024 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
//!
//! @internal
//! @~English
//! @file
//!
//! @brief Internal Image Span container and iterator classes
//!
#pragma once
#include <iterator>
#include "imagecodec.hpp"
class ImageSpan {
public:
template <typename PTR = uint8_t*>
class TexelBlockPtr {
public:
using pointer = PTR;
TexelBlockPtr(PTR ptr, const ImageSpan& span) noexcept : ptr(ptr), span(span) {}
uint32_t getPackedElement(uint32_t index) const { return span.imageCodec().getPackedElement(ptr, index); }
glm::uvec4 decodeUINT() const { return span.imageCodec().decodeUINT(ptr); }
glm::ivec4 decodeSINT() const { return span.imageCodec().decodeSINT(ptr); }
glm::vec4 decodeFLOAT() const { return span.imageCodec().decodeFLOAT(ptr); }
const ImageCodec& imageCodec() const { return span.codec; }
constexpr bool isBlockCompressed() const { return span.imageCodec().isBlockCompressed(); }
constexpr bool isPacked() const { return span.imageCodec().isPacked(); }
constexpr bool isFloat() const { return span.imageCodec().isFloat(); }
constexpr bool isFloatHalf() const { return span.imageCodec().isFloatHalf(); }
constexpr bool isSigned() const { return span.imageCodec().isSigned(); }
constexpr bool isNormalized() const { return span.imageCodec().isNormalized(); }
constexpr bool canDecodeUINT() const { return span.imageCodec().canDecodeUINT(); }
constexpr bool canDecodeSINT() const { return span.imageCodec().canDecodeSINT(); }
constexpr bool canDecodeFLOAT() const { return span.imageCodec().canDecodeFLOAT(); }
glm::uvec4 getTexelBlockDimensions() const { return span.imageCodec().getTexelBlockDimensions(); }
constexpr uint32_t getPackedElementByteSize() const { return span.imageCodec().getPackedElementByteSize(); }
constexpr uint32_t getPackedElementCount() const { return span.imageCodec().getPackedElementCount(); }
constexpr uint32_t getTexelBlockByteSize() const { return span.imageCodec().getTexelBlockByteSize(); }
constexpr uint32_t getChannelCount() const { return span.imageCodec().getChannelCount(); }
std::ptrdiff_t getTexelBlockByteOffset() const {
return ptr - span.data();
}
glm::uvec4 getTexelBlockLocation() const {
const std::ptrdiff_t blockPitch = span.imageCodec().getTexelBlockByteSize();
const std::ptrdiff_t rowPitch = span.getTexelBlockWidth() * blockPitch;
const std::ptrdiff_t slicePitch = span.getTexelBlockHeight() * rowPitch;
glm::uvec4 loc;
auto diff = getTexelBlockByteOffset();
loc.w = 0;
loc.z = static_cast<uint32_t>(diff / slicePitch);
diff = diff % slicePitch;
loc.y = static_cast<uint32_t>(diff / rowPitch);
diff = diff % rowPitch;
loc.x = static_cast<uint32_t>(diff / blockPitch);
return loc;
}
glm::uvec4 getPixelLocation() const {
return getTexelBlockLocation() * span.imageCodec().getTexelBlockDimensions();
}
private:
PTR ptr;
const ImageSpan& span;
};
template <typename TBPTR = TexelBlockPtr<uint8_t*>, bool REVERSE = false>
class Iterator {
public:
using iterator_category = std::random_access_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = TBPTR;
using pointer = TBPTR;
using reference = TBPTR;
Iterator& operator+=(difference_type rhs) noexcept { ptr = advance(rhs); return *this; }
Iterator& operator-=(difference_type rhs) noexcept { ptr = advance(-rhs); return *this; }
reference operator*() const { return reference(ptr, span); }
pointer operator->() const { return pointer(ptr, span); }
reference operator[](difference_type rhs) const { return *advance(rhs); }
Iterator& operator++() noexcept { ptr = advance(1); return *this; }
Iterator& operator--() noexcept { ptr = advance(-1); return *this; }
Iterator operator++(int) const noexcept { return Iterator(span, advance(1), stride); }
Iterator operator--(int) const noexcept { return Iterator(span, advance(-1), stride); }
difference_type operator-(const Iterator& rhs) const noexcept { return diff(rhs.ptr); }
Iterator operator+(difference_type rhs) const noexcept { return Iterator(span, advance(rhs), stride); }
Iterator operator-(difference_type rhs) const noexcept { return Iterator(span, advance(-rhs), stride); }
friend Iterator operator+(difference_type lhs, const Iterator& rhs) { return Iterator(rhs.span, rhs.advance(lhs), rhs.stride); }
friend Iterator operator-(difference_type lhs, const Iterator& rhs) { return Iterator(rhs.span, rhs.advance(-lhs), rhs.stride); }
bool operator==(const Iterator& rhs) const noexcept { return (ptr == rhs.ptr) != REVERSE; }
bool operator!=(const Iterator& rhs) const noexcept { return (ptr != rhs.ptr) != REVERSE; }
bool operator>(const Iterator& rhs) const noexcept { return (ptr > rhs.ptr) != REVERSE; }
bool operator<(const Iterator& rhs) const noexcept { return (ptr < rhs.ptr) != REVERSE; }
bool operator>=(const Iterator& rhs) const noexcept { return (ptr >= rhs.ptr) != REVERSE; }
bool operator<=(const Iterator& rhs) const noexcept { return (ptr <= rhs.ptr) != REVERSE; }
private:
const ImageSpan& span;
typename TBPTR::pointer ptr;
const uint32_t stride;
Iterator(const ImageSpan& span, typename TBPTR::pointer ptr, uint32_t stride) noexcept
: span(span), ptr(ptr), stride(stride) {}
typename TBPTR::pointer advance(difference_type diff) const noexcept {
return ptr + (REVERSE ? -1 : +1) * diff * stride;
}
difference_type diff(pointer other) const noexcept {
return (REVERSE ? -1 : +1) * (ptr - other) / stride;
}
friend class ImageSpan;
};
using value_type = TexelBlockPtr<uint8_t*>;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using reference = TexelBlockPtr<uint8_t*>;
using const_reference = TexelBlockPtr<const uint8_t*>;
using pointer = TexelBlockPtr<uint8_t*>;
using const_pointer = TexelBlockPtr<const uint8_t*>;
using iterator = Iterator<TexelBlockPtr<uint8_t*>, false>;
using const_iterator = Iterator<TexelBlockPtr<const uint8_t*>, false>;
using reverse_iterator = Iterator<TexelBlockPtr<uint8_t*>, true>;
using const_reverse_iterator = Iterator<TexelBlockPtr<const uint8_t*>, true>;
ImageSpan(uint32_t width, uint32_t height, uint32_t depth, void* pixels, const ImageCodec& imageCodec)
: texelBlockWidth((width + imageCodec.getTexelBlockDimensions()[0] - 1) / imageCodec.getTexelBlockDimensions()[0]),
texelBlockHeight((height + imageCodec.getTexelBlockDimensions()[1] - 1) / imageCodec.getTexelBlockDimensions()[1]),
texelBlockDepth((depth + imageCodec.getTexelBlockDimensions()[2] - 1) / imageCodec.getTexelBlockDimensions()[2]),
pixels(reinterpret_cast<uint8_t*>(pixels)), codec(imageCodec) {}
const ImageCodec& imageCodec() const { return codec; }
reference at(uint32_t blockX, uint32_t blockY, uint32_t blockZ) {
return reference(pixels + texelBlockByteOffset(blockX, blockY, blockZ), *this);
}
const_reference at(uint32_t blockX, uint32_t blockY, uint32_t blockZ) const {
return const_reference(pixels + texelBlockByteOffset(blockX, blockY, blockZ), *this);
}
iterator begin() noexcept { return iterator(*this, pixels, codec.getTexelBlockByteSize()); }
const_iterator begin() const noexcept { return const_iterator(*this, pixels, codec.getTexelBlockByteSize()); }
const_iterator cbegin() const noexcept { return const_iterator(*this, pixels, codec.getTexelBlockByteSize()); }
iterator end() noexcept { return iterator(*this, pixels, codec.getTexelBlockByteSize()) + size(); }
const_iterator end() const noexcept { return const_iterator(*this, pixels, codec.getTexelBlockByteSize()) + size(); }
const_iterator cend() const noexcept { return const_iterator(*this, pixels, codec.getTexelBlockByteSize()) + size(); }
reverse_iterator rbegin() noexcept { return reverse_iterator(*this, pixels, codec.getTexelBlockByteSize()) - size() + 1; }
const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(*this, pixels, codec.getTexelBlockByteSize()) - size() + 1; }
const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(*this, pixels, codec.getTexelBlockByteSize()) - size() + 1; }
reverse_iterator rend() noexcept { return reverse_iterator(*this, pixels , codec.getTexelBlockByteSize()) + 1; }
const_reverse_iterator rend() const noexcept { return const_reverse_iterator(*this, pixels, codec.getTexelBlockByteSize()) + 1; }
const_reverse_iterator crend() const noexcept { return const_reverse_iterator(*this, pixels, codec.getTexelBlockByteSize()) + 1; }
uint8_t* data() noexcept { return pixels; }
const uint8_t* data() const noexcept { return pixels; }
constexpr size_type size() const noexcept { return texelBlockWidth * texelBlockHeight; }
constexpr std::ptrdiff_t byteSize() const { return size() * codec.getTexelBlockByteSize(); }
constexpr uint32_t getTexelBlockWidth() const { return texelBlockWidth; }
constexpr uint32_t getTexelBlockHeight() const { return texelBlockHeight; }
constexpr uint32_t getTexelBlockDepth() const { return texelBlockDepth; }
private:
const uint32_t texelBlockWidth;
const uint32_t texelBlockHeight;
const uint32_t texelBlockDepth;
uint8_t* const pixels;
const ImageCodec& codec;
constexpr std::ptrdiff_t texelBlockByteOffset(uint32_t blockX, uint32_t blockY, uint32_t blockZ) const {
return (blockX + blockY * texelBlockWidth + blockZ * texelBlockWidth * texelBlockHeight) * codec.getTexelBlockByteSize();
}
};
+372
View File
@@ -0,0 +1,372 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab:
// Copyright 2022 The Khronos Group Inc.
// SPDX-License-Identifier: Apache-2.0
/**
* @internal
* @~English
* @file
*
* @brief ImageInput from JPEG format files.
*
* The following has a very useful summary of the metadata in JPEG files and
* its handling.
* https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
* This plug-in currently only handles 1 & 3 component images. 1 component
* is luminance. 3 component is YCbCr which the plug-in converts to RGB.
*
* @author Mark Callow.
*/
#include "imageio.h"
#include <iostream>
#include <sstream>
#include <stdexcept>
#include "encoder/jpgd.h"
using namespace jpgd;
class myjpgdstream : public jpeg_decoder_file_stream {
public:
void open(std::istream* pInputStream) {
isp = pInputStream;
}
int read(uint8_t* pBuf, int max_bytes_to_read, bool* pEOF_flag) {
if (!isp)
return -1;
if (isp->eof())
{
*pEOF_flag = true;
return 0;
}
if (isp->fail())
return -1;
isp->read(reinterpret_cast<char*>(pBuf), max_bytes_to_read);
// eof check must come first as fail bit is also set when there
// aren't enough characters to satisfy the full request.
if (isp->eof())
*pEOF_flag = true;
else if (isp->fail())
return -1;
return static_cast<int>(isp->gcount());
}
void rewind() {
isp->seekg(0);
}
protected:
std::istream* isp = nullptr;
};
#if defined(FORMAT_JPGD_STATUS_AS_INTEGER)
// Adding to a namespace outside its package is not recommended but
// since the decoder is part of Basis Universal ...
namespace jpgd {
int format_as(jpgd_status s) { return fmt::underlying(s); }
}
#else // be more user friendly.
template <> struct fmt::formatter<jpgd_status>: formatter<string_view> {
// parse is inherited from formatter<string_view>.
template <typename FormatContext>
auto format(jpgd_status s, FormatContext& ctx) const -> decltype(ctx.out()) {
string_view name;
switch (s) {
case JPGD_SUCCESS: name = "JPGD_SUCCESS"; break;
case JPGD_FAILED: name = "JPGD_FAILED"; break;
case JPGD_DONE: name = "JPGD_DONE"; break;
case JPGD_BAD_DHT_COUNTS: name = "JPGD_BAD_DHT_COUNTS"; break;
case JPGD_BAD_DHT_INDEX: name = "JPGD_BAD_DHT_INDEX"; break;
case JPGD_BAD_DHT_MARKER: name = "JPGD_BAD_DHT_MARKER"; break;
case JPGD_BAD_DQT_MARKER: name = "JPGD_BAD_DQT_MARKER"; break;
case JPGD_BAD_DQT_TABLE: name = "JPGD_BAD_DQT_TABLE"; break;
case JPGD_BAD_PRECISION: name = "JPGD_BAD_PRECISION"; break;
case JPGD_BAD_HEIGHT: name = "JPGD_BAD_HEIGHT"; break;
case JPGD_BAD_WIDTH: name = "JPGD_BAD_WIDTH"; break;
case JPGD_TOO_MANY_COMPONENTS: name = "JPGD_TOO_MANY_COMPONENTS"; break;
case JPGD_BAD_SOF_LENGTH: name = "JPGD_BAD_SOF_LENGTH"; break;
case JPGD_BAD_VARIABLE_MARKER: name = "JPGD_BAD_VARIABLE_MARKER"; break;
case JPGD_BAD_DRI_LENGTH: name = "JPGD_BAD_DRI_LENGTH"; break;
case JPGD_BAD_SOS_LENGTH: name = "JPGD_BAD_SOS_LENGTH"; break;
case JPGD_BAD_SOS_COMP_ID: name = "JPGD_BAD_SOS_COMP_ID"; break;
case JPGD_W_EXTRA_BYTES_BEFORE_MARKER:
name = "JPGD_W_EXTRA_BYTES_BEFORE_MARKER";
break;
case JPGD_NO_ARITHMITIC_SUPPORT:
name = "JPGD_NO_ARITHMITIC_SUPPORT";
break;
case JPGD_UNEXPECTED_MARKER: name = "JPGD_UNEXPECTED_MARKER"; break;
case JPGD_NOT_JPEG: name = "JPGD_NOT_JPEG"; break;
case JPGD_UNSUPPORTED_MARKER: name = "JPGD_UNSUPPORTED_MARKER"; break;
case JPGD_BAD_DQT_LENGTH: name = "JPGD_BAD_DQT_LENGTH"; break;
case JPGD_TOO_MANY_BLOCKS: name = "JPGD_TOO_MANY_BLOCKS"; break;
case JPGD_UNDEFINED_QUANT_TABLE:
name = "JPGD_UNDEFINED_QUANT_TABLE";
break;
case JPGD_UNDEFINED_HUFF_TABLE: name = "JPGD_UNDEFINED_HUFF_TABLE"; break;
case JPGD_NOT_SINGLE_SCAN: name = "JPGD_NOT_SINGLE_SCAN"; break;
case JPGD_UNSUPPORTED_COLORSPACE:
name = "JPGD_UNSUPPORTED_COLORSPACE";
break;
case JPGD_UNSUPPORTED_SAMP_FACTORS:
name = "JPGD_UNSUPPORTED_SAMP_FACTORS";
break;
case JPGD_DECODE_ERROR: name = "JPGD_DECODE_ERROR"; break;
case JPGD_BAD_RESTART_MARKER: name = "JPGD_BAD_RESTART_MARKER"; break;
case JPGD_BAD_SOS_SPECTRAL: name = "JPGD_BAD_SOS_SPECTRAL"; break;
case JPGD_BAD_SOS_SUCCESSIVE: name = "JPGD_BAD_SOS_SUCCESSIVE"; break;
case JPGD_STREAM_READ: name = "JPGD_STREAM_READ"; break;
case JPGD_NOTENOUGHMEM: name = "JPGD_NOTENOUGHMEM"; break;
case JPGD_TOO_MANY_SCANS: name = "JPGD_TOO_MANY_SCANS"; break;
}
return formatter<string_view>::format(name, ctx);
}
};
#endif
class JpegInput final : public ImageInput {
public:
JpegInput() : ImageInput("jpeg") {}
virtual ~JpegInput() { close(); }
virtual void open(ImageSpec& newspec) override;
virtual void close() override {
decodingBegun = false;
if (pJd) delete pJd;
}
virtual void readImage(void* buffer, size_t bufferByteCount,
uint subimage, uint miplevel,
const FormatDescriptor& targetFormat) override;
virtual void readScanline(void* buffer, size_t bufferByteCount,
uint y, uint z,
uint usubimage, uint miplevel,
const FormatDescriptor& targetFormat) override;
/// Read a single scanline (all channels) of native data into contiguous
/// memory.
virtual void readNativeScanline(void* /*buffer*/,
size_t /*bufferByteCount*/,
uint /*y*/, uint /*z*/,
uint /*subimage*/, uint /*miplevel*/) override { };
protected:
void readHeader();
myjpgdstream jstream;
jpeg_decoder* pJd = nullptr;
uint32_t nextScanline = 0;
bool decodingBegun = false;
};
ImageInput*
jpegInputCreate()
{
return new JpegInput;
}
const char* jpegInputExtensions[] = { "jpg", "jpeg", nullptr };
void
JpegInput::open(ImageSpec& newspec)
{
assert(isp != nullptr && "ImageInput not properly opened");
jstream.open(isp);
readHeader();
newspec = spec();
nextScanline = 0;
}
// This doesn't read the APP0 chunk. Although JFIF specs gamma = 1.0, most
// JPEG files are EXIF so this considers all JPEG files to be sRGB.
void
JpegInput::readHeader()
{
pJd = new jpeg_decoder(&jstream, jpeg_decoder::cFlagLinearChromaFiltering);
jpgd_status errorCode = pJd->get_error_code();
if (errorCode != JPGD_SUCCESS) {
if (errorCode == JPGD_NOT_JPEG) {
throw different_format();
} else if (errorCode == JPGD_NOTENOUGHMEM) {
throw std::runtime_error("JPEG decoder out of memory");
} else {
throw std::runtime_error(
fmt::format("JPEG decode failed: {}", errorCode)
);
}
}
// At this point we cannot use
// - jd.get_bytes_per_pixel
// - jd.get_bytes_per_scan_line
// because the underlying variables are not initialized until
// begin_decoding is called. In any case these are not helpful as they
// return what the decode() method will return not what is in the file.
images.emplace_back(ImageSpec(pJd->get_width(), pJd->get_height(), 1,
ImageSpec::Origin(ImageSpec::Origin::eLeft, ImageSpec::Origin::eTop),
pJd->get_num_components(), 8, // component bit length
static_cast<khr_df_sample_datatype_qualifiers_e>(0),
KHR_DF_TRANSFER_SRGB,
KHR_DF_PRIMARIES_BT709),
ImageInputFormatType::jpg);
}
/// @brief Read an image scanline into contiguous memory performing conversions
/// to @a format.
///
/// Supported conversions are
/// - changing channel count
/// - [GREY,RGB]->[GREY,RGB,RGBA]
/// When reducing to 1 channel it calculates luma for GREY from R,G & B.
/// When increasing from 1 it makes a luminance texture, R=G=B=GREY.
/// ALPHA is set to 1.0 when converting to 4 channels.
/// 2- and 4-channel inputs are not supported.
void
JpegInput::readScanline(void* bufferOut, size_t bufferByteCount, uint y, uint,
uint, uint,
const FormatDescriptor& format)
{
const auto& targetFormat = format.isUnknown() ? spec().format() : format;
const auto requestBits = targetFormat.largestChannelBitLength();
if (requestBits != 8)
throw std::runtime_error(fmt::format(
"Requested decode into {}-bit format is not supported.",
requestBits));
const bool targetL = targetFormat.samples[0].qualifierLinear;
const bool targetE = targetFormat.samples[0].qualifierExponent;
const bool targetS = targetFormat.samples[0].qualifierSigned;
const bool targetF = targetFormat.samples[0].qualifierFloat;
// Only UNORM requests are allowed for JPEG inputs
if (targetE || targetL || targetS || targetF)
throw std::runtime_error(fmt::format(
"Requested format conversion to {}-bit{}{}{}{} is not supported.",
requestBits,
targetL ? " Linear" : "",
targetE ? " Exponent" : "",
targetS ? " Signed" : "",
targetF ? " Float" : ""));
if (y >= spec().height())
y = spec().height() - 1;
if (y != nextScanline)
throw std::runtime_error("Random scanline seeking not yet implemented.");
if (!decodingBegun) {
if (pJd == nullptr)
throw std::runtime_error("No file opened.");
pJd->begin_decoding();
decodingBegun = true;
}
const uint8_t* pScanline = 0;
uint32_t scanlineByteCount;
int decodeStatus =
pJd->decode((const void**)&pScanline, &scanlineByteCount) ;
if (decodeStatus != JPGD_SUCCESS) {
assert(decodeStatus != JPGD_DONE);
// Only other decodeStatus is JPGD_FAILED;
throw std::runtime_error(
fmt::format("JPEG decode failed: {}", pJd->get_error_code())
);
}
const auto targetChannelCount = targetFormat.extended.channelCount;
if (targetChannelCount == 2)
throw std::runtime_error(fmt::format(
"Requested decode into 2 channels is not supported.")
);
uint8_t* pDst = static_cast<uint8_t*>(bufferOut);
uint32_t inputChannelCount = spec().format().extended.channelCount;
// decode() returns a bufferOut of 1 channel (for grayscale input)
// or 4 channels. Despite this decode() does not support 4 channel
// inputs. Nor does it support 2 channel inputs.
// TODO: Extend the following when decode supports 2- and 4-channel inputs.
if ((targetChannelCount == 1 && inputChannelCount == 1)
|| (targetChannelCount == 4 && inputChannelCount == 3))
{
if (bufferByteCount < scanlineByteCount)
throw buffer_too_small();
memcpy(bufferOut, pScanline, scanlineByteCount);
} else if (inputChannelCount == 1) {
if (targetChannelCount == 3) {
if (bufferByteCount < scanlineByteCount * 3)
throw buffer_too_small();
for (uint x = 0; x < spec().width(); x++) {
uint8 luma = pScanline[x];
pDst[0] = luma;
pDst[1] = luma;
pDst[2] = luma;
pDst += 3;
}
} else { // targetChannelCount = 4
if (bufferByteCount < scanlineByteCount * 4)
throw buffer_too_small();
for (uint x = 0; x < spec().width(); x++)
{
uint8 luma = pScanline[x];
pDst[0] = luma;
pDst[1] = luma;
pDst[2] = luma;
pDst[3] = 255;
pDst += 4;
}
}
} else if (inputChannelCount == 3) {
if (targetChannelCount == 1) {
if (bufferByteCount < spec().width())
throw buffer_too_small();
const int YR = 19595, YG = 38470, YB = 7471;
for (uint x = 0; x < spec().width(); x++) {
int r = pScanline[x * 4 + 0];
int g = pScanline[x * 4 + 1];
int b = pScanline[x * 4 + 2];
*pDst++ = static_cast<uint8>((r * YR + g * YG + b * YB + 32768) >> 16);
}
} else { //targetChannelCount = 3
if (bufferByteCount < spec().width() * 3)
throw buffer_too_small();
for (uint x = 0; x < spec().width(); x++) {
pDst[0] = pScanline[x * 4 + 0];
pDst[1] = pScanline[x * 4 + 1];
pDst[2] = pScanline[x * 4 + 2];
pDst += 3;
}
}
}
nextScanline++;
}
/// @brief Read an entire image into contiguous memory performing conversions
/// to @a format.
///
/// @sa readScanline() for supported conversions
void JpegInput::readImage(void* bufferOut, size_t bufferByteCount,
uint subimage, uint miplevel,
const FormatDescriptor& format)
{
pJd->begin_decoding();
decodingBegun = true;
ImageInput::readImage(bufferOut, bufferByteCount,
subimage, miplevel,
format);
}
+460
View File
@@ -0,0 +1,460 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab:
// Copyright 2022 The Khronos Group Inc.
// SPDX-License-Identifier: Apache-2.0
#include "imageio.h"
#include <cstdlib>
#include <fstream>
#include <iterator>
#include <string>
/** @internal
* @~English
* @file
*
* @brief ImageInput from netpbm format files (.pam, .pbm, .pgm or .ppm).
*
* Plain formats (magic numbers 'P1', 'P2' & 'P3') are not supported.
*
* PPM and PGM specify that sample values are encoded with the BT.709 OETF.
* They do not indicate that bt.709 only applies when maxval <= 255 so this
* class always reports OETF as bt.709 for color and grayscale. The
* specifications also say that both sRGB and linear encoding are often used.
* Since there is no metadata to indicate a differing OETF this loader always
* assumes bt.709.
*
* Documentation on the netpbm formats can be found at:
* http://netpbm.sourceforge.net/doc/
*
* @author Mark Callow
*/
class NpbmInput final : public ImageInput {
public:
NpbmInput() : ImageInput("npbm") { }
virtual void open(ImageSpec& newspec) override;
virtual void close() override;
//virtual bool read_native_scanline(uint32_t subimage, uint32_t miplevel, int y, int z,
// void* data) override;
virtual void readImage(void* buffer, size_t bufferByteCount,
uint32_t subimage, uint32_t miplevel,
const FormatDescriptor& targetFormat) override;
/// Read a single scanline (all channels) of native data into contiguous
/// memory.
virtual void readNativeScanline(void* buffer, size_t bufferByteCount,
uint32_t y, uint32_t z,
uint32_t subimage, uint32_t miplevel) override;
virtual uint32_t subimageCount(void) const override {
return static_cast<uint32_t>(images.size());
}
virtual bool seekSubimage(uint32_t subimage, uint32_t miplevel = 0) override;
virtual const ImageSpec& spec(void) const override {
return images[curSubimage].spec;
}
using ImageInput::spec;
private:
std::string currentLine;
std::istream::iostate exceptionsIn;
size_t pos;
unsigned int curImageScanline;
using ImageInput::rescale;
void readImageHeaders();
void parseAHeader();
enum class filetype {PGM, PPM};
void parseGPHeader(filetype ftype);
void nextLine();
void nextToken();
void skipComments(char comment = '#');
void swap(void* pBuffer, size_t nvals);
};
ImageInput*
npbmInputCreate()
{
return new NpbmInput;
}
const char* npbmInputExtensions[] = { "pam", "pbm", "pgm", "ppm", nullptr };
void
NpbmInput::open(ImageSpec& newspec)
{
assert(isp != nullptr && "istream not initialized");
currentLine = "";
pos = 0;
isp->exceptions(std::istream::failbit | std::istream::badbit | std::istream::eofbit );
readImageHeaders();
seekSubimage(0);
newspec = spec();
}
void
NpbmInput::close() {
isp->exceptions(exceptionsIn);
ImageInput::close();
}
static int
tupleComponentCount(const char* tupleType)
{
if (strcmp(tupleType, "BLACKANDWHITE") == 0)
return 1;
else if (strcmp(tupleType, "GRAYSCALE") == 0)
return 1;
else if (strcmp(tupleType, "GRAYSCALE_ALPHA") == 0)
return 2;
else if (strcmp(tupleType, "RGB") == 0)
return 3;
else if (strcmp(tupleType, "RGB_ALPHA") == 0)
return 4;
else
return -1;
}
inline void
NpbmInput::nextLine()
{
std::getline(*isp, currentLine);
pos = 0;
}
inline void
NpbmInput::nextToken()
{
while (1) {
while (isspace(currentLine[pos]))
pos++;
if (pos != currentLine.size())
break;
else
nextLine();
}
}
inline void
NpbmInput::skipComments(char comment)
{
while (1) {
nextToken();
if (currentLine[pos] == comment)
nextLine();
else
break;
}
}
void
NpbmInput::readImageHeaders()
{
for(;;) {
try {
// MagicNumber
// If not an NPBM file, there may not be a line terminator.
currentLine.resize(3);
isp->read(&currentLine[0], 3);
if (!currentLine.compare("P7\n")) {
parseAHeader();
} else if (!currentLine.compare("P5\n")) {
parseGPHeader(filetype::PGM);
//images.back().spec.colortype = colortype_e::Luminance;
} else if (!currentLine.compare("P6\n")) {
parseGPHeader(filetype::PPM);
} else if (!currentLine.compare("P1\n")
|| !currentLine.compare("P2\n")
|| !currentLine.compare("P3\n")) {
throw std::runtime_error("Plain netpbm formats are not supported.");
} else if (!currentLine.compare("P4\n")) {
throw std::runtime_error(".pbm files are not supported.");
} else {
throw different_format();
}
images.back().filepos = isp->tellg(); // Save image data start pos.
// We've only read the header. Seek to the expected end
// of the image.
isp->seekg(images.back().spec.imageByteCount(), isp->cur);
} catch (const std::istream::failure&) {
throwOnReadFailure();
}
// Check if there is any more data in the file.
try {
isp->peek();
} catch (const std::istream::failure&) {
if (isp->eof()) {
return;
} else {
throwOnReadFailure();
}
}
}
}
/*
* SwapEndian16: Swaps endianness in an array of 16-bit values
*/
static void
swapEndian16(uint16_t* pData16, size_t nvals)
{
for (size_t i = 0; i < nvals; ++i)
{
uint16_t x = *pData16;
*pData16++ = (x << 8) | (x >> 8);
}
}
static const union foo { uint16_t x; uint8_t c; } bar{1};
#define IS_LITTLE_ENDIAN (bar.c)
bool
littleendian(void) noexcept
{
return bar.c;
}
void
NpbmInput::swap(void* pBuffer, size_t nvals)
{
if (spec().format().channelBitLength(KHR_DF_CHANNEL_RGBSDA_R) == 16) {
// If 2 bytes, MSB is first.
if (littleendian()) {
swapEndian16((uint16_t*)pBuffer, nvals);
}
}
}
void
NpbmInput::readNativeScanline(void* bufferOut, size_t bufferByteCount,
uint32_t y, uint32_t z,
uint32_t subimage, uint32_t miplevel)
{
if (isp == nullptr)
throw std::runtime_error("istream not initialized");
if (z > 1)
throw std::runtime_error("npbm does not support 3d images.");
if (bufferByteCount < spec().scanlineByteCount())
throw buffer_too_small();
seekSubimage(subimage, miplevel);
if (y != curImageScanline)
isp->seekg(images[currentSubimage()].filepos + (spec().scanlineByteCount() * y),
isp->beg);
isp->read((char*)bufferOut, spec().scanlineByteCount());
swap(bufferOut, spec().scanlineByteCount());
}
bool
NpbmInput::seekSubimage(uint32_t subimage, uint32_t miplevel)
{
if (subimage == currentSubimage() && miplevel == currentMiplevel())
return true;
if (subimage >= images.size() || miplevel > 0)
return false;
isp->seekg(images[subimage].filepos, isp->beg);
curSubimage = subimage;
curMiplevel = miplevel;
curImageScanline = 0;
return true;
}
///
/// @internal
/// @~English
/// @brief parse the header of a PGM or PPM file.
///
/// @param [in] src pointer to FILE stream to read
/// @param [out] width reference to a var in which to write the image width.
/// @param [out] height reference to a var in which to write the image height
/// @param [out] maxval reference to a var in which to write the maxval.
///
/// @exception invalid_file if there is no width or height, if maxval is not
/// an integer or if maxval is out of range.
///
void
NpbmInput::parseAHeader()
{
#define MAX_TUPLETYPE_SIZE 20
#define xtupletype_sscanf_fmt(ms) tupletype_sscanf_fmt(ms)
#define tupletype_sscanf_fmt(ms) "TUPLTYPE %"#ms"s"
char tupleType[MAX_TUPLETYPE_SIZE+1]; // +1 for terminating NUL.
uint32_t width = 0, height = 0;
unsigned int numFieldsFound = 0;
uint32_t componentCount = 0;
uint32_t tCompCount = 0;
uint32_t maxVal = 0;
for (;;) {
nextLine();
skipComments();
if (currentLine.compare("ENDHDR") == 0)
break;
const char* cur_line_cs = currentLine.c_str();
if (sscanf(cur_line_cs, "HEIGHT %u", &height))
numFieldsFound++;
else if (sscanf(cur_line_cs, "WIDTH %u", &width))
numFieldsFound++;
else if (sscanf(cur_line_cs, "DEPTH %u", &componentCount))
numFieldsFound++;
else if (sscanf(cur_line_cs, "MAXVAL %u", &maxVal))
numFieldsFound++;
else if (sscanf(cur_line_cs, xtupletype_sscanf_fmt(MAX_TUPLETYPE_SIZE),
tupleType))
numFieldsFound++;
};
if (numFieldsFound < 5) {
throw invalid_file("Missing fields in pam header.");
}
if ((tCompCount = tupleComponentCount(tupleType)) < 1) {
throw invalid_file(
fmt::format("Invalid TUPLTYPE: {}.", tupleType)
);
}
if (componentCount < tCompCount) {
throw invalid_file(
fmt::format("Mismatched TUPLTYPE, {}, and DEPTH, {}.",
tupleType, componentCount)
);
}
if (maxVal <= 0 || maxVal >= (1<<16)) {
throw std::runtime_error(
fmt::format("Max color component value must be > 0 && < 65536. "
"It is {}", maxVal)
);
}
images.emplace_back(ImageSpec(width, height, 1, componentCount,
maxVal > 255 ? 16 : 8,
0U, maxVal,
static_cast<khr_df_sample_datatype_qualifiers_e>(0),
KHR_DF_TRANSFER_ITU,
KHR_DF_PRIMARIES_BT709,
tCompCount < 3
? KHR_DF_MODEL_YUVSDA
: KHR_DF_MODEL_RGBSDA),
ImageInputFormatType::npbm);
}
///
/// @internal
/// @~English
/// @brief parse the header of a PGM or PPM file.
///
/// @param [in] src pointer to FILE stream to read
/// @param [out] width reference to a var in which to write the image width.
/// @param [out] height reference to a var in which to write the image height
/// @param [out] maxval reference to a var in which to write the maxval.
///
/// @exception invalid_file if there is no width or height, if maxval is not
/// an integer or if maxval is out of range.
///
void NpbmInput::parseGPHeader(filetype ftype)
{
nextLine();
skipComments();
uint32_t numvals, width, height, maxVal;
numvals = sscanf(currentLine.c_str(), "%u %u",
&width, &height);
if (numvals != 2) {
throw invalid_file("width or height is missing.");
}
if (width <= 0 || height <= 0) {
throw invalid_file("width or height is negative.");
}
nextLine();
skipComments();
numvals = sscanf(currentLine.c_str(), "%d", &maxVal);
if (numvals == 0) {
throw invalid_file("maxval must be an integer.");
}
if (maxVal <= 0 || maxVal >= (1<<16)) {
throw invalid_file("Max color component value must be > 0 && < 65536.");
}
images.emplace_back(ImageSpec(width, height, 1,
ImageSpec::Origin(ImageSpec::Origin::eLeft, ImageSpec::Origin::eTop),
ftype == filetype::PPM ? 3 : 1, 8,
0, maxVal,
static_cast<khr_df_sample_datatype_qualifiers_e>(0),
KHR_DF_TRANSFER_ITU,
KHR_DF_PRIMARIES_BT709,
ftype == filetype::PPM
? KHR_DF_MODEL_RGBSDA
: KHR_DF_MODEL_YUVSDA),
ImageInputFormatType::npbm);
}
/// @brief Read an entire image into contiguous memory performing conversions
/// to @a requestFormat.
///
/// @sa ImageInput::readScanline() for supported conversions.
void
NpbmInput::readImage(void* pBuffer, size_t bufferByteCount,
uint32_t subimage, uint32_t miplevel,
const FormatDescriptor& format)
{
const FormatDescriptor* targetFormat;
if (isp == nullptr)
throw std::runtime_error("No open input stream");
if (bufferByteCount < spec().imageByteCount())
throw buffer_too_small();
if (format.isUnknown())
targetFormat = &spec().format();
else
targetFormat = &format;
if (*targetFormat != spec().format()) {
// Use default function which reads a scanline at a time to avoid
// having to buffer entire image for conversion.
ImageInput::readImage(pBuffer, bufferByteCount,
subimage, miplevel,
format);
return;
}
try {
seekSubimage(subimage, miplevel);
isp->read((char*)pBuffer, spec().imageByteCount());
curImageScanline = spec().height();
swap(pBuffer, spec().imageChannelCount());
} catch (const std::istream::failure&) {
throwOnReadFailure();
}
}
+411
View File
@@ -0,0 +1,411 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab:
// Copyright 2022 The Khronos Group Inc.
// SPDX-License-Identifier: Apache-2.0
/**
* @internal
* @~English
* @file
*
* @brief ImageInput from PNG format files.
*
* @author Mark Callow
*/
#include "imageio.h"
#include <array>
#include <iterator>
#include <sstream>
#include <stdexcept>
#include "imageio_utility.h"
#include "lodepng.h"
#include <KHR/khr_df.h>
#include "dfd.h"
class PngInput final : public ImageInput {
public:
PngInput() : ImageInput("png") {}
virtual ~PngInput() { close(); }
virtual void open(ImageSpec& newspec) override;
virtual void close() override {
decodingBegun = false;
}
virtual void readImage(void* bufferOut, size_t bufferByteCount,
uint32_t subimage, uint32_t miplevel,
const FormatDescriptor& targetFormat) override;
/// Read a single scanline (all channels) of native data into contiguous
/// memory.
virtual void readNativeScanline(void* /*buffer*/, size_t /*bufferByteCount*/,
uint32_t /*y*/, uint32_t /*z*/,
uint32_t /*subimage*/, uint32_t /*miplevel*/) override { };
protected:
void readHeader();
void slurp();
std::vector<char> pngBuffer;
lodepng::State state;
void* pIdat;
size_t idatsize;
bool colorConvert = false;
uint32_t nextScanline = 0;
bool decodingBegun = false;
};
ImageInput*
pngInputCreate()
{
return new PngInput;
}
const char* pngInputExtensions[] = { "png", nullptr };
void
PngInput::open(ImageSpec& newspec)
{
assert(isp != nullptr && "ImageInput not properly opened");
readHeader();
newspec = spec();
nextScanline = 0;
}
void PngInput::slurp()
{
size_t pngByteLength;
isp->seekg(0, isp->end);
pngByteLength = isp->tellg();
isp->seekg(0, isp->beg);
pngBuffer.resize(pngByteLength);
isp->read(pngBuffer.data(), pngByteLength);
}
void
PngInput::readHeader()
{
// Unfortunately LoadPNG doesn't believe in stdio. The functions
// we need either read from memory or take a file name. To avoid
// a potentially unnecessary slurp of the whole file check the
// signature ourselves.
uint8_t pngsig[8] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a
};
uint8_t filesig[sizeof(pngsig)];
isp->read((char *)filesig, sizeof(pngsig));
if (isp->fail())
throwOnReadFailure();
if (memcmp(filesig, pngsig, sizeof(pngsig)))
throw different_format();
// It's a PNG file.
isp->seekg(0, isp->beg);
// Slurp it into memory so we can use lodepng_inspect, to determine
// the data type, and lodepng_chunk_find.
//
// Why no special case for when we've already read the file into a
// stringstream (i.e. buffer.get() == isp)? Because the only way to access
// such data is to call stringstream::str() which makes a copy. So treat
// everything the same. For the same reason we slurp into a vector not
// a stringstream here.
slurp();
unsigned int lodepngError;
uint32_t w, h;
lodepngError = lodepng_decode_chunks(&pIdat, &idatsize, &w, &h, &state,
(const uint8_t*)pngBuffer.data(),
pngBuffer.size());
if (lodepngError) {
throw std::runtime_error(
fmt::format("PNG decode chunks error: {}.",
lodepng_error_text(lodepngError))
);
}
// Tell the decoder to produce the same color type as the file. Exceptions
// to this are made later.
lodepng_color_mode_copy(&state.info_raw, &state.info_png.color);
uint32_t componentCount = 0;
uint32_t bitDepth = state.info_png.color.bitdepth;
// Initialisation here keeps compilers happy in the LCT_MAX_OCTET cases.
khr_df_model_e colorModel = KHR_DF_MODEL_RGBSDA;
switch (state.info_png.color.colortype) {
case LCT_GREY:
if (state.info_png.color.key_defined) {
state.info_raw.colortype = LCT_GREY_ALPHA;
componentCount = 2;
colorModel = KHR_DF_MODEL_YUVSDA;
} else {
componentCount = 1;
colorModel = KHR_DF_MODEL_YUVSDA;
}
break;
case LCT_RGB:
if (state.info_png.color.key_defined) {
state.info_raw.colortype = LCT_RGBA;
componentCount = 4;
colorModel = KHR_DF_MODEL_RGBSDA;
} else {
componentCount = 3;
colorModel = KHR_DF_MODEL_RGBSDA;
}
break;
case LCT_PALETTE: {
// color.key_defined is not set for paletted. tRNS info is written
// directly into the palette. To determine the colortype to expand to
// here we need to check if there is a tRNS chunk.
const unsigned char *pTrnsChunk = nullptr;
// 1st chunk after header
const unsigned char* pFirstChunk = (unsigned char*)&pngBuffer[33];
pTrnsChunk = lodepng_chunk_find_const(pFirstChunk,
(unsigned char*)&pngBuffer[pngBuffer.size()-1],
"tRNS");
if (pTrnsChunk) {
state.info_raw.colortype = LCT_RGBA;
componentCount = 4;
colorModel = KHR_DF_MODEL_RGBSDA;
} else {
state.info_raw.colortype = LCT_RGB;
componentCount = 3;
colorModel = KHR_DF_MODEL_RGBSDA;
}
// There are no paletted texture formats, except an ancient one in
// OpenGL ES 1 & 2 so, rather than complicate the users of imageio
// with handling for them, cause them to be expanded to 8 bits by
// this reader and issue a warning.
if (state.info_png.color.bitdepth < 8) {
bitDepth = 8; // This value will be set in the ImageSpec and
// eventually passed back to readImage().
}
fwarning(fmt::format("Expanding {}-bit paletted image to {}",
state.info_png.color.bitdepth,
state.info_raw.colortype == LCT_RGBA ? "R8G8B8A8" : "R8G8B8"));
}
break;
case LCT_GREY_ALPHA:
componentCount = 2;
colorModel = KHR_DF_MODEL_YUVSDA;
break;
case LCT_RGBA:
colorModel = KHR_DF_MODEL_RGBSDA;
componentCount = 4;
break;
case LCT_MAX_OCTET_VALUE:
break;
}
ImageInputFormatType formatType;
switch (state.info_png.color.colortype) {
case LCT_GREY:
formatType = ImageInputFormatType::png_l;
break;
case LCT_GREY_ALPHA:
formatType = ImageInputFormatType::png_la;
break;
case LCT_RGB:
formatType = ImageInputFormatType::png_rgb;
break;
case LCT_RGBA:
formatType = ImageInputFormatType::png_rgba;
break;
case LCT_PALETTE:
formatType = ImageInputFormatType::png_rgba;
break;
case LCT_MAX_OCTET_VALUE:
break;
}
images.emplace_back(ImageSpec(w, h, 1,
ImageSpec::Origin(ImageSpec::Origin::eLeft, ImageSpec::Origin::eTop),
componentCount,
bitDepth,
static_cast<khr_df_sample_datatype_qualifiers_e>(0),
KHR_DF_TRANSFER_UNSPECIFIED,
// PNG spec. says BT.709 primaries are a
// reasonable default.
KHR_DF_PRIMARIES_BT709,
colorModel),
formatType);
// This is ugly. FIXME:
FormatDescriptor& format = const_cast<FormatDescriptor&>(spec().format());
if (state.info_png.iccp_defined) {
format.setPrimaries(KHR_DF_PRIMARIES_UNSPECIFIED);
format.setTransfer(KHR_DF_TRANSFER_UNSPECIFIED);
format.extended.iccProfile.name = state.info_png.iccp_name;
format.extended.iccProfile.profile.resize(state.info_png.iccp_profile_size);
format.extended.iccProfile.profile.insert(
format.extended.iccProfile.profile.begin(),
state.info_png.iccp_profile,
&state.info_png.iccp_profile[state.info_png.iccp_profile_size]);
if (format.extended.iccProfile.name == "ITUR_2100_PQ_FULL") {
format.setPrimaries(KHR_DF_PRIMARIES_BT2020);
format.setTransfer(KHR_DF_TRANSFER_PQ_EOTF);
}
} else if (state.info_png.srgb_defined) {
// srgb_intent is a guide for the user/application when applying
// a color transform during rendering, especially when
// gamut mapping. It does not affect the meaning or value
// of the image pixels so there is nothing to do here.
format.setTransfer(KHR_DF_TRANSFER_SRGB);
format.setPrimaries(KHR_DF_PRIMARIES_SRGB);
} else if (state.info_png.gama_defined) {
format.setTransfer(KHR_DF_TRANSFER_UNSPECIFIED);
// The value in the gAMA chunk is the exponent of the power curve
// used for encoding the image, i.e. the OETF, * 100000.
format.extended.oeGamma = (float)state.info_png.gama_gamma / 100000;
} else {
format.setTransfer(KHR_DF_TRANSFER_UNSPECIFIED);
}
if (state.info_png.chrm_defined
&& !state.info_png.srgb_defined && !state.info_png.iccp_defined) {
Primaries primaries;
primaries.Rx = (float)state.info_png.chrm_red_x / 100000;
primaries.Ry = (float)state.info_png.chrm_red_y / 100000;
primaries.Gx = (float)state.info_png.chrm_green_x / 100000;
primaries.Gy = (float)state.info_png.chrm_green_y / 100000;
primaries.Bx = (float)state.info_png.chrm_blue_x / 100000;
primaries.By = (float)state.info_png.chrm_blue_y / 100000;
primaries.Wx = (float)state.info_png.chrm_white_x / 100000;
primaries.Wy = (float)state.info_png.chrm_white_y / 100000;
format.setPrimaries(findMapping(&primaries, 0.002f));
}
}
/// @brief Read an entire image into contiguous memory performing conversions
/// to @a format.
///
/// Supported conversions are
/// - bit scaling
/// - unorm\<=8->[unorm8,unorm16]
/// - unorm8<->unorm16
/// - changing channel count
/// - [GREY,GREY_ALPHA,RGB,RGBA]->[GREY,GREY_ALPHA,RGB,RGBA]
/// When reducing to 1 or 2 channels it takes the R channel for GREY.
/// When increasing from 1 or 2 channels it makes a luminance texture,
/// R=G=B=GREY. ALPHA goes to A and vice versa. If none in the source,
/// 1.0 is used.
///
/// If the PNG file has an sBit chunk the normalized results are adjusted
/// accordingly.
void
PngInput::readImage(void* bufferOut, size_t bufferOutByteCount,
uint32_t /*subimage*/, uint32_t /*miplevel*/,
const FormatDescriptor& format)
{
const auto& targetFormat = format.isUnknown() ? spec().format() : format;
const auto channelCount = targetFormat.channelCount();
const auto height = spec().height();
const auto width = spec().width();
const auto targetBitLength = targetFormat.largestChannelBitLength();
const auto requestBits = std::max(imageio::bit_ceil(targetBitLength), 8u);
if (requestBits != 8 && requestBits != 16)
throw std::runtime_error(fmt::format(
"PNG decode error: Requested decode into {}-bit format is not supported.",
requestBits)
);
const bool targetL = targetFormat.samples[0].qualifierLinear;
const bool targetE = targetFormat.samples[0].qualifierExponent;
const bool targetS = targetFormat.samples[0].qualifierSigned;
const bool targetF = targetFormat.samples[0].qualifierFloat;
// Only UNORM requests are allowed for PNG inputs
if (targetE || targetL || targetS || targetF)
throw std::runtime_error(fmt::format(
"PNG decode error: Requested format conversion to {}-bit{}{}{}{} is not supported.",
requestBits,
targetL ? " Linear" : "",
targetE ? " Exponent" : "",
targetS ? " Signed" : "",
targetF ? " Float" : "")
);
state.info_raw.bitdepth = requestBits;
state.info_raw.colortype = [&]{
switch (targetFormat.channelCount()) {
case 1:
return LCT_GREY;
case 2:
return LCT_GREY_ALPHA;
case 3:
return LCT_RGB;
case 4:
return LCT_RGBA;
}
throw std::runtime_error(fmt::format(
"PNG decode error: Requested decode into {} channels is not supported.",
targetFormat.channelCount())
);
}();
auto lodepngError = lodepng_finish_decode(
(unsigned char*)bufferOut,
bufferOutByteCount,
width,
height,
&state,
pIdat,
idatsize);
if (lodepngError)
throw std::runtime_error(fmt::format(
"PNG decode error: {}.", lodepng_error_text(lodepngError)));
// TODO: Detect endianness
// if constexpr (std::endian::native == std::endian::little)
if (requestBits == 16) {
// LodePNG loads 16 bit channels in big endian order
auto* data = (unsigned char*) bufferOut;
for (size_t i = 0; i < bufferOutByteCount; i += 2)
std::swap(*(data + i), *(data + i + 1));
}
if (state.info_png.sbit_defined) {
// Recalculate the UNORM values based on sBit information to ensure best loading/rounding
// result regardless of what the png file's writer saved
std::array<uint32_t, 4> sBits{
state.info_png.sbit_r,
state.info_png.sbit_g,
state.info_png.sbit_b,
state.info_png.sbit_a,
};
// state.info_png reflects the input file not any changes made by
// lodepng_finish_decode, which supports adding and removing the alpha channel
// in 8-bit images. sBits will be zero for added channels. Scan for such.
for (uint32_t c = 0; c < channelCount; ++c) {
if (sBits[c] == 0) // channel added.
sBits[c] = requestBits;
}
for (uint32_t y = 0; y < height; ++y) {
for (uint32_t x = 0; x < width; ++x) {
for (uint32_t c = 0; c < channelCount; ++c) {
const auto index = y * width * channelCount + x * channelCount + c;
if (requestBits == 8) {
auto& value = *(reinterpret_cast<uint8_t*>(bufferOut) + index);
value = static_cast<uint8_t>(imageio::convertUNORM(value >> (8 - sBits[c]), sBits[c], 8));
} else { // requestBits == 16
auto& value = *(reinterpret_cast<uint16_t*>(bufferOut) + index);
value = static_cast<uint16_t>(imageio::convertUNORM(value >> (16 - sBits[c]), sBits[c], 16));
}
}
}
}
}
}
@@ -0,0 +1,51 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 expandtab:
// Copyright 2022 The Khronos Group Inc.
// SPDX-License-Identifier: Apache-2.0
/**
* @internal
* @~English
* @file
*
* @brief ImageOutput to PNG format files.
*
* @author Mark Callow
*/
#include "imageio.h"
#include <iterator>
#include <sstream>
#include <stdexcept>
#include "lodepng.h"
#include <KHR/khr_df.h>
#include "dfd.h"
class pngOutput final : public ImageOutput {
public:
pngOutput() : ImageOutput("png") {}
virtual ~pngOutput() { close(); }
virtual void close() override { }
virtual void open(const std::string& name, const ImageSpec& newspec,
OpenMode mode) override;
protected:
};
ImageOutput*
pngOutputCreate()
{
return new pngOutput;
}
const char* pngOutputExtensions[] = { "png", nullptr };
void
pngOutput::open(const std::string& /*name*/, const ImageSpec& /*newspec*/,
OpenMode /*mode*/)
{
}
+98
View File
@@ -0,0 +1,98 @@
# Copyright 2022-2023 The Khronos Group Inc.
# Copyright 2022-2023 RasterGrid Kft.
# SPDX-License-Identifier: Apache-2.0
add_executable(ktxtools
command.cpp
command.h
command_compare.cpp
command_create.cpp
command_deflate.cpp
command_encode.cpp
command_extract.cpp
command_help.cpp
command_info.cpp
command_transcode.cpp
command_validate.cpp
deflate_utils.h
encode_utils_astc.h
encode_utils_basis.h
encode_utils_common.h
format_descriptor.h
formats.h
fragment_uri.h
ktx_main.cpp
metrics_utils.h
transcode_utils.cpp
transcode_utils.h
utility.h
validate.cpp
validate.h
validation_messages.h
)
create_version_header(tools/ktx ktxtools)
set_target_properties(
ktxtools
PROPERTIES
OUTPUT_NAME ktx
PDB_NAME ktxtools # Avoid conflict with libktx's .pdb.
)
target_include_directories(
ktxtools
PRIVATE
.
$<TARGET_PROPERTY:imageio,INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:ktx,INCLUDE_DIRECTORIES>
)
target_include_directories(
ktxtools
SYSTEM
PRIVATE
${PROJECT_SOURCE_DIR}/lib
${PROJECT_SOURCE_DIR}/other_include
)
target_link_libraries(
ktxtools
PRIVATE
imageio
objUtil
ktx
${ASTCENC_LIB_TARGET}
$<IF:$<BOOL:${WIN32}>,Pathcch,> # For PathCchRemoveFileSpec on Windows
fmt::fmt
cxxopts::cxxopts
)
target_compile_definitions(
ktxtools
PRIVATE
$<TARGET_PROPERTY:ktx,INTERFACE_COMPILE_DEFINITIONS>
)
target_compile_features(
ktxtools
PUBLIC
cxx_std_17
)
if (KTX_DEVELOPER_FEATURE_PATCH)
target_sources(
ktxtools
PRIVATE
command_patch.cpp
)
target_compile_definitions(
ktxtools
PRIVATE
KTX_DEVELOPER_FEATURE_PATCH=1
)
endif()
set_tool_properties(ktxtools)
set_code_sign(ktxtools)
+197
View File
@@ -0,0 +1,197 @@
// 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 "version.h"
#include "ktx.h"
#include "sbufstream.h"
#include <stdio.h>
#include <filesystem>
#include <iostream>
#include <fmt/ostream.h>
#include <fmt/printf.h>
// -------------------------------------------------------------------------------------------------
#define QUOTE(x) #x
#define STR(x) QUOTE(x)
namespace ktx {
void Command::parseCommandLine(const std::string& name, const std::string& desc, int argc, char* argv[]) {
commandName = name;
commandDescription = desc;
cxxopts::Options commandOpts(name, "");
commandOpts.custom_help("[OPTION...]");
commandOpts.set_width(CONSOLE_USAGE_WIDTH);
initOptions(commandOpts); // virtual customization point
cxxopts::ParseResult args;
try {
args = commandOpts.parse(argc, argv);
processOptions(commandOpts, args); // virtual customization point
} catch (const cxxopts::exceptions::parsing& ex) {
fatal_usage("{}.", ex.what());
}
#if defined(_WIN32) && defined(DEBUG)
if (args["ld"].as<bool>())
launchDebugger();
#endif
}
#if defined(_WIN32) && defined(DEBUG)
// For use when debugging stdin with Visual Studio which does not have a
// "wait for executable to be launched" choice in its debugger settings.
bool Command::launchDebugger()
{
// Get System directory, typically c:\windows\system32
std::wstring systemDir(MAX_PATH + 1, '\0');
UINT nChars = GetSystemDirectoryW(&systemDir[0],
static_cast<UINT>(systemDir.length()));
if (nChars == 0) return false; // failed to get system directory
systemDir.resize(nChars);
// Get process ID and create the command line
DWORD pid = GetCurrentProcessId();
std::wostringstream s;
s << systemDir << L"\\vsjitdebugger.exe -p " << pid;
std::wstring cmdLine = s.str();
// Start debugger process
STARTUPINFOW si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessW(NULL, &cmdLine[0], NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) return false;
// Close debugger process handles to eliminate resource leak
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
// Wait for the debugger to attach
while (!IsDebuggerPresent()) Sleep(100);
// Stop execution so the debugger can take over
DebugBreak();
return true;
}
#endif
std::string version(bool testrun) {
return testrun ? STR(KTX_DEFAULT_VERSION) : STR(KTX_VERSION);
}
// -------------------------------------------------------------------------------------------------
InputStream::InputStream(const std::string& filepath, Reporter& report) :
filepath(filepath) {
if (filepath == "-") {
#if defined(_WIN32)
// Set "stdin" to binary mode
const auto setmodeResult = _setmode(_fileno(stdin), _O_BINARY);
if (setmodeResult == -1)
report.fatal(rc::IO_FAILURE, "Failed to set stdin mode to binary: {}.", setmodeResult);
// #else
// std::freopen(nullptr, "rb", stdin);
#endif
// Read everything from stdin into memory to enable random access
stdinBuffer << std::cin.rdbuf();
activeStream = &stdinBuffer;
} else {
file.open(DecodeUTF8Path(filepath).c_str(), std::ios::binary | std::ios::in);
if (!file)
report.fatal(rc::IO_FAILURE, "Could not open input file \"{}\": {}.", filepath, errnoMessage());
activeStream = &file;
}
}
// -------------------------------------------------------------------------------------------------
OutputStream::OutputStream(const std::string& filepath, Reporter& report) :
filepath(filepath) {
if (filepath == "-") {
#if defined(_WIN32)
// Set "stdout" to binary mode
const auto setmodeResult = _setmode(_fileno(stdout), _O_BINARY);
if (setmodeResult == -1)
report.fatal(rc::IO_FAILURE, "Failed to set stdout mode to binary: {}.", setmodeResult);
// #else
// std::freopen(nullptr, "wb", stdout);
#endif
file = stdout;
} else {
#if defined(_WIN32)
file = _wfopen(DecodeUTF8Path(filepath).c_str(), L"wb");
#else
file = fopen(DecodeUTF8Path(filepath).c_str(), "wb");
#endif
if (!file)
report.fatal(rc::IO_FAILURE, "Could not open output file \"{}\": {}.", filepath, errnoMessage());
}
// TODO: Investigate and resolve the portability issue with the C++ streams. The issue will most likely
// be in StreambufStream's position reporting and seeking. Currently a fallback is implemented in C above.
// if (filepath == "-") {
// #if defined(_WIN32)
// // Set "stdout" to binary mode
// const auto setmodeResult = _setmode(_fileno(stdout), _O_BINARY);
// if (setmodeResult == -1)
// report.fatal(rc::IO_FAILURE, "Failed to set stdout mode to binary: {}", setmodeResult);
// // #else
// // std::freopen(nullptr, "wb", stdout);
// #endif
// activeStream = &std::cout;
// } else {
// file.open(DecodeUTF8Path(filepath).c_str(), std::ios::binary | std::ios::out);
// if (!file)
// report.fatal(rc::IO_FAILURE, "Could not open output file \"{}\": {}", filepath, errnoMessage());
// activeStream = &file;
// }
}
OutputStream::~OutputStream() {
if (file != stdout)
fclose(file);
}
void OutputStream::write(const char* data, std::size_t size, Reporter& report) {
const auto written = std::fwrite(data, 1, size, file);
if (written != size)
report.fatal(rc::IO_FAILURE, "Failed to write output file \"{}\": {}.", fmtOutFile(filepath), errnoMessage());
}
void OutputStream::writeKTX2(ktxTexture* texture, Reporter& report) {
const auto ret = ktxTexture_WriteToStdioStream(texture, file);
if (KTX_SUCCESS != ret) {
if (file != stdout)
std::filesystem::remove(DecodeUTF8Path(filepath).c_str());
report.fatal(rc::IO_FAILURE, "Failed to write KTX file \"{}\": KTX error: {}.", filepath, ktxErrorString(ret));
}
// TODO: Investigate and resolve the portability issue with the C++ streams. The issue will most likely
// be in StreambufStream's position reporting and seeking. Currently a fallback is implemented in C above.
// StreambufStream<std::streambuf*> stream(activeStream->rdbuf(), std::ios::in | std::ios::binary);
// const auto ret = ktxTexture_WriteToStream(texture, stream.stream());
//
// if (KTX_SUCCESS != ret) {
// if (activeStream != &std::cout)
// std::filesystem::remove(DecodeUTF8Path(filepath).c_str());
// report.fatal(rc::IO_FAILURE, "Failed to write KTX file \"{}\": {}.", fmtOutFile(filepath), ktxErrorString(ret));
// }
}
// -------------------------------------------------------------------------------------------------
} // namespace ktx
+403
View File
@@ -0,0 +1,403 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "stdafx.h"
#include <string>
#include <vector>
#include <iostream>
#include "utility.h"
#include <cxxopts.hpp>
#include <fmt/ostream.h>
#include <fmt/printf.h>
#if defined(_WIN32)
#define KTX_COMMAND_EXPORT extern "C" __declspec(dllexport)
#define KTX_COMMAND_CALL __stdcall
#define KTX_COMMAND_PTR KTX_COMMAND_CALL
#else
#define KTX_COMMAND_EXPORT extern "C" __attribute__((visibility="default"))
#define KTX_COMMAND_CALL
#define KTX_COMMAND_PTR
#endif
#define KTX_COMMAND_ENTRY_POINT_DEF(CMDCLASS) \
(int argc, char* argv[]) { CMDCLASS cmd{}; return cmd.main(argc, argv); }
#if defined(KTX_COMMAND_EXECUTABLE)
// Command is built as a separate executable
// (parent command can issue it using a system call)
#define KTX_COMMAND_ENTRY_POINT(NAME, CMDCLASS) \
int _tmain KTX_COMMAND_ENTRY_POINT_DEF(CMDCLASS)
#elif defined(KTX_COMMAND_SHARED_LIB)
// Command is built as a separate shared library
// (parent command can issue it by loading its entry point)
#define KTX_COMMAND_ENTRY_POINT(NAME, CMDCLASS) \
KTX_COMMAND_EXPORT int KTX_COMMAND_CALL ktxCommandMain KTX_COMMAND_ENTRY_POINT_DEF(CMDCLASS)
#else
// Command is built statically into the executable
#define KTX_COMMAND_ENTRY_POINT(NAME, CMDCLASS) \
int NAME KTX_COMMAND_ENTRY_POINT_DEF(CMDCLASS)
#endif
#define KTX_COMMAND_BUILTIN(NAME) int NAME(int argc, char* argv[]);
// -------------------------------------------------------------------------------------------------
namespace ktx {
using pfnBuiltinCommand = int (*)(int argc, char* argv[]);
using pfnImportedCommand = int (KTX_COMMAND_PTR *)(int argc, char* argv[]);
static constexpr int CONSOLE_USAGE_WIDTH = 100;
/**
//! [command exitstatus]
- 0 - Success
- 1 - Command line error
- 2 - IO failure
- 3 - Invalid input file
- 4 - Runtime or library error
- 5 - Not supported state or operation
- 6 - Requested feature is not yet implemented
//! [command exitstatus]
*/
enum class ReturnCode {
SUCCESS = 0,
INVALID_ARGUMENTS = 1,
IO_FAILURE = 2,
INVALID_FILE = 3,
RUNTIME_ERROR = 4,
KTX_FAILURE = RUNTIME_ERROR,
DFD_FAILURE = RUNTIME_ERROR,
NOT_SUPPORTED = 5,
NOT_IMPLEMENTED = 6,
DIFFERENCE_FOUND = 7
};
using rc = ReturnCode;
[[nodiscard]] constexpr inline auto operator+(ReturnCode value) noexcept {
return to_underlying(value);
}
struct FatalError : public std::exception {
ReturnCode returnCode; /// Desired process exit code
explicit FatalError(ReturnCode returnCode) : returnCode(returnCode) {}
};
struct Reporter {
std::string commandName;
std::string commandDescription;
template <typename... Args>
void warning(Args&&... args) {
fmt::print(std::cerr, "{} warning: ", commandName);
fmt::print(std::cerr, std::forward<Args>(args)...);
fmt::print(std::cerr, "\n");
}
template <typename... Args>
void error(Args&&... args) {
fmt::print(std::cerr, "{} error: ", commandName);
fmt::print(std::cerr, std::forward<Args>(args)...);
fmt::print(std::cerr, "\n");
}
template <typename... Args>
void fatal(ReturnCode return_code, Args&&... args) {
fmt::print(std::cerr, "{} fatal: ", commandName);
fmt::print(std::cerr, std::forward<Args>(args)...);
fmt::print(std::cerr, "\n");
throw FatalError(return_code);
}
template <typename... Args>
void fatal_usage(Args&&... args) {
fmt::print(std::cerr, "{} fatal: ", commandName);
fmt::print(std::cerr, std::forward<Args>(args)...);
fmt::print(std::cerr, " See '{} --help'.\n", commandName);
throw FatalError(rc::INVALID_ARGUMENTS);
}
};
[[nodiscard]] std::string version(bool testrun);
class Command : public Reporter {
public:
Command() = default;
virtual ~Command() = default;
public:
virtual int main(int argc, char* argv[]) = 0;
protected:
void parseCommandLine(const std::string& name, const std::string& desc, int argc, char* argv[]);
virtual void initOptions(cxxopts::Options& /*opts*/) { }
virtual void processOptions(cxxopts::Options& /*opts*/, cxxopts::ParseResult& /*args*/) { };
#if defined(_WIN32) && defined(DEBUG)
bool launchDebugger();
#endif
};
// -------------------------------------------------------------------------------------------------
/**
//! [command options_generic]
<dl>
<dt>-h, \--help</dt>
<dd>Print this usage message and exit.</dd>
<dt>-v, \--version</dt>
<dd>Print the version number of this program and exit.</dd>
</dl>
//! [command options_generic]
*/
struct OptionsGeneric {
// --help
// --version
bool testrun = false; /// Indicates test run. If enabled ktx tools will only include the default version information in any output
void init(cxxopts::Options& opts) {
opts.add_options()
("h,help", "Print this usage message and exit")
("v,version", "Print the version number of this program and exit")
("testrun", "Indicates test run. If enabled the tool will produce deterministic output whenever possible")
#if defined(_WIN32) && defined(DEBUG)
("ld", "Launch debugger on startup.")
#endif
;
}
void process(cxxopts::Options& opts, cxxopts::ParseResult& args, Reporter& report) {
testrun = args["testrun"].as<bool>();
if (args.count("help")) {
fmt::print("{}: {}\n", report.commandName, report.commandDescription);
fmt::print("{}", opts.help());
throw FatalError(rc::SUCCESS);
}
if (args.count("version")) {
fmt::print("{} version: {}\n", opts.program(), version(testrun));
throw FatalError(rc::SUCCESS);
}
}
};
enum class OutputFormat {
text,
json,
json_mini,
};
/**
//! [command options_format]
<dl>
<dt>\--format text | json | mini-json</dt>
<dd>Specifies the report output format. Possible options are: <br />
@b text - Human readable text based format. <br />
@b json - Formatted JSON. <br />
@b mini-json - Minified JSON. <br />
The default format is @b text.
</dd>
</dl>
//! [command options_format]
*/
struct OptionsFormat {
OutputFormat format;
void init(cxxopts::Options& opts) {
opts.add_options()
("f,format", "Specifies the report output format. Possible options are:\n"
" text: Human readable text based format\n"
" json: Formatted JSON\n"
" mini-json: Minified JSON\n",
cxxopts::value<std::string>()->default_value("text"),
"text|json|mini-json");
}
void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) {
const auto& formatStr = to_lower_copy(args["format"].as<std::string>());
if (formatStr == "text") {
format = OutputFormat::text;
} else if (formatStr == "json") {
format = OutputFormat::json;
} else if (formatStr == "mini-json") {
format = OutputFormat::json_mini;
} else {
report.fatal_usage("Unsupported format: \"{}\".", formatStr);
}
}
};
struct OptionsSingleIn {
std::string inputFilepath;
void init(cxxopts::Options& opts) {
opts.add_options()
("stdin", "Use stdin as the input file. (Using a single dash '-' as the input file has the same effect)")
("i,input-file", "The input file. Using a single dash '-' as the input file will use stdin.", cxxopts::value<std::string>(), "filepath");
opts.parse_positional("input-file");
opts.positional_help("<input-file>");
}
void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) {
if (!args.unmatched().empty())
report.fatal_usage("Too many filenames specified.");
if (args.count("stdin") + args.count("input-file") == 0)
report.fatal_usage("Missing input file. Either <input-file> or --stdin must be specified.");
if (args.count("stdin") + args.count("input-file") > 1)
report.fatal_usage("Conflicting options: Only one can be specified from <input-file> and --stdin.");
if (args.count("stdin"))
inputFilepath = "-";
else
inputFilepath = args["input-file"].as<std::string>();
}
};
struct OptionsSingleInSingleOut {
std::string inputFilepath;
std::string outputFilepath;
void init(cxxopts::Options& opts) {
opts.add_options()
("stdin", "Use stdin as the input file. (Using a single dash '-' as the input file has the same effect)")
("stdout", "Use stdout as the output file. (Using a single dash '-' as the output file has the same effect)")
("i,input-file", "The input file. Using a single dash '-' as the input file will use stdin.", cxxopts::value<std::string>(), "filepath")
("o,output-file", "The output file. Using a single dash '-' as the output file will use stdout.", cxxopts::value<std::string>(), "filepath");
opts.parse_positional("input-file", "output-file");
opts.positional_help("<input-file> <output-file>");
}
void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) {
if (!args.unmatched().empty())
report.fatal_usage("Too many filenames specified.");
if (args.count("stdin") + args.count("input-file") == 0)
report.fatal_usage("Missing input file. Either <input-file> or --stdin must be specified.");
if (args.count("stdin") + args.count("input-file") > 1)
report.fatal_usage("Conflicting options: Only one can be specified from <input-file> and --stdin.");
if (args.count("stdout") + args.count("output-file") == 0)
report.fatal_usage("Missing output file. Either <output-file> or --stdout must be specified.");
if (args.count("stdout") + args.count("output-file") > 1)
report.fatal_usage("Conflicting options: Only one can be specified from <output-file> and --stdout.");
if (args.count("stdin"))
inputFilepath = "-";
else
inputFilepath = args["input-file"].as<std::string>();
if (args.count("stdout"))
outputFilepath = "-";
else
outputFilepath = args["output-file"].as<std::string>();
}
};
struct OptionsMultiInSingleOut {
std::vector<std::string> inputFilepaths;
std::string outputFilepath;
void init(cxxopts::Options& opts) {
opts.add_options()
("stdin", "Use stdin as the first input file. (Using a single dash '-' as the first input file has the same effect)")
("stdout", "Use stdout as the output file. (Using a single dash '-' as the output file has the same effect)")
("files", "Input/output files. Last file specified will be used as output."
" Using a single dash '-' as an input or output file will use stdin/stdout.", cxxopts::value<std::vector<std::string>>(), "<filepath>");
opts.parse_positional("files");
opts.positional_help("<input-file...> <output-file>");
}
void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) {
std::vector<std::string> files;
if (args.count("stdin"))
files.emplace_back("-");
if (args.count("files")) {
const auto& argFiles = args["files"].as<std::vector<std::string>>();
files.insert(files.end(), argFiles.begin(), argFiles.end());
}
if (args.count("stdout"))
files.emplace_back("-");
if (files.size() < 1)
report.fatal_usage("Input and output files must be specified.");
if (files.size() < 2)
report.fatal_usage("{} file must be specified.", args.count("stdout") == 0 ? "Output" : "Input");
outputFilepath = std::move(files.back());
files.pop_back();
inputFilepaths = std::move(files);
if (std::count(inputFilepaths.begin(), inputFilepaths.end(), "-") > 1)
report.fatal_usage("'-' or --stdin as input file was specified more than once.");
}
};
/// Convenience helper to combine multiple options struct together.
/// Init functions are called left to right.
/// Process functions are called in reverse order from right to left.
template <typename... Args>
struct Combine : Args... {
void init(cxxopts::Options& opts) {
(Args::init(opts), ...);
}
void process(cxxopts::Options& opts, cxxopts::ParseResult& args, Reporter& report) {
int dummy; // Reverse fold via operator= on a dummy int
(dummy = ... = (Args::process(opts, args, report), 0));
(void) dummy;
}
};
/// Helper to handle stdin and fstream uniformly
class InputStream {
std::string filepath;
std::istream* activeStream = nullptr;
std::ifstream file; // Unused for stdin/stdout
std::stringstream stdinBuffer;
public:
InputStream(const std::string& filepath, Reporter& report);
const std::string& str() {
return filepath;
}
/*explicit(false)*/ operator std::istream&() {
return *activeStream;
}
std::istream* operator->() {
return activeStream;
}
std::istream& operator*() {
return *activeStream;
}
};
/// Helper to handle stdout and fstream uniformly
class OutputStream {
std::string filepath;
FILE* file;
// std::ostream* activeStream = nullptr;
// std::ofstream file; // Unused for stdin/stdout
public:
OutputStream(const std::string& filepath, Reporter& report);
~OutputStream();
const std::string& str() {
return filepath;
}
void writeKTX2(ktxTexture* texture, Reporter& report);
void write(const char* data, std::size_t size, Reporter& report);
};
} // namespace ktx
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+320
View File
@@ -0,0 +1,320 @@
// 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 "deflate_utils.h"
#include "formats.h"
#include "sbufstream.h"
#include "utility.h"
#include "validate.h"
#include "ktx.h"
#include <array>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <regex>
#include <unordered_map>
#include <cxxopts.hpp>
#include <fmt/ostream.h>
#include <fmt/printf.h>
// -------------------------------------------------------------------------------------------------
namespace ktx {
// -------------------------------------------------------------------------------------------------
/** @page ktx_deflate ktx deflate
@~English
Deflate (supercompress) a KTX2 file.
@section ktx_deflate_synopsis SYNOPSIS
ktx deflate [option...] @e input-file @e output-file
@section ktx_deflate_description DESCRIPTION
@b ktx @b deflate deflates (supercompresses) the KTX file specified as the
@e input-file and saves 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.
If the input file is already supercompressed it will be inflated then
supercompressed again using the options specified here and a warning will
be issued. 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.
@b ktx @b deflate cannot be applied to KTX files that have been
supercompressed with BasisLZ.
@section ktx\_deflate\_options OPTIONS
The following options are available:
@snippet{doc} ktx/deflate_utils.h command options_deflate
<dl>
<dt>-q, --quiet</dt>
<dd>Silence warning about already supercompressed input fiile.</dd>
<dt>-e, --warnings-as-errors</dt>
<dd>Treat warnings as errors.</dd>
</dl>
@snippet{doc} ktx/command.h command options_generic
@section ktx_deflate_exitstatus EXIT STATUS
@snippet{doc} ktx/command.h command exitstatus
@section ktx_deflate_history HISTORY
@par Version 4.0
- Initial version
@section ktx_deflate_author AUTHOR
- Mark Callow [\@MarkCallow]
*/
class CommandDeflate : public Command {
enum {
all = -1,
};
struct Options {
inline static const char* kQuiet = "quiet";
inline static const char* kWarningsAsErrors = "warnings-as-errors";
bool quiet = false;
bool warningsAsErrors = false;
void init(cxxopts::Options& opts);
void process(cxxopts::Options& opts, cxxopts::ParseResult& args, Reporter& report);
};
Combine<Options, OptionsDeflate, OptionsSingleInSingleOut, OptionsGeneric> 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 executeDeflate();
};
// -------------------------------------------------------------------------------------------------
int CommandDeflate::main(int argc, char* argv[]) {
try {
parseCommandLine("ktx deflate",
"Deflate (supercompress) the KTX file specified as the input-file\n"
" and save it as the output-file.",
argc, argv);
executeDeflate();
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 CommandDeflate::Options::init(cxxopts::Options& opts) {
opts.add_options()
(kQuiet, "Don't print warning when input file is already supercompressed.")
(kWarningsAsErrors, "Exit with error when input file is already supercompressed");
}
void CommandDeflate::Options::process(cxxopts::Options&,
cxxopts::ParseResult& args,
Reporter& report) {
quiet = args[kQuiet].as<bool>();
warningsAsErrors = args[kWarningsAsErrors].as<bool>();
if (quiet && warningsAsErrors) {
report.fatal_usage("Cannot specify both --{} and --{}.",
this->kQuiet, this->kWarningsAsErrors);
}
}
void CommandDeflate::initOptions(cxxopts::Options& opts) {
options.init(opts);
}
void CommandDeflate::processOptions(cxxopts::Options& opts, cxxopts::ParseResult& args) {
options.process(opts, args, *this);
if (!options.zstd && !options.zlib) {
fatal_usage("Must specify --{} or --{}.",
OptionsDeflate::kZStd, OptionsDeflate::kZLib);
}
}
void CommandDeflate::executeDeflate() {
InputStream inputStream(options.inputFilepath, *this);
validateToolInput(inputStream, fmtInFile(options.inputFilepath), *this);
KTXTexture2 texture{nullptr};
StreambufStream<std::streambuf*> 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) {
switch (texture->supercompressionScheme) {
case KTX_SS_ZLIB:
case KTX_SS_ZSTD:
if (!options.quiet) {
warning("Modifying existing {} supercompression of {}.",
toString(texture->supercompressionScheme),
options.inputFilepath);
}
break;
default:
fatal(rc::INVALID_FILE,
"Cannot further deflate a KTX2 file supercompressed with {}.",
toString(texture->supercompressionScheme));
}
}
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));
}
const auto& findMetadataValue = [&](const char* const key) {
const char* value;
uint32_t valueLen;
std::string result;
auto ret = ktxHashList_FindValue(&texture->kvDataHead, key,
&valueLen, (void**)&value);
if (ret == KTX_SUCCESS) {
// The values we are looking for are required to be NUL terminated.
result.assign(value, valueLen - 1);
}
return result;
};
const auto updateMetadataValue = [&](const char* const key,
const std::string& value) {
ktxHashList_DeleteKVPair(&texture->kvDataHead, key);
ktxHashList_AddKVPair(&texture->kvDataHead, key,
static_cast<uint32_t>(value.size() + 1), // +1 to include \0
value.c_str());
};
// ======= KTXwriter and KTXwriterScParams metadata handling =======
//
// In order to preserve encoding parameters applied to the data with
// other apps prior to this deflate operation, `deflate` does the
// following if KTXwriterScParams data exists in the input file:
//
// 1. If the writer was one of the ktx suite (i.e. create or encode)
// and KTXwriterScParams contains non-deflate options, use the
// original KTXwriter. Replace an existing deflate option with
// that currently specified or append it as new.
//
// The original writer will obviously understand its own
// non-deflate options and, since it is part of the ktx suite
// it will understand the updated or new deflate option that will
// be added.
//
// Cheeky! Spec. for KTXwriter says "only the most recent writer
// Should be identified." For KTXwriterScParams it says the writer
// should "append the (new) options" when "building on operations
// done previously." To somewhat resolve the conflict it changes
// the previous "only" to "in general."
//
// 2. If the writer was another tool, preserve its options in
// KTWwriterScParams labelled with its name and append the
// currently specified deflate option like so
//
// --zstd 18 | (from <name>) option1 option2 ...
//
// where <name> is the first word of the original KTXwriter metadata,
// e.g, "tokt". Rewrite KTXwriter with the name of this tool.
//
// 3. If the writer was ktxsc or toktx remove any original deflate
// option from the preserved parameters as we know those option
// names.
bool changeWriter = true;
std::string writerScParams;
std::string origWriterName;
writerScParams = findMetadataValue(KTX_WRITER_SCPARAMS_KEY);
if (!writerScParams.empty()) {
std::string writer = findMetadataValue(KTX_WRITER_KEY);
if (!writer.empty()) {
std::regex e("ktx (?:create|deflate|encode|transcode)");
std::smatch deflateOptionMatch;
if (std::regex_search(writer, e)) {
// Writer is member of the ktx suite.
// Look for existing deflate option
e = " ?--(?:zlib|zstd) [1-9][0-9]?";
(void)std::regex_search(writerScParams, deflateOptionMatch, e);
} else {
// Writer is not a member of the ktx suite
e = "ktxsc|toktx";
if (std::regex_search(writer, e)) {
// Look for toktx/ktxsc deflate option
e = " ?--zcmp ?[1-9]?[0-9]?";
(void)std::regex_search(writerScParams,
deflateOptionMatch, e);
}
origWriterName = writer.substr(0, writer.find_first_of(' '));
}
// Remove existing deflate option since its value will not apply
// to the newly deflated data.
for (uint32_t i = 0; i < deflateOptionMatch.size(); i++) {
writerScParams.replace(deflateOptionMatch.position(i),
deflateOptionMatch.length(i),
"");
}
// Does ScParams still have data and is the original writer a
// member of the ktx suite?
if (!writerScParams.empty() && origWriterName.empty()) {
changeWriter = false;
}
}
}
if (changeWriter) {
// Create or modify KTXwriter metadata.
const auto writer = fmt::format("{} {}", commandName, version(options.testrun));
updateMetadataValue(KTX_WRITER_KEY, writer);
}
// Format new writerScParams.
auto newScParams = fmt::format("{}", options.compressOptions);
// Options always contain a leading space
assert(newScParams[0] == ' ');
if (!writerScParams.empty()) {
if (changeWriter) {
// Leading space unneeded as this param will be first.
newScParams.erase(newScParams.begin());
writerScParams = fmt::format("{} / (from {}) {}", newScParams,
origWriterName, writerScParams);
} else {
writerScParams.append(newScParams);
}
} else {
writerScParams = newScParams;
writerScParams.erase(writerScParams.begin()); // Erase leading space.
}
// Add KTXwriterScParams metadata
updateMetadataValue(KTX_WRITER_SCPARAMS_KEY, writerScParams);
// 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(ktxDeflate, ktx::CommandDeflate)
+312
View File
@@ -0,0 +1,312 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// 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"
#include "deflate_utils.h"
#include "encode_utils_basis.h"
#include "formats.h"
#include "sbufstream.h"
#include "utility.h"
#include "validate.h"
#include "ktx.h"
#include <array>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <unordered_map>
#include <cxxopts.hpp>
#include <fmt/ostream.h>
#include <fmt/printf.h>
// -------------------------------------------------------------------------------------------------
namespace ktx {
// -------------------------------------------------------------------------------------------------
/** @page ktx_encode ktx encode
@~English
Encode a KTX2 file.
@section ktx_encode_synopsis SYNOPSIS
ktx encode [option...] @e input-file @e output-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,
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
or R8G8B8A8 (or their sRGB variants).
<!--For ASTC HDR formats the input file must be TBD (e.g. R16_{,S}FLOAT,
R16G16_{,S}FLOAT ...
-->
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.
@section ktx\_encode\_options OPTIONS
@subsection ktx\_encode\_options\_general General Options
<!--Specifying both @e \--codec and @e \--format options is an error.
-->
The following options are available:
<dl>
<dt>\--codec basis-lz | uastc</dt>
<dd>Target codec followed by the codec specific options. With each choice
the specific and common encoder options listed
@ref ktx\_encode\_options\_encoding "below" become valid, otherwise
they are ignored. Case-insensitive.</dd>
@snippet{doc} ktx/encode_utils_basis.h command options_basis_encoders
<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
@subsection ktx\_encode\_options\_encoding Specific and Common Encoding Options
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_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
@section ktx_encode_exitstatus EXIT STATUS
@snippet{doc} ktx/command.h command exitstatus
@section ktx_encode_history HISTORY
@par Version 4.0
- Initial version.
@par Version 4.4
- Reorganize encoding options.
@section ktx_encode_author AUTHOR
- Mátyás Császár [Vader], RasterGrid www.rastergrid.com
- Daniel Rákos, RasterGrid www.rastergrid.com
*/
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, OptionsEncodeASTC, OptionsEncodeBasis<true>, OptionsEncodeCommon, OptionsMetrics, OptionsDeflate, OptionsSingleInSingleOut, OptionsGeneric> 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 executeEncode();
};
// -------------------------------------------------------------------------------------------------
int CommandEncode::main(int argc, char* argv[]) {
try {
parseCommandLine("ktx encode",
"Encode the KTX file specified as the input-file argument,\n"
" optionally supercompress the result, and save it as the output-file.",
argc, argv);
executeEncode();
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 CommandEncode::OptionsEncode::init(cxxopts::Options& opts) {
opts.add_options()
(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& 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) {
options.init(opts);
}
void CommandEncode::processOptions(cxxopts::Options& opts, cxxopts::ParseResult& args) {
options.process(opts, args, *this);
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.");
if (options.zlib.has_value())
fatal_usage("Cannot encode to BasisLZ and supercompress with ZLIB.");
}
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, UASTC or ASTC encoding.");
if (options.compare_psnr && !canCompare)
fatal_usage("--compare-psnr can only be used with BasisLZ, UASTC or ASTC encoding.");
if (astcCodec)
options.encodeASTC = true;
}
void CommandEncode::executeEncode() {
InputStream inputStream(options.inputFilepath, *this);
validateToolInput(inputStream, fmtInFile(options.inputFilepath), *this);
KTXTexture2 texture{nullptr};
StreambufStream<std::streambuf*> 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)));
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:
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<uint32_t>(writer.size() + 1), // +1 to include the \0
writer.c_str());
khr_df_transfer_e tf = ktxTexture2_GetTransferFunction_e(texture);
if (options.ktxBasisParams::normalMap && tf != KHR_DF_TRANSFER_LINEAR)
fatal(rc::INVALID_FILE,
"--normal-mode specified but the input file uses non-linear transfer function {}.",
toString(tf));
MetricsCalculator metrics;
metrics.saveReferenceImages(texture, options, *this);
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) {
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.codecOptions, options.commonOptions, options.compressOptions);
ktxHashList_DeleteKVPair(&texture->kvDataHead, KTX_WRITER_SCPARAMS_KEY);
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
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(ktxEncode, ktx::CommandEncode)
File diff suppressed because it is too large Load Diff
+207
View File
@@ -0,0 +1,207 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#include "command.h"
#include <filesystem>
#include <optional>
#include <cxxopts.hpp>
#include <fmt/format.h>
#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h> // For GetModuleFileNameW
#include <shellapi.h> // For ShellExecuteW
#include <pathcch.h> // For PathCchRemoveFileSpec
#include <fmt/xchar.h> // For wchat_t format
#endif
#if defined(__APPLE__)
#include <mach-o/dyld.h>
#endif
// -------------------------------------------------------------------------------------------------
namespace ktx {
struct OptionsHelp {
std::optional<std::string> command;
void init(cxxopts::Options& opts) {
opts.add_options()
("command", "The command for which usage should be displayed.", cxxopts::value<std::string>());
opts.parse_positional("command");
opts.positional_help("<command>");
}
void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) {
if (args.count("command")) {
static const std::unordered_set<std::string> command_table{
"create",
"extract",
"encode",
"transcode",
"info",
"validate",
"compare",
"help",
};
command = to_lower_copy(args["command"].as<std::string>());
if (command_table.count(*command) == 0)
report.fatal_usage("Invalid command specified: \"{}\".", *command);
}
}
};
/** @page ktx_help ktx help
@~English
Display help information about the ktx tool.
@section ktx_help_synopsis SYNOPSIS
ktx help [option...] [@e command]
@section ktx_help_description DESCRIPTION
@b ktx @b help displays the man page of a specific ktx command specified as the @e command
argument.
On windows systems the man pages are opened with the system default browser in html format.
On systems derived from Unix the man pages are opened with the man command.
To support custom install locations the tool first tries to use the man files relative to
the executable and falls back to the system man pages.
@e command specifies which command's man page will be displayed.
If @e command is s missing the main ktx tool man page will be displayed.
Possible choices are: <br />
- @ref ktx_compare "compare" <br />
- @ref ktx_create "create" <br />
- @ref ktx_create "deflate" <br />
- @ref ktx_encode "encode" <br />
- @ref ktx_extract "extract" <br />
- @ref ktx_info "info" <br />
- @ref ktx_transcode "transcode" <br />
- @ref ktx_validate "validate" <br />
- @ref ktx_help "help"
@section ktx\_help\_options OPTIONS
@snippet{doc} ktx/command.h command options_generic
@section ktx_help_exitstatus EXIT STATUS
@snippet{doc} ktx/command.h command exitstatus
@section ktx_help_history HISTORY
@par Version 4.0
- Initial version
@section ktx_help_author AUTHOR
- Mátyás Császár [Vader], RasterGrid www.rastergrid.com
- Daniel Rákos, RasterGrid www.rastergrid.com
*/
class CommandHelp : public Command {
Combine<OptionsHelp, OptionsGeneric> 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 executeHelp();
};
// -------------------------------------------------------------------------------------------------
int CommandHelp::main(int argc, char* argv[]) {
try {
parseCommandLine("ktx help",
"Displays the man page of a specific ktx command specified as the command argument."
"\nIf the command option is missing the main ktx tool man page will be displayed.",
argc, argv);
executeHelp();
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 CommandHelp::initOptions(cxxopts::Options& opts) {
options.init(opts);
}
void CommandHelp::processOptions(cxxopts::Options& opts, cxxopts::ParseResult& args) {
options.process(opts, args, *this);
}
void CommandHelp::executeHelp() {
// On windows open the html pages with the browser
// On linux/macos open the man pages with man
#if defined(_WIN32)
DWORD size = 256;
std::wstring executablePath;
do {
size *= 2;
executablePath.resize(size);
} while (GetModuleFileNameW(nullptr, executablePath.data(), size) == size);
PathCchRemoveFileSpec(executablePath.data(), executablePath.size());
executablePath.resize(wcslen(executablePath.c_str()));
const auto commandStr = options.command.value_or("");
const auto systemCommand = fmt::format(L"{}\\..\\share\\doc\\KTX-Software\\html\\ktxtools\\ktx{}{}.html",
executablePath,
options.command ? L"_" : L"",
std::wstring(commandStr.begin(), commandStr.end()));
auto result = ShellExecuteW(nullptr, nullptr, systemCommand.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
auto r = reinterpret_cast<INT_PTR>(result);
if (r <= 32) // WinAPI is weird
fatal(rc::RUNTIME_ERROR, "Failed to open the html documentation: ERROR {}", r);
#else
# if defined(__APPLE__)
char buf[PATH_MAX];
uint32_t bufsize = PATH_MAX;
if (const auto ec = _NSGetExecutablePath(buf, &bufsize))
fatal(rc::RUNTIME_ERROR, "Failed to determine executable path: ERROR {}", ec);
const auto executablePath = std::filesystem::canonical(buf);
# else // Linux
const auto executablePath = std::filesystem::canonical("/proc/self/exe");
# endif
const auto executableDir = std::filesystem::path(executablePath).remove_filename();
const auto manFile = fmt::format("{}/../share/man/man1/ktx{}{}.1",
executableDir.string(),
options.command ? "_" : "",
options.command.value_or(""));
if (std::filesystem::exists(manFile)) {
// We have relative access to the man file, prioritize opening it
// that way to support custom install locations
const auto systemCommand = fmt::format("man \"{}\"", manFile);
const auto result = std::system(systemCommand.c_str());
(void) result;
} else {
const auto systemCommand = fmt::format("man ktx{}{}",
options.command ? "_" : "",
options.command.value_or(""));
const auto result = std::system(systemCommand.c_str());
(void) result;
}
#endif
}
// -------------------------------------------------------------------------------------------------
} // namespace ktx
KTX_COMMAND_ENTRY_POINT(ktxHelp, ktx::CommandHelp)
+242
View File
@@ -0,0 +1,242 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#include "command.h"
#include "sbufstream.h"
#include "stdafx.h"
#include "utility.h"
#include "validate.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <utility>
#include <cxxopts.hpp>
#include <fmt/printf.h>
#include <ktx.h>
#include <ktxint.h>
// -------------------------------------------------------------------------------------------------
namespace ktx {
/** @page ktx_info ktx info
@~English
Print information about a KTX2 file.
@section ktx_info_synopsis SYNOPSIS
ktx info [option...] @e input-file
@section ktx_info_description DESCRIPTION
@b ktx @b info prints information about the KTX v1 or v2 file specified as the
@e input-file argument. If the @e input-file is '-' the file will be read from the stdin.
The command implicitly calls @ref ktx_validate "validate" for KTX v2 files and prints any
found errors and warnings to stdout. If the specified input file is invalid the information
is displayed based on best effort and may be incomplete.
The JSON output formats conform to the https://schema.khronos.org/ktx/info_v0.json
schema even if the input file is invalid and certain information cannot be parsed or
displayed.
Additionally, for JSON outputs the KTX file identifier is printed using "\u001A" instead of
"\x1A" as an unescaped "\x1A" sequence inside a JSON string breaks nearly every JSON tool.
Note that this does not change the value of the string only its representation.
JSON output is only supported for KTX v2 files. If requested for a KTX v1 file,
an error is generated.
@note @b ktx @b info prints using UTF-8 encoding. If your console is not
set for UTF-8 you will see incorrect characters in output of the file
identifier on each side of the "KTX nn".
@section ktx\_info\_options OPTIONS
The following options are available:
@snippet{doc} ktx/command.h command options_format
@snippet{doc} ktx/command.h command options_generic
@section ktx_info_exitstatus EXIT STATUS
@snippet{doc} ktx/command.h command exitstatus
@section ktx_info_history HISTORY
@par Version 4.0
- Initial version
@section ktx_info_author AUTHOR
- Mátyás Császár [Vader], RasterGrid www.rastergrid.com
- Daniel Rákos, RasterGrid www.rastergrid.com
*/
class CommandInfo : public Command {
Combine<OptionsFormat, OptionsSingleIn, OptionsGeneric> 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 executeInfo();
KTX_error_code printInfoText(std::istream& file);
KTX_error_code printInfoJSON(std::istream& file, bool minified);
};
// -------------------------------------------------------------------------------------------------
int CommandInfo::main(int argc, char* argv[]) {
try {
parseCommandLine("ktx info",
"Prints information about the KTX v1 or v2 file specified as the input-file argument.\n"
" The command implicitly calls validate for v2 files and prints any found errors\n"
" and warnings to stdout. For KTX v1 files only text format output is supported.",
argc, argv);
executeInfo();
return to_underlying(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 CommandInfo::initOptions(cxxopts::Options& opts) {
options.init(opts);
}
void CommandInfo::processOptions(cxxopts::Options& opts, cxxopts::ParseResult& args) {
options.process(opts, args, *this);
}
void CommandInfo::executeInfo() {
InputStream inputStream(options.inputFilepath, *this);
KTX_error_code result;
bool v2 = true;
ktx_uint8_t ktx_ident_ref[12] = KTX_IDENTIFIER_REF;
ktx_uint8_t identifier[12] = {};
inputStream->read((char*)identifier, 12);
inputStream->clear(); // Clear an unexpected EOF so seekg works.
inputStream->seekg(0); // Rewind to give validation a clean slate.
if (!memcmp(identifier, ktx_ident_ref, 12)) {
if (options.format != OutputFormat::text)
fatal_usage("JSON output formats are not supported for KTX v1 files.");
v2 = false;
StreambufStream<std::streambuf*> ktxStream{inputStream->rdbuf(), std::ios::in | std::ios::binary};
result = ktxPrintKTX1InfoTextForStream(ktxStream.stream());
} else {
switch (options.format) {
case OutputFormat::text:
result = printInfoText(inputStream);
break;
case OutputFormat::json:
result = printInfoJSON(inputStream, false);
break;
case OutputFormat::json_mini:
result = printInfoJSON(inputStream, true);
break;
default:
assert(false && "Internal error");
return;
}
}
if (result != KTX_SUCCESS)
fatal(rc::INVALID_FILE, "Failed to process KTX {} file \"{}\": {}",
v2 ? "v2" : "v1",
fmtInFile(options.inputFilepath), ktxErrorString(result));
}
KTX_error_code CommandInfo::printInfoText(std::istream& file) {
std::ostringstream messagesOS;
const auto validationResult = validateIOStream(file, fmtInFile(options.inputFilepath), false, false, [&](const ValidationReport& issue) {
fmt::print(messagesOS, "{}-{:04}: {}\n", toString(issue.type), issue.id, issue.message);
fmt::print(messagesOS, " {}\n", issue.details);
});
fmt::print("Validation {}\n", validationResult == 0 ? "successful" : "failed");
const auto validationMessages = std::move(messagesOS).str();
if (!validationMessages.empty()) {
fmt::print("\n");
fmt::print("{}", validationMessages);
}
fmt::print("\n");
file.clear(); // Clear any unexpected EOF from validation
file.seekg(0);
if (!file)
return validationResult == 0 ? KTX_FILE_SEEK_ERROR : KTX_SUCCESS;
StreambufStream<std::streambuf*> ktx2Stream{file.rdbuf(), std::ios::in | std::ios::binary};
const auto result = ktxPrintKTX2InfoTextForStream(ktx2Stream.stream());
return validationResult == 0 ? result : KTX_SUCCESS;
}
KTX_error_code CommandInfo::printInfoJSON(std::istream& file, bool minified) {
const auto base_indent = minified ? 0 : +0;
const auto indent_width = minified ? 0 : 4;
const auto space = minified ? "" : " ";
const auto nl = minified ? "" : "\n";
std::ostringstream messagesOS;
PrintIndent pi{messagesOS, base_indent, indent_width};
bool first = true;
const auto validationResult = validateIOStream(file, fmtInFile(options.inputFilepath), false, false, [&](const ValidationReport& issue) {
if (!std::exchange(first, false)) {
pi(2, "}},{}", nl);
}
pi(2, "{{{}", nl);
pi(3, "\"id\":{}{},{}", space, issue.id, nl);
pi(3, "\"type\":{}\"{}\",{}", space, toString(issue.type), nl);
pi(3, "\"message\":{}\"{}\",{}", space, escape_json_copy(issue.message), nl);
pi(3, "\"details\":{}\"{}\"{}", space, escape_json_copy(issue.details), nl);
});
file.clear(); // Clear any unexpected EOF from validation
// Workaround detection to decide if ktx will produce any output into the json
// to eliminate a trailing comma
file.seekg(0, std::ios_base::end);
const auto fileSize = file.tellg();
bool fileIdentifierIsCorrect = false;
if (fileSize >= 12) {
static constexpr uint8_t ktx2_identifier_reference[12] = KTX2_IDENTIFIER_REF;
ktx_uint8_t identifier[12];
file.seekg(0, std::ios_base::beg);
file.read(reinterpret_cast<char*>(identifier), 12);
fileIdentifierIsCorrect = std::memcmp(identifier, ktx2_identifier_reference, 12) == 0;
}
const auto ktxWillPrintOutput = fileIdentifierIsCorrect && fileSize >= KTX2_HEADER_SIZE;
PrintIndent out{std::cout, base_indent, indent_width};
out(0, "{{{}", nl);
out(1, "\"$schema\":{}\"https://schema.khronos.org/ktx/info_v0.json\",{}", space, nl);
out(1, "\"valid\":{}{},{}", space, validationResult == 0, nl);
if (!first) {
out(1, "\"messages\":{}[{}", space, nl);
fmt::print("{}", std::move(messagesOS).str());
out(2, "}}{}", nl);
out(1, "]{}{}", ktxWillPrintOutput ? "," : "", nl);
} else {
out(1, "\"messages\":{}[]{}{}", space, ktxWillPrintOutput ? "," : "", nl);
}
file.seekg(0, std::ios_base::beg);
if (!file) {
out(0, "}}{}", nl);
return validationResult == 0 ? KTX_FILE_SEEK_ERROR : KTX_SUCCESS;
}
StreambufStream<std::streambuf*> ktx2Stream{file.rdbuf(), std::ios::in | std::ios::binary};
const auto result = ktxPrintKTX2InfoJSONForStream(ktx2Stream.stream(), base_indent + 1, indent_width, minified);
out(0, "}}{}", nl);
return validationResult == 0 ? result : KTX_SUCCESS;
}
} // namespace ktx
KTX_COMMAND_ENTRY_POINT(ktxInfo, ktx::CommandInfo)
+315
View File
@@ -0,0 +1,315 @@
// Copyright 2025 The Khronos Group Inc.
// SPDX-License-Identifier: Apache-2.0
#include "command.h"
#include "platform_utils.h"
#include "sbufstream.h"
#include "utility.h"
#include "ktx.h"
#include "dfd.h"
#include "../lib/ktxint.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <cxxopts.hpp>
#include <fmt/ostream.h>
#include <fmt/printf.h>
// -------------------------------------------------------------------------------------------------
namespace ktx {
// -------------------------------------------------------------------------------------------------
#define SUPPORT_LIBKTX_OPTION 0
/** @page ktx_patch ktx patch
@internal
@~English
Apply a specified patch to a KTX2 file
@section ktx_patch_synopsis SYNOPSIS
ktx patch [option...] @e input-file
@section ktx_patch_description DESCRIPTION
@b ktx @b patch applies the specified patch to the KTX file specified as @e input-file.
Currently only two operations are supported: make-sized and make-unsized.
@section ktx\_patch\_options OPTIONS
The following options are available:
<dl>
<dt>--op &lt;operation&gt;</dt>
<dd>Specify the operation to carry out. Must be one of the following:
<dt>make_sized</dt>
<dd>If the file is supercompressed and the DFD's bytePlane0 value is 0, change
the bytesPlane values to the correct values for the DFD's color model and
write out the modified file.</dd>
<dt>make_unsized</dt>
<dd>If the file is supercompressed, change the DFD's bytesPlane values to zero
making the file unsized. Use only to create files for testing.</dd>
</dd>
<dt>-v, --verbose</dt>
<dd>Print a warning if the input file is not supercompressed.</dd>
</dl>
@snippet{doc} ktx/command.h command options_generic
@section ktx_patch_exitstatus EXIT STATUS
@snippet{doc} ktx/command.h command exitstatus
@section ktx_patch_history HISTORY
@par Version 4.0
- Initial version
@section ktx_patch_author AUTHOR
- Mark Callow [\@MarkCallow]
*/
class CommandPatch : public Command {
typedef enum {
makeSized_e,
makeUnsized_e
} Operation;
struct Options {
inline static const char* kOperation = "op";
inline static const char* kVerbose = "verbose";
std::optional<Operation> operation = makeSized_e;
bool verbose = false;
#if SUPPORT_LIBKTX_OPTION
inline static const char* kUseLibktx = "use-libktx"
inline static const char* kChangeWriter = "change-writer";
bool changeWriter = false;
bool useLibKtx = false;
#endif
void init(cxxopts::Options& opts);
void process(cxxopts::Options& opts, cxxopts::ParseResult& args, Reporter& report);
std::optional<CommandPatch::Operation> parseOperation(cxxopts::ParseResult& args,
Reporter& report) const {
static const std::unordered_map<std::string, CommandPatch::Operation> values {
{ "make-sized", Operation::makeSized_e },
{ "make-unsized", Operation::makeUnsized_e },
};
std::optional<CommandPatch::Operation> result = {};
if (args[kOperation].count()) {
auto opStr = to_lower_copy(args[kOperation].as<std::string>());
const auto it = values.find(opStr);
if (it != values.end()) {
result = it->second;
} else {
report.fatal_usage("Invalid or unsupported operation specified as --{} argument: \"{}\".",
kOperation,
args[kOperation].as<std::string>());
}
}
return result;
}
};
Combine<Options, OptionsSingleIn, OptionsGeneric> 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 patch(std::FILE* input);
void patchWithLibKtx(std::FILE* input);
void executePatch();
};
// -------------------------------------------------------------------------------------------------
int CommandPatch::main(int argc, char* argv[]) {
try {
parseCommandLine("ktx patch",
"Apply specified operation to patch the KTX file specified as the input-file.\n",
argc, argv);
executePatch();
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 CommandPatch::Options::init(cxxopts::Options& opts) {
opts.add_options()
#if SUPPORT_LIBKTX_OPTION
(kChangeWriter, "Change KTXwriter metadata to show this program. Only usable when making sized.")
#endif
(kOperation, "The patch operation to perform. It must be one of:"
"\n make-sized"
"\n make-unsized",
cxxopts::value<std::string>(), "<operation>")
(kVerbose, "Print a warning if the input file is not supercompressed.");
}
void CommandPatch::Options::process(cxxopts::Options&,
cxxopts::ParseResult& args,
Reporter& report) {
verbose = args[kVerbose].as<bool>();
#if SUPPORTLIBKTX_OPTION
changeWriter = args[kChangeWriter].as<bool>();
#endif
operation = parseOperation(args, report);
}
void CommandPatch::initOptions(cxxopts::Options& opts) {
options.init(opts);
}
void CommandPatch::processOptions(cxxopts::Options& opts, cxxopts::ParseResult& args) {
options.process(opts, args, *this);
}
// This way of patching has the following advantages:
//
// 1. Can patch a file with a deliberate error that would be rejected by libktx, such as
// many files in the CTS. But this does expect the file to have a valid identifier and
// DFD byteOffset and byteLength;
// 2. It will not modify KTXwriter metadata.
//
// and the following disadvantage:
//
// 1. it cannot be used for operations that will change the length of the file, such as
// may happen when changing metadata.
void CommandPatch::patch(std::FILE* input) {
KTX_header2 header;
std::size_t count = std::fread(&header, KTX2_HEADER_SIZE, 1, input);
if (count != 1) {
fatal(rc::IO_FAILURE, "Failed to read KTX header from input file \"{}\".",
options.inputFilepath);
}
ktx_uint8_t identifier_reference[12] = KTX2_IDENTIFIER_REF;
/* Compare identifier, is this a KTX2 file? */
if (memcmp(header.identifier, identifier_reference, 12) != 0)
fatal(rc::INVALID_FILE, "Input file \"{}\" is not a KTX v2 file.", options.inputFilepath);
if (header.supercompressionScheme == KTX_SS_NONE) {
if (options.verbose)
warning("Input file \"{}\" is not supercompressed.",
options.inputFilepath);
return;
}
if (header.dataFormatDescriptor.byteOffset == 0 || header.dataFormatDescriptor.byteLength == 0) {
fatal(rc::INVALID_FILE, "KTX header has 0 DFD offset or length.");
}
if (std::fseek(input, header.dataFormatDescriptor.byteOffset, SEEK_SET)) {
fatal(rc::IO_FAILURE, "Failed to seek to DFD in input file \"{}\".",
options.inputFilepath);
}
uint32_t* DFD = reinterpret_cast<uint32_t*>(new uint8_t[header.dataFormatDescriptor.byteLength]);
count = std::fread(DFD, header.dataFormatDescriptor.byteLength, 1, input);
if (count != 1) {
fatal(rc::IO_FAILURE, "Failed to read DFD from input file \"{}\".",
options.inputFilepath);
}
if (options.operation == Operation::makeUnsized_e) {
KHR_DFDSETVAL(DFD+1, BYTESPLANE0, 0);
KHR_DFDSETVAL(DFD+1, BYTESPLANE1, 0);
} else {
reconstructDFDBytesPlanesFromSamples(DFD);
}
std::fseek(input, header.dataFormatDescriptor.byteOffset, SEEK_SET); // Seek to start of DFD.
fwrite(DFD, header.dataFormatDescriptor.byteLength, 1, input);
}
#if SUPPORT_LIBKTX_OPTION
// This is an alternate way to perform patching bu using libktx to create a ktxTexture.
// Kept for reference. This was has the following advantage:
//
// 1. it can be used for operations that will change the length of the file;
//
// and the following disadvantages:
//
// 1. it will only work on valid (per libktx's checks) KTX files;
// 2. KTXwriter will be updated to show either the current version of libktx or "__default__".
// The latter happens if the current or newly provided app id contains "__default__".
void CommandPatch::patchWithLibKtx(std::FILE* input) {
ktxTexture2* texture;
auto ret = ktxTexture2_CreateFromStdioStream(input, KTX_TEXTURE_CREATE_NO_FLAGS, &texture);
if (ret != KTX_SUCCESS)
fatal(rc::INVALID_FILE, "Failed to create KTX2 texture: {}", ktxErrorString(ret));
// Creating the texture2 object fixes the bytesPlane fields.
switch (texture->supercompressionScheme) {
case KTX_SS_BASIS_LZ:
{
uint32_t* pBdb = texture->pDfd + 1;
if (KHR_DFDVAL(pBdb, MODEL) != KHR_DF_MODEL_ETC1S) {
std::fclose(input);
fatal(rc::DFD_FAILURE, "Input file \"{}\" has invalid color model for supercompression scheme",
options.inputFilepath);
}
}
[[fallthrough]];
case KTX_SS_ZLIB:
case KTX_SS_ZSTD:
// Loading the data causes any unsized DFDs to be reconstructed into sized.
ret = ktxTexture2_LoadDeflatedImageData(texture, nullptr, 0);
if (ret != KTX_SUCCESS)
fatal(rc::INVALID_FILE, "Failed to load data for KTX2 texture: {}", ktxErrorString(ret));
break;
case KTX_SS_NONE:
if (options.verbose)
warning("Input file \"{}\" is not supercompressed.",
options.inputFilepath);
[[fallthrough]];
default:
std::fclose(input);
return;
}
if (options.operation == Operation::makeUnsized_e) {
KHR_DFDSETVAL(texture->pDfd+1, BYTESPLANE0, 0);
KHR_DFDSETVAL(texture->pDfd+1, BYTESPLANE1, 0);
}
const auto updateMetadataValue = [&](const char* const key,
const std::string& value) {
ktxHashList_DeleteKVPair(&texture->kvDataHead, key);
ktxHashList_AddKVPair(&texture->kvDataHead, key,
static_cast<uint32_t>(value.size() + 1), // +1 to include \0
value.c_str());
};
if (options.changeWriter) {
// Create or modify KTXwriter metadata.
const auto writer = fmt::format("{} {}", commandName, version(options.testrun));
updateMetadataValue(KTX_WRITER_KEY, writer);
}
std::fseek(input, 0, SEEK_SET); // Seek to start.
ktxTexture_WriteToStdioStream(ktxTexture(texture), input);
}
#endif
void CommandPatch::executePatch() {
std::FILE* input = std::fopen(options.inputFilepath.c_str(), "r+b");
if (input == nullptr)
fatal(rc::IO_FAILURE, "Could not open input file \"{}\": {}.",
options.inputFilepath, errnoMessage());
#if SUPPORT_LIBKTX_OPTION
if (options.useLibKtx)
patchWithLibKtx(input);
else
#endif
patch(input);
std::fclose(input);
}
} // namespace ktx
KTX_COMMAND_ENTRY_POINT(ktxPatch, ktx::CommandPatch)
+197
View File
@@ -0,0 +1,197 @@
// 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 "deflate_utils.h"
#include "transcode_utils.h"
#include "formats.h"
#include "sbufstream.h"
#include "utility.h"
#include "validate.h"
#include "ktx.h"
#include "image.hpp"
#include <array>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <unordered_map>
#include <cxxopts.hpp>
#include <fmt/ostream.h>
#include <fmt/printf.h>
// -------------------------------------------------------------------------------------------------
namespace ktx {
// -------------------------------------------------------------------------------------------------
/** @page ktx_transcode ktx transcode
@~English
Transcode a KTX2 file.
@section ktx_transcode_synopsis SYNOPSIS
ktx transcode [option...] @e input-file @e output-file
@section ktx_transcode_description DESCRIPTION
@b ktx @b transcode can transcode 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 transcodable (it must be either BasisLZ supercompressed or has UASTC
color model in the DFD).
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.
@section ktx\_transcode\_options OPTIONS
The following options are available:
<dl>
<dt>\--target &lt;target&gt;</dt>
<dd>Target transcode format.
If the target option is not set the r8, rg8, rgb8 or rgba8 target will be
selected based on the number of channels in the input texture.
Block compressed transcode targets can only be saved in raw format.
Case-insensitive. Possible options are:
etc-rgb | etc-rgba | eac-r11 | eac-rg11 | bc1 | bc3 | bc4 | bc5 | bc7 | astc |
r8 | rg8 | rgb8 | rgba8.
etc-rgb is ETC1; etc-rgba, eac-r11 and eac-rg11 are ETC2.
</dd>
</dl>
@snippet{doc} ktx/deflate_utils.h command options_deflate
@snippet{doc} ktx/command.h command options_generic
@section ktx_transcode_exitstatus EXIT STATUS
@snippet{doc} ktx/command.h command exitstatus
@section ktx_transcode_history HISTORY
@par Version 4.0
- Initial version
@section ktx_transcode_author AUTHOR
- Mátyás Császár [Vader], RasterGrid www.rastergrid.com
- Daniel Rákos, RasterGrid www.rastergrid.com
*/
class CommandTranscode : public Command {
enum {
all = -1,
};
struct OptionsTranscode {
void init(cxxopts::Options& opts);
void process(cxxopts::Options& opts, cxxopts::ParseResult& args, Reporter& report);
};
Combine<OptionsTranscode, OptionsTranscodeTarget<true>, OptionsDeflate, OptionsSingleInSingleOut, OptionsGeneric> 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 executeTranscode();
};
// -------------------------------------------------------------------------------------------------
int CommandTranscode::main(int argc, char* argv[]) {
try {
parseCommandLine("ktx transcode",
"Transcode the KTX file specified as the input-file argument,\n"
" optionally supercompress the result, and save it as the output-file.",
argc, argv);
executeTranscode();
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 CommandTranscode::OptionsTranscode::init(cxxopts::Options& opts) {
opts.add_options()
("target", "Target transcode format."
" Block compressed transcode targets can only be saved in raw format."
" Case-insensitive."
"\nPossible options are:"
" etc-rgb | etc-rgba | eac-r11 | eac-rg11 | bc1 | bc3 | bc4 | bc5 | bc7 | astc |"
" r8 | rg8 | rgb8 | rgba8."
"\netc-rgb is ETC1; etc-rgba, eac-r11 and eac-rg11 are ETC2.",
cxxopts::value<std::string>(), "<target>");
}
void CommandTranscode::OptionsTranscode::process(cxxopts::Options&, cxxopts::ParseResult&, Reporter&) {
}
void CommandTranscode::initOptions(cxxopts::Options& opts) {
options.init(opts);
}
void CommandTranscode::processOptions(cxxopts::Options& opts, cxxopts::ParseResult& args) {
options.process(opts, args, *this);
}
void CommandTranscode::executeTranscode() {
InputStream inputStream(options.inputFilepath, *this);
validateToolInput(inputStream, fmtInFile(options.inputFilepath), *this);
KTXTexture2 texture{nullptr};
StreambufStream<std::streambuf*> 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 (!ktxTexture2_NeedsTranscoding(texture))
fatal(rc::INVALID_FILE, "KTX file is not transcodable.");
texture = transcode(std::move(texture), options, *this);
if (options.zstd) {
ret = ktxTexture2_DeflateZstd(texture, *options.zstd);
if (ret != KTX_SUCCESS)
fatal(rc::KTX_FAILURE, "Zstd deflation failed. KTX Error: {}", ktxErrorString(ret));
}
if (options.zlib) {
ret = ktxTexture2_DeflateZLIB(texture, *options.zlib);
if (ret != KTX_SUCCESS)
fatal(rc::KTX_FAILURE, "ZLIB deflation failed. KTX Error: {}", ktxErrorString(ret));
}
// 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<uint32_t>(writer.size() + 1), // +1 to include the \0
writer.c_str());
// Add KTXwriterScParams metadata if supercompression was used
const auto writerScParams = options.compressOptions;
ktxHashList_DeleteKVPair(&texture->kvDataHead, KTX_WRITER_SCPARAMS_KEY);
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
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(ktxTranscode, ktx::CommandTranscode)
+197
View File
@@ -0,0 +1,197 @@
// 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 "validate.h"
#include <iostream>
#include <sstream>
#include <utility>
#include <cxxopts.hpp>
#include <fmt/printf.h>
// -------------------------------------------------------------------------------------------------
namespace ktx {
/** @page ktx_validate ktx validate
@~English
Validate a KTX2 file.
@section ktx_validate_synopsis SYNOPSIS
ktx validate [option...] @e input-file
@section ktx_validate_description DESCRIPTION
@b ktx @b validate validates the Khronos texture format version 2 (KTX2) file specified
as the @e input-file argument. It prints any found errors and warnings to stdout.
If the @e input-file is '-' the file will be read from the stdin.
The validation rules and checks are based on the official specification:
KTX File Format Specification - https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html
The JSON output formats conform to the https://schema.khronos.org/ktx/validate_v0.json
schema.
@note @b ktx @b validate prints using UTF-8 encoding. If your console is not
set for UTF-8 you will see incorrect characters in output of the file
identifier on each side of the "KTX nn".
@section ktx\_validate\_options OPTIONS
The following options are available:
@snippet{doc} ktx/command.h command options_format
<dl>
<dt>-g, \--gltf-basisu</dt>
<dd>Check compatibility with KHR_texture_basisu glTF extension.</dd>
<dt>-e, \--warnings-as-errors</dt>
<dd>Treat warnings as errors.</dd>
</dl>
@snippet{doc} ktx/command.h command options_generic
@section ktx_validate_exitstatus EXIT STATUS
@snippet{doc} ktx/command.h command exitstatus
@section ktx_validate_history HISTORY
@par Version 4.0
- Initial version
@section ktx_validate_author AUTHOR
- Mátyás Császár [Vader], RasterGrid www.rastergrid.com
- Daniel Rákos, RasterGrid www.rastergrid.com
*/
class CommandValidate : public Command {
struct OptionsValidate {
bool warningsAsErrors = false;
bool GLTFBasisU = false;
void init(cxxopts::Options& opts) {
opts.add_options()
("e,warnings-as-errors", "Treat warnings as errors.")
("g,gltf-basisu", "Check compatibility with KHR_texture_basisu glTF extension.");
}
void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter&) {
warningsAsErrors = args["warnings-as-errors"].as<bool>();
GLTFBasisU = args["gltf-basisu"].as<bool>();
}
};
Combine<OptionsValidate, OptionsFormat, OptionsSingleIn, OptionsGeneric> 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 executeValidate();
};
// -------------------------------------------------------------------------------------------------
int CommandValidate::main(int argc, char* argv[]) {
try {
parseCommandLine("ktx validate",
"Validates the Khronos texture format version 2 (KTX2) file specified\n"
" as the input-file argument. It prints any found errors and warnings to stdout.",
argc, argv);
executeValidate();
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 CommandValidate::initOptions(cxxopts::Options& opts) {
options.init(opts);
}
void CommandValidate::processOptions(cxxopts::Options& opts, cxxopts::ParseResult& args) {
options.process(opts, args, *this);
}
void CommandValidate::executeValidate() {
InputStream inputStream(options.inputFilepath, *this);
switch (options.format) {
case OutputFormat::text: {
std::ostringstream messagesOS;
const auto validationResult = validateIOStream(
inputStream,
fmtInFile(options.inputFilepath),
options.warningsAsErrors,
options.GLTFBasisU,
[&](const ValidationReport& issue) {
fmt::print(messagesOS, "{}-{:04}: {}\n", toString(issue.type), issue.id, issue.message);
fmt::print(messagesOS, " {}\n", issue.details);
});
const auto validationMessages = std::move(messagesOS).str();
if (!validationMessages.empty()) {
fmt::print("Validation {}\n", validationResult == 0 ? "successful" : "failed");
fmt::print("\n");
fmt::print("{}", validationMessages);
}
if (validationResult != 0)
throw FatalError(rc::INVALID_FILE);
break;
}
case OutputFormat::json: [[fallthrough]];
case OutputFormat::json_mini: {
const auto base_indent = options.format == OutputFormat::json ? +0 : 0;
const auto indent_width = options.format == OutputFormat::json ? 4 : 0;
const auto space = options.format == OutputFormat::json ? " " : "";
const auto nl = options.format == OutputFormat::json ? "\n" : "";
std::ostringstream messagesOS;
PrintIndent pi{messagesOS, base_indent, indent_width};
bool first = true;
const auto validationResult = validateIOStream(
inputStream,
fmtInFile(options.inputFilepath),
options.warningsAsErrors,
options.GLTFBasisU,
[&](const ValidationReport& issue) {
if (!std::exchange(first, false)) {
pi(2, "}},{}", nl);
}
pi(2, "{{{}", nl);
pi(3, "\"id\":{}{},{}", space, issue.id, nl);
pi(3, "\"type\":{}\"{}\",{}", space, toString(issue.type), nl);
pi(3, "\"message\":{}\"{}\",{}", space, escape_json_copy(issue.message), nl);
pi(3, "\"details\":{}\"{}\"{}", space, escape_json_copy(issue.details), nl);
});
PrintIndent out{std::cout, base_indent, indent_width};
out(0, "{{{}", nl);
out(1, "\"$schema\":{}\"https://schema.khronos.org/ktx/validate_v0.json\",{}", space, nl);
out(1, "\"valid\":{}{},{}", space, validationResult == 0, nl);
if (!first) {
out(1, "\"messages\":{}[{}", space, nl);
fmt::print("{}", std::move(messagesOS).str());
out(2, "}}{}", nl);
out(1, "]{}", nl);
} else {
out(1, "\"messages\":{}[]{}", space, nl);
}
out(0, "}}{}", nl);
if (validationResult != 0)
throw FatalError(rc::INVALID_FILE);
break;
}
}
}
} // namespace ktx
KTX_COMMAND_ENTRY_POINT(ktxValidate, ktx::CommandValidate)
+81
View File
@@ -0,0 +1,81 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "command.h"
#include "utility.h"
// -------------------------------------------------------------------------------------------------
namespace ktx {
/**
//! [command options_deflate]
<dl>
<dt>\--zstd &lt;level&gt;</dt>
<dd>
Supercompress the data with Zstandard.
Cannot be used with ETC1S / BasisLZ format.
Level range is [1,22].
Lower levels give faster but worse compression.
Values above 20 should be used with caution as they require more memory.
</dd>
<dt>\--zlib &lt;level&gt;</dt>
<dd>
Supercompress the data with ZLIB.
Cannot be used with ETC1S / BasisLZ format.
Level range is [1,9].
Lower levels give faster but worse compression.
</dd>
</dl>
//! [command options_deflate]
*/
struct OptionsDeflate {
inline static const char* kZStd = "zstd";
inline static const char* kZLib = "zlib";
std::string compressOptions{};
std::optional<uint32_t> zstd;
std::optional<uint32_t> zlib;
void init(cxxopts::Options& opts) {
opts.add_options()
(kZStd, "Supercompress the data with Zstandard."
" Cannot be used with ETC1S / BasisLZ format."
" Level range is [1,22]."
" Lower levels give faster but worse compression."
" Values above 20 should be used with caution as they require more memory.",
cxxopts::value<uint32_t>(), "<level>")
(kZLib, "Supercompress the data with ZLIB."
" Cannot be used with ETC1S / BasisLZ format."
" Level range is [1,9]."
" Lower levels give faster but worse compression.",
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[kZStd].count()) {
zstd = captureCompressOption<uint32_t>(args, kZStd);
if (zstd < 1u || zstd > 22u)
report.fatal_usage("Invalid zstd level: \"{}\". Value must be between 1 and 22 inclusive.", zstd.value());
}
if (args[kZLib].count()) {
zlib = captureCompressOption<uint32_t>(args, kZLib);
if (zlib < 1u || zlib > 9u)
report.fatal_usage("Invalid zlib level: \"{}\". Value must be between 1 and 9 inclusive.", zlib.value());
}
if (zstd.has_value() && zlib.has_value())
report.fatal_usage("Conflicting options: zstd and zlib cannot be used at the same time.");
}
};
} // namespace ktx
+135
View File
@@ -0,0 +1,135 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "command.h"
#include "utility.h"
#include <thread>
// -------------------------------------------------------------------------------------------------
namespace ktx {
/**
//! [command options_encode_astc]
<dl>
<dt>
ASTC:
</dt>
<dd></dd>
<dl>
<dt>\--astc-quality &lt;level&gt;</dt>
<dd>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:
<table>
<tr><th>Level </th> <th> Quality </th></tr>
<tr><td>fastest </td> <td>(equivalent to quality = 0) </td></tr>
<tr><td>fast </td> <td>(equivalent to quality = 10) </td></tr>
<tr><td>medium </td> <td>(equivalent to quality = 60) </td></tr>
<tr><td>thorough </td> <td>(equivalent to quality = 98) </td></tr>
<tr><td>exhaustive </td> <td>(equivalent to quality = 100) </td></tr>
</table>
</dd>
<dt>\--astc-perceptual</dt>
<dd>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.</dd>
</dl>
</dl>
//! [command options_encode_astc]
*/
struct OptionsEncodeASTC : public ktxAstcParams {
inline static const char* kAstcQuality = "astc-quality";
inline static const char* kAstcPerceptual = "astc-perceptual";
inline static const char* kAstcOptions[] = {
kAstcQuality,
kAstcPerceptual
};
std::string astcOptions{};
bool encodeASTC = false;
ClampedOption<ktx_uint32_t> qualityLevel{ktxAstcParams::qualityLevel, 0, KTX_PACK_ASTC_QUALITY_LEVEL_MAX};
OptionsEncodeASTC() : ktxAstcParams() {
threadCount = std::max<ktx_uint32_t>(1u, 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<std::string>(), "<level>")
(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 <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[kAstcQuality].count()) {
static std::unordered_map<std::string, ktx_pack_astc_quality_levels_e> 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<std::string>(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
+471
View File
@@ -0,0 +1,471 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "command.h"
#include "utility.h"
#include <thread>
#include <unordered_map>
// -------------------------------------------------------------------------------------------------
namespace ktx {
enum class BasisCodec {
NONE = 0,
BasisLZ,
UASTC,
INVALID = 0x7FFFFFFF
};
/**
//! [command options_basis_encoders]
<dl>
<dt>
basis-lz:
</dt>
<dd>
Supercompress the image data with transcodable ETC1S / BasisLZ.
RED images will become RGB with RED in each component (RRR). RG
images will have R in the RGB part and G in the alpha part of
the compressed texture (RRRG). When set, the @e basis-lz options
become valid.
</dd>
<dt>
uastc:
</dt>
<dd>
Create a texture in high-quality transcodable UASTC format. When set
the @e uastc options become valid.
</dd>
</dl>
//! [command options_basis_encoders]
*/
/**
//! [command options_encode_basis]
<dl>
<dt>
basis-lz:
</dt>
<dd></dd>
<dl>
<dt>\--clevel &lt;level&gt;</dt>
<dd>ETC1S / BasisLZ compression level, an encoding speed vs.
quality tradeoff. Range is [0,6], default is 1. Higher values
are slower but give higher quality. Use @b \--qlevel first.</dd>
<dt>\--qlevel &lt;level&gt;</dt>
<dd>ETC1S / BasisLZ quality level. Range is [1,255]. Lower
gives better compression/lower quality/faster. Higher gives
less compression/higher quality/slower. @b --qlevel
automatically determines values for @b --max-endpoints,
@b --max-selectors, @b --endpoint-rdo-threshold and
@b --selector-rdo-threshold for the target quality level.
Setting these options overrides the values determined by
-qlevel which defaults to 128 if neither it nor
@b --max-endpoints and @b --max-selectors have been set.
Note that both of @b --max-endpoints and @b --max-selectors
must be set for them to have any effect. If all three options
are set, a warning will be issued that @b --qlevel will be
ignored.
Note also that @b --qlevel will only determine values for
@b --endpoint-rdo-threshold and @b --selector-rdo-threshold
when its value exceeds 128, otherwise their defaults will be
used.</dd>
<dt>\--max-endpoints &lt;arg&gt;</dt>
<dd>Manually set the maximum number of color endpoint clusters.
Range is [1,16128]. Default is 0, unset.</dd>
<dt>\--endpoint-rdo-threshold &lt;arg&gt;</dt>
<dd>Set endpoint RDO quality threshold. The default is 1.25.
Lower is higher quality but less quality per output bit (try
[1.0,3.0]). This will override the value chosen by
@b --qlevel.</dd>
<dt>\--max-selectors &lt;arg&gt;</dt>
<dd>Manually set the maximum number of color selector clusters
from [1,16128]. Default is 0, unset.</dd>
<dt>\--selector-rdo-threshold &lt;arg&gt;</dt>
<dd>Set selector RDO quality threshold. The default is 1.25.
Lower is higher quality but less quality per output bit (try
[1.0,3.0]). This will override the value chosen by
@b --qlevel.</dd>
<dt>\--no-endpoint-rdo</dt>
<dd>Disable endpoint rate distortion optimizations. Slightly
faster, less noisy output, but lower quality per output bit.
Default is to do endpoint RDO.</dd>
<dt>\--no-selector-rdo</dt>
<dd>Disable selector rate distortion optimizations. Slightly
faster, less noisy output, but lower quality per output bit.
Default is to do selector RDO.</dd>
</dl>
<dt>
uastc:
</dt>
<dd></dd>
<dl>
<dt>\--uastc-quality &lt;level&gt;</dt>
<dd>This optional parameter selects a speed vs quality
tradeoff as shown in the following table:
<table>
<tr><th>Level</th> <th>Speed</th> <th>Quality</th></tr>
<tr><td>0 </td><td> Fastest </td><td> 43.45dB</td></tr>
<tr><td>1 </td><td> Faster </td><td> 46.49dB</td></tr>
<tr><td>2 </td><td> Default </td><td> 47.47dB</td></tr>
<tr><td>3 </td><td> Slower </td><td> 48.01dB</td></tr>
<tr><td>4 </td><td> Very slow </td><td> 48.24dB</td></tr>
</table>
You are strongly encouraged to also specify @b --zstd to
losslessly compress the UASTC data. This and any LZ-style
compression can be made more effective by conditioning the
UASTC texture data using the Rate Distortion Optimization (RDO)
post-process stage. When uastc encoding is set the following
options become available for controlling RDO:</dd>
<dt>\--uastc-rdo</dt>
<dd>Enable UASTC RDO post-processing.</dd>
<dt>\--uastc-rdo-l &lt;lambda&gt;</dt>
<dd>Set UASTC RDO quality scalar (lambda) to @e lambda. Lower values yield
higher quality/larger LZ compressed files, higher values yield
lower quality/smaller LZ compressed files. A good range to try
is [.25,10]. For normal maps a good range is [.25,.75]. The
full range is [.001,10.0]. Default is 1.0.
Note that previous versions used the @b --uastc-rdo-q option
which was removed because the RDO algorithm changed.</dd>
<dt>\--uastc-rdo-d &lt;dictsize&gt;</dt>
<dd>Set UASTC RDO dictionary size in bytes. Default is 4096.
Lower values=faster, but give less compression. Range is
[64,65536].</dd>
<dt>\--uastc-rdo-b &lt;scale&gt;</dt>
<dd>Set UASTC RDO max smooth block error scale. Range is
[1.0,300.0]. Default is 10.0, 1.0 is disabled. Larger values
suppress more artifacts (and allocate more bits) on smooth
blocks.</dd>
<dt>\--uastc-rdo-s &lt;deviation&gt;</dt>
<dd>Set UASTC RDO max smooth block standard deviation. Range is
[.01,65536.0]. Default is 18.0. Larger values expand the range
of blocks considered smooth.</dd>
<dt>\--uastc-rdo-f</dt>
<dd>Do not favor simpler UASTC modes in RDO mode.</dd>
<dt>\--uastc-rdo-m</dt>
<dd>Disable RDO multithreading (slightly higher compression,
deterministic).</dd>
</dl>
</dl>
//! [command options_encode_basis]
*/
template <bool ENCODE_CMD>
struct OptionsEncodeBasis : public ktxBasisParams {
inline static const char* kCLevel = "clevel";
inline static const char* kQLevel = "qlevel";
inline static const char* kMaxEndpoints = "max-endpoints";
inline static const char* kEndpointRdoThreshold = "endpoint-rdo-threshold";
inline static const char* kMaxSelectors = "max-selectors";
inline static const char* kSelectorRdoThreshold = "selector-rdo-threshold";
inline static const char* kNoEndpointRdo = "no-endpoint-rdo";
inline static const char* kNoSelectorRdo = "no-selector-rdo";
inline static const char* kUastcQuality = "uastc-quality";
inline static const char* kUastcRdo = "uastc-rdo";
inline static const char* kUastcRdoL = "uastc-rdo-l";
inline static const char* kUastcRdoD = "uastc-rdo-d";
inline static const char* kUastcRdoB = "uastc-rdo-b";
inline static const char* kUastcRdoS = "uastc-rdo-s";
inline static const char* kUastcRdoF = "uastc-rdo-f";
inline static const char* kUastcRdoM = "uastc-rdo-m";
// The remaining numeric fields are clamped within the Basis library
ClampedOption<ktx_uint32_t> qualityLevel;
ClampedOption<ktx_uint32_t> maxEndpoints;
ClampedOption<ktx_uint32_t> maxSelectors;
ClampedOption<ktx_uint32_t> uastcRDODictSize;
ClampedOption<float> uastcRDOQualityScalar;
ClampedOption<float> uastcRDOMaxSmoothBlockErrorScale;
ClampedOption<float> uastcRDOMaxSmoothBlockStdDev;
OptionsEncodeBasis() :
qualityLevel(ktxBasisParams::qualityLevel, 1, 255),
maxEndpoints(ktxBasisParams::maxEndpoints, 1, 16128),
maxSelectors(ktxBasisParams::maxSelectors, 1, 16128),
uastcRDODictSize(ktxBasisParams::uastcRDODictSize, 256, 65536),
uastcRDOQualityScalar(ktxBasisParams::uastcRDOQualityScalar,
0.001f, 50.0f),
uastcRDOMaxSmoothBlockErrorScale(
ktxBasisParams::uastcRDOMaxSmoothBlockErrorScale,
1.0f, 300.0f),
uastcRDOMaxSmoothBlockStdDev(
ktxBasisParams::uastcRDOMaxSmoothBlockStdDev,
0.01f, 65536.0f) {
threadCount = std::max<ktx_uint32_t>(1u, std::thread::hardware_concurrency());
noSSE = false;
structSize = sizeof(ktxBasisParams);
// - 1 is to match what basisu_tool does (since 1.13).
compressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL - 1;
qualityLevel.clear();
maxEndpoints.clear();
endpointRDOThreshold = 0.0f;
maxSelectors.clear();
selectorRDOThreshold = 0.0f;
normalMap = false;
separateRGToRGB_A = false;
preSwizzle = false;
noEndpointRDO = false;
noSelectorRDO = false;
uastc = false; // Default to ETC1S.
uastcRDO = false;
uastcFlags = KTX_PACK_UASTC_LEVEL_DEFAULT;
uastcRDODictSize.clear();
uastcRDOQualityScalar.clear();
uastcRDODontFavorSimplerModes = false;
uastcRDONoMultithreading = false;
verbose = false; // Default to quiet operation.
for (int i = 0; i < 4; i++) inputSwizzle[i] = 0;
}
std::string codecOptions{};
std::string codecName;
BasisCodec codec;
void init(cxxopts::Options& opts) {
opts.add_options("Encode BasisLZ")
(kCLevel, "BasisLZ compression level, an encoding speed vs. quality level tradeoff. "
"Range is [0,6], default is 1. Higher values are slower but give higher quality.",
cxxopts::value<uint32_t>(), "<level>")
(kQLevel, "BasisLZ quality level. Range is [1,255]. Lower gives better compression/lower "
"quality/faster. Higher gives less compression/higher quality/slower. --qlevel "
"automatically determines values for --max-endpoints, --max-selectors, "
"--endpoint-rdo-threshold and --selector-rdo-threshold for the target quality level. "
"Setting these options overrides the values determined by --qlevel which defaults to "
"128 if neither it nor --max-endpoints and --max-selectors have been set.",
cxxopts::value<uint32_t>(), "<level>")
(kMaxEndpoints, "Manually set the maximum number of color endpoint clusters. Range "
"is [1,16128]. Default is 0, unset.",
cxxopts::value<uint32_t>(), "<arg>")
(kEndpointRdoThreshold, "Set endpoint RDO quality threshold. The default is 1.25. Lower "
"is higher quality but less quality per output bit (try [1.0,3.0]). This will override "
"the value chosen by --qlevel.", cxxopts::value<float>(), "<arg>")
(kMaxSelectors, "Manually set the maximum number of color selector clusters from [1,16128]. "
"Default is 0, unset.", cxxopts::value<uint32_t>(), "<arg>")
(kSelectorRdoThreshold, "Set selector RDO quality threshold. The default is 1.25. Lower "
"is higher quality but less quality per output bit (try [1.0,3.0]). This will override "
"the value chosen by --qlevel.", cxxopts::value<float>(), "<arg>")
(kNoEndpointRdo, "Disable endpoint rate distortion optimizations. Slightly faster, "
"less noisy output, but lower quality per output bit. Default is to do endpoint RDO.")
(kNoSelectorRdo, "Disable selector rate distortion optimizations. Slightly faster, "
"less noisy output, but lower quality per output bit. Default is to do selector RDO.");
opts.add_options("Encode UASTC")
(kUastcQuality, "UASTC compression level, an encoding speed vs. quality level tradeoff. "
"Range is [0,4], default is 1. Higher values are slower but give higher quality.",
cxxopts::value<uint32_t>(), "<level>")
(kUastcRdo, "Enable UASTC RDO post-processing.")
(kUastcRdoL, "Set UASTC RDO quality scalar to the specified value. Lower values yield "
"higher quality/larger supercompressed files, higher values yield lower quality/smaller "
"supercompressed files. A good range to try is [.25,10]. For normal maps a good range is "
"[.25,.75]. The full range is [.001,10.0]. Default is 1.0.",
cxxopts::value<float>(), "<lambda>")
(kUastcRdoD, "Set UASTC RDO dictionary size in bytes. Default is 4096. Lower values=faster, "
"but give less compression. Range is [64,65536].",
cxxopts::value<uint32_t>(), "<dictsize>")
(kUastcRdoB, "Set UASTC RDO max smooth block error scale. Range is [1.0,300.0]. Default "
"is 10.0, 1.0 is disabled. Larger values suppress more artifacts (and allocate more bits) "
"on smooth blocks.", cxxopts::value<float>(), "<scale>")
(kUastcRdoS, "Set UASTC RDO max smooth block standard deviation. Range is [.01,65536.0]. "
"Default is 18.0. Larger values expand the range of blocks considered smooth.",
cxxopts::value<float>(), "<deviation>")
(kUastcRdoF, "Do not favor simpler UASTC modes in RDO mode.")
(kUastcRdoM, "Disable RDO multithreading (slightly higher compression, deterministic).");
}
BasisCodec validateBasisCodec(const cxxopts::OptionValue& codecOpt) const {
static const std::unordered_map<std::string, BasisCodec> codecs = {
{ "basis-lz", BasisCodec::BasisLZ },
{ "uastc", BasisCodec::UASTC }
};
if (codecOpt.count()) {
auto it = codecs.find(to_lower_copy(codecOpt.as<std::string>()));
if (it != codecs.end()) {
return it->second;
} else {
return BasisCodec::INVALID;
}
} else {
return BasisCodec::NONE;
}
}
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 == BasisCodec::NONE)
report.fatal(rc::INVALID_ARGUMENTS,
"Invalid use of argument --{} that only applies to encoding.", name);
}
void validateBasisLZArg(Reporter& report, const char* name) {
if (codec != BasisCodec::BasisLZ)
report.fatal(rc::INVALID_ARGUMENTS,
"Invalid use of argument --{} that only applies when the used codec is BasisLZ.", name);
}
void validateBasisLZEndpointRDOArg(Reporter& report, const char* name) {
validateBasisLZArg(report, name);
if (noEndpointRDO)
report.fatal(rc::INVALID_ARGUMENTS,
"Invalid use of argument --{} when endpoint RDO is disabled.", name);
}
void validateBasisLZSelectorRDOArg(Reporter& report, const char* name) {
validateBasisLZArg(report, name);
if (noSelectorRDO)
report.fatal(rc::INVALID_ARGUMENTS,
"Invalid use of argument --{} when selector RDO is disabled.", name);
}
void validateUASTCArg(Reporter& report, const char* name) {
if (codec != BasisCodec::UASTC)
report.fatal(rc::INVALID_ARGUMENTS,
"Invalid use of argument --{} that only applies when the used codec is UASTC.", name);
}
void validateUASTCRDOArg(Reporter& report, const char* name) {
validateUASTCArg(report, name);
if (!uastcRDO)
report.fatal(rc::INVALID_ARGUMENTS,
"Invalid use of argument --{} when UASTC RDO post-processing was not enabled.", name);
}
void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) {
std::string codec_option{"encode"};
if (ENCODE_CMD) {
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) {
uastc = 1;
}
// NOTE: The order of the validation below matters
if (args[kCLevel].count()) {
validateBasisLZArg(report, kCLevel);
compressionLevel = captureCodecOption<uint32_t>(args, kCLevel);;
}
if (args[kQLevel].count()) {
validateBasisLZArg(report, kQLevel);
qualityLevel = captureCodecOption<uint32_t>(args, kQLevel);
}
if (args[kNoEndpointRdo].count()) {
validateBasisLZArg(report, kNoEndpointRdo);
captureCodecOption(kNoEndpointRdo);
noEndpointRDO = 1;
}
if (args[kNoSelectorRdo].count()) {
validateBasisLZArg(report, kNoSelectorRdo);
captureCodecOption(kNoSelectorRdo);
noSelectorRDO = 1;
}
if (args[kMaxEndpoints].count()) {
validateBasisLZEndpointRDOArg(report, kMaxEndpoints);
maxEndpoints = captureCodecOption<uint32_t>(args, kMaxEndpoints);
}
if (args[kEndpointRdoThreshold].count()) {
validateBasisLZEndpointRDOArg(report, kEndpointRdoThreshold);
endpointRDOThreshold = captureCodecOption<float>(args, kEndpointRdoThreshold);
}
if (args[kMaxSelectors].count()) {
validateBasisLZSelectorRDOArg(report, kMaxSelectors);
maxSelectors = captureCodecOption<uint32_t>(args, kMaxSelectors);
}
if (args[kSelectorRdoThreshold].count()) {
validateBasisLZSelectorRDOArg(report, kSelectorRdoThreshold);
selectorRDOThreshold = captureCodecOption<float>(args, kSelectorRdoThreshold);
}
if (args[kUastcQuality].count()) {
validateUASTCArg(report, kUastcQuality);
uint32_t level = captureCodecOption<uint32_t>(args, kUastcQuality);
level = std::clamp<uint32_t>(level, 0, KTX_PACK_UASTC_MAX_LEVEL);
uastcFlags = (unsigned int)~KTX_PACK_UASTC_LEVEL_MASK;
uastcFlags |= level;
}
if (args[kUastcRdo].count()) {
validateUASTCArg(report, kUastcRdo);
captureCodecOption(kUastcRdo);
uastcRDO = 1;
}
if (args[kUastcRdoL].count()) {
validateUASTCRDOArg(report, kUastcRdoL);
uastcRDOQualityScalar = captureCodecOption<float>(args, kUastcRdoL);
}
if (args[kUastcRdoD].count()) {
validateUASTCRDOArg(report, kUastcRdoD);
uastcRDODictSize = captureCodecOption<uint32_t>(args, kUastcRdoD);
}
if (args[kUastcRdoB].count()) {
validateUASTCRDOArg(report, kUastcRdoB);
uastcRDOMaxSmoothBlockErrorScale = captureCodecOption<float>(args, kUastcRdoB);
}
if (args[kUastcRdoS].count()) {
validateUASTCRDOArg(report, kUastcRdoS);
uastcRDOMaxSmoothBlockStdDev = captureCodecOption<float>(args, kUastcRdoS);
}
if (args[kUastcRdoF].count()) {
validateUASTCRDOArg(report, kUastcRdoF);
captureCodecOption(kUastcRdoF);
uastcRDODontFavorSimplerModes = 1;
}
if (args[kUastcRdoM].count()) {
validateUASTCRDOArg(report, kUastcRdoM);
captureCodecOption(kUastcRdoM);
uastcRDONoMultithreading = 1;
}
}
};
} // namespace ktx
+138
View File
@@ -0,0 +1,138 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "command.h"
#include "utility.h"
#include <thread>
#include <unordered_map>
// -------------------------------------------------------------------------------------------------
namespace ktx {
/**
//! [command options_encode_common]
<dl>
<dt>
common:
</dt>
<dd></dd>
<dl>
<dt>\--normal-mode</dt>
<dd>Only valid for linear textures with two or more components.
If the input texture has three or four linear components it is
assumed to be a three component linear normal map storing unit
length normals as (R=X, G=Y, B=Z). A fourth component will be
ignored. The map will be converted to a two component X+Y
normal map stored as (RGB=X, A=Y) prior to encoding. If unsure
that your normals are unit length, use @b --normalize. If the
input has 2 linear components it is assumed to be an X+Y map
of unit normals.
The Z component can be recovered programmatically in shader
code by using the equations:
<pre>
nml.xy = texture(...).ga; // Load in [0,1]
nml.xy = nml.xy * 2.0 - 1.0; // Unpack to [-1,1]
nml.z = sqrt(1 - dot(nml.xy, nml.xy)); // Compute Z
</pre>
For ETC1S / BasisLZ encoding, @b '--encode basis-lz', RDO is disabled
(no selector RDO, no endpoint RDO) to provide better quality.</dd>
<dt>\--threads &lt;count&gt;</dt>
<dd>Explicitly set the number of threads to use during
compression. By default, ETC1S / BasisLZ will use the number of
threads reported by @c thread::hardware_concurrency or 1 if value
returned is 0.</dd>
<dt>\--no-sse</dt>
<dd>Forbid use of the SSE instruction set. Ignored if CPU does
not support SSE. SSE can only be disabled for the basis-lz
and uastc encoders. Ignored for other encoders..</dd>
</dl>
</dl>
//! [command options_encode_common]
*/
struct OptionsEncodeCommon {
inline static const char* kNormalMode = "normal-mode";
inline static const char* kThreads = "threads";
inline static const char* kNoSse = "no-sse";
std::string commonOptions{};
bool normalMap{false};
ktx_uint32_t threadCount{1};
ktx_bool_t noSSE;
OptionsEncodeCommon() {
threadCount = std::max<ktx_uint32_t>(1u, std::thread::hardware_concurrency());
noSSE = false;
}
void init(cxxopts::Options& opts) {
opts.add_options("Encode common")
(kNormalMode, "Optimizes for encoding textures with normal data. If the input texture has "
"three or four linear components it is assumed to be a three component linear normal "
"map storing unit length normals as (R=X, G=Y, B=Z). A fourth component will be ignored. "
"The map will be converted to a two component X+Y normal map stored as (RGB=X, A=Y) "
"prior to encoding. If unsure that your normals are unit length, use --normalize. "
"If the input has 2 linear components it is assumed to be an X+Y map of unit normals.\n"
"The Z component can be recovered programmatically in shader code by using the equations:\n"
" nml.xy = texture(...).ga; // Load in [0,1]\n"
" nml.xy = nml.xy * 2.0 - 1.0; // Unpack to [-1,1]\n"
" nml.z = sqrt(1 - dot(nml.xy, nml.xy)); // Compute Z\n"
"ETC1S / BasisLZ encoding, RDO is disabled (no selector RDO, no endpoint RDO) to provide better quality.")
(kThreads, "Sets the number of threads to use during encoding. By default, encoding "
"will use the number of threads reported by thread::hardware_concurrency or 1 if "
"value returned is 0.", cxxopts::value<uint32_t>(), "<count>")
(kNoSse, "Forbid use of the SSE instruction set. Ignored if CPU does "
"not support SSE. SSE can only be disabled on the basis-lz and "
"uastc compressors.");
}
void captureCommonOption(const char* name) {
commonOptions += fmt::format(" --{}", name);
}
template <typename T>
T captureCodecOption(cxxopts::ParseResult& args, const char* name) {
const T value = args[name].as<T>();
commonOptions += fmt::format(" --{} {}", name, value);
return value;
}
void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter&) {
if (args[kNormalMode].count()) {
captureCommonOption(kNormalMode);
normalMap = true;
}
if (args[kThreads].count()) {
threadCount = captureCodecOption<uint32_t>(args, kThreads);
}
if (args[kNoSse].count()) {
captureCommonOption(kNoSse);
noSSE = true;
}
}
};
template <typename Options, typename Codec>
constexpr void fillOptionsCodec(Options &options) {
options.Codec::threadCount = options.OptionsEncodeCommon::threadCount;
options.Codec::normalMap = options.OptionsEncodeCommon::normalMap;
}
template <typename Options>
constexpr void fillOptionsCodecBasis(Options &options) {
fillOptionsCodec<decltype(options), ktxBasisParams>(options);
options.ktxBasisParams::noSSE = options.OptionsEncodeCommon::noSSE;
}
template <typename Options>
constexpr void fillOptionsCodecAstc(Options &options) {
fillOptionsCodec<decltype(options), ktxAstcParams>(options);
}
} // namespace ktx
+65
View File
@@ -0,0 +1,65 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "command.h"
#include "formats.h"
#include "imageio.h"
namespace ktx {
[[nodiscard]] inline FormatDescriptor createFormatDescriptor(const uint32_t* dfd) {
const auto* bdfd = dfd + 1;
FormatDescriptor::basicDescriptor basic;
basic.colorModel = khr_df_model_e(KHR_DFDVAL(bdfd, MODEL));
basic.colorPrimaries = khr_df_primaries_e(KHR_DFDVAL(bdfd, PRIMARIES));
basic.transferFunction = khr_df_transfer_e(KHR_DFDVAL(bdfd, TRANSFER));
basic.dataFlags = khr_df_flags_e(KHR_DFDVAL(bdfd, FLAGS));
basic.texelBlockDimension0 = KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION0);
basic.texelBlockDimension1 = KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION1);
basic.texelBlockDimension2 = KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION2);
basic.texelBlockDimension3 = KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION3);
basic.bytesPlane0 = KHR_DFDVAL(bdfd, BYTESPLANE0);
basic.bytesPlane1 = KHR_DFDVAL(bdfd, BYTESPLANE1);
basic.bytesPlane2 = KHR_DFDVAL(bdfd, BYTESPLANE2);
basic.bytesPlane3 = KHR_DFDVAL(bdfd, BYTESPLANE3);
basic.bytesPlane4 = KHR_DFDVAL(bdfd, BYTESPLANE4);
basic.bytesPlane5 = KHR_DFDVAL(bdfd, BYTESPLANE5);
basic.bytesPlane6 = KHR_DFDVAL(bdfd, BYTESPLANE6);
basic.bytesPlane7 = KHR_DFDVAL(bdfd, BYTESPLANE7);
std::vector<FormatDescriptor::sample> samples;
for (uint32_t i = 0; i < KHR_DFDSAMPLECOUNT(bdfd); ++i) {
auto& sample = samples.emplace_back();
sample.bitOffset = KHR_DFDSVAL(bdfd, i, BITOFFSET);
sample.bitLength = KHR_DFDSVAL(bdfd, i, BITLENGTH);
sample.channelType = KHR_DFDSVAL(bdfd, i, CHANNELID);
const auto dataType = KHR_DFDSVAL(bdfd, i, QUALIFIERS);
sample.qualifierFloat = (dataType & KHR_DF_SAMPLE_DATATYPE_FLOAT) != 0;
sample.qualifierSigned = (dataType & KHR_DF_SAMPLE_DATATYPE_SIGNED) != 0;
sample.qualifierExponent = (dataType & KHR_DF_SAMPLE_DATATYPE_EXPONENT) != 0;
sample.qualifierLinear = (dataType & KHR_DF_SAMPLE_DATATYPE_LINEAR) != 0;
sample.samplePosition0 = KHR_DFDSVAL(bdfd, i, SAMPLEPOSITION0);
sample.samplePosition1 = KHR_DFDSVAL(bdfd, i, SAMPLEPOSITION1);
sample.samplePosition2 = KHR_DFDSVAL(bdfd, i, SAMPLEPOSITION2);
sample.samplePosition3 = KHR_DFDSVAL(bdfd, i, SAMPLEPOSITION3);
sample.lower = KHR_DFDSVAL(bdfd, i, SAMPLELOWER);
sample.upper = KHR_DFDSVAL(bdfd, i, SAMPLEUPPER);
}
return FormatDescriptor{basic, std::move(samples)};
}
[[nodiscard]] inline FormatDescriptor createFormatDescriptor(VkFormat vkFormat, Reporter& report) {
const auto dfd = std::unique_ptr<uint32_t[], decltype(std::free)*>(vk2dfd(vkFormat), std::free);
if (!dfd)
report.fatal(rc::DFD_FAILURE, "Failed to create format descriptor for: {}", toString(vkFormat));
return createFormatDescriptor(dfd.get());
}
} // namespace ktx
+895
View File
@@ -0,0 +1,895 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "vkformat_enum.h"
#include <fmt/format.h>
#include "ktx.h"
#define LIBKTX // To stop dfdutils including vulkan_core.h.
#include "dfdutils/dfd.h"
#include <string>
#include <string_view>
#include "utility.h"
// -------------------------------------------------------------------------------------------------
// External Functions
// These are part of libktx but not part of its public API.
extern "C" {
bool isProhibitedFormat(VkFormat format);
bool isValidFormat(VkFormat format);
uint32_t vkFormatTypeSize(VkFormat format);
const char* vkFormatString(VkFormat format);
VkFormat stringToVkFormat(const char* str);
}
// -------------------------------------------------------------------------------------------------
namespace ktx {
[[nodiscard]] inline std::string toString(VkFormat format) noexcept {
std::string str = vkFormatString(format);
if (str != "VK_UNKNOWN_FORMAT")
return str;
return fmt::format("(0x{:08X})", static_cast<uint32_t>(format));
}
[[nodiscard]] inline std::string toString(ktxSupercmpScheme scheme) noexcept {
std::string str = ktxSupercompressionSchemeString(scheme);
if (str == "Invalid scheme value")
str = fmt::format("(0x{:08X})", static_cast<uint32_t>(scheme));
else if (str == "Vendor or reserved scheme")
str = fmt::format("Vendor or reserved scheme (0x{:08X})", static_cast<uint32_t>(scheme));
return str;
}
[[nodiscard]] inline std::string toString(khr_df_vendorid_e vendorId) noexcept {
const auto str = dfdToStringVendorID(vendorId);
return str ? std::string(str) : fmt::format("(0x{:05X})", static_cast<uint32_t>(vendorId));
}
[[nodiscard]] inline std::string toString(khr_df_vendorid_e vendorId, khr_df_khr_descriptortype_e descType) noexcept {
if (vendorId == KHR_DF_VENDORID_KHRONOS) {
const auto str = dfdToStringDescriptorType(descType);
return str ? std::string(str) : fmt::format("(0x{:04X})", static_cast<uint32_t>(descType));
} else {
return fmt::format("(0x{:04X})", static_cast<uint32_t>(descType));
}
}
[[nodiscard]] inline std::string toString(khr_df_versionnumber_e version) noexcept {
const auto str = dfdToStringVersionNumber(version);
return str ? std::string(str) : fmt::format("(0x{:04X})", static_cast<uint32_t>(version));
}
[[nodiscard]] inline std::string toString(khr_df_model_e model) noexcept {
const auto str = dfdToStringColorModel(model);
return str ? std::string(str) : fmt::format("(0x{:02X})", static_cast<uint32_t>(model));
}
[[nodiscard]] inline std::string toString(khr_df_primaries_e primaries) noexcept {
const auto str = dfdToStringColorPrimaries(primaries);
return str ? std::string(str) : fmt::format("(0x{:02X})", static_cast<uint32_t>(primaries));
}
[[nodiscard]] inline std::string toString(khr_df_transfer_e transfer) noexcept {
const auto str = dfdToStringTransferFunction(transfer);
return str ? std::string(str) : fmt::format("(0x{:02X})", static_cast<uint32_t>(transfer));
}
[[nodiscard]] inline std::string toString(khr_df_model_e colorModel, khr_df_model_channels_e channelType) noexcept {
const auto str = dfdToStringChannelId(colorModel, channelType);
return str ? std::string(str) : fmt::format("(0x{:01X})", static_cast<uint32_t>(channelType));
}
// -------------------------------------------------------------------------------------------------
/// Parses a VkFormat. VK_FORMAT_ prefix is optional. Case insensitive.
[[nodiscard]] inline std::optional<VkFormat> parseVkFormat(const std::string& str) noexcept {
const auto vkFormat = stringToVkFormat(str.c_str());
if (vkFormat == VK_FORMAT_UNDEFINED)
return std::nullopt;
return vkFormat;
}
// -------------------------------------------------------------------------------------------------
[[nodiscard]] constexpr inline bool isSupercompressionWithGlobalData(ktxSupercmpScheme scheme) noexcept {
switch (scheme) {
case KTX_SS_BASIS_LZ:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr inline bool isSupercompressionWithNoGlobalData(ktxSupercmpScheme scheme) noexcept {
switch (scheme) {
case KTX_SS_ZSTD:
case KTX_SS_ZLIB:
return true;
default:
return false;
}
}
// -------------------------------------------------------------------------------------------------
[[nodiscard]] constexpr inline bool isColorModelBlockCompressed(khr_df_model_e colorModel) noexcept {
return colorModel >= KHR_DF_MODEL_DXT1A;
}
[[nodiscard]] inline bool isColorPrimariesValid(khr_df_primaries_e primaries) noexcept {
// dfdToStringColorPrimaries yields a nullptr if the value is invalid
return dfdToStringColorPrimaries(primaries) != nullptr;
}
[[nodiscard]] inline bool isChannelTypeValid(khr_df_model_e colorModel, khr_df_model_channels_e channelType) noexcept {
// dfdToStringChannelId yields a nullptr if the model and channel combination is invalid
return dfdToStringChannelId(colorModel, channelType) != nullptr;
}
[[nodiscard]] inline khr_df_model_e getColorModelForBlockCompressedFormat(VkFormat format) noexcept {
switch (format) {
case VK_FORMAT_BC1_RGB_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC1_RGB_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
return KHR_DF_MODEL_BC1A;
case VK_FORMAT_BC2_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC2_SRGB_BLOCK:
return KHR_DF_MODEL_BC2;
case VK_FORMAT_BC3_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC3_SRGB_BLOCK:
return KHR_DF_MODEL_BC3;
case VK_FORMAT_BC4_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC4_SNORM_BLOCK:
return KHR_DF_MODEL_BC4;
case VK_FORMAT_BC5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC5_SNORM_BLOCK:
return KHR_DF_MODEL_BC5;
case VK_FORMAT_BC6H_UFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_BC6H_SFLOAT_BLOCK:
return KHR_DF_MODEL_BC6H;
case VK_FORMAT_BC7_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC7_SRGB_BLOCK:
return KHR_DF_MODEL_BC7;
case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_EAC_R11_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_EAC_R11_SNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_EAC_R11G11_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
return KHR_DF_MODEL_ETC2;
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT:
return KHR_DF_MODEL_ASTC;
case VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG:
return KHR_DF_MODEL_PVRTC;
case VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG:
return KHR_DF_MODEL_PVRTC2;
default:
assert(false);
return KHR_DF_MODEL_UNSPECIFIED;
}
}
// -------------------------------------------------------------------------------------------------
[[nodiscard]] inline bool isFormatValid(VkFormat format) noexcept {
// isValidFormat would accept negative values, handle it separately
return isValidFormat(format) && format >= 0;
}
[[nodiscard]] inline bool isFormatKnown(VkFormat format) noexcept {
const std::string_view str = vkFormatString(format);
return str != "VK_UNKNOWN_FORMAT";
}
[[nodiscard]] constexpr inline bool isFormatStencil(VkFormat format) noexcept {
switch (format) {
// Stencil only formats:
case VK_FORMAT_S8_UINT: [[fallthrough]];
// Depth and Stencil mixed formats:
case VK_FORMAT_D16_UNORM_S8_UINT: [[fallthrough]];
case VK_FORMAT_D24_UNORM_S8_UINT: [[fallthrough]];
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr inline bool isFormatDepth(VkFormat format) noexcept {
switch (format) {
// Depth only formats:
case VK_FORMAT_D16_UNORM: [[fallthrough]];
case VK_FORMAT_X8_D24_UNORM_PACK32: [[fallthrough]];
case VK_FORMAT_D32_SFLOAT: [[fallthrough]];
// Depth and Stencil mixed formats:
case VK_FORMAT_D16_UNORM_S8_UINT: [[fallthrough]];
case VK_FORMAT_D24_UNORM_S8_UINT: [[fallthrough]];
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr inline bool isFormatDepthStencil(VkFormat format) noexcept {
return isFormatDepth(format) || isFormatStencil(format);
}
[[nodiscard]] constexpr inline bool isSupercompressionBlockCompressed(ktxSupercmpScheme scheme) noexcept {
switch (scheme) {
case KTX_SS_BASIS_LZ:
return true;
default:
return false;
}
}
/// SINT or UINT
[[nodiscard]] constexpr inline bool isFormatINT(VkFormat format) noexcept {
switch (format) {
case VK_FORMAT_R8_UINT: [[fallthrough]];
case VK_FORMAT_R8_SINT: [[fallthrough]];
case VK_FORMAT_R8G8_UINT: [[fallthrough]];
case VK_FORMAT_R8G8_SINT: [[fallthrough]];
case VK_FORMAT_R8G8B8_UINT: [[fallthrough]];
case VK_FORMAT_R8G8B8_SINT: [[fallthrough]];
case VK_FORMAT_B8G8R8_UINT: [[fallthrough]];
case VK_FORMAT_B8G8R8_SINT: [[fallthrough]];
case VK_FORMAT_R8G8B8A8_UINT: [[fallthrough]];
case VK_FORMAT_R8G8B8A8_SINT: [[fallthrough]];
case VK_FORMAT_B8G8R8A8_UINT: [[fallthrough]];
case VK_FORMAT_B8G8R8A8_SINT: [[fallthrough]];
case VK_FORMAT_A8B8G8R8_UINT_PACK32: [[fallthrough]];
case VK_FORMAT_A8B8G8R8_SINT_PACK32: [[fallthrough]];
case VK_FORMAT_A2R10G10B10_UINT_PACK32: [[fallthrough]];
case VK_FORMAT_A2R10G10B10_SINT_PACK32: [[fallthrough]];
case VK_FORMAT_A2B10G10R10_UINT_PACK32: [[fallthrough]];
case VK_FORMAT_A2B10G10R10_SINT_PACK32: [[fallthrough]];
case VK_FORMAT_R16_UINT: [[fallthrough]];
case VK_FORMAT_R16_SINT: [[fallthrough]];
case VK_FORMAT_R16G16_UINT: [[fallthrough]];
case VK_FORMAT_R16G16_SINT: [[fallthrough]];
case VK_FORMAT_R16G16B16_UINT: [[fallthrough]];
case VK_FORMAT_R16G16B16_SINT: [[fallthrough]];
case VK_FORMAT_R16G16B16A16_UINT: [[fallthrough]];
case VK_FORMAT_R16G16B16A16_SINT: [[fallthrough]];
case VK_FORMAT_R32_UINT: [[fallthrough]];
case VK_FORMAT_R32_SINT: [[fallthrough]];
case VK_FORMAT_R32G32_UINT: [[fallthrough]];
case VK_FORMAT_R32G32_SINT: [[fallthrough]];
case VK_FORMAT_R32G32B32_UINT: [[fallthrough]];
case VK_FORMAT_R32G32B32_SINT: [[fallthrough]];
case VK_FORMAT_R32G32B32A32_UINT: [[fallthrough]];
case VK_FORMAT_R32G32B32A32_SINT: [[fallthrough]];
case VK_FORMAT_R64_UINT: [[fallthrough]];
case VK_FORMAT_R64_SINT: [[fallthrough]];
case VK_FORMAT_R64G64_UINT: [[fallthrough]];
case VK_FORMAT_R64G64_SINT: [[fallthrough]];
case VK_FORMAT_R64G64B64_UINT: [[fallthrough]];
case VK_FORMAT_R64G64B64_SINT: [[fallthrough]];
case VK_FORMAT_R64G64B64A64_UINT: [[fallthrough]];
case VK_FORMAT_R64G64B64A64_SINT: [[fallthrough]];
case VK_FORMAT_S8_UINT: [[fallthrough]];
case VK_FORMAT_D16_UNORM_S8_UINT: [[fallthrough]];
case VK_FORMAT_D24_UNORM_S8_UINT: [[fallthrough]];
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr inline bool isFormatBlockCompressed(VkFormat format) noexcept {
switch (format) {
case VK_FORMAT_BC1_RGB_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC1_RGB_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_BC2_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC2_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_BC3_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC3_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_BC4_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC4_SNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC5_SNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC6H_UFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_BC6H_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_BC7_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_BC7_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_EAC_R11_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_EAC_R11_SNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_EAC_R11G11_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_EAC_R11G11_SNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr inline bool isFormat3DBlockCompressed(VkFormat format) noexcept {
switch (format) {
case VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr inline bool isFormatSRGB(VkFormat format) noexcept {
switch (format) {
case VK_FORMAT_R8_SRGB: [[fallthrough]];
case VK_FORMAT_R8G8_SRGB: [[fallthrough]];
case VK_FORMAT_R8G8B8_SRGB: [[fallthrough]];
case VK_FORMAT_B8G8R8_SRGB: [[fallthrough]];
case VK_FORMAT_R8G8B8A8_SRGB: [[fallthrough]];
case VK_FORMAT_B8G8R8A8_SRGB: [[fallthrough]];
case VK_FORMAT_A8B8G8R8_SRGB_PACK32: [[fallthrough]];
case VK_FORMAT_BC1_RGB_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_BC2_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_BC3_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_BC7_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr inline bool isFormatNotSRGBButHasSRGBVariant(VkFormat format) noexcept {
switch (format) {
// VK_FORMAT_R8_SRGB
case VK_FORMAT_R8_UNORM: [[fallthrough]];
case VK_FORMAT_R8_SNORM: [[fallthrough]];
case VK_FORMAT_R8_UINT: [[fallthrough]];
case VK_FORMAT_R8_SINT: [[fallthrough]];
// VK_FORMAT_R8G8_SRGB
case VK_FORMAT_R8G8_UNORM: [[fallthrough]];
case VK_FORMAT_R8G8_SNORM: [[fallthrough]];
case VK_FORMAT_R8G8_UINT: [[fallthrough]];
case VK_FORMAT_R8G8_SINT: [[fallthrough]];
// VK_FORMAT_R8G8B8_SRGB
case VK_FORMAT_R8G8B8_UNORM: [[fallthrough]];
case VK_FORMAT_R8G8B8_SNORM: [[fallthrough]];
case VK_FORMAT_R8G8B8_UINT: [[fallthrough]];
case VK_FORMAT_R8G8B8_SINT: [[fallthrough]];
// VK_FORMAT_B8G8R8_SRGB
case VK_FORMAT_B8G8R8_UNORM: [[fallthrough]];
case VK_FORMAT_B8G8R8_SNORM: [[fallthrough]];
case VK_FORMAT_B8G8R8_UINT: [[fallthrough]];
case VK_FORMAT_B8G8R8_SINT: [[fallthrough]];
// VK_FORMAT_R8G8B8A8_SRGB
case VK_FORMAT_R8G8B8A8_UNORM: [[fallthrough]];
case VK_FORMAT_R8G8B8A8_SNORM: [[fallthrough]];
case VK_FORMAT_R8G8B8A8_UINT: [[fallthrough]];
case VK_FORMAT_R8G8B8A8_SINT: [[fallthrough]];
// VK_FORMAT_B8G8R8A8_SRGB
case VK_FORMAT_B8G8R8A8_UNORM: [[fallthrough]];
case VK_FORMAT_B8G8R8A8_SNORM: [[fallthrough]];
case VK_FORMAT_B8G8R8A8_UINT: [[fallthrough]];
case VK_FORMAT_B8G8R8A8_SINT: [[fallthrough]];
// VK_FORMAT_A8B8G8R8_SRGB_PACK32
case VK_FORMAT_A8B8G8R8_UNORM_PACK32: [[fallthrough]];
case VK_FORMAT_A8B8G8R8_SNORM_PACK32: [[fallthrough]];
case VK_FORMAT_A8B8G8R8_UINT_PACK32: [[fallthrough]];
case VK_FORMAT_A8B8G8R8_SINT_PACK32: [[fallthrough]];
// VK_FORMAT_BC1_RGB_SRGB_BLOCK
case VK_FORMAT_BC1_RGB_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_BC1_RGBA_SRGB_BLOCK
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_BC2_SRGB_BLOCK
case VK_FORMAT_BC2_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_BC3_SRGB_BLOCK
case VK_FORMAT_BC3_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_BC7_SRGB_BLOCK
case VK_FORMAT_BC7_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK
case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK
case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK
case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_4x4_SRGB_BLOCK
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_5x4_SRGB_BLOCK
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_5x5_SRGB_BLOCK
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_6x5_SRGB_BLOCK
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_6x6_SRGB_BLOCK
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_8x5_SRGB_BLOCK
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_8x6_SRGB_BLOCK
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_8x8_SRGB_BLOCK
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_10x5_SRGB_BLOCK
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_10x6_SRGB_BLOCK
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_10x8_SRGB_BLOCK
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_10x10_SRGB_BLOCK
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_12x10_SRGB_BLOCK
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_ASTC_12x12_SRGB_BLOCK
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: [[fallthrough]];
// VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG
case VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG: [[fallthrough]];
// VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG
case VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG: [[fallthrough]];
// VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG
case VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG: [[fallthrough]];
// VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG
case VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG: [[fallthrough]];
// VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT
case VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
// VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT
case VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
// VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT
case VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT: [[fallthrough]];
// VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT
case VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
// VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT
case VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
// VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT
case VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT: [[fallthrough]];
// VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT
case VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
// VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT
case VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
// VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT
case VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT: [[fallthrough]];
// VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT
case VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr inline bool isFormat422(VkFormat format) noexcept {
switch (format) {
case VK_FORMAT_G8B8G8R8_422_UNORM: [[fallthrough]];
case VK_FORMAT_B8G8R8G8_422_UNORM: [[fallthrough]];
case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM: [[fallthrough]];
case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM: [[fallthrough]];
case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16: [[fallthrough]];
case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16: [[fallthrough]];
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16: [[fallthrough]];
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16: [[fallthrough]];
case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16: [[fallthrough]];
case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16: [[fallthrough]];
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16: [[fallthrough]];
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16: [[fallthrough]];
case VK_FORMAT_G16B16G16R16_422_UNORM: [[fallthrough]];
case VK_FORMAT_B16G16R16G16_422_UNORM: [[fallthrough]];
case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM: [[fallthrough]];
case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr inline bool isFormatAstc(VkFormat format) noexcept {
switch (format) {
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT:
return true;
default:
return false;
}
}
[[nodiscard]] constexpr inline bool isFormatAstcLDR(VkFormat format) noexcept {
switch (format) {
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT: [[fallthrough]];
case VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT:
return true;
default:
return false;
}
}
} // namespace ktx
+266
View File
@@ -0,0 +1,266 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <fmt/format.h>
#include <limits>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
// -------------------------------------------------------------------------------------------------
namespace ktx {
struct All_t {};
static constexpr All_t all{};
using RangeIndex = uint32_t;
static constexpr RangeIndex RangeEnd = std::numeric_limits<RangeIndex>::max();
class SelectorRange {
public:
// Half open range [begin, end)
struct HalfRange {
RangeIndex begin = 0;
RangeIndex end = RangeEnd;
[[nodiscard]] friend constexpr bool operator==(const HalfRange& lhs, const HalfRange& rhs) {
return lhs.begin == rhs.begin && lhs.end == rhs.end;
}
};
private:
std::vector<HalfRange> ranges;
public:
SelectorRange(RangeIndex begin, RangeIndex end) {
ranges.emplace_back(HalfRange{begin, end});
}
SelectorRange() = default;
explicit SelectorRange(All_t) : SelectorRange(0, RangeEnd) {};
explicit SelectorRange(RangeIndex index) : SelectorRange(index, index + 1u) {};
[[nodiscard]] friend bool operator==(const SelectorRange& lhs, const SelectorRange& rhs) {
return lhs.ranges == rhs.ranges;
}
[[nodiscard]] bool is_empty() const {
if (ranges.empty())
return true;
for (const auto& range : ranges)
if (range.begin != range.end)
return false;
return true;
}
[[nodiscard]] bool is_single() const {
if (ranges.empty())
return false;
const auto index = ranges[0].begin;
for (const auto& range : ranges)
if (range.begin != index || range.end != index + 1)
return false;
return true;
}
[[nodiscard]] bool is_multi() const {
return !is_single();
}
void clear() {
ranges.clear();
}
[[nodiscard]] bool is_undefined() const {
return ranges.empty();
}
void add(HalfRange range) {
ranges.emplace_back(range);
}
[[nodiscard]] bool contains(RangeIndex index) const {
for (const auto& range : ranges)
if (range.begin <= index && index < range.end)
return true;
return false;
}
[[nodiscard]] bool validate(RangeIndex last) const {
for (const auto& range : ranges) {
if (range.begin > last)
return false;
if (range.end > last && range.end != RangeEnd)
return false;
}
return true;
}
[[nodiscard]] RangeIndex last() const {
RangeIndex last = 0;
for (const auto& range : ranges)
if (range.begin != range.end && range.end > last + 1)
last = range.end - 1;
return last;
}
// Only used for fmt print
[[nodiscard]] const std::vector<HalfRange>& _ranges() const {
return ranges;
}
SelectorRange& operator=(All_t) & {
ranges.clear();
ranges.emplace_back(HalfRange{0, RangeEnd});
return *this;
}
SelectorRange& operator=(RangeIndex index) & {
ranges.clear();
ranges.emplace_back(HalfRange{index, index + 1});
return *this;
}
friend bool operator==(const SelectorRange& var, All_t) {
return var == SelectorRange{all};
}
friend bool operator==(All_t, const SelectorRange& var) {
return var == all;
}
friend bool operator!=(const SelectorRange& var, All_t) {
return !(var == all);
}
friend bool operator!=(All_t, const SelectorRange& var) {
return !(var == all);
}
friend bool operator==(const SelectorRange& var, RangeIndex index) {
return var == SelectorRange{index};
}
friend bool operator==(RangeIndex index, const SelectorRange& var) {
return var == index;
}
};
} // namespace ktx
namespace fmt {
template<> struct formatter<ktx::SelectorRange> : fmt::formatter<uint32_t> {
template <typename FormatContext>
auto format(const ktx::SelectorRange& var, FormatContext& ctx) const -> decltype(ctx.out()) {
if (var == ktx::all)
return formatter<std::string_view>{}.format("all", ctx);
else if (var.is_empty())
return formatter<std::string_view>{}.format("none", ctx);
else {
bool first = true;
auto out = ctx.out();
for (const auto& range : var._ranges()) {
if (!first) {
out = fmt::format_to(out, ",");
ctx.advance_to(out);
}
first = false;
if (range.begin + 1 == range.end) {
out = fmt::format_to(out, "{}", range.begin);
ctx.advance_to(out);
} else if (range.end == ktx::RangeEnd) {
out = fmt::format_to(out, "{}..last", range.begin);
ctx.advance_to(out);
} else {
out = fmt::format_to(out, "{}..{}", range.begin, range.end - 1);
ctx.advance_to(out);
}
}
return out;
}
}
};
} // namespace fmt
namespace ktx {
/// https://registry.khronos.org/KTX/specs/2.0/ktx-frag.html
///
/// KTX fragments support addressing the KTX files payload along 5 dimensions
/// mip - This dimension denotes a range of mip levels in the KTX file.
/// stratal - This dimension denotes a range of array layers when the KTX file contains an array texture.
/// temporal - This dimension denotes a specific time range in a KTX file containing KTXanimData metadata. Since a frame is an array layer, this is an alternate way of selecting in the stratal dimension.
/// facial - This dimension denotes a range of faces when the KTX file contains a cube map.
/// spatial - xyzwhd This dimension denotes a range of pixels in the KTX file such as "a volume with size (100,100,1) with its origin at (10,10,0).
struct FragmentURI {
SelectorRange mip;
SelectorRange stratal;
// SelectorTemporal temporal; // Temporal selector is outside the current scope
SelectorRange facial;
// SelectorSpatial spatial; // Spatial selector is outside the current scope
bool validate(uint32_t numLevels, uint32_t numLayers, uint32_t numFaces) {
return mip.validate(numLevels) &&
stratal.validate(numLayers) &&
facial.validate(numFaces);
}
};
[[nodiscard]] inline SelectorRange::HalfRange parseHalfRange(std::string_view key, std::string_view str) {
const auto indexComma = str.find(',');
const auto strBegin = indexComma == std::string_view::npos ? str : str.substr(0, indexComma);
const auto strEnd = indexComma == std::string_view::npos ? "" : str.substr(indexComma + 1);
try {
const auto begin = strBegin.empty() ? 0u : static_cast<uint32_t>(std::stoi(std::string(strBegin)));
const auto end = strEnd.empty() ? RangeEnd : static_cast<uint32_t>(std::stoi(std::string(strEnd))) + 1u;
return {begin, end};
} catch (const std::exception& ex) {
throw std::invalid_argument(fmt::format("Invalid key-value \"{}={}\": {}", key, str, ex.what()));
}
}
[[nodiscard]] inline FragmentURI parseFragmentURI(std::string_view str) {
// Name and value components are separated by an equal sign (=),
// while multiple name-value pairs are separated by an ampersand (&).
FragmentURI result;
result.mip.clear();
result.stratal.clear();
result.facial.clear();
while (true) {
const auto indexAmp = str.find('&');
const auto strKeyValue = indexAmp == std::string_view::npos ? str : str.substr(0, indexAmp);
const auto indexEqual = strKeyValue.find('=');
const auto strKey = indexEqual == std::string_view::npos ? strKeyValue : strKeyValue.substr(0, indexEqual);
const auto strValue = indexEqual == std::string_view::npos ? "" : strKeyValue.substr(indexEqual + 1);
if (strKey == "m" || strKey == "%6D")
result.mip.add(parseHalfRange(strKey, strValue));
else if (strKey == "a" || strKey == "%61")
result.stratal.add(parseHalfRange(strKey, strValue));
else if (strKey == "t" || strKey == "%74")
throw std::invalid_argument(fmt::format("Temporal selector (t) is not yet supported."));
else if (strKey == "f")
result.facial.add(parseHalfRange(strKey, strValue));
else if (strKey == "xyzwhd")
throw std::invalid_argument(fmt::format("Spatial selector (xyzwhd) is not yet supported."));
else if (!strKey.empty())
throw std::invalid_argument(fmt::format("Unknown key \"{}\"", strKey));
if (indexAmp != std::string_view::npos)
str.remove_prefix(indexAmp + 1);
else
break;
}
return result;
}
} // namespace ktx
+238
View File
@@ -0,0 +1,238 @@
// 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 "stdafx.h"
#include <iostream>
#include <string>
#include <unordered_map>
#include <cxxopts.hpp>
#include <fmt/ostream.h>
#include <fmt/printf.h>
// -------------------------------------------------------------------------------------------------
namespace ktx {
/** @page ktx ktx
@~English
Unified CLI frontend for the KTX-Software library.
@section ktx_synopsis SYNOPSIS
ktx &lt;command&gt; [command-option...]<br>
ktx [option...]
@section ktx_description DESCRIPTION
Unified CLI frontend for the KTX-Software library with sub-commands for specific operations
for the KTX File Format Specification
https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html.
The following commands are available:
<dl>
<dt>@ref ktx_create "create"</dt>
<dd>
Create a KTX2 file from various input files.
</dd>
<dt>@ref ktx_deflate "deflate"</dt>
<dd>
Deflate (supercompress) a KTX2 file.
</dd>
<dt>@ref ktx_extract "extract"</dt>
<dd>
Extract selected images from a KTX2 file.
</dd>
<dt>@ref ktx_encode "encode"</dt>
<dd>
Encode a KTX2 file.
</dd>
<dt>@ref ktx_transcode "transcode"</dt>
<dd>
Transcode a KTX2 file.
</dd>
<dt>@ref ktx_info "info"</dt>
<dd>
Print information about a KTX2 file.
</dd>
<dt>@ref ktx_validate "validate"</dt>
<dd>
Validate a KTX2 file.
</dd>
<dt>@ref ktx_compare "compare"</dt>
<dd>
Compare two KTX2 files.
</dd>
<dt>@ref ktx_help "help"</dt>
<dd>
Display help information about the ktx tool.
</dd>
</dl>
@section ktx\_main\_options OPTIONS
The following options are also available without a command:
@snippet{doc} ktx/command.h command options_generic
@section ktx_exitstatus EXIT STATUS
@snippet{doc} ktx/command.h command exitstatus
@section ktx_history HISTORY
@par Version 4.0
- Initial version
@section ktx_author AUTHOR
- Mátyás Császár [Vader], RasterGrid www.rastergrid.com
- Daniel Rákos, RasterGrid www.rastergrid.com
*/
class Tools : public Command {
bool testrun = false; /// Indicates test run. If enabled ktx tools will only include the default version information in any output
public:
using Command::Command;
virtual ~Tools() {};
public:
virtual int main(int argc, char* argv[]) override;
void printUsage(std::ostream& os, const cxxopts::Options& options);
};
// -------------------------------------------------------------------------------------------------
int Tools::main(int argc, char* argv[]) {
cxxopts::Options options("ktx", "");
options.custom_help("[<command>] [OPTION...]");
options.set_width(CONSOLE_USAGE_WIDTH);
options.add_options()
("h,help", "Print this usage message and exit")
("v,version", "Print the version number of this program and exit")
("testrun", "Indicates test run. If enabled the tool will produce deterministic output whenever possible");
options.allow_unrecognised_options();
cxxopts::ParseResult args;
try {
args = options.parse(argc, argv);
} catch (const std::exception& ex) {
fmt::print(std::cerr, "{}: {}\n", options.program(), ex.what());
printUsage(std::cerr, options);
return +rc::INVALID_ARGUMENTS;
}
testrun = args["testrun"].as<bool>();
if (args.count("help")) {
fmt::print(std::cout, "{}: Unified CLI frontend for the KTX-Software library with sub-commands for specific operations.\n", options.program());
printUsage(std::cout, options);
return +rc::SUCCESS;
}
if (args.count("version")) {
fmt::print("{} version: {}\n", options.program(), version(testrun));
return +rc::SUCCESS;
}
if (args.unmatched().empty()) {
fmt::print(std::cerr, "{}: Missing command.\n", options.program());
printUsage(std::cerr, options);
} else {
if (argv[1][0] != '-') {
fmt::print(std::cerr, "{}: Unrecognized command: \"{}\"\n", options.program(), argv[1]);
} else {
fmt::print(std::cerr, "{}: Unrecognized argument: \"{}\"\n", options.program(), args.unmatched()[0]);
}
printUsage(std::cerr, options);
}
return +rc::INVALID_ARGUMENTS;
}
void Tools::printUsage(std::ostream& os, const cxxopts::Options& options) {
fmt::print(os, "{}", options.help());
fmt::print(os, "\n");
fmt::print(os, "Available commands:\n");
fmt::print(os, " create Create a KTX2 file from various input files\n");
fmt::print(os, " deflate Deflate (supercompress) a KTX2 file\n");
fmt::print(os, " extract Extract selected images from a KTX2 file\n");
fmt::print(os, " encode Encode a KTX2 file\n");
fmt::print(os, " transcode Transcode a KTX2 file\n");
fmt::print(os, " info Print information about a KTX2 file\n");
fmt::print(os, " validate Validate a KTX2 file\n");
fmt::print(os, " compare Compare two KTX2 files\n");
fmt::print(os, " help Display help information about the ktx tool\n");
#if KTX_DEVELOPER_FEATURE_PATCH
fmt::print(os, " patch Apple certain patch operations to a KTX2 file.");
#endif
fmt::print(os, "\n");
fmt::print(os, "For detailed usage and description of each subcommand use 'ktx help <command>'\n"
"or 'ktx <command> --help'\n");
}
} // namespace ktx ---------------------------------------------------------------------------------
KTX_COMMAND_BUILTIN(ktxCreate)
KTX_COMMAND_BUILTIN(ktxDeflate)
KTX_COMMAND_BUILTIN(ktxExtract)
KTX_COMMAND_BUILTIN(ktxEncode)
KTX_COMMAND_BUILTIN(ktxTranscode)
KTX_COMMAND_BUILTIN(ktxInfo)
KTX_COMMAND_BUILTIN(ktxValidate)
KTX_COMMAND_BUILTIN(ktxCompare)
KTX_COMMAND_BUILTIN(ktxHelp)
#if KTX_DEVELOPER_FEATURE_PATCH
KTX_COMMAND_BUILTIN(ktxPatch)
#endif
std::unordered_map<std::string, ktx::pfnBuiltinCommand> builtinCommands = {
{ "create", ktxCreate },
{ "deflate", ktxDeflate },
{ "extract", ktxExtract },
{ "encode", ktxEncode },
{ "transcode", ktxTranscode },
{ "info", ktxInfo },
{ "validate", ktxValidate },
{ "compare", ktxCompare },
{ "help", ktxHelp },
#if KTX_DEVELOPER_FEATURE_PATCH
{ "patch", ktxPatch }
#endif
};
int main(int argc, char* argv[]) {
// If -NSDocumentRevisionsDebugMode YES ever causes any problem it should be discarded here
// by creating a new argc and argv pair and excluding the problematic arguments from them.
// This way downstream tools will not have to deal with this issue
// // -NSDocumentRevisionsDebugMode YES is appended to the end
// // of the command by Xcode when debugging and "Allow debugging when
// // using document Versions Browser" is checked in the scheme. It
// // defaults to checked and is saved in a user-specific file not the
// // pbxproj file, so it can't be disabled in a generated project.
// // Remove these from the arguments under consideration.
InitUTF8CLI(argc, argv);
if (argc >= 2) {
// Has a subcommand, attempt to lookup
const auto it = builtinCommands.find(argv[1]);
if (it != builtinCommands.end()) {
// Call built-in subcommand, trimming the first parameter.
return it->second(argc - 1, argv + 1);
} else {
// In the future it is possible to add further logic here to allow loading command plugins
// from shared libraries or call external commands. There is no defined configuration
// mechanism to do so, but the command framework has been designed to be able to build
// subcommands as separate executables or shared libraries.
}
}
// If no sub-command was specified or if it was not found, call the main command's entry point.
ktx::Tools cmd;
return cmd.main(argc, argv);
}
+213
View File
@@ -0,0 +1,213 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "command.h"
#include "transcode_utils.h"
#include "utility.h"
#include "image.hpp"
#include "ktx.h"
#include "ktxint.h"
#include "texture2.h"
#include <basisu/encoder/basisu_enc.h>
#include <basisu/encoder/basisu_ssim.h>
// -------------------------------------------------------------------------------------------------
namespace ktx {
/**
//! [command options_metrics]
<dl>
<!-- Empty dt, and dd are so we can add another dl for indentation. -->
<dt></dt>
<dd></dd>
<dl>
<dt>\--compare-ssim</dt>
<dd>Calculate encoding structural similarity index measure (SSIM) and print it to stdout.
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, UASTC or ASTC encoding.</dd>
</dl>
</dl>
//! [command options_metrics]
*/
struct OptionsMetrics {
bool compare_ssim;
bool compare_psnr;
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, 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&) {
compare_ssim = args["compare-ssim"].as<bool>();
compare_psnr = args["compare-psnr"].as<bool>();
}
};
class MetricsCalculator {
uint32_t referenceNumChannels = 0;
std::vector<basisu::image> referenceImages;
public:
void saveReferenceImages(KTXTexture2& texture, const OptionsMetrics& opts, Reporter&) {
if (!opts.compare_ssim && !opts.compare_psnr)
return;
const auto numChannels = ktxTexture2_GetNumComponents(texture);
referenceNumChannels = numChannels;
// Format is R/RG/RGB/RGBA 8bit UNORM/SRGB
for (uint32_t levelIndex = 0; levelIndex < texture->numLevels; ++levelIndex) {
const uint32_t imageWidth = std::max(texture->baseWidth >> levelIndex, 1u);
const uint32_t imageHeight = std::max(texture->baseHeight >> levelIndex, 1u);
const uint32_t imageDepths = std::max(texture->baseDepth >> levelIndex, 1u);
for (uint32_t layerIndex = 0; layerIndex < texture->numLayers; ++layerIndex) {
for (uint32_t faceIndex = 0; faceIndex < texture->numFaces; ++faceIndex) {
for (uint32_t depthSliceIndex = 0; depthSliceIndex < imageDepths; ++depthSliceIndex) {
ktx_size_t imageOffset;
ktxTexture_GetImageOffset(texture, levelIndex, layerIndex, faceIndex + depthSliceIndex, &imageOffset);
const auto* imageData = texture->pData + imageOffset;
auto& ref = referenceImages.emplace_back(imageWidth, imageHeight);
for (uint32_t y = 0; y < imageHeight; ++y) {
for (uint32_t x = 0; x < imageWidth; ++x) {
ref(x, y) = basisu::color_rgba(0, 0, 0, 255);
for (uint32_t c = 0; c < numChannels; ++c)
ref(x, y)[c] = *(imageData + y * imageWidth * numChannels + x * numChannels + c);
}
}
}
}
}
}
}
void decodeAndCalculateMetrics(KTXTexture2& encodedTexture, const OptionsMetrics& opts, Reporter& report) {
if (!opts.compare_ssim && !opts.compare_psnr)
return;
KTXTexture2 texture{static_cast<ktxTexture2*>(malloc(sizeof(ktxTexture2)))};
ktxTexture2_constructCopy(texture, encodedTexture);
// 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
const auto* bdfd = texture->pDfd + 1;
if (khr_df_model_e(KHR_DFDVAL(bdfd, MODEL)) == KHR_DF_MODEL_ASTC) {
ec = ktxTexture2_DecodeAstc(texture);
}
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));
float overallSSIM[4] = {};
float overallPSNR = 0;
auto refIt = referenceImages.begin();
for (uint32_t levelIndex = 0; levelIndex < texture->numLevels; ++levelIndex) {
const uint32_t imageWidth = std::max(texture->baseWidth >> levelIndex, 1u);
const uint32_t imageHeight = std::max(texture->baseHeight >> levelIndex, 1u);
const uint32_t imageDepth = std::max(texture->baseDepth >> levelIndex, 1u);
for (uint32_t layerIndex = 0; layerIndex < texture->numLayers; ++layerIndex) {
for (uint32_t faceIndex = 0; faceIndex < texture->numFaces; ++faceIndex) {
for (uint32_t depthSliceIndex = 0; depthSliceIndex < imageDepth; ++depthSliceIndex) {
assert(refIt != referenceImages.end() && "Internal error");
ktx_size_t imageOffset;
ktxTexture_GetImageOffset(texture, levelIndex, layerIndex, faceIndex + depthSliceIndex, &imageOffset);
auto* imageData = texture->pData + imageOffset;
rgba8image imageView(imageWidth, imageHeight, reinterpret_cast<rgba8color*>(imageData));
imageView.swizzle(tSwizzleInfo.swizzle);
auto& ref = *refIt++;
basisu::image basisuImage;
basisuImage.resize(imageWidth, imageHeight);
for (uint32_t y = 0; y < imageHeight; ++y) {
for (uint32_t x = 0; x < imageWidth; ++x) {
basisuImage(x, y) = basisu::color_rgba(0, 0, 0, 255);
for (uint32_t c = 0; c < referenceNumChannels; ++c)
basisuImage(x, y)[c] = imageView(x, y)[c];
}
}
if (referenceImages.size() != 1)
fmt::print("Level {}{}{}{}:\n",
levelIndex,
texture->isArray ? fmt::format(" Layer {}", layerIndex) : "",
texture->isCubemap ? fmt::format(" Face {}", faceIndex) : "",
texture->numDimensions == 3 ? fmt::format(" Depth {}", depthSliceIndex) : "");
if (opts.compare_ssim) {
const auto ssim = basisu::compute_ssim(ref, basisuImage, false, false);
if (referenceImages.size() != 1) {
if (referenceNumChannels > 3)
fmt::print(" SSIM R: {:+7.6f}, G: {:+7.6f}, B: {:+7.6f}, A: {:+7.6f}\n", ssim[0], ssim[1], ssim[2], ssim[3]);
else if (referenceNumChannels > 2)
fmt::print(" SSIM R: {:+7.6f}, G: {:+7.6f}, B: {:+7.6f}\n", ssim[0], ssim[1], ssim[2]);
else if (referenceNumChannels > 1)
fmt::print(" SSIM R: {:+7.6f}, G: {:+7.6f}\n", ssim[0], ssim[1]);
else if (referenceNumChannels > 0)
fmt::print(" SSIM R: {:+7.6f}\n", ssim[0]);
}
for (int i = 0; i < 4; ++i)
overallSSIM[i] += ssim[i];
}
if (opts.compare_psnr) {
basisu::image_metrics im;
im.calc(ref, basisuImage);
if (referenceImages.size() != 1)
fmt::print(" PSNR: {:9.6f}\n", im.m_psnr);
overallPSNR = std::max(overallPSNR, im.m_psnr);
}
}
}
}
}
assert(refIt == referenceImages.end() && "Internal error");
fmt::print("{}Overall:\n", referenceImages.size() != 1 ? "\n" : "");
if (opts.compare_ssim) {
const auto numIf = static_cast<float>(referenceImages.size());
if (referenceNumChannels > 3)
fmt::print(" SSIM Avg R: {:+7.6f}, G: {:+7.6f}, B: {:+7.6f}, A: {:+7.6f}\n", overallSSIM[0] / numIf, overallSSIM[1] / numIf, overallSSIM[2] / numIf, overallSSIM[3] / numIf);
else if (referenceNumChannels > 2)
fmt::print(" SSIM Avg R: {:+7.6f}, G: {:+7.6f}, B: {:+7.6f}\n", overallSSIM[0] / numIf, overallSSIM[1] / numIf, overallSSIM[2] / numIf);
else if (referenceNumChannels > 1)
fmt::print(" SSIM Avg R: {:+7.6f}, G: {:+7.6f}\n", overallSSIM[0] / numIf, overallSSIM[1] / numIf);
else
fmt::print(" SSIM Avg R: {:+7.6f}\n", overallSSIM[0] / numIf);
}
if (opts.compare_psnr) {
fmt::print(" PSNR Max: {:9.6f}\n", overallPSNR);
}
}
};
} // namespace ktx
File diff suppressed because it is too large Load Diff
+908
View File
@@ -0,0 +1,908 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "https://schema.khronos.org/ktx/info_v0.json",
"definitions": {
"enum_vkFormat": {
"oneOf": [
{ "type": "integer" },
{
"type": "string",
"enum": [
"VK_FORMAT_UNDEFINED",
"VK_FORMAT_R4G4_UNORM_PACK8",
"VK_FORMAT_R4G4B4A4_UNORM_PACK16",
"VK_FORMAT_B4G4R4A4_UNORM_PACK16",
"VK_FORMAT_R5G6B5_UNORM_PACK16",
"VK_FORMAT_B5G6R5_UNORM_PACK16",
"VK_FORMAT_R5G5B5A1_UNORM_PACK16",
"VK_FORMAT_B5G5R5A1_UNORM_PACK16",
"VK_FORMAT_A1R5G5B5_UNORM_PACK16",
"VK_FORMAT_R8_UNORM",
"VK_FORMAT_R8_SNORM",
"VK_FORMAT_R8_USCALED",
"VK_FORMAT_R8_SSCALED",
"VK_FORMAT_R8_UINT",
"VK_FORMAT_R8_SINT",
"VK_FORMAT_R8_SRGB",
"VK_FORMAT_R8G8_UNORM",
"VK_FORMAT_R8G8_SNORM",
"VK_FORMAT_R8G8_USCALED",
"VK_FORMAT_R8G8_SSCALED",
"VK_FORMAT_R8G8_UINT",
"VK_FORMAT_R8G8_SINT",
"VK_FORMAT_R8G8_SRGB",
"VK_FORMAT_R8G8B8_UNORM",
"VK_FORMAT_R8G8B8_SNORM",
"VK_FORMAT_R8G8B8_USCALED",
"VK_FORMAT_R8G8B8_SSCALED",
"VK_FORMAT_R8G8B8_UINT",
"VK_FORMAT_R8G8B8_SINT",
"VK_FORMAT_R8G8B8_SRGB",
"VK_FORMAT_B8G8R8_UNORM",
"VK_FORMAT_B8G8R8_SNORM",
"VK_FORMAT_B8G8R8_USCALED",
"VK_FORMAT_B8G8R8_SSCALED",
"VK_FORMAT_B8G8R8_UINT",
"VK_FORMAT_B8G8R8_SINT",
"VK_FORMAT_B8G8R8_SRGB",
"VK_FORMAT_R8G8B8A8_UNORM",
"VK_FORMAT_R8G8B8A8_SNORM",
"VK_FORMAT_R8G8B8A8_USCALED",
"VK_FORMAT_R8G8B8A8_SSCALED",
"VK_FORMAT_R8G8B8A8_UINT",
"VK_FORMAT_R8G8B8A8_SINT",
"VK_FORMAT_R8G8B8A8_SRGB",
"VK_FORMAT_B8G8R8A8_UNORM",
"VK_FORMAT_B8G8R8A8_SNORM",
"VK_FORMAT_B8G8R8A8_USCALED",
"VK_FORMAT_B8G8R8A8_SSCALED",
"VK_FORMAT_B8G8R8A8_UINT",
"VK_FORMAT_B8G8R8A8_SINT",
"VK_FORMAT_B8G8R8A8_SRGB",
"VK_FORMAT_A8B8G8R8_UNORM_PACK32",
"VK_FORMAT_A8B8G8R8_SNORM_PACK32",
"VK_FORMAT_A8B8G8R8_USCALED_PACK32",
"VK_FORMAT_A8B8G8R8_SSCALED_PACK32",
"VK_FORMAT_A8B8G8R8_UINT_PACK32",
"VK_FORMAT_A8B8G8R8_SINT_PACK32",
"VK_FORMAT_A8B8G8R8_SRGB_PACK32",
"VK_FORMAT_A2R10G10B10_UNORM_PACK32",
"VK_FORMAT_A2R10G10B10_SNORM_PACK32",
"VK_FORMAT_A2R10G10B10_USCALED_PACK32",
"VK_FORMAT_A2R10G10B10_SSCALED_PACK32",
"VK_FORMAT_A2R10G10B10_UINT_PACK32",
"VK_FORMAT_A2R10G10B10_SINT_PACK32",
"VK_FORMAT_A2B10G10R10_UNORM_PACK32",
"VK_FORMAT_A2B10G10R10_SNORM_PACK32",
"VK_FORMAT_A2B10G10R10_USCALED_PACK32",
"VK_FORMAT_A2B10G10R10_SSCALED_PACK32",
"VK_FORMAT_A2B10G10R10_UINT_PACK32",
"VK_FORMAT_A2B10G10R10_SINT_PACK32",
"VK_FORMAT_R16_UNORM",
"VK_FORMAT_R16_SNORM",
"VK_FORMAT_R16_USCALED",
"VK_FORMAT_R16_SSCALED",
"VK_FORMAT_R16_UINT",
"VK_FORMAT_R16_SINT",
"VK_FORMAT_R16_SFLOAT",
"VK_FORMAT_R16G16_UNORM",
"VK_FORMAT_R16G16_SNORM",
"VK_FORMAT_R16G16_USCALED",
"VK_FORMAT_R16G16_SSCALED",
"VK_FORMAT_R16G16_UINT",
"VK_FORMAT_R16G16_SINT",
"VK_FORMAT_R16G16_SFLOAT",
"VK_FORMAT_R16G16B16_UNORM",
"VK_FORMAT_R16G16B16_SNORM",
"VK_FORMAT_R16G16B16_USCALED",
"VK_FORMAT_R16G16B16_SSCALED",
"VK_FORMAT_R16G16B16_UINT",
"VK_FORMAT_R16G16B16_SINT",
"VK_FORMAT_R16G16B16_SFLOAT",
"VK_FORMAT_R16G16B16A16_UNORM",
"VK_FORMAT_R16G16B16A16_SNORM",
"VK_FORMAT_R16G16B16A16_USCALED",
"VK_FORMAT_R16G16B16A16_SSCALED",
"VK_FORMAT_R16G16B16A16_UINT",
"VK_FORMAT_R16G16B16A16_SINT",
"VK_FORMAT_R16G16B16A16_SFLOAT",
"VK_FORMAT_R32_UINT",
"VK_FORMAT_R32_SINT",
"VK_FORMAT_R32_SFLOAT",
"VK_FORMAT_R32G32_UINT",
"VK_FORMAT_R32G32_SINT",
"VK_FORMAT_R32G32_SFLOAT",
"VK_FORMAT_R32G32B32_UINT",
"VK_FORMAT_R32G32B32_SINT",
"VK_FORMAT_R32G32B32_SFLOAT",
"VK_FORMAT_R32G32B32A32_UINT",
"VK_FORMAT_R32G32B32A32_SINT",
"VK_FORMAT_R32G32B32A32_SFLOAT",
"VK_FORMAT_R64_UINT",
"VK_FORMAT_R64_SINT",
"VK_FORMAT_R64_SFLOAT",
"VK_FORMAT_R64G64_UINT",
"VK_FORMAT_R64G64_SINT",
"VK_FORMAT_R64G64_SFLOAT",
"VK_FORMAT_R64G64B64_UINT",
"VK_FORMAT_R64G64B64_SINT",
"VK_FORMAT_R64G64B64_SFLOAT",
"VK_FORMAT_R64G64B64A64_UINT",
"VK_FORMAT_R64G64B64A64_SINT",
"VK_FORMAT_R64G64B64A64_SFLOAT",
"VK_FORMAT_B10G11R11_UFLOAT_PACK32",
"VK_FORMAT_E5B9G9R9_UFLOAT_PACK32",
"VK_FORMAT_D16_UNORM",
"VK_FORMAT_X8_D24_UNORM_PACK32",
"VK_FORMAT_D32_SFLOAT",
"VK_FORMAT_S8_UINT",
"VK_FORMAT_D16_UNORM_S8_UINT",
"VK_FORMAT_D24_UNORM_S8_UINT",
"VK_FORMAT_D32_SFLOAT_S8_UINT",
"VK_FORMAT_BC1_RGB_UNORM_BLOCK",
"VK_FORMAT_BC1_RGB_SRGB_BLOCK",
"VK_FORMAT_BC1_RGBA_UNORM_BLOCK",
"VK_FORMAT_BC1_RGBA_SRGB_BLOCK",
"VK_FORMAT_BC2_UNORM_BLOCK",
"VK_FORMAT_BC2_SRGB_BLOCK",
"VK_FORMAT_BC3_UNORM_BLOCK",
"VK_FORMAT_BC3_SRGB_BLOCK",
"VK_FORMAT_BC4_UNORM_BLOCK",
"VK_FORMAT_BC4_SNORM_BLOCK",
"VK_FORMAT_BC5_UNORM_BLOCK",
"VK_FORMAT_BC5_SNORM_BLOCK",
"VK_FORMAT_BC6H_UFLOAT_BLOCK",
"VK_FORMAT_BC6H_SFLOAT_BLOCK",
"VK_FORMAT_BC7_UNORM_BLOCK",
"VK_FORMAT_BC7_SRGB_BLOCK",
"VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK",
"VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK",
"VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK",
"VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK",
"VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK",
"VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK",
"VK_FORMAT_EAC_R11_UNORM_BLOCK",
"VK_FORMAT_EAC_R11_SNORM_BLOCK",
"VK_FORMAT_EAC_R11G11_UNORM_BLOCK",
"VK_FORMAT_EAC_R11G11_SNORM_BLOCK",
"VK_FORMAT_ASTC_4x4_UNORM_BLOCK",
"VK_FORMAT_ASTC_4x4_SRGB_BLOCK",
"VK_FORMAT_ASTC_5x4_UNORM_BLOCK",
"VK_FORMAT_ASTC_5x4_SRGB_BLOCK",
"VK_FORMAT_ASTC_5x5_UNORM_BLOCK",
"VK_FORMAT_ASTC_5x5_SRGB_BLOCK",
"VK_FORMAT_ASTC_6x5_UNORM_BLOCK",
"VK_FORMAT_ASTC_6x5_SRGB_BLOCK",
"VK_FORMAT_ASTC_6x6_UNORM_BLOCK",
"VK_FORMAT_ASTC_6x6_SRGB_BLOCK",
"VK_FORMAT_ASTC_8x5_UNORM_BLOCK",
"VK_FORMAT_ASTC_8x5_SRGB_BLOCK",
"VK_FORMAT_ASTC_8x6_UNORM_BLOCK",
"VK_FORMAT_ASTC_8x6_SRGB_BLOCK",
"VK_FORMAT_ASTC_8x8_UNORM_BLOCK",
"VK_FORMAT_ASTC_8x8_SRGB_BLOCK",
"VK_FORMAT_ASTC_10x5_UNORM_BLOCK",
"VK_FORMAT_ASTC_10x5_SRGB_BLOCK",
"VK_FORMAT_ASTC_10x6_UNORM_BLOCK",
"VK_FORMAT_ASTC_10x6_SRGB_BLOCK",
"VK_FORMAT_ASTC_10x8_UNORM_BLOCK",
"VK_FORMAT_ASTC_10x8_SRGB_BLOCK",
"VK_FORMAT_ASTC_10x10_UNORM_BLOCK",
"VK_FORMAT_ASTC_10x10_SRGB_BLOCK",
"VK_FORMAT_ASTC_12x10_UNORM_BLOCK",
"VK_FORMAT_ASTC_12x10_SRGB_BLOCK",
"VK_FORMAT_ASTC_12x12_UNORM_BLOCK",
"VK_FORMAT_ASTC_12x12_SRGB_BLOCK",
"VK_FORMAT_G8B8G8R8_422_UNORM",
"VK_FORMAT_B8G8R8G8_422_UNORM",
"VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM",
"VK_FORMAT_G8_B8R8_2PLANE_420_UNORM",
"VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM",
"VK_FORMAT_G8_B8R8_2PLANE_422_UNORM",
"VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM",
"VK_FORMAT_R10X6_UNORM_PACK16",
"VK_FORMAT_R10X6G10X6_UNORM_2PACK16",
"VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16",
"VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16",
"VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16",
"VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16",
"VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16",
"VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16",
"VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16",
"VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16",
"VK_FORMAT_R12X4_UNORM_PACK16",
"VK_FORMAT_R12X4G12X4_UNORM_2PACK16",
"VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16",
"VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16",
"VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16",
"VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16",
"VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16",
"VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16",
"VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16",
"VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16",
"VK_FORMAT_G16B16G16R16_422_UNORM",
"VK_FORMAT_B16G16R16G16_422_UNORM",
"VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM",
"VK_FORMAT_G16_B16R16_2PLANE_420_UNORM",
"VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM",
"VK_FORMAT_G16_B16R16_2PLANE_422_UNORM",
"VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM",
"VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG",
"VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG",
"VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG",
"VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG",
"VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG",
"VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG",
"VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG",
"VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG",
"VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK",
"VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT",
"VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT",
"VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT",
"VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT",
"VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT",
"VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT",
"VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT",
"VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT",
"VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT",
"VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT",
"VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT",
"VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT",
"VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT",
"VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT",
"VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT",
"VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT",
"VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT",
"VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT",
"VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT",
"VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT",
"VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT",
"VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT",
"VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT",
"VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT",
"VK_FORMAT_A8_UNORM_KHR",
"VK_FORMAT_A1B5G5R5_UNORM_PACK16_KHR",
"VK_FORMAT_A4R4G4B4_UNORM_PACK16",
"VK_FORMAT_A4B4G4R4_UNORM_PACK16"
]
}
]
},
"enum_supercompressionScheme": {
"oneOf": [
{ "type": "integer" },
{
"type": "string",
"enum": [
"KTX_SS_NONE",
"KTX_SS_BASIS_LZ",
"KTX_SS_ZSTD",
"KTX_SS_ZLIB"
]
}
]
},
"enum_descriptorType": {
"oneOf": [
{ "type": "integer" },
{
"type": "string",
"enum": [
"KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT",
"KHR_DF_KHR_DESCRIPTORTYPE_ADDITIONAL_PLANES",
"KHR_DF_KHR_DESCRIPTORTYPE_ADDITIONAL_DIMENSIONS"
]
}
]
},
"enum_vendorId": {
"oneOf": [
{ "type": "integer" },
{
"type": "string",
"enum": [
"KHR_DF_VENDORID_KHRONOS"
]
}
]
},
"enum_versionNumber": {
"oneOf": [
{ "type": "integer" },
{
"type": "string",
"enum": [
"KHR_DF_VERSIONNUMBER_1_1",
"KHR_DF_VERSIONNUMBER_1_2",
"KHR_DF_VERSIONNUMBER_1_3"
]
}
]
},
"enum_flags": {
"oneOf": [
{ "type": "integer" },
{
"type": "string",
"enum": [
"KHR_DF_FLAG_ALPHA_PREMULTIPLIED",
"KHR_DF_FLAG_ALPHA_STRAIGHT"
]
}
]
},
"enum_transferFunction": {
"oneOf": [
{ "type": "integer" },
{
"type": "string",
"enum": [
"KHR_DF_TRANSFER_UNSPECIFIED",
"KHR_DF_TRANSFER_LINEAR",
"KHR_DF_TRANSFER_SRGB",
"KHR_DF_TRANSFER_ITU",
"KHR_DF_TRANSFER_NTSC",
"KHR_DF_TRANSFER_SLOG",
"KHR_DF_TRANSFER_SLOG2",
"KHR_DF_TRANSFER_BT1886",
"KHR_DF_TRANSFER_HLG_OETF",
"KHR_DF_TRANSFER_HLG_EOTF",
"KHR_DF_TRANSFER_PQ_EOTF",
"KHR_DF_TRANSFER_PQ_OETF",
"KHR_DF_TRANSFER_DCIP3",
"KHR_DF_TRANSFER_PAL_OETF",
"KHR_DF_TRANSFER_PAL625_EOTF",
"KHR_DF_TRANSFER_ST240",
"KHR_DF_TRANSFER_ACESCC",
"KHR_DF_TRANSFER_ACESCCT",
"KHR_DF_TRANSFER_ADOBERGB"
]
}
]
},
"enum_colorPrimaries": {
"oneOf": [
{ "type": "integer" },
{
"type": "string",
"enum": [
"KHR_DF_PRIMARIES_UNSPECIFIED",
"KHR_DF_PRIMARIES_BT709",
"KHR_DF_PRIMARIES_BT601_EBU",
"KHR_DF_PRIMARIES_BT601_SMPTE",
"KHR_DF_PRIMARIES_BT2020",
"KHR_DF_PRIMARIES_CIEXYZ",
"KHR_DF_PRIMARIES_ACES",
"KHR_DF_PRIMARIES_ACESCC",
"KHR_DF_PRIMARIES_NTSC1953",
"KHR_DF_PRIMARIES_PAL525",
"KHR_DF_PRIMARIES_DISPLAYP3",
"KHR_DF_PRIMARIES_ADOBERGB"
]
}
]
},
"enum_colorModel": {
"oneOf": [
{ "type": "integer" },
{
"type": "string",
"enum": [
"KHR_DF_MODEL_UNSPECIFIED",
"KHR_DF_MODEL_RGBSDA",
"KHR_DF_MODEL_YUVSDA",
"KHR_DF_MODEL_YIQSDA",
"KHR_DF_MODEL_LABSDA",
"KHR_DF_MODEL_CMYKA",
"KHR_DF_MODEL_XYZW",
"KHR_DF_MODEL_HSVA_ANG",
"KHR_DF_MODEL_HSLA_ANG",
"KHR_DF_MODEL_HSVA_HEX",
"KHR_DF_MODEL_HSLA_HEX",
"KHR_DF_MODEL_YCGCOA",
"KHR_DF_MODEL_YCCBCCRC",
"KHR_DF_MODEL_ICTCP",
"KHR_DF_MODEL_CIEXYZ",
"KHR_DF_MODEL_CIEXYY",
"KHR_DF_MODEL_BC1A",
"KHR_DF_MODEL_BC2",
"KHR_DF_MODEL_BC3",
"KHR_DF_MODEL_BC4",
"KHR_DF_MODEL_BC5",
"KHR_DF_MODEL_BC6H",
"KHR_DF_MODEL_BC7",
"KHR_DF_MODEL_ETC1",
"KHR_DF_MODEL_ETC2",
"KHR_DF_MODEL_ASTC",
"KHR_DF_MODEL_ETC1S",
"KHR_DF_MODEL_PVRTC",
"KHR_DF_MODEL_PVRTC2",
"KHR_DF_MODEL_UASTC"
]
}
]
},
"enum_qualifier": {
"oneOf": [
{ "type": "integer" },
{
"type": "string",
"enum": [
"KHR_DF_SAMPLE_DATATYPE_LINEAR",
"KHR_DF_SAMPLE_DATATYPE_EXPONENT",
"KHR_DF_SAMPLE_DATATYPE_SIGNED",
"KHR_DF_SAMPLE_DATATYPE_FLOAT"
]
}
]
},
"enum_channelType": {
"oneOf": [
{ "type": "integer" },
{
"type": "string",
"enum": [
"KHR_DF_CHANNEL_RGBSDA_RED",
"KHR_DF_CHANNEL_RGBSDA_GREEN",
"KHR_DF_CHANNEL_RGBSDA_BLUE",
"KHR_DF_CHANNEL_RGBSDA_STENCIL",
"KHR_DF_CHANNEL_RGBSDA_DEPTH",
"KHR_DF_CHANNEL_RGBSDA_ALPHA",
"KHR_DF_CHANNEL_YUVSDA_Y",
"KHR_DF_CHANNEL_YUVSDA_U",
"KHR_DF_CHANNEL_YUVSDA_V",
"KHR_DF_CHANNEL_YUVSDA_STENCIL",
"KHR_DF_CHANNEL_YUVSDA_DEPTH",
"KHR_DF_CHANNEL_YUVSDA_ALPHA",
"KHR_DF_CHANNEL_YIQSDA_Y",
"KHR_DF_CHANNEL_YIQSDA_I",
"KHR_DF_CHANNEL_YIQSDA_Q",
"KHR_DF_CHANNEL_YIQSDA_STENCIL",
"KHR_DF_CHANNEL_YIQSDA_DEPTH",
"KHR_DF_CHANNEL_YIQSDA_ALPHA",
"KHR_DF_CHANNEL_LABSDA_L",
"KHR_DF_CHANNEL_LABSDA_A",
"KHR_DF_CHANNEL_LABSDA_B",
"KHR_DF_CHANNEL_LABSDA_STENCIL",
"KHR_DF_CHANNEL_LABSDA_DEPTH",
"KHR_DF_CHANNEL_LABSDA_ALPHA",
"KHR_DF_CHANNEL_CMYKSDA_CYAN",
"KHR_DF_CHANNEL_CMYKSDA_MAGENTA",
"KHR_DF_CHANNEL_CMYKSDA_YELLOW",
"KHR_DF_CHANNEL_CMYKSDA_BLACK",
"KHR_DF_CHANNEL_CMYKSDA_ALPHA",
"KHR_DF_CHANNEL_XYZW_X",
"KHR_DF_CHANNEL_XYZW_Y",
"KHR_DF_CHANNEL_XYZW_Z",
"KHR_DF_CHANNEL_XYZW_W",
"KHR_DF_CHANNEL_HSVA_ANG_VALUE",
"KHR_DF_CHANNEL_HSVA_ANG_SATURATION",
"KHR_DF_CHANNEL_HSVA_ANG_HUE",
"KHR_DF_CHANNEL_HSVA_ANG_ALPHA",
"KHR_DF_CHANNEL_HSLA_ANG_LIGHTNESS",
"KHR_DF_CHANNEL_HSLA_ANG_SATURATION",
"KHR_DF_CHANNEL_HSLA_ANG_HUE",
"KHR_DF_CHANNEL_HSLA_ANG_ALPHA",
"KHR_DF_CHANNEL_HSVA_HEX_VALUE",
"KHR_DF_CHANNEL_HSVA_HEX_SATURATION",
"KHR_DF_CHANNEL_HSVA_HEX_HUE",
"KHR_DF_CHANNEL_HSVA_HEX_ALPHA",
"KHR_DF_CHANNEL_HSLA_HEX_LIGHTNESS",
"KHR_DF_CHANNEL_HSLA_HEX_SATURATION",
"KHR_DF_CHANNEL_HSLA_HEX_HUE",
"KHR_DF_CHANNEL_HSLA_HEX_ALPHA",
"KHR_DF_CHANNEL_YCGCOA_Y",
"KHR_DF_CHANNEL_YCGCOA_CG",
"KHR_DF_CHANNEL_YCGCOA_CO",
"KHR_DF_CHANNEL_YCGCOA_ALPHA",
"KHR_DF_CHANNEL_CIEXYZ_X",
"KHR_DF_CHANNEL_CIEXYZ_Y",
"KHR_DF_CHANNEL_CIEXYZ_Z",
"KHR_DF_CHANNEL_CIEXYY_X",
"KHR_DF_CHANNEL_CIEXYY_YCHROMA",
"KHR_DF_CHANNEL_CIEXYY_YLUMA",
"KHR_DF_CHANNEL_BC1A_COLOR",
"KHR_DF_CHANNEL_BC1A_ALPHA",
"KHR_DF_CHANNEL_BC2_COLOR",
"KHR_DF_CHANNEL_BC2_ALPHA",
"KHR_DF_CHANNEL_BC3_COLOR",
"KHR_DF_CHANNEL_BC3_ALPHA",
"KHR_DF_CHANNEL_BC4_DATA",
"KHR_DF_CHANNEL_BC5_RED",
"KHR_DF_CHANNEL_BC5_GREEN",
"KHR_DF_CHANNEL_BC6H_COLOR",
"KHR_DF_CHANNEL_BC7_COLOR",
"KHR_DF_CHANNEL_ETC1_COLOR",
"KHR_DF_CHANNEL_ETC2_RED",
"KHR_DF_CHANNEL_ETC2_GREEN",
"KHR_DF_CHANNEL_ETC2_COLOR",
"KHR_DF_CHANNEL_ETC2_ALPHA",
"KHR_DF_CHANNEL_ASTC_DATA",
"KHR_DF_CHANNEL_ETC1S_RGB",
"KHR_DF_CHANNEL_ETC1S_RRR",
"KHR_DF_CHANNEL_ETC1S_GGG",
"KHR_DF_CHANNEL_ETC1S_AAA",
"KHR_DF_CHANNEL_PVRTC_COLOR",
"KHR_DF_CHANNEL_PVRTC2_COLOR",
"KHR_DF_CHANNEL_UASTC_RGB",
"KHR_DF_CHANNEL_UASTC_RGBA",
"KHR_DF_CHANNEL_UASTC_RRR",
"KHR_DF_CHANNEL_UASTC_RRRG",
"KHR_DF_CHANNEL_UASTC_RG",
"KHR_DF_CHANNEL_UNSPECIFIED_0",
"KHR_DF_CHANNEL_UNSPECIFIED_1",
"KHR_DF_CHANNEL_UNSPECIFIED_2",
"KHR_DF_CHANNEL_UNSPECIFIED_3",
"KHR_DF_CHANNEL_UNSPECIFIED_4",
"KHR_DF_CHANNEL_UNSPECIFIED_5",
"KHR_DF_CHANNEL_UNSPECIFIED_6",
"KHR_DF_CHANNEL_UNSPECIFIED_7",
"KHR_DF_CHANNEL_UNSPECIFIED_8",
"KHR_DF_CHANNEL_UNSPECIFIED_9",
"KHR_DF_CHANNEL_UNSPECIFIED_10",
"KHR_DF_CHANNEL_UNSPECIFIED_11",
"KHR_DF_CHANNEL_UNSPECIFIED_12",
"KHR_DF_CHANNEL_UNSPECIFIED_13",
"KHR_DF_CHANNEL_UNSPECIFIED_14",
"KHR_DF_CHANNEL_UNSPECIFIED_15"
]
}
]
},
"enum_imageFlags" : {
"oneOf": [
{ "type": "integer" },
{
"type": "string",
"enum": [
"ETC1S_P_FRAME"
]
}
]
}
},
"type": "object",
"required": [
"valid",
"messages"
],
"properties": {
"valid": { "type": "boolean" },
"messages": {
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"type",
"message",
"details"
],
"additionalProperties": false,
"properties": {
"id": { "type": "integer" },
"type": {
"type": "string",
"enum": [
"fatal",
"error",
"warning"
]
},
"message": { "type": "string" },
"details": { "type": "string" }
}
}
},
"header": {
"type": "object",
"required": [
"identifier",
"vkFormat",
"typeSize",
"pixelWidth",
"pixelHeight",
"pixelDepth",
"layerCount",
"faceCount",
"levelCount",
"supercompressionScheme"
],
"additionalProperties": false,
"properties": {
"identifier": { "type": "string" },
"vkFormat": { "$ref": "#/definitions/enum_vkFormat" },
"typeSize": { "type": "integer" },
"pixelWidth": { "type": "integer" },
"pixelHeight": { "type": "integer" },
"pixelDepth": { "type": "integer" },
"layerCount": { "type": "integer" },
"faceCount": { "type": "integer" },
"levelCount": { "type": "integer" },
"supercompressionScheme": { "$ref": "#/definitions/enum_supercompressionScheme" }
}
},
"index": {
"type": "object",
"required": [
"dataFormatDescriptor",
"keyValueData",
"supercompressionGlobalData",
"levels"
],
"properties": {
"dataFormatDescriptor": {
"type": "object",
"required": [
"byteOffset",
"byteLength"
],
"additionalProperties": false,
"properties": {
"byteOffset": { "type": "integer" },
"byteLength": { "type": "integer" }
}
},
"keyValueData": {
"type": "object",
"required": [
"byteOffset",
"byteLength"
],
"additionalProperties": false,
"properties": {
"byteOffset": { "type": "integer" },
"byteLength": { "type": "integer" }
}
},
"supercompressionGlobalData": {
"type": "object",
"required": [
"byteOffset",
"byteLength"
],
"additionalProperties": false,
"properties": {
"byteOffset": { "type": "integer" },
"byteLength": { "type": "integer" }
}
},
"levels": {
"type": "array",
"items": {
"type": "object",
"required": [
"byteOffset",
"byteLength",
"uncompressedByteLength"
],
"additionalProperties": false,
"properties": {
"byteOffset": { "type": "integer" },
"byteLength": { "type": "integer" },
"uncompressedByteLength": { "type": "integer" }
}
}
}
}
},
"dataFormatDescriptor": {
"type": "object",
"required": [
"totalSize",
"blocks"
],
"additionalProperties": false,
"properties": {
"totalSize": { "type": "integer" },
"blocks": {
"type": "array",
"items": {
"type": "object",
"required": [
"vendorId",
"descriptorType",
"versionNumber",
"descriptorBlockSize"
],
"additionalProperties": false,
"properties": {
"descriptorType": { "$ref": "#/definitions/enum_descriptorType" },
"vendorId": { "$ref": "#/definitions/enum_vendorId" },
"descriptorBlockSize": { "type": "integer" },
"versionNumber": { "$ref": "#/definitions/enum_versionNumber" },
"flags": {
"type": "array",
"items": { "$ref": "#/definitions/enum_flags" },
"uniqueItems": true
},
"transferFunction": { "$ref": "#/definitions/enum_transferFunction" },
"colorPrimaries": { "$ref": "#/definitions/enum_colorPrimaries" },
"colorModel": { "$ref": "#/definitions/enum_colorModel" },
"texelBlockDimension": {
"type": "array",
"items": { "type": "integer" },
"minItems": 4,
"maxItems": 4
},
"bytesPlane": {
"type": "array",
"items": { "type": "integer" },
"minItems": 8,
"maxItems": 8
},
"samples": {
"type": "array",
"items": {
"type": "object",
"required": [
"qualifiers",
"channelType",
"bitLength",
"bitOffset",
"samplePosition",
"sampleLower",
"sampleUpper"
],
"additionalProperties": false,
"properties": {
"qualifiers": {
"type": "array",
"items": { "$ref": "#/definitions/enum_qualifier" },
"uniqueItems": true
},
"channelType": { "$ref": "#/definitions/enum_channelType" },
"bitLength": { "type": "integer" },
"bitOffset": { "type": "integer" },
"samplePosition": {
"type": "array",
"items": { "type": "integer" },
"minItems": 4,
"maxItems": 4
},
"sampleLower": { "type": "integer" },
"sampleUpper": { "type": "integer" }
}
}
}
}
}
}
}
},
"keyValueData": {
"type": "object",
"additionalProperties": true,
"properties": {
"KTXcubemapIncomplete": {
"type": "object",
"required": [
"positiveX",
"negativeX",
"positiveY",
"negativeY",
"positiveZ",
"negativeZ"
],
"additionalProperties": false,
"properties": {
"positiveX": { "type": "boolean" },
"negativeX": { "type": "boolean" },
"positiveY": { "type": "boolean" },
"negativeY": { "type": "boolean" },
"positiveZ": { "type": "boolean" },
"negativeZ": { "type": "boolean" }
}
},
"KTXorientation": { "type": "string" },
"KTXglFormat": {
"type": "object",
"required": [
"glInternalformat",
"glFormat",
"glType"
],
"additionalProperties": false,
"properties": {
"glInternalformat": { "type": "integer" },
"glFormat": { "type": "integer" },
"glType": { "type": "integer" }
}
},
"KTXdxgiFormat__": { "type": "integer" },
"KTXmetalPixelFormat": { "type": "integer" },
"KTXswizzle": { "type": "string" },
"KTXwriter": { "type": "string" },
"KTXwriterScParams": { "type": "string" },
"KTXastcDecodeMode": { "type": "string" },
"KTXanimData": {
"type": "object",
"required": [
"duration",
"timescale",
"loopCount"
],
"additionalProperties": false,
"properties": {
"duration": { "type": "integer" },
"timescale": { "type": "integer" },
"loopCount": { "type": "integer" }
}
}
}
},
"supercompressionGlobalData": {
"type": "object",
"properties": {
"type": { "$ref": "#/definitions/enum_supercompressionScheme" },
"endpointCount": { "type": "integer" },
"selectorCount": { "type": "integer" },
"endpointsByteLength": { "type": "integer" },
"selectorsByteLength": { "type": "integer" },
"tablesByteLength": { "type": "integer" },
"extendedByteLength": { "type": "integer" },
"images": {
"type": "array",
"items": [
{
"type": "object",
"required": [
"imageFlags",
"rgbSliceByteLength",
"rgbSliceByteOffset",
"alphaSliceByteLength",
"alphaSliceByteOffset"
],
"properties": {
"imageFlags": {
"type": "array",
"items": { "$ref": "#/definitions/enum_imageFlags" },
"uniqueItems": true
},
"rgbSliceByteLength": { "type": "integer" },
"rgbSliceByteOffset": { "type": "integer" },
"alphaSliceByteLength": { "type": "integer" },
"alphaSliceByteOffset": { "type": "integer" }
}
}
]
}
}
}
}
}
+38
View File
@@ -0,0 +1,38 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "https://schema.khronos.org/ktx/validate_v0.json",
"type": "object",
"required": [
"valid",
"messages"
],
"properties": {
"valid": { "type": "boolean" },
"messages": {
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"type",
"message",
"details"
],
"additionalProperties": false,
"properties": {
"id": { "type": "integer" },
"type": {
"type": "string",
"enum": [
"fatal",
"error",
"warning"
]
},
"message": { "type": "string" },
"details": { "type": "string" }
}
}
}
}
}
+75
View File
@@ -0,0 +1,75 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#include "transcode_utils.h"
#include "image.hpp"
// -------------------------------------------------------------------------------------------------
namespace ktx {
std::optional<khr_df_model_channels_e> getChannelType(const KTXTexture2& texture, uint32_t index) {
const auto* bdfd = (texture->pDfd + 1);
if (KHR_DFDSAMPLECOUNT(bdfd) <= index)
return std::nullopt;
return khr_df_model_channels_e(KHR_DFDSVAL(bdfd, index, CHANNELID));
}
TranscodeSwizzleInfo determineTranscodeSwizzle(const KTXTexture2& texture, Reporter& report) {
TranscodeSwizzleInfo result;
const auto* bdfd = texture->pDfd + 1;
const auto sample0 = getChannelType(texture, 0);
const auto sample1 = getChannelType(texture, 1);
if (texture->supercompressionScheme == KTX_SS_BASIS_LZ) {
result.defaultNumComponents = 0;
if (sample0 == KHR_DF_CHANNEL_ETC1S_RGB && sample1 == KHR_DF_CHANNEL_ETC1S_AAA) {
result.defaultNumComponents = 4;
result.swizzle = "rgba";
} else if (sample0 == KHR_DF_CHANNEL_ETC1S_RGB) {
result.defaultNumComponents = 3;
result.swizzle = "rgb1";
} else if (sample0 == KHR_DF_CHANNEL_ETC1S_RRR && sample1 == KHR_DF_CHANNEL_ETC1S_GGG) {
result.defaultNumComponents = 2;
result.swizzle = "ra01";
} else if (sample0 == KHR_DF_CHANNEL_ETC1S_RRR) {
result.defaultNumComponents = 1;
result.swizzle = "r001";
} else {
report.fatal(rc::INVALID_FILE, "Unsupported channel types for Basis-LZ transcoding: {}, {}",
sample0 ? toString(KHR_DF_MODEL_ETC1S, *sample0) : "-",
sample1 ? toString(KHR_DF_MODEL_ETC1S, *sample1) : "-");
}
} else if (khr_df_model_e(KHR_DFDVAL(bdfd, MODEL)) == KHR_DF_MODEL_UASTC) {
result.defaultNumComponents = 0;
if (sample0 == KHR_DF_CHANNEL_UASTC_RGBA) {
result.defaultNumComponents = 4;
result.swizzle = "rgba";
} else if (sample0 == KHR_DF_CHANNEL_UASTC_RGB) {
result.defaultNumComponents = 3;
result.swizzle = "rgb1";
} else if (sample0 == KHR_DF_CHANNEL_UASTC_RRRG) {
result.defaultNumComponents = 2;
result.swizzle = "ra01";
} else if (sample0 == KHR_DF_CHANNEL_UASTC_RG) {
result.defaultNumComponents = 2;
result.swizzle = "rg01";
} else if (sample0 == KHR_DF_CHANNEL_UASTC_RRR) {
result.defaultNumComponents = 1;
result.swizzle = "r001";
} else {
report.fatal(rc::INVALID_FILE, "Unsupported channel type for UASTC transcoding: {}",
sample0 ? toString(KHR_DF_MODEL_UASTC, *sample0) : "-");
}
} else {
report.fatal(rc::INVALID_FILE, "Requested transcoding but input file is neither BasisLZ, nor UASTC");
}
return result;
}
} // namespace ktx
+198
View File
@@ -0,0 +1,198 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "command.h"
#include "formats.h"
#include "image.hpp"
#include "utility.h"
#include "dfdutils/dfd.h"
#include <KHR/khr_df.h>
#include <tuple>
#include <string>
#include <unordered_map>
#include <optional>
// -------------------------------------------------------------------------------------------------
namespace ktx {
struct TranscodeSwizzleInfo {
uint32_t defaultNumComponents = 0;
std::string swizzle;
};
std::optional<khr_df_model_channels_e> getChannelType(const KTXTexture2& texture, uint32_t index);
TranscodeSwizzleInfo determineTranscodeSwizzle(const KTXTexture2& texture, Reporter& report);
template <bool TRANSCODE_CMD>
struct OptionsTranscodeTarget {
std::optional<ktx_transcode_fmt_e> transcodeTarget;
std::string transcodeTargetName;
uint32_t transcodeSwizzleComponents = 0;
std::string transcodeSwizzle;
void init(cxxopts::Options&) {}
void process(cxxopts::Options&, cxxopts::ParseResult& args, Reporter& report) {
// "transcode" command - optional "target" argument
// "extract" command - optional "transcode" argument
const auto argName = TRANSCODE_CMD ? "target" : "transcode";
static const std::unordered_map<std::string, std::pair<ktx_transcode_fmt_e, uint32_t>> targets{
{"etc-rgb", {KTX_TTF_ETC1_RGB, 0}},
{"etc-rgba", {KTX_TTF_ETC2_RGBA, 0}},
{"eac-r11", {KTX_TTF_ETC2_EAC_R11, 0}},
{"eac-rg11", {KTX_TTF_ETC2_EAC_RG11, 0}},
{"bc1", {KTX_TTF_BC1_RGB, 0}},
{"bc3", {KTX_TTF_BC3_RGBA, 0}},
{"bc4", {KTX_TTF_BC4_R, 0}},
{"bc5", {KTX_TTF_BC5_RG, 0}},
{"bc7", {KTX_TTF_BC7_RGBA, 0}},
{"astc", {KTX_TTF_ASTC_4x4_RGBA, 0}},
{"r8", {KTX_TTF_RGBA32, 1}},
{"rg8", {KTX_TTF_RGBA32, 2}},
{"rgb8", {KTX_TTF_RGBA32, 3}},
{"rgba8", {KTX_TTF_RGBA32, 4}},
};
if (args[argName].count()) {
const auto argStr = to_lower_copy(args[argName].as<std::string>());
const auto it = targets.find(argStr);
if (it == targets.end())
report.fatal_usage("Invalid transcode target: \"{}\".", argStr);
transcodeTarget = it->second.first;
transcodeTargetName = argStr;
transcodeSwizzleComponents = it->second.second;
}
}
void validateTextureTranscode(const KTXTexture2& texture, Reporter& report) {
const auto tswizzle = determineTranscodeSwizzle(texture, report);
if (!transcodeTarget.has_value()) {
transcodeTarget = KTX_TTF_RGBA32;
transcodeTargetName = "rgba8";
transcodeSwizzleComponents = tswizzle.defaultNumComponents;
}
transcodeSwizzle = tswizzle.swizzle;
}
};
template <bool TRANSCODE_CMD>
KTXTexture2 transcode(KTXTexture2&& texture, OptionsTranscodeTarget<TRANSCODE_CMD>& options, Reporter& report) {
options.validateTextureTranscode(texture, report);
auto ret = ktxTexture2_TranscodeBasis(texture, options.transcodeTarget.value(), 0);
if (ret != KTX_SUCCESS)
report.fatal(rc::INVALID_FILE, "Failed to transcode KTX2 texture: {}", ktxErrorString(ret));
// Need to perform format conversion and swizzling if needed
bool needFormatConversion = false;
bool needSwizzle = false;
if (options.transcodeSwizzleComponents != 0) {
if (options.transcodeSwizzleComponents == 4) {
if (options.transcodeSwizzle != "rgba") {
needSwizzle = true;
}
} else {
needFormatConversion = true;
needSwizzle = true;
}
}
KTXTexture2 convertedTexture{nullptr};
if (needFormatConversion) {
ktxTextureCreateInfo createInfo;
std::memset(&createInfo, 0, sizeof(createInfo));
const bool srgb = (texture->vkFormat == VK_FORMAT_R8G8B8A8_SRGB);
switch (options.transcodeSwizzleComponents) {
case 1:
createInfo.vkFormat = srgb ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM;
break;
case 2:
createInfo.vkFormat = srgb ? VK_FORMAT_R8G8_SRGB : VK_FORMAT_R8G8_UNORM;
break;
case 3:
createInfo.vkFormat = srgb ? VK_FORMAT_R8G8B8_SRGB : VK_FORMAT_R8G8B8_UNORM;
break;
default:
assert(false);
}
createInfo.baseWidth = texture->baseWidth;
createInfo.baseHeight = texture->baseHeight;
createInfo.baseDepth = texture->baseDepth;
createInfo.generateMipmaps = texture->generateMipmaps;
createInfo.isArray = texture->isArray;
createInfo.numDimensions = texture->numDimensions;
createInfo.numFaces = texture->numFaces;
createInfo.numLayers = texture->numLayers;
createInfo.numLevels = texture->numLevels;
createInfo.pDfd = nullptr;
ret = ktxTexture2_Create(&createInfo, KTX_TEXTURE_CREATE_ALLOC_STORAGE, convertedTexture.pHandle());
if (KTX_SUCCESS != ret)
report.fatal(rc::IO_FAILURE, "Failed to create output texture: {}", ktxErrorString(ret));
}
KTXTexture2& outputTexture = (convertedTexture.handle() != nullptr) ? convertedTexture : texture;
if (needFormatConversion || needSwizzle) {
for (uint32_t levelIndex = 0; levelIndex < texture->numLevels; ++levelIndex) {
const auto imageWidth = std::max(1u, texture->baseWidth >> levelIndex);
const auto imageHeight = std::max(1u, texture->baseHeight >> levelIndex);
const auto imageDepth = std::max(1u, texture->baseDepth >> levelIndex);
for (uint32_t faceIndex = 0; faceIndex < texture->numFaces; ++faceIndex) {
for (uint32_t layerIndex = 0; layerIndex < texture->numLayers; ++layerIndex) {
for (uint32_t depthIndex = 0; depthIndex < imageDepth; ++depthIndex) {
ktx_size_t srcImageOffset;
ktxTexture_GetImageOffset(texture, levelIndex, layerIndex, faceIndex + depthIndex, &srcImageOffset);
ktx_size_t dstImageOffset;
ktxTexture_GetImageOffset(outputTexture, levelIndex, layerIndex, faceIndex + depthIndex, &dstImageOffset);
auto srcImageData = texture->pData + srcImageOffset;
auto dstImageData = outputTexture->pData + dstImageOffset;
rgba8image srcImage(imageWidth, imageHeight, reinterpret_cast<rgba8color*>(srcImageData));
switch (options.transcodeSwizzleComponents) {
case 1: {
r8image dstImage(imageWidth, imageHeight, reinterpret_cast<r8color*>(dstImageData));
srcImage.copyToR(dstImage, options.transcodeSwizzle);
break;
}
case 2: {
rg8image dstImage(imageWidth, imageHeight, reinterpret_cast<rg8color*>(dstImageData));
srcImage.copyToRG(dstImage, options.transcodeSwizzle);
break;
}
case 3: {
rgb8image dstImage(imageWidth, imageHeight, reinterpret_cast<rgb8color*>(dstImageData));
srcImage.copyToRGB(dstImage, options.transcodeSwizzle);
break;
}
case 4: {
// Swizzle in-place
assert(srcImageData == dstImageData);
srcImage.swizzle(options.transcodeSwizzle);
break;
}
default:
assert(false);
}
}
}
}
}
}
return std::move(outputTexture);
}
} // namespace ktx
+401
View File
@@ -0,0 +1,401 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "ktx.h"
#include "imageio_utility.h"
#include <fmt/ostream.h>
#include <fmt/printf.h>
#include <algorithm>
#include <array>
#include <functional>
#include <optional>
#include <type_traits>
// -------------------------------------------------------------------------------------------------
namespace ktx {
// TODO: Detect endianness
// C++20: std::endian::native == std::endian::little
constexpr bool is_big_endian = false;
template <typename T>
[[nodiscard]] constexpr inline T align(const T value, const T alignment) noexcept {
assert(alignment != 0);
return (alignment - 1 + value) / alignment * alignment;
}
template <typename T>
[[nodiscard]] constexpr inline T* align(T* ptr, std::uintptr_t alignment) noexcept {
return reinterpret_cast<T*>(align(reinterpret_cast<std::uintptr_t>(ptr), alignment));
}
template <typename T>
[[nodiscard]] constexpr inline T ceil_div(const T x, const T y) noexcept {
assert(y != 0);
return (x + y - 1) / y;
}
/// log2 floor
[[nodiscard]] constexpr inline uint32_t log2(uint32_t v) noexcept {
uint32_t e = 0;
// http://aggregate.org/MAGIC/
v |= (v >> 1);
v |= (v >> 2);
v |= (v >> 4);
v |= (v >> 8);
v |= (v >> 16);
v = v & ~(v >> 1);
e = (v & 0xAAAAAAAA) ? 1 : 0;
e |= (v & 0xCCCCCCCC) ? 2 : 0;
e |= (v & 0xF0F0F0F0) ? 4 : 0;
e |= (v & 0xFF00FF00) ? 8 : 0;
e |= (v & 0xFFFF0000) ? 16 : 0;
return e;
}
// C++20 - std::popcount
template <typename T>
[[nodiscard]] constexpr inline int popcount(T value) noexcept {
int count = 0;
for (; value != 0; value >>= 1)
if (value & 1)
count++;
return count;
}
// C++20 - std::to_underlying
template <typename E>
[[nodiscard]] constexpr inline auto to_underlying(E e) noexcept {
static_assert(std::is_enum_v<E>, "E has to be an enum type");
return static_cast<std::underlying_type_t<E>>(e);
}
// C++20 - std::byteswap
template <typename T>
[[nodiscard]] constexpr inline T byteswap(T value) noexcept {
static_assert(std::has_unique_object_representations_v<T>, "T may have padding bits");
auto representation = imageio::bit_cast<std::array<std::byte, sizeof(T)>>(value);
for (uint32_t i = 0; i < representation.size() / 2u; ++i)
std::swap(representation[i], representation[representation.size() - 1 - i]);
return imageio::bit_cast<T>(representation);
}
// C++20 - string.starts_with(prefix)
[[nodiscard]] constexpr inline bool starts_with(std::string_view string, std::string_view prefix) noexcept {
return prefix == string.substr(0, prefix.size());
}
// C++20 - string.contains(char)
[[nodiscard]] constexpr inline bool contains(std::string_view string, char c) noexcept {
return string.find(c) != std::string_view::npos;
}
/// C++20 - std::identity
/// identity is a function object type whose operator() returns its argument unchanged.
struct identity {
using is_transparent = void;
template <typename T>
[[nodiscard]] constexpr inline T&& operator()(T&& arg) const noexcept {
return std::forward<T>(arg);
}
};
// -------------------------------------------------------------------------------------------------
template <typename Range, typename Comp = std::less<>, typename Proj = identity>
[[nodiscard]] constexpr inline bool is_sorted(const Range& range, Comp&& comp = {}, Proj&& proj = {}) {
return std::is_sorted(std::begin(range), std::end(range), [&](const auto& lhs, const auto& rhs) {
return comp(std::invoke(proj, lhs), std::invoke(proj, rhs));
});
}
template <typename Range, typename Comp = std::less<>, typename Proj = identity>
constexpr inline void sort(Range& range, Comp&& comp = {}, Proj&& proj = {}) {
return std::sort(std::begin(range), std::end(range), [&](const auto& lhs, const auto& rhs) {
return comp(std::invoke(proj, lhs), std::invoke(proj, rhs));
});
}
inline void to_lower_inplace(std::string& string) {
for (auto& c : string)
c = static_cast<char>(std::tolower(c));
}
[[nodiscard]] inline std::string to_lower_copy(std::string string) {
to_lower_inplace(string);
return string;
}
inline void to_upper_inplace(std::string& string) {
for (auto& c : string)
c = static_cast<char>(std::toupper(c));
}
[[nodiscard]] inline std::string to_upper_copy(std::string string) {
to_upper_inplace(string);
return string;
}
inline void replace_all_inplace(std::string& string, std::string_view search, std::string_view replace) {
auto pos = string.find(search);
while (pos != std::string::npos) {
string.replace(pos, search.size(), replace);
pos = string.find(search, pos + replace.size());
}
}
[[nodiscard]] inline std::string replace_all_copy(std::string string, std::string_view search, std::string_view replace) {
replace_all_inplace(string, search, replace);
return string;
}
[[nodiscard]] inline std::string escape_json_copy(std::string string) {
replace_all_inplace(string, "\\", "\\\\");
replace_all_inplace(string, "\"", "\\\"");
replace_all_inplace(string, "\n", "\\n");
return string;
}
// -------------------------------------------------------------------------------------------------
/// Remaps the value from [from_lo..from_hi] to [to_lo..to_hi] with extrapolation
template <typename T>
[[nodiscard]] constexpr inline T remap(T value, T from_lo, T from_hi, T to_lo, T to_hi) noexcept {
return to_lo + (value - from_lo) * (to_hi - to_lo) / (from_hi - from_lo);
}
// --- UTF-8 ---------------------------------------------------------------------------------------
/**
* @internal
* @brief Given the lead byte of a UTF-8 sequence returns the expected length of the codepoint
* @param[in] leadByte The lead byte of a UTF-8 sequence
* @return The expected length of the codepoint */
[[nodiscard]] constexpr inline int sequenceLength(uint8_t leadByte) noexcept {
if ((leadByte & 0b1000'0000u) == 0b0000'0000u)
return 1;
if ((leadByte & 0b1110'0000u) == 0b1100'0000u)
return 2;
if ((leadByte & 0b1111'0000u) == 0b1110'0000u)
return 3;
if ((leadByte & 0b1111'1000u) == 0b1111'0000u)
return 4;
return 0;
}
/**
* @internal
* @brief Checks if the codepoint was coded as a longer than required sequence
* @param[in] codepoint The unicode codepoint
* @param[in] length The UTF-8 sequence length
* @return True if the sequence length was inappropriate for the given codepoint */
[[nodiscard]] constexpr inline bool isOverlongSequence(uint32_t codepoint, int length) noexcept {
if (codepoint < 0x80)
return length != 1;
else if (codepoint < 0x800)
return length != 2;
else if (codepoint < 0x10000)
return length != 3;
else
return false;
}
/**
* @internal
* @brief Checks if the codepoint is valid
* @param[in] codepoint The unicode codepoint
* @return True if the codepoint is a valid unicode codepoint */
[[nodiscard]] constexpr inline bool isCodepointValid(uint32_t codepoint) noexcept {
return codepoint <= 0x0010FFFFu
&& !(0xD800u <= codepoint && codepoint <= 0xDBFFu);
}
/**
* @internal
* @brief Safely checks and advances a UTF-8 sequence iterator to the start of the next unicode codepoint
* @param[in] it iterator to be advanced
* @param[in] end iterator pointing to the end of the range
* @return True if the advance operation was successful and the advanced codepoint was a valid UTF-8 sequence */
template <typename Iterator>
[[nodiscard]] constexpr bool advanceUTF8(Iterator& it, Iterator end) noexcept {
if (it == end)
return false;
const auto length = sequenceLength(*it);
if (length == 0)
return false;
if (std::distance(it, end) < length)
return false;
for (int i = 1; i < length; ++i) {
const auto trailByte = *(it + i);
if ((static_cast<uint8_t>(trailByte) & 0b1100'0000u) != 0b1000'0000u)
return false;
}
uint32_t codepoint = 0;
switch (length) {
case 1:
codepoint |= *it++;
break;
case 2:
codepoint |= (*it++ & 0b0001'1111u) << 6u;
codepoint |= (*it++ & 0b0011'1111u);
break;
case 3:
codepoint |= (*it++ & 0b0000'1111u) << 12u;
codepoint |= (*it++ & 0b0011'1111u) << 6u;
codepoint |= (*it++ & 0b0011'1111u);
break;
case 4:
codepoint |= (*it++ & 0b0000'0111u) << 18u;
codepoint |= (*it++ & 0b0011'1111u) << 12u;
codepoint |= (*it++ & 0b0011'1111u) << 6u;
codepoint |= (*it++ & 0b0011'1111u);
break;
}
if (!isCodepointValid(codepoint))
return false;
if (isOverlongSequence(codepoint, length))
return false;
return true;
}
/**
* @internal
* @brief Validates a UTF-8 sequence
* @param[in] text The string to be validated
* @return nullopt if the sequence is valid otherwise the first index where an invalid UTF-8 character was found */
[[nodiscard]] constexpr inline std::optional<std::size_t> validateUTF8(std::string_view text) noexcept {
auto it = text.begin();
const auto end = text.end();
while (it != end) {
if (!advanceUTF8(it, end))
return std::distance(text.begin(), it);
}
return std::nullopt;
}
// -------------------------------------------------------------------------------------------------
[[nodiscard]] inline std::string fmtInFile(std::string_view filepath) {
return filepath == "-" ? std::string("stdin") : std::string(filepath);
}
[[nodiscard]] inline std::string fmtOutFile(std::string_view filepath) {
return filepath == "-" ? std::string("stdout") : std::string(filepath);
}
// -------------------------------------------------------------------------------------------------
struct PrintIndent {
std::ostream& os;
int indentBase = 0;
int indentWidth = 4;
public:
template <typename Fmt, typename... Args>
inline void operator()(int depth, Fmt&& fmt, Args&&... args) {
fmt::print(os, "{:{}}", "", indentWidth * (indentBase + depth));
fmt::print(os, std::forward<Fmt>(fmt), std::forward<Args>(args)...);
}
};
[[nodiscard]] inline std::string errnoMessage() {
return std::make_error_code(static_cast<std::errc>(errno)).message();
}
// -------------------------------------------------------------------------------------------------
/// RAII Handler for ktxTexture
class KTXTexture2 final {
private:
ktxTexture2* handle_ = nullptr;
public:
explicit KTXTexture2(std::nullptr_t) : handle_{nullptr} { }
explicit KTXTexture2(ktxTexture2* handle) : handle_{handle} { }
KTXTexture2(const KTXTexture2&) = delete;
KTXTexture2& operator=(const KTXTexture2&) = delete;
KTXTexture2(KTXTexture2&& other) noexcept :
handle_{other.handle_} {
other.handle_ = nullptr;
}
KTXTexture2& operator=(KTXTexture2&& other) & {
handle_ = other.handle_;
other.handle_ = nullptr;
return *this;
}
~KTXTexture2() {
if (handle_ != nullptr) {
ktxTexture_Destroy(ktxTexture(handle_));
handle_ = nullptr;
}
}
inline ktxTexture2* handle() const {
return handle_;
}
inline ktxTexture2** pHandle() {
return &handle_;
}
/*implicit*/ inline operator ktxTexture*() {
return ktxTexture(handle_);
}
/*implicit*/ inline operator ktxTexture2*() {
return handle_;
}
inline ktxTexture2* operator->() const {
return handle_;
}
};
template <typename T>
struct ClampedOption {
ClampedOption(T& option, T min_v, T max_v) : option(option), min(min_v), max(max_v) {}
void clear() {
option = 0;
}
operator T() const {
return option;
}
T operator=(T v) {
option = std::clamp<T>(v, min, max);
return option;
}
T& option;
T min;
T max;
};
} // namespace ktx
File diff suppressed because it is too large Load Diff
+98
View File
@@ -0,0 +1,98 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <cstdint>
#include <functional>
#include <string>
#include "validation_messages.h"
#include "command.h"
// -------------------------------------------------------------------------------------------------
namespace ktx {
static constexpr uint32_t MAX_NUM_DFD_BLOCKS = 10;
static constexpr uint32_t MAX_NUM_BDFD_SAMPLES = 16;
static constexpr uint32_t MAX_NUM_KV_ENTRIES = 100;
struct DFDHeader {
uint32_t vendorId: 17;
uint32_t descriptorType: 15;
uint32_t versionNumber: 16;
uint32_t descriptorBlockSize: 16;
};
static_assert(sizeof(DFDHeader) == 8);
struct BDFD {
uint32_t vendorId: 17;
uint32_t descriptorType: 15;
uint32_t versionNumber: 16;
uint32_t descriptorBlockSize: 16;
uint32_t model: 8;
uint32_t primaries: 8;
uint32_t transfer: 8;
uint32_t flags: 8;
uint32_t texelBlockDimension0: 8;
uint32_t texelBlockDimension1: 8;
uint32_t texelBlockDimension2: 8;
uint32_t texelBlockDimension3: 8;
std::array<uint8_t, 8> bytesPlanes;
[[nodiscard]] bool matchTexelBlockDimensions(uint8_t dim0, uint8_t dim1, uint8_t dim2, uint8_t dim3) const {
return texelBlockDimension0 == dim0
&& texelBlockDimension1 == dim1
&& texelBlockDimension2 == dim2
&& texelBlockDimension3 == dim3;
}
[[nodiscard]] bool hasNonZeroBytePlane() const {
return bytesPlanes[0] != 0 || bytesPlanes[1] != 0
|| bytesPlanes[2] != 0 || bytesPlanes[3] != 0
|| bytesPlanes[4] != 0 || bytesPlanes[5] != 0
|| bytesPlanes[6] != 0 || bytesPlanes[7] != 0;
}
};
static_assert(sizeof(BDFD) == 24);
struct SampleType {
uint32_t bitOffset: 16;
uint32_t bitLength: 8;
uint32_t channelType: 4;
uint32_t qualifierLinear: 1;
uint32_t qualifierExponent: 1;
uint32_t qualifierSigned: 1;
uint32_t qualifierFloat: 1;
uint32_t samplePosition0: 8;
uint32_t samplePosition1: 8;
uint32_t samplePosition2: 8;
uint32_t samplePosition3: 8;
uint32_t lower;
uint32_t upper;
};
static_assert(sizeof(SampleType) == 16);
struct ValidationReport {
IssueType type;
uint16_t id;
std::string message;
std::string details;
};
/// Common function for tools to validates the input file (and rewind the stream)
/// @param stream the stream to be validated
/// @param inputFilepath only used for logging
/// @param report
/// @throw FatalError if there was any error or the file is considered invalid
void validateToolInput(std::istream& stream, const std::string& inputFilepath, Reporter& report);
int validateIOStream(std::istream& stream, const std::string& filepath, bool warningsAsErrors, bool GLTFBasisU, std::function<void(const ValidationReport&)> callback);
int validateMemory(const char* data, std::size_t size, bool warningsAsErrors, bool GLTFBasisU, std::function<void(const ValidationReport&)> callback);
int validateNamedFile(const std::string& filepath, bool warningsAsErrors, bool GLTFBasisU, std::function<void(const ValidationReport&)> callback);
int validateStdioStream(FILE* file, const std::string& filepath, bool warningsAsErrors, bool GLTFBasisU, std::function<void(const ValidationReport&)> callback);
} // namespace ktx
+800
View File
@@ -0,0 +1,800 @@
// Copyright 2022-2023 The Khronos Group Inc.
// Copyright 2022-2023 RasterGrid Kft.
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <cassert>
#include <cstdint>
#include <string_view>
// -------------------------------------------------------------------------------------------------
namespace ktx {
using IssueID = uint16_t;
enum class IssueType : uint8_t {
warning,
error,
fatal,
};
[[nodiscard]] constexpr inline std::string_view toString(IssueType value) noexcept {
switch (value) {
case IssueType::warning:
return "warning";
case IssueType::error:
return "error";
case IssueType::fatal:
return "fatal";
}
assert(false && "Invalid IssueType enum value");
return "<<invalid>>";
}
struct Issue {
IssueType type;
IssueID id;
std::string_view message;
std::string_view detailsFmt;
constexpr inline Issue(IssueType type, IssueID id, std::string_view message, std::string_view detailsFmt) :
type(type),
id(id),
message(message),
detailsFmt(detailsFmt) {}
};
struct IssueWarning : Issue {
constexpr inline IssueWarning(IssueID id, std::string_view message, std::string_view detailsFmt) :
Issue(IssueType::warning, id, message, detailsFmt) {}
};
struct IssueError : Issue {
constexpr inline IssueError(IssueID id, std::string_view message, std::string_view detailsFmt) :
Issue(IssueType::error, id, message, detailsFmt) {}
};
struct IssueFatal : Issue {
constexpr inline IssueFatal(IssueID id, std::string_view message, std::string_view detailsFmt) :
Issue(IssueType::fatal, id, message, detailsFmt) {}
};
// -------------------------------------------------------------------------------------------------
struct IOError {
static constexpr IssueFatal FileOpen{
1001, "Failed to open file.",
"Failed to open file {}: {}."
};
static constexpr IssueFatal FileReadFailure{
1002, "Failed to read from file.",
"Requested {} bytes but only read {} byte(s) at offset {} to access {}. File error {}."
};
static constexpr IssueFatal UnexpectedEOF{
1003, "Unexpected end of file.",
"Unexpected end of file. Failed to read {} byte(s) at offset {} to access {}. Only able to read {} byte(s)."
};
// static constexpr IssueFatal FileSeekEndFailure{
// 1004, "Failed to seek to the end of the file.",
// "Failed to seek to the end of the file: {}."
// };
// static constexpr IssueFatal FileTellFailure{
// 1005, "Failed to determine the size of the file.",
// "Failed to determine the size of the file: {}."
// };
static constexpr IssueFatal RewindFailure{
1006, "Failed to seek to the start of the file.",
"Failed to seek to the start of the file: {}."
};
static constexpr IssueFatal FileSeekFailure{
1007, "Failed to seek in the file.",
"Failed to seek to {} to access {}. File error {}."
};
};
struct FileError {
static constexpr IssueFatal NotKTX2{
2001, "Not a KTX2 file.",
"Not a KTX2 file. The beginning of the file does not match the expected file identifier \"«KTX 20»\\r\\n\\x1A\\n\"."
};
// static constexpr IssueFatal IncorrectDataSize{
// 2003, "Size of image data in file does not match size calculated from levelIndex."
// };
};
struct HeaderData {
// 30xx - KTX Header related issues
static constexpr IssueError ProhibitedFormat{
3001, "Prohibited VkFormat.",
"VkFormat {} is prohibited in a KTX2 file."
};
static constexpr IssueError InvalidFormat{
3002, "Invalid VkFormat.",
"Invalid VkFormat {}."
};
static constexpr IssueWarning UnknownFormat{
3003, "Unknown VkFormat. Possibly an extension format.",
"Unknown VkFormat {}, possibly an extension format."
};
static constexpr IssueError VkFormatAndBasis{
3004, "Invalid VkFormat. VkFormat must be VK_FORMAT_UNDEFINED for BASIS_LZ supercompression.",
"VkFormat is {} but for supercompressionScheme BASIS_LZ it must be VK_FORMAT_UNDEFINED."
};
static constexpr IssueError TypeSizeNotOne{
3005, "Invalid typeSize. typeSize must be 1 for block-compressed or supercompressed formats.",
"typeSize is {} but for block-compressed or supercompressed format {} it must be 1."
};
static constexpr IssueError WidthZero{
3006, "Invalid pixelWidth. pixelWidth must not be 0.",
"pixelWidth is 0 but textures must have width."
};
static constexpr IssueError BlockCompressedNoHeight{
3007, "Invalid pixelHeight. pixelHeight must not be 0 for a block compressed formats.",
"pixelHeight is 0 but for block-compressed {} it must not be 0."
};
static constexpr IssueError CubeHeightWidthMismatch{
3008, "Mismatching pixelWidth and pixelHeight for a cube map.",
"pixelWidth is {} and pixelHeight is {} but for a cube map they must be equal."
};
static constexpr IssueError DepthNoHeight{
3009, "Invalid pixelHeight. pixelHeight must not be 0 if pixelDepth is not also 0.",
"pixelHeight is 0 and pixelDepth is {} but pixelHeight must not be 0 if pixelDepth is not 0 as well."
};
static constexpr IssueError DepthBlockCompressedNoDepth{
3010, "Invalid pixelDepth. pixelDepth must not be 0 for block-compressed formats with non-zero block depth.",
"pixelDepth is 0 but for {} (which is a block-compressed format with non-zero block depth) it must not be 0."
};
static constexpr IssueError DepthStencilFormatWithDepth{
3011, "Invalid pixelDepth. pixelDepth must be 0 for depth or stencil formats.",
"pixelDepth is {} but for depth or stencil format {} it must be 0."
};
static constexpr IssueError TypeSizeMismatch{
3012, "Invalid typeSize. The value must match the expected typeSize of the VkFormat.",
"typeSize is {} but for VkFormat {} it must be {}."
};
static constexpr IssueError CubeWithDepth{
3013, "Invalid pixelDepth. pixelDepth must be 0 for cube maps.",
"pixelDepth is {} but for cube maps it must be 0 (cube map faces must be 2D)."
};
static constexpr IssueWarning ThreeDArray{
3014, "File contains a 3D array texture.",
"File contains a 3D array texture. No API supports these."
};
static constexpr IssueError InvalidFaceCount{
3015, "Invalid faceCount. faceCount must be either 6 for Cubemaps and Cubemap Arrays or 1 otherwise.",
"faceCount is {} but it must be either 6 for Cubemaps and Cubemap Arrays or 1 otherwise."
};
static constexpr IssueError TooManyMipLevels{
3016, "Too many mip levels.",
"levelCount is {} but for the largest image dimension which is {} it is too many level."
};
static constexpr IssueError BlockCompressedNoLevel{
3017, "Invalid levelCount. levelCount cannot be 0 for block-compressed formats.",
"levelCount is 0 but for block-compressed {} it must not be 0."
};
static constexpr IssueWarning VendorSupercompression{
3018, "Using vendor supercompressionScheme. Cannot validate.",
"supercompressionScheme is {} which falls into the reserved vendor range. Cannot validate."
};
static constexpr IssueError InvalidSupercompression{
3019, "Invalid supercompressionScheme.",
"Invalid supercompressionScheme: {}."
};
// Header index related issues:
static constexpr IssueError IndexDFDMissing{
3020, "Missing Data Format Descriptor.",
"Data Format Descriptor is mandatory but dfdByteOffset is {} and dfdByteLength is {}."
};
static constexpr IssueError IndexDFDInvalidOffset{
3021, "Invalid dfdByteOffset.",
"dfdByteOffset is {} but the Data Format Descriptor must immediately follow (with 4 byte alignment) the Level Index so it must be {}."
};
static constexpr IssueError IndexKVDInvalidOffset{
3022, "Invalid kvdByteOffset.",
"kvdByteOffset is {} but the Key/Value Data must immediately follow (with 4 byte alignment) the Data Format Descriptor so it must be {}."
};
static constexpr IssueError IndexKVDOffsetWithoutLength{
3023, "kvdByteOffset must be 0 when kvdByteLength is 0.",
"kvdByteOffset is {} but must be 0 when kvdByteLength is 0."
};
static constexpr IssueError IndexSGDInvalidOffset{
3024, "Invalid sgdByteOffset.",
"sgdByteOffset is {} but the Supercompression Global Data must immediately follow (with 8 byte alignment) the preceding block so it must be {}."
};
static constexpr IssueError IndexSGDOffsetWithoutLength{
3025, "sgdByteOffset must be 0 when sgdByteLength is 0.",
"sgdByteOffset is {} but must be 0 when sgdByteLength is 0."
};
static constexpr IssueError IndexSGDMissing{
3026, "sgdByteLength must not be 0 for supercompression schemes with global data.",
"sgdByteLength is 0 but for supercompression scheme {} (which has global data) it must not be 0."
};
static constexpr IssueError IndexSGDNotApplicable{
3027, "sgdByteLength must be 0 for supercompression schemes with no global data.",
"sgdByteLength is {} but for supercompression scheme {} (which does not have global data) it must be 0."
};
static constexpr IssueError IndexDFDInvalidLength{
3028, "Invalid dfdByteLength. If there is Key/Value Data the dfdByteLength/dfdTotalSize must be equal to kvdByteOffset - dfdByteOffset.",
"dfdByteLength is {} but it must be equal to kvdByteOffset - dfdByteOffset which is {}."
};
// 31xx - GLTF KHR_texture_basisu compatibility
static constexpr IssueError InvalidSupercompressionGLTFBU{
3101, "Invalid supercompressionScheme for KHR_texture_basisu compatibility.",
"supercompressionScheme is {} but it must either be BASIS_LZ for ETC1S or either NONE or ZSTD for UASTC textures for KHR_texture_basisu compatibility."
};
static constexpr IssueError InvalidTextureTypeGLTFBU{
3102, "Texture type must be 2D for KHR_texture_basisu compatibility.",
"Texture type is not 2D as {} is {} instead of {} which is incompatible with KHR_texture_basisu requirements."
};
static constexpr IssueError InvalidPixelWidthHeightGLTFBU{
3103, "pixelWidth and pixelHeight must be multiples of 4 for KHR_texture_basisu compatibility.",
"{} is {} which is not an integer multiple of 4 as required for KHR_texture_basisu compatibility."
};
static constexpr IssueError InvalidLevelCountGLTFBU{
3104, "When multiple mip levels are present KHR_texture_basisu requires a full mip pyramid.",
"levelCount is {} but it must be 1 (single level) or {} (full mip pyramid) for KHR_texture_basisu compatibility."
};
};
struct LevelIndex {
// 40xx - Level index related issues
static constexpr IssueError IncorrectIndexOrder{
4001, "Invalid Level Index. Indices must be sorted from the largest level to the smallest level.",
"Indexes for level {} with uncompressedByteLength {} and level {} with uncompressedByteLength {} are incorrectly ordered."
};
static constexpr IssueError IncorrectLevelOrder{
4002, "Invalid Level Index. Level images must be sorted from the smallest level to the largest level in the file.",
"Level Image for level {} with byteOffset {} and level {} with byteOffset {} are incorrectly ordered."
};
static constexpr IssueError IncorrectByteOffsetUnaligned{
4003, "Invalid byteOffset in Level Index. byteOffset has to be aligned to lcm(texel_block_size, 4) and must match expected value.",
"Level {} byteOffset is {} but based on the vkFormat, DFD and image sizes the required alignment is {} and the expected value is {}."
};
static constexpr IssueError IncorrectByteOffset{
4004, "Invalid byteOffset in Level Index. byteOffset must match the expected value.",
"Level {} byteOffset is {} but based on the vkFormat, DFD and image sizes the expected value is {}."
};
static constexpr IssueError IncorrectByteLength{
4005, "Invalid byteLength in Level Index. byteLength must match the expected value.",
"Level {} byteLength is {} but based on the vkFormat, DFD and image sizes the expected value is {}."
};
static constexpr IssueError IncorrectUncompressedByteLength{
4006, "Invalid uncompressedByteLength in Level Index. For non-supercompressed files the uncompressedByteLength must match the expected value of byteLength.",
"Level {} uncompressedByteLength is {} but based on the vkFormat, DFD and image sizes the expected value is {}."
};
static constexpr IssueError NonZeroUBLForBLZE{
4007, "Invalid uncompressedByteLength in Level Index. For BasisLZ supercompression uncompressedByteLength must be 0.",
"Level {} uncompressedByteLength is {} but for BasisLZ supercompression it must be 0."
};
static constexpr IssueError UncompressedByteLengthMismatch{
4008, "Mismatch between uncompresedByteLength in Level Index and actually decompressed bytes.",
"Decompressing supercompression {} resulted in a different number of bytes than expected according to uncompressedByteLength."
};
static constexpr IssueError ZeroUncompressedLength{
4009, "Invalid uncompressedByteLength in Level Index. For non-BasisLZ files with VK_FORMAT_UNDEFINED uncompressedByteLength must not be 0.",
"Level {} uncompressedByteLength is 0 but for non-BasisLZ files with VK_FORMAT_UNDEFINED uncompressedByteLength must not be 0."
};
static constexpr IssueError InvalidUncompressedLength{
4010, "Invalid uncompressedByteLength in Level Index. uncompressedByteLength must be equally divisible between every face and layer.",
"Level {} uncompressedByteLength is {} but it must be divisible with faceCount * max(1, layerCount)."
};
};
struct Validator {
// 50xx - Validator or KTX Library related issues
static constexpr IssueError CreateExpectedDFDFailure{
5001, "Failed to create expected DFD for the given VkFormat.",
"Failed to create expected DFD for the given VkFormat {}."
};
static constexpr IssueError CreateDFDRoundtripFailed{
5002, "Failed to re-interpret expected DFD.",
"DFD created for VkFormat {} confused interpretDFD()."
};
static constexpr IssueWarning UnsupportedFeature{
5003, "Feature not supported by libktx.",
"KTX 2.0 file is valid but it is not currently supported by libktx."
};
static constexpr IssueWarning SupportedNonConformantFile{
5004, "Non-conformant texture file accepted by libktx.",
"KTX 2.0 file does not conform to the specification but it is currently accepted by libktx."
};
static constexpr IssueFatal CreateFailure{
5005, "Failed to load texture using libktx.",
"KTX 2.0 file is valid but libktx loading returned error: {}"
};
static constexpr IssueError DecompressChecksumError{
5006, "Checksum error during decompression.",
"Decompressing supercompression {} resulted in a checksum error."
};
static constexpr IssueError TranscodeFailure{
5007, "Failed to transcode texture.",
"Transcoding of texture with color model {} failed with the error: {}"
};
};
struct DFD {
// 60xx - Generic DFD related issues:
static constexpr IssueError SizeMismatch{
6001, "Mismatching dfdTotalSize and dfdByteLength. dfdTotalSize must match dfdByteLength.",
"dfdTotalSize is {} but dfdByteLength is {} and they must match."
};
static constexpr IssueWarning TooManyDFDBlocks{
6002, "Too many DFD blocks. The number of DFD blocks exceeds the validator limit.",
"The number of DFD blocks exceeds the validator limit of {}. Skipping validation of the remaining {} byte(s)."
};
static constexpr IssueWarning UnknownDFDBlock{
6003, "Unrecognized DFD block.",
"DFD block #{} vendorId {} and descriptorType {} is not recognized and thus ignored."
};
static constexpr IssueError NotEnoughDataForBlockHeader{
6004, "Invalid DFD data. Not enough data left to process another DFD block header.",
"DFD has {} byte(s) unprocessed but for a valid DFD at least 8 bytes are required."
};
static constexpr IssueWarning MultipleBDFD{
6005, "Multiple basic DFD blocks.",
"DFD block #{} is a basic DFD block but one was already processed before. It will be ignored."
};
static constexpr IssueError DescriptorBlockSizeTooSmall{
6006, "DFD block descriptorBlockSize is too small.",
"DFD block #{} descriptorBlockSize is {} but has to be at least 8 bytes."
};
static constexpr IssueError DescriptorBlockSizeTooBig{
6007, "DFD block descriptorBlockSize is too big.",
"DFD block #{} descriptorBlockSize is {} but only {} byte(s) left in the DFD."
};
static constexpr IssueError MissingBDFD{
6008, "Missing basic DFD block.",
"No basic data format descriptor block is found in the DFD, or it is not the first DFD block."
};
// Basic Data Format Descriptor Block related issues:
static constexpr IssueError BasicDescriptorBlockSizeInvalid{
6009, "Basic DFD block descriptorBlockSize is invalid.",
"DFD block #{} descriptorBlockSize is {} which does not fit the criteria (descriptorBlockSize - 24) % 16 == 0 for basic DFD blocks."
};
static constexpr IssueError BasicDescriptorBlockSizeTooSmall{
6010, "Basic DFD block descriptorBlockSize is too small.",
"DFD block #{} descriptorBlockSize is {} which is smaller than the minimum size of a basic DFD block (24 bytes)."
};
static constexpr IssueError BasicVersionNotSupported{
6011, "Unsupported basic DFD block version.",
"DFD block #{} versionNumber in basic DFD block is {} but it must be KHR_DF_VERSIONNUMBER_1_3."
};
static constexpr IssueError BasicInvalidTransferFunction{
6012, "Invalid transferFunction in basic DFD block. It is not a value known to the KTX specification.",
"DFD block #{} transferFunction in basic DFD block is {} but values must be between 0 (KHR_DF_TRANSFER_UNSPECIFIED) and 18 (KHR_DF_TRANSFER_ADOBERGB)."
};
static constexpr IssueError BasicSRGBMismatch{
6013, "Invalid transferFunction in basic DFD block. For an sRGB VkFormat it must be KHR_DF_TRANSFER_SRGB.",
"DFD block #{} transferFunction in basic DFD block is {} but for VkFormat {} it must be KHR_DF_TRANSFER_SRGB."
};
static constexpr IssueError BasicNotSRGBMismatch{
6014, "Invalid transferFunction in basic DFD block. For a non-sRGB VkFormat with sRGB variants it must not be KHR_DF_TRANSFER_SRGB.",
"DFD block #{} transferFunction is KHR_DF_TRANSFER_SRGB but for VkFormat {} it must not be KHR_DF_TRANSFER_SRGB."
};
static constexpr IssueError IncorrectModelForRGB{
6015, "Invalid colorModel in basic DFD block for RGB VkFormat.",
"DFD block #{} colorModel in basic DFD block is {} but for VkFormat {} it must be KHR_DF_MODEL_RGBSDA."
};
static constexpr IssueError IncorrectModelForBlock{
6016, "Invalid colorModel in basic DFD block for block compressed VkFormat.",
"DFD block #{} colorModel in basic DFD block is {} but for VkFormat {} it must be {}."
};
static constexpr IssueError IncorrectModelFor422{
6017, "Invalid colorModel in basic DFD block for *_422_* VkFormat.",
"DFD block #{} colorModel in basic DFD block is {} but for VkFormat {} it must be KHR_DF_MODEL_YUVSDA."
};
static constexpr IssueError IncorrectModelForBLZE{
6018, "Invalid colorModel in basic DFD block for BasisLZ supercompression.",
"DFD block #{} colorModel in basic DFD block is {} but for BasisLZ supercompression it must be KHR_DF_MODEL_ETC1S."
};
static constexpr IssueError InvalidColorPrimaries{
6019, "Invalid colorPrimaries in basic DFD block.",
"DFD block #{} colorPrimaries in basic DFD block is invalid: {}."
};
static constexpr IssueError InvalidTexelBlockDimension{
6020, "Invalid texelBlockDimensions in basic DFD block.",
"DFD block #{} texel block dimensions in basic DFD block are {}x{}x{}x{} but these must be {}x{}x{}x{} for {} textures."
};
static constexpr IssueError BytesPlanesMismatch{
6021, "Invalid bytesPlanes in basic DFD block. The values do not match the expected values.",
"DFD block #{} bytesPlanes in basic DFD block are {} {} {} {} {} {} {} {} but for {} textures these must be {} {} {} {} {} {} {} {}."
};
static constexpr IssueError BytesPlane0Zero{
6023, "Invalid bytesPlane0 in basic DFD block. BytesPlane0 must be non-zero for non-supercompressed VK_FORMAT_UNDEFINED textures.",
"DFD block #{} bytesPlane0 in basic DFD block is {} but it must be non-zero for non-supercompressed VK_FORMAT_UNDEFINED textures."
};
static constexpr IssueError MultiplaneFormatsNotSupported{
6024, "Invalid bytesPlanes in basic DFD block. Multiplane formats are not supported.",
"DFD block #{} bytesPlanes in basic DFD block are {} {} {} {} {} {} {} {} but bytesPlane[1-7] must be 0 as multiplane formats are not supported."
};
static constexpr IssueError SampleCountMismatch{
6025, "Invalid sample count in basic DFD block. The sample count must match the expected sample count of the VkFormat.",
"DFD block #{} sample count in basic DFD block is {} but for VkFormat {} it must be {}."
};
static constexpr IssueError InvalidSampleCount{
6026, "Invalid sample count in basic DFD block. The sample count must match the expected sample count of the texture.",
"DFD block #{} sample count in basic DFD block is {} but for {} textures it must be {}."
};
static constexpr IssueError ZeroSamples{
6027, "Invalid sample count in basic DFD block. The sample count must be non-zero for non-supercompressed textures with VK_FORMAT_UNDEFINED.",
"DFD block #{} sample count in basic DFD block is 0 but non-supercompressed VK_FORMAT_UNDEFINED textures must have sample information."
};
static constexpr IssueError FormatMismatch{
6028, "Invalid sample in basic DFD block. The samples must match the expected samples of the VkFormat.",
"DFD block #{} sample #{} {} in basic DFD block is {} but the expected value is {} for {}."
};
static constexpr IssueWarning TooManySamples{
6029, "Too many BDFD samples. The number of BDFD samples exceeds the validator limit.",
"DFD block #{} sample count in basic DFD block is {} which exceeds the validator limit of {}. Skipping validation of the last {} sample(s) ({} byte(s))."
};
static constexpr IssueWarning BytesPlanesUnsized{
6030, "Deprecated unsized bytesPlanes in basic DFD block. Since specification 2.0.4 bytesPlanes must be sized (non-zero) for supercompressed textures.",
"DFD block #{} bytesPlanes0 in basic DFD block is {} but for {} supercompressed textures, since specification 2.0.4, it must be non-zero."
};
// 61xx - Basic Data Format Descriptor Block sample related issues:
static constexpr IssueError InvalidChannelForModel{
6101, "Invalid sample channelType for colorModel in the basic DFD block.",
"DFD block #{} sample #{} channelType in basic DFD block is {} which is not valid for colorModel {}."
};
static constexpr IssueError InvalidBitOffsetForUASTC{
6102, "Invalid sample bitOffset for UASTC texture in the basic DFD block.",
"DFD block #{} sample #{} bitOffset in basic DFD block is {} but for UASTC textures it must be 0."
};
static constexpr IssueError InvalidBitOffsetForBLZE{
6103, "Invalid sample bitOffset for BasisLZ/ETC1S texture in the basic DFD block.",
"DFD block #{} sample #{} bitOffset in basic DFD block is {} but for BasisLZ/ETC1S textures it must be {}."
};
static constexpr IssueError InvalidBitLengthForUASTC{
6104, "Invalid sample bitLength for UASTC texture in the basic DFD block.",
"DFD block #{} sample #{} bitLength in basic DFD block is {} but for UASTC textures it must be 127."
};
static constexpr IssueError InvalidBitLengthForBLZE{
6105, "Invalid sample bitLength for BasisLZ/ETC1S texture in the basic DFD block.",
"DFD block #{} sample #{} bitLength in basic DFD block is {} but for BasisLZ/ETC1S textures it must be 63."
};
static constexpr IssueError InvalidLower{
6106, "Invalid sample lower for UASTC or BasisLZ/ETC1S texture in the basic DFD block.",
"DFD block #{} sample #{} lower in basic DFD block is {} but for {} textures it must be {}."
};
static constexpr IssueError InvalidUpper{
6107, "Invalid sample upper for UASTC or BasisLZ/ETC1S texture in the basic DFD block.",
"DFD block #{} sample #{} upper in basic DFD block is {} but for {} textures it must be {}."
};
// 62xx - InterpretDFD related issues:
static constexpr IssueError InterpretDFDMixedChannels{
6203, "Mixed sample types. The Signed/Unsigned and Float/Integer flags of Basic DFD samples must be the consistent.",
"DFD block #{} has mixed Signed/Unsigned or Float/Integer samples but they must be consistent."
};
static constexpr IssueError InterpretDFDMultisample{
6204, "Unsupported multiple-sample format. Every sample position must be zero.",
"DFD block #{} indicates multiple sample locations but multisample formats are not supported."
};
static constexpr IssueError InterpretDFDNonTrivialEndianness{
6205, "Non-trivial endianness detected in the basic DFD block.",
"DFD block #{} describes non little-endian or unsupported format."
};
// 63xx - GLTF KHR_texture_basisu compatibility
static constexpr IssueError IncorrectModelGLTFBU{
6301, "Invalid colorModel in basic DFD block for KHR_texture_basisu compatibility.",
"DFD block #{} colorModel in basic DFD block is {} but for KHR_texture_basisu compatibility it must be either ETC1S or UASTC."
};
static constexpr IssueError IncompatibleModelGLTFBU{
6302, "Incompatible supercompressionScheme and colorModel for KHR_texture_basisu compatibility.",
"DFD block #{} colorModel is {} while supercompressionScheme is {} but KHR_texture_basisu requires supercompressionScheme {} for this colorModel."
};
static constexpr IssueError InvalidChannelGLTFBU{
6303, "Invalid sample channelType for colorModel for KHR_texture_basisu compatibility.",
"DFD block #{} colorModel is {} but sample #{} channelType is {} while KHR_texture_basisu requires {}."
};
static constexpr IssueError InvalidColorSpaceGLTFBU{
6304, "Color space information is incompatible with KHR_texture_basisu.",
"DFD block #{} primaries is {} and transfer is {} but KHR_texture_basisu requires either KHR_DF_PRIMARIES_BT709 with KHR_DF_TRANSFER_SRGB or KHR_DF_PRIMARIES_UNSPECIFIED with KHR_DF_TRANSFER_LINEAR."
};
};
struct Metadata {
// 70xx - Generic Key-Value related issues:
static constexpr IssueWarning TooManyEntries{
7001, "Too many Key/Value entries. The number of key-value entries exceeds the validator limit.",
"The number of key-value entries exceeds the validator limit of {}. Skipping validation of the remaining {} byte(s)."
};
static constexpr IssueError NotEnoughDataForAnEntry{
7002, "Invalid Key/Value Data. Not enough data left in Key/Value Data to process another key-value entry",
"Key/Value Data has {} byte(s) unprocessed but for a key value entry at least 6 bytes are required (4 byte size + 1 byte key + null terminator)."
};
static constexpr IssueError KeyAndValueByteLengthTooLarge{
7003, "Invalid keyAndValueByteLength. keyAndValueByteLength is greater than the amount of bytes left in the Key/Value Data.",
"keyAndValueByteLength is {} but the Key/Value Data only has {} byte(s) left for the key-value pair."
};
static constexpr IssueError KeyAndValueByteLengthTooSmall{
7004, "Invalid keyAndValueByteLength. keyAndValueByteLength must be at least 2.",
"keyAndValueByteLength is {} but it must be at least 2 (1 byte key + null terminator)."
};
static constexpr IssueError KeyMissingNullTerminator{
7005, "Invalid key in Key/Value Data. Key is missing the NULL terminator.",
"The key-value entry \"{}\" is missing the NULL terminator but every key-value entry must have a NULL terminator separating the key from the value."
};
static constexpr IssueError KeyForbiddenBOM{
7006, "Invalid key in Key/Value Data. Key must not contain BOM.",
"The beginning of the key \"{}\" has forbidden BOM."
};
static constexpr IssueError KeyInvalidUTF8{
7007, "Invalid key in Key/Value Data. Key must be a valid UTF-8 string.",
"Key is \"{}\", which contains an invalid UTF-8 character at index: {}."
};
static constexpr IssueError SizesDontAddUp{
7008, "kvdByteLength must add up to the sum of the size of the key-value entries with paddings.",
"The processed Key/Value Data length is {} byte(s) but kvdByteLength is {} byte(s) and they must match."
};
static constexpr IssueError UnknownReservedKey{
7009, "Invalid key in Key/Value Data. Keys with \"KTX\" or \"ktx\" prefix are reserved.",
"The key is \"{}\" but its not recognized and keys with \"KTX\" or \"ktx\" prefix are reserved."
};
static constexpr IssueWarning CustomMetadata{
7010, "Custom key in Key/Value Data.",
"Custom key \"{}\" found in Key/Value Data."
};
static constexpr IssueError PaddingNotZero{
7011, "Invalid padding byte value. Every padding byte's value must be 0.",
"A padding byte value is {:d} {} but it must be 0."
};
static constexpr IssueError OutOfOrder{
7012, "Invalid Key/Value Data. Key-value entries must be sorted by key.",
"Key-value entries are not sorted but they must be sorted by key.",
};
static constexpr IssueError DuplicateKey{
7013, "Invalid Key/Value Data. Keys must be unique.",
"Duplicate key-value entry with key \"{}\"."
};
static constexpr IssueError KeyEmpty{
7013, "Empty key in Key/Value Data.",
"Key length is 0 byte in key-value entry."
};
static constexpr IssueError KTXcubemapIncompleteInvalidBitSet{
7101, "Invalid KTXcubemapIncomplete value. The two MSB must be 0.",
"The value is {:08b} but the two MSB must be 0 (00XXXXXX)."
};
static constexpr IssueWarning KTXcubemapIncompleteAllBitsSet{
7102, "KTXcubemapIncomplete is not incomplete. Every face is marked present.",
"Every face bit is set as present, KTXcubemapIncomplete key is unnecessary."
};
static constexpr IssueError KTXcubemapIncompleteNoBitSet{
7103, "Invalid KTXcubemapIncomplete value. No face is marked present.",
"No face bit is set as present but at least 1 face must be present."
};
static constexpr IssueError KTXcubemapIncompleteIncompatibleLayerCount{
7104, "Incompatible KTXcubemapIncomplete and layerCount. layerCount must be the multiple of the number of faces present.",
"layerCount is {} and KTXcubemapIncomplete indicates {} faces present but layerCount must the multiple of the number of faces present."
};
static constexpr IssueError KTXcubemapIncompleteWithFaceCountNot1{
7105, "Invalid faceCount. faceCount must be 1 if KTXcubemapIncomplete is present.",
"faceCount is {} but if KTXcubemapIncomplete is present it must be 1."
};
static constexpr IssueError KTXcubemapIncompleteInvalidSize{
7106, "Invalid KTXcubemapIncomplete metadata. The size of the value must be 1 byte.",
"The size of the KTXcubemapIncomplete value is {} byte(s) but it must be 1 byte."
};
static constexpr IssueError KTXorientationMissingNull{
7107, "Invalid KTXorientation metadata. The value is missing the NULL terminator.",
"The last byte of the value must be a NULL terminator."
};
static constexpr IssueError KTXorientationIncorrectDimension{
7108, "Invalid KTXorientation value. The number of dimensions specified must match the number of dimensions in the texture type.",
"The value has {} dimension but the texture type has {} and they must match."
};
static constexpr IssueError KTXorientationInvalidValue{
7109, "Invalid KTXorientation value. The value must match /^[rl]$/ for 1D, /^[rl][du]$/ for 2D and /^[rl][du][oi]$/ for 3D texture types.",
"Dimension {} is \"{}\" but it must be either \"{}\" or \"{}\"."
};
// 71xx - Known Key-Value related issues:
static constexpr IssueError KTXglFormatInvalidSize{
7110, "Invalid KTXglFormat metadata. The size of the value must be 12 bytes.",
"The size of KTXglFormat value is {} byte(s) but it must be 12 bytes."
};
static constexpr IssueError KTXglFormatWithVkFormat{
7111, "Incompatible KTXglFormat and VkFormat. If KTXglFormat is present vkFormat must be VK_FORMAT_UNDEFINED.",
"vkFormat is {} but it must be VK_FORMAT_UNDEFINED if KTXglFormat is present."
};
static constexpr IssueError KTXglFormatInvalidValueForCompressed{
7112, "Invalid KTXglFormatInvalidValue value. glFormat and glType must be zero for compressed formats.",
"glFormat is {} and glType is {} but for compressed formats with {} both must be zero."
};
static constexpr IssueError KTXdxgiFormatInvalidSize{
7113, "Invalid KTXdxgiFormat__ metadata. The size of the value must be 4 byte.",
"The size of KTXdxgiFormat__ value is {} byte(s) but it must be 4 byte."
};
static constexpr IssueError KTXdxgiFormatWithVkFormat{
7114, "Incompatible KTXdxgiFormat__ and VkFormat. If KTXdxgiFormat__ is present vkFormat must be VK_FORMAT_UNDEFINED.",
"vkFormat is {} but it must be VK_FORMAT_UNDEFINED if KTXdxgiFormat__ is present."
};
static constexpr IssueError KTXmetalPixelFormatInvalidSize{
7115, "Invalid KTXmetalPixelFormat metadata. The size of the value must be 4 byte.",
"The size of KTXmetalPixelFormat value is {} byte(s) but it must be 4 byte."
};
static constexpr IssueError KTXmetalPixelFormatWithVkFormat{
7116, "Incompatible KTXmetalPixelFormat and VkFormat. If KTXmetalPixelFormat is present vkFormat must be VK_FORMAT_UNDEFINED.",
"vkFormat is {} but it must be VK_FORMAT_UNDEFINED if KTXmetalPixelFormat is present."
};
static constexpr IssueError KTXswizzleMissingNull{
7117, "Invalid KTXswizzle value. The value is missing the NULL terminator.",
"The last byte of the value must be a NULL terminator."
};
static constexpr IssueError KTXswizzleInvalidSize{
7118, "Invalid KTXswizzle value. The size of the value must be 5 bytes (including the NULL terminator).",
"The size of KTXswizzle value is {} byte(s) but it must be 5 bytes (including the NULL terminator)."
};
static constexpr IssueError KTXswizzleInvalidValue{
7119, "Invalid KTXswizzle value. The value must match /^[rgba01]{4}$/.",
"The character at index {} is \"{}\" but it must be one of \"rgba01\"."
};
// Unused 7120
static constexpr IssueWarning KTXswizzleWithDepthOrStencil{
7121, "KTXswizzle has no effect on depth or stencil texture formats.",
"KTXswizzle is present but for VkFormat {} it has no effect."
};
static constexpr IssueError KTXwriterMissingNull{
7122, "Invalid KTXwriter metadata. The value is missing the NULL terminator.",
"The last byte of the value must be a NULL terminator."
};
static constexpr IssueError KTXwriterInvalidUTF8{
7123, "Invalid KTXwriter value. The value must be a valid UTF8 string.",
"The value contains an invalid UTF8 character at index: {}."
};
static constexpr IssueError KTXwriterRequiredButMissing{
7124, "Missing KTXwriter metadata. When KTXwriterScParams is present KTXwriter must also be present.",
"KTXwriter metadata is missing. When KTXwriterScParams is present KTXwriter must also be present."
};
static constexpr IssueWarning KTXwriterMissing{
7125, "Missing KTXwriter metadata. Writers are strongly urged to identify themselves via this.",
"KTXwriter metadata is missing. Writers are strongly urged to identify themselves via this."
};
static constexpr IssueError KTXwriterScParamsMissingNull{
7126, "Invalid KTXwriterScParams metadata. The value is missing the NULL terminator.",
"The last byte of the value must be a NULL terminator."
};
static constexpr IssueError KTXwriterScParamsInvalidUTF8{
7127, "Invalid KTXwriterScParams value. The value must be a valid UTF8 string.",
"The value contains an invalid UTF8 character at index: {}."
};
static constexpr IssueError KTXanimDataInvalidSize{
7128, "Invalid KTXanimData metadata. The size of the value must be 12 bytes.",
"The size of KTXanimData value is {} byte(s) but it must be 12 bytes."
};
static constexpr IssueError KTXanimDataNotArray{
7129, "Invalid KTXanimData metadata. KTXanimData can only be used with array textures.",
"KTXanimData is present but with layerCount {} the texture is not an array texture."
};
static constexpr IssueError KTXanimDataWithCubeIncomplete{
7130, "Incompatible KTXanimData and KTXcubemapIncomplete metadata. KTXanimData and KTXcubemapIncomplete cannot be present at the same time.",
"Both KTXanimData and KTXcubemapIncomplete is present but they are incompatible."
};
static constexpr IssueError KTXastcDecodeModeMissingNull{
7131, "Invalid KTXastcDecodeMode metadata. The value is missing the NULL terminator.",
"The last byte of the value must be a NULL terminator."
};
static constexpr IssueError KTXastcDecodeModeInvalidValue{
7132, "Invalid KTXastcDecodeMode value. The value must be either \"rgb9e5\" or \"unorm8\".",
"The value is \"{}\" but the value must be either \"rgb9e5\" or \"unorm8\"."
};
static constexpr IssueError KTXastcDecodeModeunorm8NotLDR{
7133, "Invalid KTXastcDecodeMode value. \"unorm8\" is only valid for LDR formats.",
"The value is \"unorm8\" but it is invalid for non-LDR VkFormat {}."
};
static constexpr IssueWarning KTXastcDecodeModeNotASTC{
7134, "KTXastcDecodeMode has no effect on and should not be present in KTX files that use a non-ASTC formats.",
"KTXastcDecodeMode is present but for colorModel {} it has no effect."
};
static constexpr IssueWarning KTXastcDecodeModeWithsRGB{
7135, "KTXastcDecodeMode has no effect on and should not be present in KTX files that use the sRGB transfer function.",
"KTXastcDecodeMode is present but for transferFunction {} it has no effect."
};
// 72xx - GLTF KHR_texture_basisu compatibility
static constexpr IssueError KTXswizzleInvalidGLTFBU{
7201, "Invalid KTXswizzle metadata for KHR_texture_basisu compatibility.",
"KTXswizzle is \"{}\" but must be \"rgba\", if present, for KHR_texture_basisu compatibility."
};
static constexpr IssueError KTXorientationInvalidGLTFBU{
7202, "Invalid KTXorientation metadata for KHR_texture_basisu compatibility.",
"KTXorientation is \"{}\" but must be \"rd\", if present, for KHR_texture_basis compatibility."
};
};
struct SGD {
// 80xx - Generic SGD issues:
// Currently none
// 81xx - BASIS_LZ related issues:
static constexpr IssueError BLZESizeTooSmallHeader{
8101, "Invalid sgdByteLength for BasisLZ/ETC1S. sgdByteLength must be at least 20 bytes (sizeof ktxBasisLzGlobalHeader).",
"sgdByteLength is {} but for BasisLZ/ETC1S textures it must be at least 20 bytes (sizeof ktxBasisLzGlobalHeader)."
};
static constexpr IssueError BLZESizeIncorrect{
8102, "Invalid sgdByteLength for BasisLZ/ETC1S. sgdByteLength must be consistent with image count and BasisLzGlobalHeader.",
"sgdByteLength is {} but based on image count of {} and the BasisLzGlobalHeader the expected value is {} (20 + 20 * imageCount + endpointsByteLength + selectorsByteLength + tablesByteLength + extendedByteLength)."
};
static constexpr IssueError BLZEExtendedByteLengthNotZero{
8103, "Invalid extendedByteLength in BasisLzGlobalHeader. For BasisLZ/ETC1S the extendedByteLength must be 0.",
"extendedByteLength is {} but for BasisLZ/ETC1S it must be 0."
};
static constexpr IssueError BLZEInvalidImageFlagBit{
8104, "Invalid imageFlags in BasisLzEtc1sImageDesc.",
"For Level {} Layer {} Face {} zSlice {} the imageFlags is 0x{:08X} which has an invalid bit set."
};
static constexpr IssueError BLZENoAnimationSequencesPFrame{
8105, "Incompatible PFrame with missing KTXanimData. Only animation sequences can have PFrames.",
"There is a PFrame in a BasisLzEtc1sImageDesc but the KTXanimData is missing."
};
static constexpr IssueError BLZEZeroRGBLength{
8106, "Invalid rgbSliceByteLength in BasisLzEtc1sImageDesc. rgbSliceByteLength must not be 0.",
"For Level {} Layer {} Face {} zSlice {} the rgbSliceByteLength is {} but it must not be 0."
};
static constexpr IssueError BLZEInvalidRGBSlice{
8107, "Invalid rgbSliceByteOffset or rgbSliceByteLength. The defined byte region must be within the corresponding mip level.",
"For Level {} Layer {} Face {} zSlice {} the rgbSliceByteOffset is {} and the rgbSliceByteLength is {} but the defined region must fit in the level's byteLength of {}."
};
static constexpr IssueError BLZEInvalidAlphaSlice{
8108, "Invalid alphaSliceByteOffset or alphaSliceByteLength. The defined byte region must be within the corresponding mip level.",
"For Level {} Layer {} Face {} zSlice {} the alphaSliceByteOffset is {} and the alphaSliceByteLength is {} but the defined region must fit in the level's byteLength of {}."
};
static constexpr IssueError BLZEDFDMismatchAlpha{
8109, "Incompatible alphaSliceByteLength and DFD sampleCount. If DFD indicates an alpha slice the alphaSliceByteLength in BasisLzEtc1sImageDesc must not be 0.",
"For Level {} Layer {} Face {} zSlice {} the alphaSliceByteLength is 0 but DFD indicates an alpha slice so it must not be 0."
};
static constexpr IssueError BLZEDFDMismatchNoAlpha{
8110, "Incompatible alphaSliceByteLength and DFD sampleCount. If DFD indicates no alpha slice the alphaSliceByteLength in BasisLzEtc1sImageDesc must be 0.",
"For Level {} Layer {} Face {} zSlice {} the alphaSliceByteLength is {} but DFD indicates no alpha slice so it must be 0."
};
};
struct System {
// 90xx - Generic System issues
static constexpr IssueError OutOfMemory{
9001, "System ran out of memory during a validation step.",
"An allocation failed during {} validation: {}."
};
};
// -------------------------------------------------------------------------------------------------
} // namespace ktx
+7
View File
@@ -0,0 +1,7 @@
/*
// [Code version]
exitcode-128-NOTFOUND
// [Code version]
*/
#define KTX_VERSION exitcode-128-NOTFOUND
#define KTX_DEFAULT_VERSION v4.0.__default__
+38
View File
@@ -0,0 +1,38 @@
# Copyright 2017-2020 The Khronos Group Inc.
# SPDX-License-Identifier: Apache-2.0
add_executable( ktx2check
ktx2check.cpp
)
create_version_header( tools/ktx2check ktx2check )
set_target_properties(
ktx2check PROPERTIES
CXX_VISIBILITY_PRESET ${STATIC_APP_LIB_SYMBOL_VISIBILITY}
)
target_compile_features(
ktx2check
PUBLIC
# C++14 for std::make_unique on AppleClang and Linux GCC
cxx_std_14
)
target_include_directories(
ktx2check
PRIVATE
.
$<TARGET_PROPERTY:ktx,INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:objUtil,INTERFACE_INCLUDE_DIRECTORIES>
${PROJECT_SOURCE_DIR}/lib
${PROJECT_SOURCE_DIR}/other_include
)
target_link_libraries(
ktx2check
ktx
objUtil
)
set_tool_properties(ktx2check)
set_code_sign(ktx2check)
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
/*
// [Code version]
exitcode-128-NOTFOUND
// [Code version]
*/
#define KTX2CHECK_VERSION exitcode-128-NOTFOUND
#define KTX2CHECK_DEFAULT_VERSION v4.0.__default__
+26
View File
@@ -0,0 +1,26 @@
# Copyright 2017-2020 The Khronos Group Inc.
# SPDX-License-Identifier: Apache-2.0
add_executable( ktx2ktx2
ktx2ktx2.cpp
)
create_version_header( tools/ktx2ktx2 ktx2ktx2 )
target_include_directories(
ktx2ktx2
PRIVATE
.
$<TARGET_PROPERTY:ktx,INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:objUtil,INTERFACE_INCLUDE_DIRECTORIES>
${PROJECT_SOURCE_DIR}/lib
${PROJECT_SOURCE_DIR}/other_include
)
target_link_libraries(
ktx2ktx2
ktx
objUtil
)
set_tool_properties(ktx2ktx2)
set_code_sign(ktx2ktx2)
+434
View File
@@ -0,0 +1,434 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 sts=4 expandtab:
// Copyright 2019-2020 The Khronos Group, Inc.
// SPDX-License-Identifier: Apache-2.0
// To use, download from http://www.billbaxter.com/projects/imdebug/
// Put imdebug.dll in %SYSTEMROOT% (usually C:\WINDOWS), imdebug.h in
// ../../include, imdebug.lib in ../../build/msvs/<platform>/vs<ver> &
// add ..\imdebug.lib to the libraries list in the project properties.
#define IMAGE_DEBUG 0
#include "ktxapp.h"
#include <cstdlib>
#include <errno.h>
#include <iostream>
#include <sstream>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "version.h"
#if (IMAGE_DEBUG) && defined(_DEBUG) && defined(_WIN32) && !defined(_WIN32_WCE)
# include "imdebug.h"
#elif defined(IMAGE_DEBUG) && IMAGE_DEBUG
# undef IMAGE_DEBUG
# define IMAGE_DEBUG 0
#endif
#if defined(_MSC_VER)
#define strncasecmp _strnicmp
#define fileno _fileno
#define isatty _isatty
#endif
#if IMAGE_DEBUG
static void dumpImage(char* name, int width, int height, int components,
int componentSize, bool isLuminance,
unsigned char* srcImage);
#endif
using namespace std;
/** @page ktx2ktx2 ktx2ktx2
@~English
Create a KTX 2 file from a KTX file.
@section ktx2ktx2_synopsis SYNOPSIS
ktx2ktx2 [options] [@e infile ...]
@section ktx2ktx2_description DESCRIPTION
@b ktx2ktx2 creates Khronos texture format version 2 files (KTX2) from
Khronos texture format version 1 files. @b ktx2ktx2 reads each named
@e infile. Output files have the same name as the input but with the
extension changed to @c .ktx2. When @b infile is not specified, a single
file will be read from stdin and the output written to standard out.
If unrecognized metadata with keys beginning "KTX" or "ktx" is found in
the input file, it is dropped and a warning is written to standard error.
The following options are available:
<dl>
<dt>-b, \--rewritebado</dt>
<dd>Rewrite bad orientation metadata. Some in-the-wild KTX files
have orientation metadata with the key "KTXOrientation"
instead of KTXorientaion. This option will rewrite such
bad metadata instead of dropping it.
<dt>-o outfile, --output=outfile</dt>
<dd>Name the output file @e outfile. If @e outfile is 'stdout', output will
be written to stdout. If there is more than 1 input file, the command
prints its usage message and exits.</dd>
<dt>-f, \--force</dt>
<dd>If the destination file already exists, remove it and create a
new file, without prompting for confirmation regardless of its
permissions.</dd>
</dl>
@snippet{doc} ktxapp.h ktxApp options
@section ktx2ktx2_exitstatus EXIT STATUS
@b ktx2ktx2 exits 0 on success, 1 on command line errors and 2 on
functional errors.
@section ktx2ktx2_history HISTORY
@par Version 4.0
- Initial version.
@section ktx2ktx2_author AUTHOR
Mark Callow, github.com/MarkCallow
*/
#define QUOTE(x) #x
#define STR(x) QUOTE(x)
std::string myversion(STR(KTX2KTX2_VERSION));
std::string mydefversion(STR(KTX2KTX2_DEFAULT_VERSION));
class ktxUpgrader : public ktxApp {
public:
ktxUpgrader();
virtual int main(int argc, char* argv[]);
virtual void usage();
protected:
virtual bool processOption(argparser& parser, int opt);
void validateOptions();
struct commandOptions : public ktxApp::commandOptions {
bool useStdout;
bool force;
bool rewriteBadOrientation;
commandOptions() {
useStdout = false;
force = false;
rewriteBadOrientation = false;
}
} options;
};
ktxUpgrader::ktxUpgrader() : ktxApp(myversion, mydefversion, options)
{
argparser::option my_option_list[] = {
{ "force", argparser::option::no_argument, NULL, 'f' },
{ "outfile", argparser::option::required_argument, NULL, 'o' },
{ "rewritebado", argparser::option::no_argument, NULL, 'b' },
};
const int lastOptionIndex = sizeof(my_option_list)
/ sizeof(argparser::option);
option_list.insert(option_list.begin(), my_option_list,
my_option_list + lastOptionIndex);
short_opts += "bd:fo:";
}
void
ktxUpgrader::usage()
{
cerr <<
"Usage: " << name << " [options] [<infile> ...]\n"
"\n"
" infile The source ktx file. The output is written to a file of the\n"
" same name with the extension changed to '.ktx2'. If it is not\n"
" specified input will be read from stdin and the converted texture\n"
" written to stdout.\n"
"\n"
" Options are:\n"
"\n"
" -b, --rewritebado\n"
" Rewrite bad orientation metadata. Some in-the-wild KTX files\n"
" have orientation metadata with the key \"KTXOrientation\"\n"
" instead of \"KTXorientaion\". This option will rewrite such\n"
" bad metadata instead of dropping it.\n"
" -o outfile, --output=outfile\n"
" Name the output file outfile. If @e outfile is 'stdout', output\n"
" will be written to stdout. If there is more than 1 infile,\n"
" the command prints its usage message and exits.\n"
" -f, --force If the output file already exists, remove it and create a\n"
" new file, without prompting for confirmation regardless of\n"
" its permissions.\n";
ktxApp::usage();
}
static ktxUpgrader ktx2ktx2;
ktxApp& theApp = ktx2ktx2;
int
ktxUpgrader::main(int argc, char* argv[])
{
FILE *inf, *outf = nullptr;
KTX_error_code result;
ktxTexture1* texture = 0;
int exitCode = 0;
processCommandLine(argc, argv);
validateOptions();
std::vector<string>::const_iterator it;
for (it = options.infiles.begin(); it < options.infiles.end(); it++) {
string infile = *it;
string outfile;
if (infile.compare("-") == 0) {
inf = stdin;
#if defined(_WIN32)
/* Set "stdin" to have binary mode */
(void)_setmode( _fileno( stdin ), _O_BINARY );
#endif
} else {
inf = fopenUTF8(infile, "rb");
}
if (inf) {
if (infile.compare("-")
&& !options.useStdout && !options.outfile.length())
{
size_t dot;
outfile = infile;
dot = outfile.find_last_of('.');
if (dot !=string::npos) {
outfile.erase(dot,string::npos);
}
outfile += ".ktx2";
} else if (options.outfile.length()) {
outfile = options.outfile;
}
if (options.useStdout) {
outf = stdout;
#if defined(_WIN32)
/* Set "stdout" to have binary mode */
(void)_setmode( _fileno( stdout ), _O_BINARY );
#endif
} else if (outfile.length()) {
outf = fopen_write_if_not_exists(outfile);
}
if (!outf && errno == EEXIST) {
bool force = options.force;
if (!force) {
if (isatty(fileno(stdin))) {
char answer;
cout << "Output file " << options.outfile
<< " exists. Overwrite? [Y or n] ";
cin >> answer;
if (answer == 'Y') {
force = true;
}
}
}
if (force) {
outf = fopenUTF8(outfile, "wb");
}
}
if (outf) {
result = ktxTexture1_CreateFromStdioStream(inf,
KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
&texture);
if (result != KTX_SUCCESS) {
if (result == KTX_UNKNOWN_FILE_FORMAT) {
cerr << infile << " is not a KTX v1 file." << endl;
} else if (result != KTX_SUCCESS) {
cerr << name
<< " failed to create ktxTexture from " << infile
<< ": " << ktxErrorString(result) << endl;
}
(void)fclose(inf);
(void)fclose(outf);
(void)unlinkUTF8(options.outfile.c_str());
exitCode = 2;
goto cleanup;
}
// Some in-the-wild KTX files have incorrect KTXOrientation
// Warn about dropping invalid metadata.
ktxHashListEntry* pEntry;
for (pEntry = texture->kvDataHead;
pEntry != NULL;
pEntry = ktxHashList_Next(pEntry)) {
unsigned int keyLen;
char* key;
ktxHashListEntry_GetKey(pEntry, &keyLen, &key);
if (strncasecmp(key, "KTX", 3) == 0) {
if (strcmp(key, KTX_ORIENTATION_KEY)
&& strcmp(key, KTX_WRITER_KEY)) {
if (strcmp(key, "KTXOrientation") == 0
&& options.rewriteBadOrientation) {
unsigned int orientLen;
char* orientation;
ktxHashListEntry_GetValue(pEntry,
&orientLen,
(void**)&orientation);
ktxHashList_AddKVPair(&texture->kvDataHead,
KTX_ORIENTATION_KEY,
orientLen,
orientation);
} else {
cerr << name
<< ": Warning: Dropping unrecognized "
<< "metadata \"" << key << "\""
<< std::endl;
}
ktxHashList_DeleteEntry(&texture->kvDataHead,
pEntry);
}
}
}
// Add required writer metadata.
std::stringstream writer;
writeId(writer, options.test != 0);
ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_KEY,
(ktx_uint32_t)writer.str().length() + 1,
writer.str().c_str());
result = ktxTexture1_WriteKTX2ToStdioStream(texture, outf);
ktxTexture_Destroy(ktxTexture(texture));
(void)fclose(inf);
(void)fclose(outf);
if (result != KTX_SUCCESS) {
cerr << name
<< " failed to write KTX2 file; "
<< ktxErrorString(result) << endl;
(void)unlinkUTF8(options.outfile.c_str());
exitCode = 2;
goto cleanup;
}
} else {
cerr << name
<< " could not open output file \""
<< (options.outfile.length() ? options.outfile.c_str()
: "stdout")
<< "\". " << strerror(errno) << endl;
exitCode = 2;
goto cleanup;
}
} else {
cerr << name
<< " could not open input file \""
<< (infile.compare("-") ? infile : "stdin") << "\". "
<< strerror(errno) << endl;
exitCode = 2;
goto cleanup;
}
}
cleanup:
return exitCode;
}
void
ktxUpgrader::validateOptions()
{
if (options.infiles.size() > 1 && options.outfile.length()) {
cerr << "Can't use -o when there are multiple infiles." << endl;
usage();
exit(1);
}
}
/*
* @brief process a command line option
*
* @return true if option processed, false otherwise.
*
* @param[in] parser, an @c argparser holding the options to process.
* @param[in,out] options commandOptions struct in which option information
* is set.
*/
bool
ktxUpgrader::processOption(argparser& parser, int opt)
{
switch (opt) {
case 'b':
options.rewriteBadOrientation = true;
break;
case 'f':
options.force = true;
break;
case 'o':
options.outfile = parser.optarg;
if (!options.outfile.compare("stdout")) {
options.useStdout = true;
} else {
size_t dot;
dot = options.outfile.find_last_of('.');
if (dot ==string::npos) {
options.outfile += ".ktx2";
}
}
break;
default:
return false;
}
return true;
}
#if IMAGE_DEBUG
static void
dumpImage(char* name, int width, int height, int components, int componentSize,
bool isLuminance, unsigned char* srcImage)
{
char formatstr[2048];
char *imagefmt;
char *fmtname;
int bitsPerComponent = componentSize == 2 ? 16 : 8;
switch (components) {
case 1:
if (isLuminance) {
imagefmt = "lum b=";
fmtname = "LUMINANCE";
} else {
imagefmt = "a b=";
fmtname = "ALPHA";
}
break;
case 2:
imagefmt = "luma b=";
fmtname = "LUMINANCE_ALPHA";
break;
case 3:
imagefmt = "rgb b=";
fmtname = "RGB";
break;
case 4:
imagefmt = "rgba b=";
fmtname = "RGBA";
break;
default:
assert(0);
}
sprintf(formatstr, "%s%d w=%%d h=%%d t=\'%s %s%d\' %%p",
imagefmt,
bitsPerComponent,
name,
fmtname,
bitsPerComponent);
imdebug(formatstr, width, height, srcImage);
}
#endif
+7
View File
@@ -0,0 +1,7 @@
/*
// [Code version]
exitcode-128-NOTFOUND
// [Code version]
*/
#define KTX2KTX2_VERSION exitcode-128-NOTFOUND
#define KTX2KTX2_DEFAULT_VERSION v4.0.__default__
+25
View File
@@ -0,0 +1,25 @@
# Copyright 2017-2020 The Khronos Group Inc.
# SPDX-License-Identifier: Apache-2.0
add_executable( ktxinfo
ktxinfo.cpp
)
create_version_header( tools/ktxinfo ktxinfo )
target_include_directories(
ktxinfo
PRIVATE
.
$<TARGET_PROPERTY:ktx,INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:objUtil,INTERFACE_INCLUDE_DIRECTORIES>
${PROJECT_SOURCE_DIR}/lib
)
target_link_libraries(
ktxinfo
ktx
objUtil
)
set_tool_properties(ktxinfo)
set_code_sign(ktxinfo)
+246
View File
@@ -0,0 +1,246 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 sts=4 expandtab:
//
// Copyright 2019-2020 The Khronos Group, Inc.
// SPDX-License-Identifier: Apache-2.0
//
// To use, download from http://www.billbaxter.com/projects/imdebug/
// Put imdebug.dll in %SYSTEMROOT% (usually C:\WINDOWS), imdebug.h in
// ../../include, imdebug.lib in ../../build/msvs/<platform>/vs<ver> &
// add ..\imdebug.lib to the libraries list in the project properties.
#define IMAGE_DEBUG 0
#include "ktxapp.h"
#include <cstdlib>
#include <errno.h>
#include <iostream>
#include <sstream>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "version.h"
#if (IMAGE_DEBUG) && defined(_DEBUG) && defined(_WIN32) && !defined(_WIN32_WCE)
# include "imdebug.h"
#elif defined(IMAGE_DEBUG) && IMAGE_DEBUG
# undef IMAGE_DEBUG
# define IMAGE_DEBUG 0
#endif
struct commandOptions {
string outfile;
string outdir;
bool force;
std::vector<string> infiles;
commandOptions() {
force = false;
}
};
#if IMAGE_DEBUG
static void dumpImage(char* name, int width, int height, int components,
int componentSize, bool isLuminance,
unsigned char* srcImage);
#endif
using namespace std;
/** @page ktxinfo ktxinfo
@~English
Print information about KTX or KTX2 files.
@section ktxinfo_synopsis SYNOPSIS
ktxinfo [options] [@e infile ...]
@section ktxinfo_description DESCRIPTION
@b ktxinfo prints information about the KTX files provided as arguments.
If no arguments are given, it prints information about a single file
read from standard input.
@note @b ktxinfo prints using UTF-8 encoding. If your console is not
set for UTF-8 you will see incorrect characters in output of the file
identifier on each side of the "KTX nn".
The following options are available:
@snippet{doc} ktxapp.h ktxApp options
@section ktxinfo_exitstatus EXIT STATUS
@b ktxinfo exits 0 on success, 1 on command line errors and 2 if one of
the input files is not a KTX file.
@section ktxinfo_history HISTORY
@par Version 4.0
- Initial version
@section ktxinfo_author AUTHOR
Mark Callow, github.com/MarkCallow
*/
#define QUOTE(x) #x
#define STR(x) QUOTE(x)
std::string myversion(STR(KTXINFO_VERSION));
std::string mydefversion(STR(KTXINFO_DEFAULT_VERSION));
class ktxInfo : public ktxApp {
public:
ktxInfo();
virtual int main(int argc, char* argv[]);
virtual void usage();
protected:
virtual bool processOption(argparser& parser, int opt);
ktxApp::commandOptions options;
};
ktxInfo::ktxInfo() : ktxApp(myversion, mydefversion, options)
{
}
void
ktxInfo::usage()
{
cerr <<
"Usage: " << name << " [options] [<infile> ...]\n"
"\n"
" infile ... The file or files about which to print information. If\n"
" not specified, stdin is read.\n"
"\n"
" Note that ktxinfo prints using UTF-8 encoding. If your console is not\n"
" set for UTF-8 you will see incorrect characters in output of the file\n"
" identifier on each side of the \"KTX nn\".\n"
"\n"
" Options are:\n\n";
ktxApp::usage();
}
static ktxInfo ktxinfo;
ktxApp& theApp = ktxinfo;
int
ktxInfo::main(int argc, char* argv[])
{
FILE *inf;
int exitCode = 0;
processCommandLine(argc, argv);
std::vector<string>::const_iterator it;
for (it = options.infiles.begin(); it < options.infiles.end(); it++) {
string infile = *it;
if (!infile.compare("-")) {
inf = stdin;
#if defined(_WIN32)
/* Set "stdin" to have binary mode */
(void)_setmode( _fileno( stdin ), _O_BINARY );
#endif
} else {
inf = fopenUTF8(infile, "rb");
}
if (inf) {
KTX_error_code result;
result = ktxPrintInfoForStdioStream(inf);
if (result == KTX_FILE_UNEXPECTED_EOF) {
cerr << name
<< ": Unexpected end of file reading \""
<< (infile.compare("-") ? infile : "stdin" ) << "\"."
<< endl;
exitCode = 2;
goto cleanup;
}
if (result == KTX_UNKNOWN_FILE_FORMAT) {
cerr << name
<< ": " << (infile.compare("-") ? infile : "stdin")
<< " is not a KTX or KTX2 file."
<< endl;
exitCode = 2;
goto cleanup;
}
if (result == KTX_FILE_READ_ERROR) {
cerr << name
<< ": Error reading \""
<< (infile.compare("-") ? infile : "stdin") << "\"."
<< strerror(errno) << endl;
exitCode = 2;
goto cleanup;
}
} else {
cerr << name
<< " could not open input file \""
<< (infile.compare("-") ? infile : "stdin") << "\". "
<< strerror(errno) << endl;
exitCode = 2;
goto cleanup;
}
}
cleanup:
return exitCode;
}
bool
ktxInfo::processOption(argparser&, int)
{
return false;
}
#if IMAGE_DEBUG
static void
dumpImage(char* name, int width, int height, int components, int componentSize,
bool isLuminance, unsigned char* srcImage)
{
char formatstr[2048];
char *imagefmt;
char *fmtname;
int bitsPerComponent = componentSize == 2 ? 16 : 8;
switch (components) {
case 1:
if (isLuminance) {
imagefmt = "lum b=";
fmtname = "LUMINANCE";
} else {
imagefmt = "a b=";
fmtname = "ALPHA";
}
break;
case 2:
imagefmt = "luma b=";
fmtname = "LUMINANCE_ALPHA";
break;
case 3:
imagefmt = "rgb b=";
fmtname = "RGB";
break;
case 4:
imagefmt = "rgba b=";
fmtname = "RGBA";
break;
default:
assert(0);
}
sprintf(formatstr, "%s%d w=%%d h=%%d t=\'%s %s%d\' %%p",
imagefmt,
bitsPerComponent,
name,
fmtname,
bitsPerComponent);
imdebug(formatstr, width, height, srcImage);
}
#endif
+7
View File
@@ -0,0 +1,7 @@
/*
// [Code version]
exitcode-128-NOTFOUND
// [Code version]
*/
#define KTXINFO_VERSION exitcode-128-NOTFOUND
#define KTXINFO_DEFAULT_VERSION v4.0.__default__
+32
View File
@@ -0,0 +1,32 @@
# Copyright 2019-2020 The Khronos Group Inc.
# SPDX-License-Identifier: Apache-2.0
add_executable( ktxsc
ktxsc.cpp
)
create_version_header( tools/ktxsc ktxsc )
target_compile_features(
ktxsc
PUBLIC
cxx_std_17
)
target_include_directories(
ktxsc
PRIVATE
.
$<TARGET_PROPERTY:ktx,INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:objUtil,INTERFACE_INCLUDE_DIRECTORIES>
${PROJECT_SOURCE_DIR}/lib
${PROJECT_SOURCE_DIR}/other_include
)
target_link_libraries(
ktxsc
ktx
objUtil
)
set_tool_properties(ktxsc)
set_code_sign(ktxsc)
+406
View File
@@ -0,0 +1,406 @@
// -*- tab-width: 4; -*-
// vi: set sw=2 ts=4 sts=4 expandtab:
// Copyright 2019-2020 Mark Callow
// SPDX-License-Identifier: Apache-2.0
#include "scapp.h"
#include <cstdlib>
#include <errno.h>
#include <filesystem>
#include <iostream>
#include <sstream>
#include <thread>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "version.h"
#if defined(_MSC_VER)
#define strncasecmp _strnicmp
#define fileno _fileno
#define mktemp _mkstemp
#define isatty _isatty
#endif
#if defined(_MSC_VER)
#undef min
#undef max
#endif
using namespace std;
/** @page ktxsc ktxsc
@~English
Supercompress the images in a KTX2 file.
@section ktxsc_synopsis SYNOPSIS
ktxsc [options] [@e infile ...]
@section ktxsc_description DESCRIPTION
@b ktxsc can encode and supercompress the images in Khronos texture
format version 2 files (KTX2). Uncompressed files, i.e those whose vkFormat
name does not end in @c _BLOCK can be encoded to ASTC, Basis Universal
(encoded to ETC1S then supercompressed with an integrated LZ step)
or UASTC and optionally supercompressed with Zstandard (zstd). Any image
format, except Basis Universal, can be supercompressed with zstd. For best
results with UASTC, the data should be conditioned for zstd by using the
@e --uastc_rdo_q and, optionally, @e --uastc_rdo_d options.
@b ktxsc reads each named @e infile and compresses it in place. When
@e infile is not specified, a single file will be read from @e stdin and the
output written to @e stdout. When one or more files is specified each will
be compressed in place.
The following options are available:
<dl>
<dt>-o outfile, --output=outfile</dt>
<dd>Write the output to @e outfile. If @e outfile is 'stdout', output will
be written to stdout. Parent directories will be created, if
necessary. If there is more than 1 @e infile the command prints its
usage message and exits.</dd>
<dt>-f, \--force</dt>
<dd>If the destination file cannot be opened, remove it and create a
new file, without prompting for confirmation regardless of its
permissions.</dd>
<dt>\--t2</dt>
<dd>Output a KTX version2 file. Always true.</dd>
</dl>
@snippet{doc} scapp.h scApp options
@section ktxsc_exitstatus EXIT STATUS
@b ktxsc exits 0 on success, 1 on command line errors and 2 on
functional errors.
@section ktxsc_history HISTORY
@par Version 4.0
- Initial version.
@section ktxsc_author AUTHOR
Mark Callow, github.com/MarkCallow
*/
#define QUOTE(x) #x
#define STR(x) QUOTE(x)
std::string myversion(STR(KTXSC_VERSION));
std::string mydefversion(STR(KTXSC_DEFAULT_VERSION));
class ktxSupercompressor : public scApp {
public:
ktxSupercompressor();
virtual int main(int argc, char* argv[]);
virtual void usage();
protected:
virtual bool processOption(argparser& parser, int opt);
void validateOptions();
struct commandOptions : public scApp::commandOptions {
bool useStdout;
bool force;
commandOptions() {
force = false;
useStdout = false;
}
} options;
};
ktxSupercompressor::ktxSupercompressor() : scApp(myversion, mydefversion, options)
{
argparser::option my_option_list[] = {
{ "force", argparser::option::no_argument, NULL, 'f' },
{ "outfile", argparser::option::required_argument, NULL, 'o' },
};
const int lastOptionIndex = sizeof(my_option_list)
/ sizeof(argparser::option);
option_list.insert(option_list.begin(), my_option_list,
my_option_list + lastOptionIndex);
short_opts += "fo:";
}
void
ktxSupercompressor::usage()
{
cerr <<
"Usage: " << name << " [options] [<infile> ...]\n"
"\n"
" infile The ktx2 file(s) to supercompress. The output is written to a\n"
" file of the same name. If infile not specified input will be read\n"
" from stdin and the compressed texture written to stdout.\n"
"\n"
" Options are:\n"
"\n"
" -o outfile, --output=outfile\n"
" Writes the output to outfile. If outfile is 'stdout', output\n"
" will be written to stdout. Parent directories will be\n"
" created if necessary. If there is more than 1 input file\n"
" the command prints its usage message and exits.\n"
" -f, --force If the output file cannot be opened, remove it and create a\n"
" new file, without prompting for confirmation regardless of\n"
" its permissions.\n";
scApp::usage();
}
static string dir_name(const string& path)
{
// Supports both Unix-style and Windows-style.
size_t last_separator = path.find_last_of("/\\");
if (last_separator != string::npos) {
return path.substr(0, last_separator + 1);
} else {
return std::basic_string<char>();
}
}
static ktxSupercompressor ktxsc;
ktxApp& theApp = ktxsc;
int
ktxSupercompressor::main(int argc, char* argv[])
{
FILE *inf, *outf = nullptr;
KTX_error_code result;
ktxTexture2* texture = 0;
int exitCode = 0;
string tmpfile;
processCommandLine(argc, argv, eAllowStdin);
validateOptions();
std::vector<string>::const_iterator it;
for (it = options.infiles.begin(); it < options.infiles.end(); it++) {
string infile = *it;
if (infile.compare("-") == 0) {
inf = stdin;
#if defined(_WIN32)
/* Set "stdin" to have binary mode */
(void)_setmode( _fileno( stdin ), _O_BINARY );
#endif
} else {
inf = fopenUTF8(infile, "rb");
}
if (inf) {
if (options.useStdout) {
outf = stdout;
#if defined(_WIN32)
/* Set "stdout" to have binary mode */
(void)_setmode( _fileno( stdout ), _O_BINARY );
#endif
} else if (options.outfile.length()) {
const auto outputPath = filesystem::path(DecodeUTF8Path(options.outfile));
if (outputPath.has_parent_path())
filesystem::create_directories(outputPath.parent_path());
outf = fopen_write_if_not_exists(options.outfile);
} else {
// Make a temporary file in the same directory as the source
// file to avoid cross-device rename issues later.
tmpfile = dir_name(infile) + "ktxsc.tmp.XXXXXX";
#if defined(_WIN32)
// Despite receiving size() the debug CRT version of mktemp_s
// asserts that the string template is NUL terminated.
tmpfile.push_back('\0');
if (_wmktemp_s(&DecodeUTF8Path(tmpfile)[0], tmpfile.size()) == 0)
outf = fopenUTF8(tmpfile, "wb");
#else
int fd_tmp = mkstemp(&tmpfile[0]);
outf = fdopen(fd_tmp, "wb");
#endif
}
if (!outf && errno == EEXIST) {
bool force = options.force;
if (!force) {
if (isatty(fileno(stdin))) {
char answer;
cout << "Output file " << options.outfile
<< " exists. Overwrite? [Y or n] ";
cin >> answer;
if (answer == 'Y') {
force = true;
}
}
}
if (force) {
outf = fopenUTF8(options.outfile, "wb");
}
}
if (outf) {
result = ktxTexture2_CreateFromStdioStream(inf,
KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
&texture);
if (result == KTX_UNKNOWN_FILE_FORMAT) {
cerr << infile << " is not a KTX v2 file." << endl;
exitCode = 2;
goto cleanup;
} else if (result != KTX_SUCCESS) {
cerr << name
<< " failed to create ktxTexture from " << infile
<< ": " << ktxErrorString(result) << endl;
exitCode = 2;
goto cleanup;
}
(void)fclose(inf);
if (texture->classId != ktxTexture2_c) {
cerr << name << ": "
<< "Only KTX texture version 2 files can be supercompressed."
<< endl;
exitCode = 1;
goto cleanup;
}
if (texture->supercompressionScheme != KTX_SS_NONE) {
cerr << name << ": "
<< "Cannot supercompress already supercompressed files."
<< endl;
exitCode = 1;
goto cleanup;
}
if ((options.astc || options.etc1s || options.bopts.uastc) && texture->isCompressed) {
cerr << name << ": "
<< "Cannot encode already block-compressed textures "
<< "to ASTC, Basis Universal or UASTC."
<< endl;
exitCode = 1;
goto cleanup;
}
// Modify the writer metadata.
stringstream writer;
writeId(writer, true);
ktxHashList_DeleteKVPair(&texture->kvDataHead, KTX_WRITER_KEY);
ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_KEY,
(ktx_uint32_t)writer.str().length() + 1,
writer.str().c_str());
exitCode = encode(texture, options.inputSwizzle, infile);
if (exitCode)
goto cleanup;
result = ktxTexture_WriteToStdioStream(ktxTexture(texture), outf);
if (result != KTX_SUCCESS) {
cerr << name
<< " failed to write KTX file; "
<< ktxErrorString(result) << endl;
exitCode = 2;
goto cleanup;
}
(void)fclose(outf);
if (!options.outfile.length() && !options.useStdout) {
// Move the new file over the original.
assert(tmpfile.size() > 0 && infile.length());
#if defined(_WIN32)
// Windows' rename() fails if the destination file exists!
if (!MoveFileExW(DecodeUTF8Path(tmpfile).c_str(), DecodeUTF8Path(infile).c_str(),
MOVEFILE_REPLACE_EXISTING))
#else
if (rename(tmpfile.c_str(), infile.c_str()))
#endif
{
cerr << name
<< ": rename of \"" << tmpfile << "\" to \""
<< infile << "\" failed: "
<< strerror(errno) << endl;
exitCode = 2;
goto cleanup;
}
}
} else {
cerr << name
<< " could not open output file \""
<< (options.useStdout ? "stdout" : options.outfile) << "\". "
<< strerror(errno) << endl;
exitCode = 2;
goto cleanup;
}
} else {
cerr << name
<< " could not open input file \""
<< (infile.compare("-") == 0 ? "stdin" : infile) << "\". "
<< strerror(errno) << endl;
exitCode = 2;
goto cleanup;
}
}
return 0;
cleanup:
if (outf) { // Windows debug CRT fclose asserts that outf != nullptr...
(void)fclose(outf); // N.B Windows refuses to unlink an open file.
}
if (tmpfile.size() > 0) (void)unlinkUTF8(tmpfile);
if (options.outfile.length()) (void)unlinkUTF8(options.outfile);
return exitCode;
}
void
ktxSupercompressor::validateOptions()
{
scApp::validateOptions();
if (options.infiles.size() > 1 && options.outfile.length()) {
cerr << "Can't use -o when there are multiple infiles." << endl;
usage();
exit(1);
}
if (options.etc1s && options.zcmp) {
cerr << "Can't encode to etc1s and supercompress with zstd." << endl;
usage();
exit(1);
}
if (!options.astc && !options.etc1s && !options.zcmp && !options.bopts.uastc) {
cerr << "Must specify one of --zcmp, --etc1s (deprecated --bcmp) or --uastc." << endl;
usage();
exit(1);
}
}
/*
* @brief process a command line option
*
* @return true of option processed.
*
* @param[in] parser, an @c argparser holding the options to process.
*/
bool
ktxSupercompressor::processOption(argparser& parser, int opt)
{
switch (opt) {
case 'f':
options.force = true;
break;
case 'o':
options.outfile = parser.optarg;
if (!options.outfile.compare("stdout")) {
options.useStdout = true;
} else {
size_t dot;
size_t slash;
dot = options.outfile.find_last_of('.');
slash = options.outfile.find_last_of('/');
if (slash == string::npos) {
slash = options.outfile.find_last_of('\\');
}
// dot < slash means there's a dot but it is not prefixing
// a file extension.
if (dot == string::npos
|| (slash != string::npos && dot < slash)) {
options.outfile += ".ktx2";
}
}
break;
default:
return scApp::processOption(parser, opt);
}
return true;
}
+7
View File
@@ -0,0 +1,7 @@
/*
// [Code version]
exitcode-128-NOTFOUND
// [Code version]
*/
#define KTXSC_VERSION exitcode-128-NOTFOUND
#define KTXSC_DEFAULT_VERSION v4.0.__default__
+54
View File
@@ -0,0 +1,54 @@
@mainpage
<!--
Can't put at start. Doxygen requires page title on first line.
Copyright 2023 The Khronos Group Inc.
SPDX-License-Identifier: Apache-2.0
-->
There are two sets of tools: a new set with a unified front end, @ref ktx, and an older set of individual tools. Both are documented here.
ktx Overview
------------
@ref ktx includes the following tools:
| Tool | Description | Equivalent old tool |
| :--- | ----------- | ------------------- |
| @ref ktx_compare | Compare two KTX2 files. | |
| @ref ktx_create | Create a KTX2 file from various input files | `toktx` |
| @ref ktx_deflate | Deflate (supercompress) a KTX2 file. | `ktxsc` |
| @ref ktx_extract | Export selected images from a KTX2 file | - |
| @ref ktx_encode | Encode a KTX2 file | `ktxsc` |
| @ref ktx_info | Prints information about a KTX2 file | `ktxinfo` |
| @ref ktx_transcode | Transcode a KTX2 file | - |
| @ref ktx_validate | Validate a KTX2 file | `ktx2check` |
| @ref ktx_help | Display help information about the ktx tools | - |
Equivalent old tools are deprecated and will be removed soon.
Some features of old tools are not currently available in the new equivalent.
| Old Tool | New Tool | Missing Features |
| :------: | :------: | ---------------- |
| @ref toktx | @ref ktx_create "create" | JPEG and NBPM input. |
The command-line syntax and semantics differ from the old tools including, but not limited to:
* KTX 1.0 files are not supported by the new tools.
* Words in multi-word option names are connected with `-` instead of `_`.
* Individual option names may differ between the old and new tools.
* The `ktx validate` tool may be stricter than `ktx2check` or otherwise differ in behavior, as the new tool enforces all rules of the KTX 2.0 specification. In addition, all new tools that accept KTX 2.0 files as input will be validated in a similar fashion as they would be with the `ktx validate` tool and will fail on the first specification rule violation, if there is one. It also has the option to output the validation results in human readable text format or in JSON format (both formatted and minified options are available), as controlled by the `--format` command-line option.
* The `ktx validate` tool also supports validating KTX 2.0 files against the additional restrictions defined by the _KHR\_texture\_basisu_ extension. Use the `--gltf-basisu` command-line option to verify glTF and WebGL compatibility.
* The new `ktx info` tool produces a unified and complete output of all metadata in KTX 2.0 files and can provide output in human readable text format or in JSON format (both formatted and minified options are available), as controlled by the `--format` command-line option.
* The source repository also includes the JSON schemas that the JSON outputs of the `ktx info` and `ktx validate` tools comply to.
* The `ktx create` tool takes an explicit Vulkan format argument (`--format`) instead of inferring the format based on the provided input files as `toktx`, and thus doesn't perform any implicit color-space conversions except gamma 2.2 to sRGB. Use the `--assign-tf`, `--convert-tf`, `--assign-primaries`, and the new `--convert-primaries` for fine grained control over color-space interpretation and conversion.
* The `ktx create` tool does not support resizing or scaling like `toktx`, and, in general, does not perform any image transformations except the optional color-space conversion and mipmap generation options. Users should resize input images to the appropriate resolution before converting them to KTX 2.0 files.
* The `ktx create` and `ktx extract` tools consume and produce, respectively, image file formats that best suit the used Vulkan format. In general, barring special cases, 8-bit and 16-bit normalized integer formats are imported from and exported to PNG files, while integer and floating point formats are imported from and exported to EXR files based on predefined rules. This may be extended in the future using additional command line options and in response to support for other image file formats.
* The new tools and updated `libktx` support ZLIB supercompression besides the BasisLZ and Zstd supercompression schemes supported previously.
Please refer to the manual pages or use the `--help` command-line option for further details on the options available and associated semantics for each individual command.
---
@par This page last modified $Date$
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<!--
Copyright 2019-2020 Andreas Atteneder
SPDX-License-Identifier: Apache-2.0
-->
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>KTX-Software</title>
<style>
body > * { text-align:center; font-family:sans-serif;}
.success{font-size: 6em;}
.terminal{font-family:monospace;margin:0 0.5em 0 .5em;}
</style>
</head>
<body>
<div class="success"></div>
<h2>The installation was successful.</h2>
<p>The software was installed.</p>
<p>To uninstall, run <span class="terminal">sudo ktx-uninstall</span> in a terminal</p>
</body>
</html>
+21
View File
@@ -0,0 +1,21 @@
#! /bin/zsh
# Copyright 2019-2020 The Khronos Group, Inc.
# SPDX-License-Identifier: Apache-2.0
if [ "$EUID" -ne 0 ]
then echo "Please run as root to uninstall KTX-Software" >&2
exit -1
fi
cd /
for pkg in $(pkgutil --pkgs='com.khronos.ktx.*')
do
# pkgutil's --only-files shows only regular files. There are symbolic
# links we need to deal with.
pkgutil --files $pkg | xargs -n 1 sh -c 'if [ -f $0 -o -L $0 ]; then rm $0; fi'
# Deal with doc/KTX-Software
pkgutil --only-dirs --files $pkg | xargs -n 1 sh -c 'if [ -d $0 -a $(basename $0) = "KTX-Software" ]; then rm -rf $0; fi'
pkgutil --forget $pkg
done
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -0,0 +1,32 @@
{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf500
{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fswiss\fcharset0 Helvetica-Bold;\f2\fmodern\fcharset0 CourierNewPSMT;
}
{\colortbl;\red255\green255\blue255;}
{\*\expandedcolortbl;;}
\paperw11900\paperh16840\margl1440\margr1440\vieww9500\viewh7900\viewkind0
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0
\f0\fs24 \cf0 \
\
\pard\pardeftab720\sl280\qc\partightenfactor0
\cf0 {{\NeXTGraphic Conclusion_Success_32@2x.png \width1280 \height1280 \appleattachmentpadding0 \appleembedtype0 \appleaqc
}¬}\
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0
\cf0 \
\f1\b\fs40 The installation was successful.\
\
\f0\b0\fs24 The software was installed.\
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0
\cf0 \
\
\
\
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\qc\partightenfactor0
\cf0 You can uninstall this software by running the command\
\f2 sudo ktx-uninstall
\f0 \
in a terminal.\
}
+44
View File
@@ -0,0 +1,44 @@
# Copyright 2010-2020 The Khronos Group Inc.
# SPDX-License-Identifier: Apache-2.0
add_executable( toktx
toktx.cc
)
create_version_header( tools/toktx toktx )
target_include_directories(
toktx
PRIVATE
.
$<TARGET_PROPERTY:imageio,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:ktx,INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:objUtil,INTERFACE_INCLUDE_DIRECTORIES>
)
target_include_directories(
toktx
SYSTEM
PRIVATE
$<TARGET_PROPERTY:imageio,INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>
"${PROJECT_SOURCE_DIR}/lib"
"${PROJECT_SOURCE_DIR}/external/dfdutils"
)
target_link_libraries(
toktx
imageio
ktx
objUtil
)
target_compile_definitions(
toktx
PRIVATE
$<TARGET_PROPERTY:ktx,INTERFACE_COMPILE_DEFINITIONS>
)
target_compile_features(toktx PUBLIC cxx_std_11)
set_tool_properties(toktx)
set_code_sign(toktx)
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
/*
// [Code version]
exitcode-128-NOTFOUND
// [Code version]
*/
#define TOKTX_VERSION exitcode-128-NOTFOUND
#define TOKTX_DEFAULT_VERSION v4.0.__default__