diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 50facbbf6f..1753129e33 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,12 +39,14 @@ jobs: env: ANDROID_ABI: arm64-v8a ANDROID_NDK: ${{ steps.setup-ndk.outputs.ndk-path }} + ASTC_ISA: "ISA_NEON=ON" - name: android_arm64-v8a run: ./ci_scripts/build_android.sh env: ANDROID_ABI: arm64-v8a ANDROID_NDK: ${{ steps.setup-ndk.outputs.ndk-path }} + ASTC_ISA: "ISA_NEON=ON" - name: android_armeabi-v7a run: ./ci_scripts/build_android.sh diff --git a/.reuse/dep5 b/.reuse/dep5 index e63c609664..f73326b3a6 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -70,7 +70,7 @@ Files: */*.json Copyright: 2017-2020 Mark Callow License: Apache-2.0 -Files: lib/astc-encoder/Docs/* lib/astc-encoder/Test/* lib/astc-encoder/jenkins/* lib/astc-encoder/.gitattributes lib/astc-encoder/.gitignore lib/astc-encoder/.gitmodules lib/astc-encoder/.pylintrc lib/astc-encoder/README.md +Files: lib/astc-encoder/Docs/* lib/astc-encoder/Test/* lib/astc-encoder/jenkins/* lib/astc-encoder/.gitattributes lib/astc-encoder/.gitignore lib/astc-encoder/.gitmodules lib/astc-encoder/.pylintrc lib/astc-encoder/README.md lib/astc-encoder/Utils/Example/README.md Copyright: 2020-2021 Arm Limited License: Apache-2.0 diff --git a/.travis.yml b/.travis.yml index 58723b5dbb..2a281521c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,6 +51,10 @@ jobs: # Phase 3: Install cache components - Currently not using a cache # then the following phases: before_install: +- echo "Running on the following travis CI runner" +- uname -a +- echo "CMake version on the runner is" +- cmake --version - eval "${MATRIX_EVAL}" - echo -e "machine github.com\n login $GITHUB_TOKEN" >> ~/.netrc # Prevent rate limiting on Git LFS. - cat ~/.netrc @@ -103,6 +107,12 @@ script: after_script: - sleep 10 +# after_failure: +# - echo "Now uploading the failed tests" +# - ls -alsh ./tests/testimages/toktx* +# - tar -cvf failed-tests.tar ./tests/testimages/toktx* +# - curl --upload-file failed-tests.tar https://transfer.sh/toktx-failed-tests.tar + # Errors in something run in after_success: don't cause the build to fail so don't use. before_deploy: diff --git a/CMakeLists.txt b/CMakeLists.txt index b0d1c78e7f..240e84676d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,7 +74,7 @@ endif() if(APPLE) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.11" CACHE STRING "macOS Deployment Target") if(IOS) - set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "10.0" CACHE STRING "iOS Deployment Target") + set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "11.0" CACHE STRING "iOS Deployment Target") set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO) endif() endif() @@ -446,6 +446,7 @@ target_sources( ktx PRIVATE lib/basis_encode.cpp + lib/astc_encode.cpp ${BASISU_ENCODER_C_SRC} ${BASISU_ENCODER_CXX_SRC} lib/writer1.c @@ -639,6 +640,49 @@ PUBLIC add_subdirectory(interface/basisu_c_binding) +# Only one architecture is supported at once, if neither of +# ISA_SSE41 and ISA_SSE2 are defined ISA_AVX2 is chosen. +# If ISA_AVX2 fails to compile user must chose other x86 options. +# On arm based systems ISA_NEON is default + +list(FIND CMAKE_OSX_ARCHITECTURES "$(ARCHS_STANDARD)" ASTC_BUILD_UNIVERSAL) + +if(${ASTC_BUILD_UNIVERSAL} EQUAL -1) + if (${ISA_NONE}) + set(ASTC_LIB_NAME astcenc-none-static) + else() + if(CPU_ARCHITECTURE STREQUAL x86_64 OR CPU_ARCHITECTURE STREQUAL x86) + if (${ISA_SSE41}) + set(ASTC_LIB_NAME astcenc-sse4.1-static) + elseif (${ISA_SSE2}) + set(ASTC_LIB_NAME astcenc-sse2-static) + else() + set(ISA_AVX2 ON) + set(ASTC_LIB_NAME astcenc-avx2-static) + endif() + if(CPU_ARCHITECTURE STREQUAL x86) + set(ISA_NONE ON) + set(ISA_AVX2 OFF) + set(ASTCENC_POPCNT 0) + set(ASTC_LIB_NAME astcenc-none-static) + endif() + elseif(CPU_ARCHITECTURE STREQUAL armv8 OR CPU_ARCHITECTURE STREQUAL arm64) + set(ASTC_LIB_NAME astcenc-neon-static) + else() + message(STATUS "Unsupported ISA for ${CPU_ARCHITECTURE} arch, using ISA_NONE.") + set(ISA_NONE ON) + endif() + endif() +else() + set(ASTC_LIB_NAME astcenc-static) +endif() + +# astcenc +set(CLI OFF) # Only build as library not the CLI astcencoder +add_subdirectory(lib/astc-encoder) +set_property(TARGET ${ASTC_LIB_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) +target_link_libraries(ktx PRIVATE ${ASTC_LIB_NAME}) + # Tools if(KTX_FEATURE_TOOLS) add_subdirectory(tools) @@ -652,13 +696,18 @@ if(KTX_FEATURE_DOC) include(cmake/docs.cmake) endif() +set(KTX_INSTALL_TARGETS ktx) + +if(KTX_FEATURE_STATIC_LIBRARY) + list(APPEND KTX_INSTALL_TARGETS ${ASTC_LIB_NAME}) +endif() # Install if(APPLE OR LINUX) # Have library's name links as separate component set(KTX_NAMELINKS ON) - install(TARGETS ktx + install(TARGETS ${KTX_INSTALL_TARGETS} EXPORT KTXTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -668,7 +717,7 @@ if(APPLE OR LINUX) DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT dev ) - install(TARGETS ktx + install(TARGETS ${KTX_INSTALL_TARGETS} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library @@ -677,7 +726,7 @@ if(APPLE OR LINUX) else() # No name links on Windows set(KTX_NAMELINKS OFF) - install(TARGETS ktx + install(TARGETS ${KTX_INSTALL_TARGETS} EXPORT KTXTargets ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/ci_scripts/build_android.sh b/ci_scripts/build_android.sh index 42408cb544..877a2838cb 100755 --- a/ci_scripts/build_android.sh +++ b/ci_scripts/build_android.sh @@ -6,9 +6,10 @@ set -e # Fallback to arm64-v8a ANDROID_ABI=${ANDROID_ABI:-'arm64-v8a'} +ASTC_ISA=${ASTC_ISA:-'ISA_NONE=ON'} # You need to set the following environment variables first -# ANDROID_NDK= +# ANDROID_NDK= echo "Configure KTX-Software (Android $ANDROID_ABI Release)" cmake . -G Ninja -B "build-android-$ANDROID_ABI" \ @@ -17,7 +18,8 @@ cmake . -G Ninja -B "build-android-$ANDROID_ABI" \ -DANDROID_NDK="$ANDROID_NDK" \ -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK/build/cmake/android.toolchain.cmake" \ -DCMAKE_BUILD_TYPE=Release \ --DBASISU_SUPPORT_SSE=OFF +-DBASISU_SUPPORT_SSE=OFF \ +-D${ASTC_ISA} pushd "build-android-$ANDROID_ABI" diff --git a/ci_scripts/build_android_debug.sh b/ci_scripts/build_android_debug.sh index dfa79e8fc6..06e550ae5d 100755 --- a/ci_scripts/build_android_debug.sh +++ b/ci_scripts/build_android_debug.sh @@ -6,9 +6,10 @@ set -e # Fallback to arm64-v8a ANDROID_ABI=${ANDROID_ABI:-'arm64-v8a'} +ASTC_ISA=${ASTC_ISA:-'ISA_NONE=ON'} # You need to set the following environment variables first -# ANDROID_NDK= +# ANDROID_NDK= echo "Configure KTX-Software (Android $ANDROID_ABI Debug)" cmake . -G Ninja -B "build-android-$ANDROID_ABI-debug" \ @@ -17,7 +18,8 @@ cmake . -G Ninja -B "build-android-$ANDROID_ABI-debug" \ -DANDROID_NDK="$ANDROID_NDK" \ -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK/build/cmake/android.toolchain.cmake" \ -DCMAKE_BUILD_TYPE=Debug \ --DBASISU_SUPPORT_SSE=OFF +-DBASISU_SUPPORT_SSE=OFF \ +-D${ASTC_ISA} pushd "build-android-$ANDROID_ABI-debug" diff --git a/ci_scripts/build_macos.sh b/ci_scripts/build_macos.sh index ffdfcfef9e..dcd286d087 100755 --- a/ci_scripts/build_macos.sh +++ b/ci_scripts/build_macos.sh @@ -63,8 +63,8 @@ echo "Configure KTX-Software (macOS x86_64) with SSE support" cmake -GXcode -Bbuild-macos-sse \ -DCMAKE_OSX_ARCHITECTURES="x86_64" \ -DKTX_FEATURE_LOADTEST_APPS=ON \ - -DBASISU_SUPPORT_SSE=ON - + -DBASISU_SUPPORT_SSE=ON \ + -DISA_SSE41=ON # Cause the build pipes below to set the exit to the exit code of the # last program to exit non-zero. @@ -117,7 +117,7 @@ popd # echo "Configure KTX-Software (iOS)" -cmake -GXcode -Bbuild-ios -DCMAKE_SYSTEM_NAME=iOS -DKTX_FEATURE_LOADTEST_APPS=ON -DKTX_FEATURE_DOC=OFF +cmake -GXcode -Bbuild-ios -DISA_NEON=ON -DCMAKE_SYSTEM_NAME=iOS -DKTX_FEATURE_LOADTEST_APPS=ON -DKTX_FEATURE_DOC=OFF pushd build-ios echo "Build KTX-Software (iOS Debug)" cmake --build . --config Debug -- -sdk iphoneos CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO | handle_compiler_output diff --git a/include/ktx.h b/include/ktx.h index e9b1a6c794..b83e52347f 100644 --- a/include/ktx.h +++ b/include/ktx.h @@ -1092,8 +1092,137 @@ typedef enum ktx_pack_uastc_flag_bits_e { } ktx_pack_uastc_flag_bits_e; typedef ktx_uint32_t ktx_pack_uastc_flags; +/** + * @~English + * @brief Options specifiying ASTC encoding quality levels. + */ +typedef enum ktx_pack_astc_quality_levels_e { + KTX_PACK_ASTC_QUALITY_LEVEL_FASTEST = 0, + /*!< Fastest compression. */ + KTX_PACK_ASTC_QUALITY_LEVEL_FAST = 10, + /*!< Fast compression. */ + KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM = 60, + /*!< Medium compression. */ + KTX_PACK_ASTC_QUALITY_LEVEL_THOROUGH = 98, + /*!< Slower compression. */ + KTX_PACK_ASTC_QUALITY_LEVEL_EXHAUSTIVE = 100, + /*!< Very slow compression. */ + KTX_PACK_ASTC_QUALITY_LEVEL_MAX = KTX_PACK_ASTC_QUALITY_LEVEL_EXHAUSTIVE, + /*!< Maximum supported quality level. */ +} ktx_pack_astc_quality_levels_e; + +/** + * @~English + * @brief Options specifiying ASTC encoding block dimensions + */ +typedef enum ktx_pack_astc_block_dimension_e { + // 2D formats + KTX_PACK_ASTC_BLOCK_DIMENSION_4x4, //: 8.00 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_5x4, //: 6.40 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_5x5, //: 5.12 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_6x5, //: 4.27 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_6x6, //: 3.56 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_8x5, //: 3.20 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_8x6, //: 2.67 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_10x5, //: 2.56 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_10x6, //: 2.13 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_8x8, //: 2.00 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_10x8, //: 1.60 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_10x10, //: 1.28 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_12x10, //: 1.07 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_12x12, //: 0.89 bpp + // 3D formats + KTX_PACK_ASTC_BLOCK_DIMENSION_3x3x3, //: 4.74 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_4x3x3, //: 3.56 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x3, //: 2.67 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x4, //: 2.00 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_5x4x4, //: 1.60 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x4, //: 1.28 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x5, //: 1.02 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_6x5x5, //: 0.85 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x5, //: 0.71 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x6, //: 0.59 bpp + KTX_PACK_ASTC_BLOCK_DIMENSION_MAX = KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x6 + /*!< Maximum supported blocks. */ +} ktx_pack_astc_block_dimension_e; + +/** + * @~English + * @brief Options specifiying ASTC encoder profile function + */ +typedef enum ktx_pack_astc_encoder_function_e { + KTX_PACK_ASTC_ENCODER_FUNCTION_UNKNOWN, + KTX_PACK_ASTC_ENCODER_FUNCTION_SRGB, + KTX_PACK_ASTC_ENCODER_FUNCTION_LINEAR, + KTX_PACK_ASTC_ENCODER_FUNCTION_MAX = KTX_PACK_ASTC_ENCODER_FUNCTION_LINEAR +} ktx_pack_astc_encoder_function_e; + +/** + * @~English + * @brief Options specifying ASTC encoder profile mode + * This and function is used later to derive the profile. + */ +typedef enum ktx_pack_astc_encoder_mode_e { + KTX_PACK_ASTC_ENCODER_MODE_DEFAULT, + KTX_PACK_ASTC_ENCODER_MODE_LDR, + KTX_PACK_ASTC_ENCODER_MODE_HDR, + KTX_PACK_ASTC_ENCODER_MODE_MAX = KTX_PACK_ASTC_ENCODER_MODE_HDR +} ktx_pack_astc_encoder_mode_e; + extern KTX_API const ktx_uint32_t KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL; +/** + * @memberof ktxTexture + * @~English + * @brief Structure for passing extended parameters to + * ktxTexture_CompressAstc. + * + * Passing a struct initialized to 0 (e.g. " = {};") will use the default + * values. Only those settings to be modified need be non-zero. + */ +typedef struct ktxAstcParams { + ktx_uint32_t structSize; + /*!< Size of this struct. Used so library can tell which version + of struct is being passed. + */ + ktx_bool_t verbose; + /*!< If true, prints Astc encoder operation details to + @c stdout. Not recommended for GUI apps. + */ + ktx_uint32_t threadCount; + /*!< Number of threads used for compression. Default is 1. */ + + /* astcenc params */ + ktx_uint32_t blockDimension; + /*!< Combinations of block dimensions that astcenc supports + i.e. 6x6, 8x8, 6x5 etc*/ + + ktx_uint32_t function; + /*!< Can be {linear/srgb} from astcenc*/ + + ktx_uint32_t mode; + /*!< Can be {ldr/hdr} from astcenc*/ + + ktx_uint32_t qualityLevel; + /*!< astcenc supports -fastest, -fast, -medium, -thorough, -exhaustive*/ + + ktx_bool_t normalMap; + /*!< Tunes codec parameters for better quality on normal maps + In this mode normals are compressed to X,Y components + Discarding Z component, reader will need to generate Z + component in shaders. + */ + char inputSwizzle[4]; + /*!< A swizzle to provide as input to astcenc. It must match the regular + expression /^[rgba01]{4}$/.*/ +} ktxAstcParams; + +KTX_API KTX_error_code KTX_APIENTRY +ktxTexture_CompressAstcEx(ktxTexture* This, ktxAstcParams* params); + +KTX_API KTX_error_code KTX_APIENTRY +ktxTexture_CompressAstc(ktxTexture* This, ktx_uint32_t quality); + /** * @memberof ktxTexture2 * @~English @@ -1648,4 +1777,3 @@ Initial release. */ #endif /* KTX_H_A55A6F00956F42F3A137C11929827FE1 */ - diff --git a/lib/astc_encode.cpp b/lib/astc_encode.cpp new file mode 100644 index 0000000000..f3fd8389dd --- /dev/null +++ b/lib/astc_encode.cpp @@ -0,0 +1,829 @@ +/* -*- tab-width: 4; -*- */ +/* vi: set sw=2 ts=4 expandtab: */ + +/* + * Copyright (c) 2021, Arm Limited and Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @internal + * @file astc_encode.cpp + * @~English + * + * @brief Functions for compressing a texture to ASTC format. + * + * @author Wasim Abbas , www.arm.com + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dfdutils/dfd.h" +#include "ktx.h" +#include "ktxint.h" +#include "texture2.h" +#include "vkformat_enum.h" +#include "vk_format.h" + +#include "astc-encoder/Source/astcenc.h" +#include "../tools/toktx/image.hpp" + +// Provide pthreads support on windows +#if defined(_WIN32) && !defined(__CYGWIN__) + +#define WIN32_LEAN_AND_MEAN +#include + +typedef HANDLE pthread_t; +typedef int pthread_attr_t; + +/* Public function, see header file for detailed documentation */ +static int +pthread_create(pthread_t* thread, const pthread_attr_t* attribs, + void* (*threadfunc)(void*), void* thread_arg) { + (void)attribs; + LPTHREAD_START_ROUTINE func = (LPTHREAD_START_ROUTINE)threadfunc; + *thread = CreateThread(nullptr, 0, func, thread_arg, 0, nullptr); + return 0; +} + +/* Public function, see header file for detailed documentation */ +static int +pthread_join(pthread_t thread, void** value) { + (void)value; + WaitForSingleObject(thread, INFINITE); + return 0; +} + +#endif + +static astcenc_image* +imageAllocate(uint32_t bitness, + uint32_t dim_x, uint32_t dim_y, uint32_t dim_z) { + astcenc_image *img = new astcenc_image; + assert(img); + + img->dim_x = dim_x; + img->dim_y = dim_y; + img->dim_z = dim_z; + + if (bitness == 8) { + void **data = new void *[dim_z]; + img->data_type = ASTCENC_TYPE_U8; + img->data = data; + + for (uint32_t z = 0; z < dim_z; z++) { + data[z] = new uint8_t[dim_x * dim_y * 4]; + } + } + else if (bitness == 16) { + void **data = new void *[dim_z]; + img->data_type = ASTCENC_TYPE_F16; + img->data = data; + + for (uint32_t z = 0; z < dim_z; z++) { + data[z] = new uint16_t[dim_x * dim_y * 4]; + } + } + else { // if (bitness == 32) + assert(bitness == 32); + void **data = new void *[dim_z]; + img->data_type = ASTCENC_TYPE_F32; + img->data = data; + + for (uint32_t z = 0; z < dim_z; z++) { + data[z] = new float[dim_x * dim_y * 4]; + } + } + + return img; +} + +static void +imageFree(astcenc_image *img) { + if (img == nullptr) { + return; + } + + for (uint32_t z = 0; z < img->dim_z; z++) { + delete[](char *) img->data[z]; + } + + delete[] img->data; + delete img; +} + +static astcenc_image* +unorm8x1ArrayToImage(const uint8_t *data, uint32_t dim_x, uint32_t dim_y) { + astcenc_image *img = imageAllocate(8, dim_x, dim_y, 1); + assert(img); + + for (uint32_t y = 0; y < dim_y; y++) { + uint8_t * data8 = static_cast(img->data[0]); + const uint8_t *src = data + dim_x * y; + + for (uint32_t x = 0; x < dim_x; x++) { + data8[(4 * dim_x * y) + (4 * x) ] = src[x]; + data8[(4 * dim_x * y) + (4 * x + 1)] = src[x]; + data8[(4 * dim_x * y) + (4 * x + 2)] = src[x]; + data8[(4 * dim_x * y) + (4 * x + 3)] = 255; + } + } + + return img; +} + +static astcenc_image* +unorm8x2ArrayToImage(const uint8_t *data, uint32_t dim_x, uint32_t dim_y) { + astcenc_image *img = imageAllocate(8, dim_x, dim_y, 1); + assert(img); + + for (uint32_t y = 0; y < dim_y; y++) { + uint8_t * data8 = static_cast(img->data[0]); + const uint8_t *src = data + 2 * dim_x * y; + + for (uint32_t x = 0; x < dim_x; x++) { + data8[(4 * dim_x * y) + (4 * x) ] = src[2 * x ]; + data8[(4 * dim_x * y) + (4 * x + 1)] = src[2 * x ]; + data8[(4 * dim_x * y) + (4 * x + 2)] = src[2 * x ]; + data8[(4 * dim_x * y) + (4 * x + 3)] = src[2 * x + 1]; + } + } + + return img; +} + +static astcenc_image* +unorm8x3ArrayToImage(const uint8_t *data, uint32_t dim_x, uint32_t dim_y) { + astcenc_image *img = imageAllocate(8, dim_x, dim_y, 1); + assert(img); + + for (uint32_t y = 0; y < dim_y; y++) { + uint8_t * data8 = static_cast(img->data[0]); + const uint8_t *src = data + 3 * dim_x * y; + + for (uint32_t x = 0; x < dim_x; x++) { + data8[(4 * dim_x * y) + (4 * x) ] = src[3 * x ]; + data8[(4 * dim_x * y) + (4 * x + 1)] = src[3 * x + 1]; + data8[(4 * dim_x * y) + (4 * x + 2)] = src[3 * x + 2]; + data8[(4 * dim_x * y) + (4 * x + 3)] = 255; + } + } + + return img; +} + +static astcenc_image* +unorm8x4ArrayToImage(const uint8_t *data, uint32_t dim_x, uint32_t dim_y) { + astcenc_image *img = imageAllocate(8, dim_x, dim_y, 1); + assert(img); + + for (uint32_t y = 0; y < dim_y; y++) { + uint8_t * data8 = static_cast(img->data[0]); + const uint8_t *src = data + 4 * dim_x * y; + + for (uint32_t x = 0; x < dim_x; x++) { + data8[(4 * dim_x * y) + (4 * x) ] = src[4 * x ]; + data8[(4 * dim_x * y) + (4 * x + 1)] = src[4 * x + 1]; + data8[(4 * dim_x * y) + (4 * x + 2)] = src[4 * x + 2]; + data8[(4 * dim_x * y) + (4 * x + 3)] = src[4 * x + 3]; + } + } + + return img; +} + +/** + * @memberof ktxTexture + * @ingroup write + * @~English + * @brief Creates default ASTC parameters + * + * @return ktxAstcParams with default options for ASTC compressor + */ +static ktxAstcParams +astcDefaultOptions() { + ktxAstcParams params{}; + params.structSize = sizeof(params); + params.verbose = false; + params.threadCount = 1; + params.blockDimension = KTX_PACK_ASTC_BLOCK_DIMENSION_6x6; + params.function = KTX_PACK_ASTC_ENCODER_FUNCTION_UNKNOWN; + params.mode = KTX_PACK_ASTC_ENCODER_MODE_LDR; + params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM; + params.normalMap = false; + + return params; +} + +/** + * @memberof ktxTexture + * @ingroup write + * @~English + * @brief Should be used to get VkFormat from ASTC block enum + * + * @return VKFormat for a specific ASTC block size + */ +static VkFormat +astcVkFormat(ktx_uint32_t block_size, bool sRGB) { + if (sRGB) { + switch (block_size) { + case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4: return VK_FORMAT_ASTC_4x4_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4: return VK_FORMAT_ASTC_5x4_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5: return VK_FORMAT_ASTC_5x5_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5: return VK_FORMAT_ASTC_6x5_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6: return VK_FORMAT_ASTC_6x6_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_8x5: return VK_FORMAT_ASTC_8x5_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_8x6: return VK_FORMAT_ASTC_8x6_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_8x8: return VK_FORMAT_ASTC_8x8_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_10x5: return VK_FORMAT_ASTC_10x5_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_10x6: return VK_FORMAT_ASTC_10x6_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_10x8: return VK_FORMAT_ASTC_10x8_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_10x10: return VK_FORMAT_ASTC_10x10_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_12x10: return VK_FORMAT_ASTC_12x10_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_12x12: return VK_FORMAT_ASTC_12x12_SRGB_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_3x3x3: return VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_4x3x3: return VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x3: return VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x4: return VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4x4: return VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x4: return VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x5: return VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5x5: return VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x5: return VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x6: return VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT; + } + } else { + switch (block_size) { + case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4: return VK_FORMAT_ASTC_4x4_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4: return VK_FORMAT_ASTC_5x4_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5: return VK_FORMAT_ASTC_5x5_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5: return VK_FORMAT_ASTC_6x5_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6: return VK_FORMAT_ASTC_6x6_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_8x5: return VK_FORMAT_ASTC_8x5_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_8x6: return VK_FORMAT_ASTC_8x6_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_8x8: return VK_FORMAT_ASTC_8x8_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_10x5: return VK_FORMAT_ASTC_10x5_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_10x6: return VK_FORMAT_ASTC_10x6_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_10x8: return VK_FORMAT_ASTC_10x8_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_10x10: return VK_FORMAT_ASTC_10x10_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_12x10: return VK_FORMAT_ASTC_12x10_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_12x12: return VK_FORMAT_ASTC_12x12_UNORM_BLOCK; + case KTX_PACK_ASTC_BLOCK_DIMENSION_3x3x3: return VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_4x3x3: return VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x3: return VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x4: return VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4x4: return VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x4: return VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x5: return VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5x5: return VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x5: return VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x6: return VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT; + } + } + + return VK_FORMAT_ASTC_6x6_SRGB_BLOCK; // Default is 6x6 sRGB image +} + +/** + * @memberof ktxTexture + * @ingroup write + * @~English + * @brief Creates valid ASTC encoder action from string. + * + * @return Valid astc_profile from string + */ +static astcenc_profile +astcEncoderAction(const ktxAstcParams ¶ms, const uint32_t* bdb) { + + if (params.function == KTX_PACK_ASTC_ENCODER_FUNCTION_SRGB && + params.mode == KTX_PACK_ASTC_ENCODER_MODE_LDR) { + return ASTCENC_PRF_LDR_SRGB; + } + else if (params.function == KTX_PACK_ASTC_ENCODER_FUNCTION_LINEAR && + params.mode == KTX_PACK_ASTC_ENCODER_MODE_LDR) { + return ASTCENC_PRF_LDR; + } + else if (params.function == KTX_PACK_ASTC_ENCODER_FUNCTION_LINEAR && + params.mode == KTX_PACK_ASTC_ENCODER_MODE_HDR) { + return ASTCENC_PRF_HDR; + } + else if (params.function == KTX_PACK_ASTC_ENCODER_FUNCTION_UNKNOWN) { + // If no options provided assume the user wants to use + // color space info provided from the file + + ktx_uint32_t transfer = KHR_DFDVAL(bdb, TRANSFER); + if (transfer == KHR_DF_TRANSFER_SRGB && + params.mode == KTX_PACK_ASTC_ENCODER_MODE_LDR) + return ASTCENC_PRF_LDR_SRGB; + else if (transfer == KHR_DF_TRANSFER_LINEAR) { + if (params.mode == KTX_PACK_ASTC_ENCODER_MODE_LDR) + return ASTCENC_PRF_LDR; + else + return ASTCENC_PRF_HDR; + } + } + // TODO: Add support for the following + // KTX_PACK_ASTC_ENCODER_ACTION_COMP_HDR_RGB_LDR_ALPHA; not supported + + return ASTCENC_PRF_LDR_SRGB; +} + + +/** + * @memberof ktxTexture + * @ingroup write + * @~English + * @brief Creates valid ASTC encoder swizzle from string. + * + * @return Valid astcenc_swizzle from string + */ +static astcenc_swizzle +astcSwizzle(const ktxAstcParams ¶ms) { + + astcenc_swizzle swizzle{ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A}; + + std::vector swizzle_array{&swizzle.r, &swizzle.g, &swizzle.b, &swizzle.a}; + + for (int i = 0; i < 4; i++) { + if (params.inputSwizzle[i] == 'r') + *swizzle_array[i] = ASTCENC_SWZ_R; + else if (params.inputSwizzle[i] == 'g') + *swizzle_array[i] = ASTCENC_SWZ_G; + else if (params.inputSwizzle[i] == 'b') + *swizzle_array[i] = ASTCENC_SWZ_B; + else if (params.inputSwizzle[i] == 'a') + *swizzle_array[i] = ASTCENC_SWZ_A; + else if (params.inputSwizzle[i] == '0') + *swizzle_array[i] = ASTCENC_SWZ_0; + else if (params.inputSwizzle[i] == '1') + *swizzle_array[i] = ASTCENC_SWZ_1; + } + + return swizzle; +} + +static void +astcBlockDimensions(ktx_uint32_t block_size, + uint32_t& block_x, uint32_t& block_y, uint32_t& block_z) { + switch (block_size) { + case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4 : block_x = 4; block_y = 4; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4 : block_x = 5; block_y = 4; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5 : block_x = 5; block_y = 5; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5 : block_x = 6; block_y = 5; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6 : block_x = 6; block_y = 6; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_8x5 : block_x = 8; block_y = 5; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_8x6 : block_x = 8; block_y = 6; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_10x5 : block_x = 10; block_y = 5; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_10x6 : block_x = 10; block_y = 6; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_8x8 : block_x = 8; block_y = 8; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_10x8 : block_x = 10; block_y = 8; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_10x10 : block_x = 10; block_y = 10; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_12x10 : block_x = 12; block_y = 10; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_12x12 : block_x = 12; block_y = 12; block_z = 1; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_3x3x3 : block_x = 3; block_y = 3; block_z = 3; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_4x3x3 : block_x = 4; block_y = 3; block_z = 3; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x3 : block_x = 4; block_y = 4; block_z = 3; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x4 : block_x = 4; block_y = 4; block_z = 4; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4x4 : block_x = 5; block_y = 4; block_z = 4; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x4 : block_x = 5; block_y = 5; block_z = 4; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x5 : block_x = 5; block_y = 5; block_z = 5; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5x5 : block_x = 6; block_y = 5; block_z = 5; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x5 : block_x = 6; block_y = 6; block_z = 5; break; + case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x6 : block_x = 6; block_y = 6; block_z = 6; break; + default: + block_x = 6; block_y = 6; block_z = 1; break; + } +} + +static float +astcQuality(ktx_uint32_t quality_level) { + switch (quality_level) { + case KTX_PACK_ASTC_QUALITY_LEVEL_FASTEST: return ASTCENC_PRE_FASTEST; + case KTX_PACK_ASTC_QUALITY_LEVEL_FAST: return ASTCENC_PRE_FAST; + case KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM: return ASTCENC_PRE_MEDIUM; + case KTX_PACK_ASTC_QUALITY_LEVEL_THOROUGH: return ASTCENC_PRE_THOROUGH; + case KTX_PACK_ASTC_QUALITY_LEVEL_EXHAUSTIVE: return ASTCENC_PRE_EXHAUSTIVE; + } + + return ASTCENC_PRE_MEDIUM; +} + +struct CompressionWorkload { + astcenc_context* context; + astcenc_image* image; + astcenc_swizzle swizzle; + uint8_t* data_out; + size_t data_len; + astcenc_error error; +}; + +static void +compressionWorkloadRunner(int threadCount, int threadId, void* payload) { + (void)threadCount; + + CompressionWorkload* work = static_cast(payload); + astcenc_error error = astcenc_compress_image( + work->context, work->image, &work->swizzle, + work->data_out, work->data_len, threadId); + + // This is a racy update, so which error gets returned is a random, but it + // will reliably report an error if an error occurs + if (error != ASTCENC_SUCCESS) { + work->error = error; + } +} + +/** + * @brief Worker thread helper payload for launchThreads. + */ +struct LaunchDesc { + /** The native thread handle. */ + pthread_t threadHandle; + /** The total number of threads in the thread pool. */ + int threadCount; + /** The thread index in the thread pool. */ + int threadId; + /** The user thread function to execute. */ + void (*func)(int, int, void*); + /** The user thread payload. */ + void* payload; +}; + +/** + * @brief Helper function to translate thread entry points. + * + * Convert a (void*) thread entry to an (int, void*) thread entry, where the + * integer contains the thread ID in the thread pool. + * + * @param p The thread launch helper payload. + */ +static void* +launchThreadsHelper(void *p) { + LaunchDesc* ltd = (LaunchDesc*)p; + ltd->func(ltd->threadCount, ltd->threadId, ltd->payload); + return nullptr; +} + +/* Public function, see header file for detailed documentation */ +static void +launchThreads(int threadCount, void (*func)(int, int, void*), void *payload) { + // Directly execute single threaded workloads on this thread + if (threadCount <= 1) { + func(1, 0, payload); + return; + } + + // Otherwise spawn worker threads + LaunchDesc *threadDescs = new LaunchDesc[threadCount]; + for (int i = 0; i < threadCount; i++) { + threadDescs[i].threadCount = threadCount; + threadDescs[i].threadId = i; + threadDescs[i].payload = payload; + threadDescs[i].func = func; + + pthread_create(&(threadDescs[i].threadHandle), nullptr, + launchThreadsHelper, (void*)&(threadDescs[i])); + } + + // ... and then wait for them to complete + for (int i = 0; i < threadCount; i++) { + pthread_join(threadDescs[i].threadHandle, nullptr); + } + + delete[] threadDescs; +} + +/** + * @memberof ktxTexture + * @ingroup writer + * @~English + * @brief Encode and compress a ktx texture with uncompressed images to astc. + * + * The images are encoded to ASTC block-compressed format. The encoded images + * replace the original images and the texture's fields including the DFD are + * modified to reflect the new state. + * + * Such textures can be directly uploaded to a GPU via a graphics API. + * + * @param[in] This pointer to the ktxTexture object of interest. + * @param[in] params pointer to ASTC params object. + * + * @return KTX_SUCCESS on success, other KTX_* enum values on error. + * + * @exception KTX_INVALID_OPERATION + * The texture's images are supercompressed. + * @exception KTX_INVALID_OPERATION + * The texture's images are in a block compressed + * format. + * @exception KTX_INVALID_OPERATION + * The texture image's format is a packed format + * (e.g. RGB565). + * @exception KTX_INVALID_OPERATION + * The texture image format's component size is not + * 8-bits. + * @exception KTX_INVALID_OPERATION + * The texture's images are 1D. Only 2D images can + * be supercompressed. + * @exception KTX_INVALID_OPERATION + * ASTC compressor failed to compress image for any + reason. + * @exception KTX_OUT_OF_MEMORY Not enough memory to carry out compression. + */ +extern "C" KTX_error_code +ktxTexture_CompressAstcEx(ktxTexture* _This, ktxAstcParams* params) { + // FIXME: At the moment defaults to ktx2 textures only + assert(_This->classId == ktxTexture2_c && "Only support ktx2 ASTC."); + ktxTexture2* This = (ktxTexture2*)_This; + + KTX_error_code result; + + if (!params) + return KTX_INVALID_VALUE; + + if (params->structSize != sizeof(struct ktxAstcParams)) + return KTX_INVALID_VALUE; + + if (This->supercompressionScheme != KTX_SS_NONE) + return KTX_INVALID_OPERATION; // Can't apply multiple schemes. + + if (This->isCompressed) + return KTX_INVALID_OPERATION; // Only non-block compressed formats + // can be encoded into an ASTC format. + + if (This->_protected->_formatSize.flags & KTX_FORMAT_SIZE_PACKED_BIT) + return KTX_INVALID_OPERATION; + + // Basic descriptor block begins after the total size field. + const uint32_t* BDB = This->pDfd+1; + + uint32_t num_components, component_size; + getDFDComponentInfoUnpacked(This->pDfd, &num_components, &component_size); + + if (component_size != 1) + return KTX_INVALID_OPERATION; // Can only deal with 8-bit components at the moment + + if (This->pData == NULL) { + result = ktxTexture2_LoadImageData((ktxTexture2*)This, nullptr, 0); + + if (result != KTX_SUCCESS) + return result; + } + + ktx_uint32_t threadCount = params->threadCount; + if (threadCount < 1) + threadCount = 1; + + ktx_uint32_t transfer = KHR_DFDVAL(BDB, TRANSFER); + bool sRGB = transfer == KHR_DF_TRANSFER_SRGB; + + VkFormat vkFormat = astcVkFormat(params->blockDimension, sRGB); + + // This->numLevels = 0 not allowed for block compressed formats + // But just in case make sure its not zero + This->numLevels = MAX(1, This->numLevels); + + // Create a prototype texture to use for calculating sizes in the target + // format and, as useful side effects, provide us with a properly sized + // data allocation and the DFD for the target format. + ktxTextureCreateInfo createInfo; + createInfo.glInternalformat = 0; + createInfo.vkFormat = vkFormat; + createInfo.baseWidth = This->baseWidth; + createInfo.baseHeight = This->baseHeight; + createInfo.baseDepth = This->baseDepth; + createInfo.generateMipmaps = This->generateMipmaps; + createInfo.isArray = This->isArray; + createInfo.numDimensions = This->numDimensions; + createInfo.numFaces = This->numFaces; + createInfo.numLayers = This->numLayers; + createInfo.numLevels = This->numLevels; + createInfo.pDfd = nullptr; + + ktxTexture2* prototype; + result = ktxTexture2_Create(&createInfo, KTX_TEXTURE_CREATE_ALLOC_STORAGE, + &prototype); + + if (result != KTX_SUCCESS) { + assert(result == KTX_OUT_OF_MEMORY && "Out of memory allocating texture."); + return result; + } + + astcenc_profile profile{ASTCENC_PRF_LDR_SRGB}; + + astcenc_swizzle swizzle{ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A}; + + uint32_t block_size_x{6}; + uint32_t block_size_y{6}; + uint32_t block_size_z{1}; + float quality{ASTCENC_PRE_MEDIUM}; + uint32_t flags{params->normalMap ? ASTCENC_FLG_MAP_NORMAL : 0}; + + astcBlockDimensions(params->blockDimension, + block_size_x, block_size_y, block_size_z); + quality = astcQuality(params->qualityLevel); + profile = astcEncoderAction(*params, BDB); + swizzle = astcSwizzle(*params); + + astcenc_config astc_config; + astcenc_context *astc_context; + astcenc_error astc_error = astcenc_config_init(profile, + block_size_x, block_size_y, block_size_z, + quality, flags, + &astc_config); + + if (astc_error != ASTCENC_SUCCESS) { + std::cout << "ASTC config init failed with error " << astcenc_get_error_string(astc_error) << std::endl; + return KTX_INVALID_OPERATION; + } + + astc_error = astcenc_context_alloc(&astc_config, threadCount, + &astc_context); + + if (astc_error != ASTCENC_SUCCESS) { + std::cout << "ASTC context alloc failed with error " << astcenc_get_error_string(astc_error) << std::endl; + return KTX_INVALID_OPERATION; + } + + // Walk in reverse on levels so we don't have to do this later + assert(prototype->dataSize && "Prototype texture size not initialized.\n"); + + if (!prototype->pData) { + return KTX_OUT_OF_MEMORY; + } + + uint8_t* buffer_out = prototype->pData; + + for (int32_t level = This->numLevels - 1; level >= 0; level--) { + uint32_t width = MAX(1, This->baseWidth >> level); + uint32_t height = MAX(1, This->baseHeight >> level); + uint32_t depth = MAX(1, This->baseDepth >> level); + ktx_size_t levelImageSizeIn = 0; + ktx_size_t levelImageSizeOut = 0; + ktx_uint32_t levelImages = 0; + + levelImages = This->numLayers * This->numFaces * depth; + levelImageSizeIn = ktxTexture_calcImageSize(ktxTexture(This), level, + KTX_FORMAT_VERSION_TWO); + levelImageSizeOut = ktxTexture_calcImageSize(ktxTexture(prototype), level, + KTX_FORMAT_VERSION_TWO); + ktx_size_t offset = ktxTexture2_levelDataOffset(This, level); + + for (uint32_t image = 0; image < levelImages; image++) { + if (params->verbose) + std::cout << "ASTC compressor: compressing image " << + (This->numLevels - level - 1) * levelImages + image + 1 + << " of " << This->numLevels * levelImages + << std::endl; + + astcenc_image *input_image = nullptr; + if (num_components == 1) + input_image = unorm8x1ArrayToImage(This->pData + offset, + width, height); + else if (num_components == 2) + input_image = unorm8x2ArrayToImage(This->pData + offset, + width, height); + else if (num_components == 3) + input_image = unorm8x3ArrayToImage(This->pData + offset, + width, height); + else // assume (num_components == 4) + input_image = unorm8x4ArrayToImage(This->pData + offset, + width, height); + + assert(input_image); + + CompressionWorkload work; + work.context = astc_context; + work.image = input_image; + work.swizzle = swizzle; + work.data_out = buffer_out; + work.data_len = levelImageSizeOut; + work.error = ASTCENC_SUCCESS; + + launchThreads(threadCount, compressionWorkloadRunner, &work); + + if (work.error != ASTCENC_SUCCESS) { + std::cout << "ASTC compressor failed\n" << + astcenc_get_error_string(work.error) << std::endl; + + imageFree(input_image); + + astcenc_context_free(astc_context); + return KTX_INVALID_OPERATION; + } + + buffer_out += levelImageSizeOut; + + // Reset ASTC context for next image + astcenc_compress_reset(astc_context); + offset += levelImageSizeIn; + } + } + + // We are done with astcencoder + astcenc_context_free(astc_context); + + assert(KHR_DFDVAL(prototype->pDfd+1, MODEL) == KHR_DF_MODEL_ASTC + && "Invalid dfd generated for ASTC image\n"); + assert((transfer == KHR_DF_TRANSFER_SRGB + ? KHR_DFDVAL(prototype->pDfd+1, PRIMARIES) == KHR_DF_PRIMARIES_SRGB + : true) && "Not a valid sRGB image\n"); + + // Fix up the current (This) texture + #undef DECLARE_PRIVATE + #undef DECLARE_PROTECTED + #define DECLARE_PRIVATE(n,t2) ktxTexture2_private& n = *(t2->_private) + #define DECLARE_PROTECTED(n,t2) ktxTexture_protected& n = *(t2->_protected) + + DECLARE_PROTECTED(thisPrtctd, This); + DECLARE_PRIVATE(protoPriv, prototype); + DECLARE_PROTECTED(protoPrtctd, prototype); + memcpy(&thisPrtctd._formatSize, &protoPrtctd._formatSize, + sizeof(ktxFormatSize)); + This->vkFormat = vkFormat; + This->isCompressed = prototype->isCompressed; + This->supercompressionScheme = KTX_SS_NONE; + This->_private->_requiredLevelAlignment = protoPriv._requiredLevelAlignment; + // Copy the levelIndex from the prototype to This. + memcpy(This->_private->_levelIndex, protoPriv._levelIndex, + This->numLevels * sizeof(ktxLevelIndexEntry)); + // Move the DFD and data from the prototype to This. + free(This->pDfd); + This->pDfd = prototype->pDfd; + prototype->pDfd = 0; + free(This->pData); + This->pData = prototype->pData; + This->dataSize = prototype->dataSize; + prototype->pData = 0; + prototype->dataSize = 0; + + ktxTexture2_Destroy(prototype); + return KTX_SUCCESS; +} + +/** + * @memberof ktxTexture + * @ingroup writer + * @~English + * @brief Encode and compress a ktx texture with uncompressed images to astc. + * + * The images are either encoded to ASTC block-compressed format. The encoded images + * replace the original images and the texture's fields including the DFD are modified to reflect the new + * state. + * + * Such textures can be directly uploaded to a GPU via a graphics API. + * + * @memberof ktxTexture + * @ingroup writer + * @~English + * + * @param[in] This pointer to the ktxTexture object of interest. + * @param[in] quality Compression quality, a value from 0 - 100. + Higher=higher quality/slower speed. + Lower=lower quality/faster speed. + Negative values for quality are considered > 100. + * + * @return KTX_SUCCESS on success, other KTX_* enum values on error. + * + * @exception KTX_INVALID_OPERATION + * The texture is already supercompressed. + * @exception KTX_INVALID_OPERATION + * The texture's image are in a block compressed + * format. + * @exception KTX_INVALID_OPERATION + * The texture image's format is a packed format + * (e.g. RGB565). + * @exception KTX_INVALID_OPERATION + * The texture image format's component size is not 8-bits. + * @exception KTX_INVALID_OPERATION + * The texture's images are 1D. Only 2D images can + * be supercompressed. + * @exception KTX_OUT_OF_MEMORY Not enough memory to carry out supercompression. + */ +extern "C" KTX_error_code +ktxTexture_CompressAstc(ktxTexture* This, ktx_uint32_t quality) { + ktxAstcParams params = astcDefaultOptions(); + + if (quality >= KTX_PACK_ASTC_QUALITY_LEVEL_FASTEST) + params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_FASTEST; + + if (quality >= KTX_PACK_ASTC_QUALITY_LEVEL_FAST) + params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_FAST; + + if (quality >= KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM) + params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM; + + if (quality >= KTX_PACK_ASTC_QUALITY_LEVEL_THOROUGH) + params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_THOROUGH; + + if (quality >= KTX_PACK_ASTC_QUALITY_LEVEL_EXHAUSTIVE) + params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_EXHAUSTIVE; + + return ktxTexture_CompressAstcEx(This, ¶ms); +} diff --git a/lib/basis_encode.cpp b/lib/basis_encode.cpp index 14b6430cb7..c16dbce48b 100644 --- a/lib/basis_encode.cpp +++ b/lib/basis_encode.cpp @@ -379,9 +379,9 @@ static bool basisuEncoderInitialized = false; * @return KTX_SUCCESS on success, other KTX_* enum values on error. * * @exception KTX_INVALID_OPERATION - * The texture is already supercompressed. + * The texture's images are supercompressed. * @exception KTX_INVALID_OPERATION - * The texture's image are in a block compressed + * The texture's images are in a block compressed * format. * @exception KTX_INVALID_OPERATION * The texture image's format is a packed format @@ -390,14 +390,11 @@ static bool basisuEncoderInitialized = false; * The texture image format's component size is not 8-bits. * @exception KTX_INVALID_OPERATION * @c separateRGToRGB_A is specified but the texture - * is only 1D. - * @exception KTX_INVALID_OPERATION - * The texture's images are 1D. Only 2D images can - * be supercompressed. + * has only 1 component. * @exception KTX_INVALID_OPERATION * Both preSwizzle and and inputSwizzle are specified * in @a params. - * @exception KTX_OUT_OF_MEMORY Not enough memory to carry out supercompression. + * @exception KTX_OUT_OF_MEMORY Not enough memory to carry out compression. */ extern "C" KTX_error_code ktxTexture2_CompressBasisEx(ktxTexture2* This, ktxBasisParams* params) @@ -994,7 +991,7 @@ ktxTexture2_CompressBasisEx(ktxTexture2* This, ktxBasisParams* params) } This->vkFormat = VK_FORMAT_UNDEFINED; - // Since we only allow 8-bit components to be compressed ... + // Block-compressed textures never need byte swapping so typeSize is 1. assert(This->_protected->_typeSize == 1); // Copy in the compressed image data. @@ -1056,9 +1053,6 @@ extern "C" KTX_API const ktx_uint32_t KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL * @exception KTX_INVALID_OPERATION * The texture's image are in a block compressed * format. - * @exception KTX_INVALID_OPERATION - * The texture's images are 1D. Only 2D images can - * be supercompressed. * @exception KTX_OUT_OF_MEMORY Not enough memory to carry out supercompression. */ extern "C" KTX_error_code diff --git a/tests/testimages/astc_ldr_6x6_posx.ktx2 b/tests/testimages/astc_ldr_6x6_posx.ktx2 new file mode 100644 index 0000000000..e954da1b62 --- /dev/null +++ b/tests/testimages/astc_ldr_6x6_posx.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d50804f2d150217c437c22af9bc10db7fa1bac1d6d4bb7a84a8e69998996f86 +size 1871712 diff --git a/tests/testimages/astc_mipmap_ldr_6x6_posx.ktx2 b/tests/testimages/astc_mipmap_ldr_6x6_posx.ktx2 new file mode 100644 index 0000000000..0dc1f33b53 --- /dev/null +++ b/tests/testimages/astc_mipmap_ldr_6x6_posx.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fed44dfe8a2f46364bb5d59e748feee6e85825ff3c357cb219ed972b720052e2 +size 2498256 diff --git a/tests/testimages/astc_mipmap_ldr_6x6_posy.ktx2 b/tests/testimages/astc_mipmap_ldr_6x6_posy.ktx2 new file mode 100644 index 0000000000..e2be06b4b0 --- /dev/null +++ b/tests/testimages/astc_mipmap_ldr_6x6_posy.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bb7f8db7221e265435b2ce8eab11e8aea295d8be5ad3d0366a9f04d333835fc +size 2498256 diff --git a/tests/testimages/astc_mipmap_ldr_6x6_posz.ktx2 b/tests/testimages/astc_mipmap_ldr_6x6_posz.ktx2 new file mode 100644 index 0000000000..6145471a71 --- /dev/null +++ b/tests/testimages/astc_mipmap_ldr_6x6_posz.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9688c162e5bf3b0bc048703c8b5db4bd7c5b3a062e5677b19e8d7126f428274 +size 2498256 diff --git a/tests/testimages/astc_mipmap_ldr_cubemap_6x6.ktx2 b/tests/testimages/astc_mipmap_ldr_cubemap_6x6.ktx2 new file mode 100644 index 0000000000..d4b7755585 --- /dev/null +++ b/tests/testimages/astc_mipmap_ldr_cubemap_6x6.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82791276b46fd852949bfa8cc73bd3dc0ba1e928f8b38d22fc9c3f56e21b456c +size 14986816 diff --git a/tests/toktx-tests b/tests/toktx-tests index d1df637665..1202a7c9e5 100755 --- a/tests/toktx-tests +++ b/tests/toktx-tests @@ -104,18 +104,18 @@ else fi numtests=$numtests+1 -if ! $toktx --zcmp --bcmp a b 2> /dev/null; then +if ! $toktx --zcmp --etc1s a b 2> /dev/null; then passed=$passed+1 else - echo "Simultaneous --bcmp & --zcmp allowed" + echo "Simultaneous --etc1s & --zcmp allowed" failed=$failed+1 fi numtests=$numtests+1 -if ! $toktx --bcmp --uastc a b 2> /dev/null; then +if ! $toktx --etc1s --uastc a b 2> /dev/null; then passed=$passed+1 else - echo "Simultaneous --bcmp & --uastc allowed" + echo "Simultaneous --etc1s & --uastc allowed" failed=$failed+1 fi @@ -167,7 +167,7 @@ function gencmpktx() { for i in $*; do if [ ${i:0:2} == "--" ]; then args="$args $i" - shift + shift fi done #echo $toktx $args $tempfile $* diff --git a/tests/toktx-tests.cmake b/tests/toktx-tests.cmake index f9672bb641..b412d1ed3c 100644 --- a/tests/toktx-tests.cmake +++ b/tests/toktx-tests.cmake @@ -168,3 +168,9 @@ set_tests_properties( PROPERTIES ENVIRONMENT TOKTX_OPTIONS=--lower_left_maps_to_s0t0 ) + +gencmpktx( astc_mipmap_ldr_cubemap_6x6 astc_mipmap_ldr_cubemap_6x6.ktx2 "../srcimages/Yokohama3/posx.jpg ../srcimages/Yokohama3/negx.jpg ../srcimages/Yokohama3/posy.jpg ../srcimages/Yokohama3/negy.jpg ../srcimages/Yokohama3/posz.jpg ../srcimages/Yokohama3/negz.jpg" "--test --encode astc --astc_blk_d 6x6 --genmipmap --cubemap" "" "" ) +gencmpktx( astc_mipmap_ldr_6x6_posx astc_mipmap_ldr_6x6_posx.ktx2 ../srcimages/Yokohama3/posx.jpg "--test --encode astc --astc_blk_d 6x6 --genmipmap" "" "" ) +gencmpktx( astc_ldr_6x6_posx astc_ldr_6x6_posx.ktx2 ../srcimages/Yokohama3/posx.jpg "--test --encode astc --astc_blk_d 6x6" "" "" ) +gencmpktx( astc_mipmap_ldr_6x6_posz astc_mipmap_ldr_6x6_posz.ktx2 ../srcimages/Yokohama3/posz.jpg "--test --encode astc --astc_blk_d 6x6 --genmipmap" "" "" ) +gencmpktx( astc_mipmap_ldr_6x6_posy astc_mipmap_ldr_6x6_posy.ktx2 ../srcimages/Yokohama3/posy.jpg "--test --encode astc --astc_blk_d 6x6 --genmipmap" "" "" ) diff --git a/tools/ktxsc/ktxsc.cpp b/tools/ktxsc/ktxsc.cpp index 4376bdabf8..07461ad30c 100644 --- a/tools/ktxsc/ktxsc.cpp +++ b/tools/ktxsc/ktxsc.cpp @@ -260,7 +260,7 @@ ktxSupercompressor::main(int argc, _TCHAR *argv[]) exitCode = 1; goto cleanup; } - if ((options.bcmp || options.bopts.uastc) && texture->isCompressed) { + if ((options.etc1s || options.bopts.uastc) && texture->isCompressed) { cerr << name << ": " << "Cannot encode already block-compressed textures " << "to Basis Universal or UASTC." @@ -277,7 +277,7 @@ ktxSupercompressor::main(int argc, _TCHAR *argv[]) (ktx_uint32_t)writer.str().length() + 1, writer.str().c_str()); - if (options.bcmp || options.bopts.uastc) { + if (options.etc1s || options.bopts.uastc) { commandOptions::basisOptions& bopts = options.bopts; ktx_uint32_t transfer = ktxTexture2_GetOETF(texture); if (bopts.normalMap && transfer != KHR_DF_TRANSFER_LINEAR) { @@ -387,8 +387,8 @@ ktxSupercompressor::validateOptions() usage(); exit(1); } - if (!options.bcmp && !options.zcmp && !options.bopts.uastc) { - cerr << "Must specify one of --zcmp, --bcmp or --uastc." << endl; + if (!options.etc1s && !options.zcmp && !options.bopts.uastc) { + cerr << "Must specify one of --zcmp, --etc1s (deprecated --bcmp) or --uastc." << endl; usage(); exit(1); } diff --git a/tools/toktx/image.cc b/tools/toktx/image.cc index c6314a29c6..2f5706821f 100644 --- a/tools/toktx/image.cc +++ b/tools/toktx/image.cc @@ -27,7 +27,7 @@ const std::vector Image::CreateFunctions = { }; Image* Image::CreateFromFile(const _tstring& name, - bool transformOETF, bool rescaleTo8Bit) { + bool transformOETF, rescale_e rescale) { FILE* f; Image* image; @@ -44,7 +44,7 @@ Image* Image::CreateFromFile(const _tstring& name, func = CreateFunctions.begin(); for (; func < CreateFunctions.end(); func++ ) { try { - image = (*func)(f, transformOETF, rescaleTo8Bit); + image = (*func)(f, transformOETF, rescale); return image; } catch (different_format&) { rewind(f); diff --git a/tools/toktx/image.hpp b/tools/toktx/image.hpp index 0a234bf831..8f47fc48c0 100644 --- a/tools/toktx/image.hpp +++ b/tools/toktx/image.hpp @@ -267,6 +267,9 @@ class Image { enum colortype_e { eLuminance=0, eLuminanceAlpha=1, eR=2, eRG, eRGB, eRGBA }; + enum rescale_e { + eNoRescale, eAlwaysRescaleTo8Bits, eRescaleTo8BitsIfLess + }; virtual ~Image() { }; @@ -283,18 +286,18 @@ class Image { } typedef Image* (*CreateFunction)(FILE* f, bool transformOETF, - bool rescaleTo8Bitbool); + rescale_e rescale); static const std::vector CreateFunctions; static Image* CreateFromNPBM(FILE*, bool transformOETF = true, - bool rescaleTo8Bitbool = false); + rescale_e rescale = eNoRescale); static Image* CreateFromJPG(FILE* f, bool transformOETF = true, - bool rescaleTo8Bitbool = false); + rescale_e rescale = eNoRescale); static Image* CreateFromPNG(FILE* f, bool transformOETF = true, - bool rescaleTo8Bitbool = false); + rescale_e rescale = eNoRescale); static Image* CreateFromFile(const _tstring& name, bool transformOETF = true, - bool rescaleTo8Bitbool = false); + rescale_e rescale = eNoRescale); virtual operator uint8_t*() = 0; diff --git a/tools/toktx/jpgimage.cc b/tools/toktx/jpgimage.cc index b0b660023a..2a6f5f9d36 100644 --- a/tools/toktx/jpgimage.cc +++ b/tools/toktx/jpgimage.cc @@ -74,7 +74,7 @@ class myjpgdstream : public jpeg_decoder_file_stream { // All JPEG files are sRGB. Image* -Image::CreateFromJPG(FILE* src, bool, bool) +Image::CreateFromJPG(FILE* src, bool, rescale_e) { myjpgdstream stream(src); uint32_t componentCount; diff --git a/tools/toktx/npbmimage.cc b/tools/toktx/npbmimage.cc index f7d700c1cb..f18271a766 100644 --- a/tools/toktx/npbmimage.cc +++ b/tools/toktx/npbmimage.cc @@ -93,9 +93,9 @@ void skipNonData(FILE *src) skipSpaces(src); } -static Image* createFromPPM(FILE*, bool transformOETF, bool rescaleTo8Bits); -static Image* createFromPGM(FILE*, bool transformOETF, bool rescaleTo8Bits); -static Image* createFromPAM(FILE*, bool transformOETF, bool rescaleTo8Bits); +static Image* createFromPPM(FILE*, bool transformOETF, Image::rescale_e rescale); +static Image* createFromPGM(FILE*, bool transformOETF, Image::rescale_e rescale); +static Image* createFromPAM(FILE*, bool transformOETF, Image::rescale_e rescale); static void parseHeader(FILE* src, uint32_t& width, uint32_t& height, int32_t& maxval); static void readImage(FILE* src, Image& image, int32_t maxval); @@ -120,7 +120,7 @@ static void readImage(FILE* src, Image& image, int32_t maxval); //! @author Mark Callow //! Image* -Image::CreateFromNPBM(FILE* src, bool transformOETF, bool rescaleTo8Bits) +Image::CreateFromNPBM(FILE* src, bool transformOETF, Image::rescale_e rescale) { char line[255]; int numvals; @@ -130,11 +130,11 @@ Image::CreateFromNPBM(FILE* src, bool transformOETF, bool rescaleTo8Bits) numvals = fscanf(src, "%3s", line); if (numvals != 0) { if (strcmp(line, "P6") == 0) { - return createFromPPM(src, transformOETF, rescaleTo8Bits); + return createFromPPM(src, transformOETF, rescale); } else if (strcmp(line, "P5") == 0) { - return createFromPGM(src, transformOETF, rescaleTo8Bits); + return createFromPGM(src, transformOETF, rescale); } else if (strcmp(line, "P7") == 0) { - return createFromPAM(src, transformOETF, rescaleTo8Bits); + return createFromPAM(src, transformOETF, rescale); } else if (strcmp(line, "P3") == 0) { throw std::runtime_error("Plain PPM format is not supported."); } @@ -170,7 +170,7 @@ Image::CreateFromNPBM(FILE* src, bool transformOETF, bool rescaleTo8Bits) //! @author Mark Callow //! Image* -createFromPPM(FILE* src, bool transformOETF, bool rescaleTo8Bits) +createFromPPM(FILE* src, bool transformOETF, Image::rescale_e rescale) { int32_t maxval; uint32_t width, height; @@ -182,7 +182,7 @@ createFromPPM(FILE* src, bool transformOETF, bool rescaleTo8Bits) //fprintf(stderr, "maxval is %d\n",maxval); // PPM is 3 components - if (maxval > 255 && !rescaleTo8Bits) + if (maxval > 255 && rescale != Image::rescale_e::eAlwaysRescaleTo8Bits) image = new rgb16image(width, height); else image = new rgb8image(width, height); @@ -232,7 +232,7 @@ createFromPPM(FILE* src, bool transformOETF, bool rescaleTo8Bits) //! @author Mark Callow //! Image* -createFromPGM(FILE* src, bool transformOETF, bool rescaleTo8Bits) +createFromPGM(FILE* src, bool transformOETF, Image::rescale_e rescale) { int maxval; uint32_t width, height; @@ -243,7 +243,7 @@ createFromPGM(FILE* src, bool transformOETF, bool rescaleTo8Bits) parseHeader(src, width, height, maxval); // PGM is 1 component. Treat as luminance for consistency with .png & .jpg. - if (maxval > 255 && !rescaleTo8Bits) + if (maxval > 255 && rescale != Image::rescale_e::eAlwaysRescaleTo8Bits) image = new r16image(width, height); else image = new r8image(width, height); @@ -297,7 +297,7 @@ createFromPGM(FILE* src, bool transformOETF, bool rescaleTo8Bits) //! @author Mark Callow //! Image* -createFromPAM(FILE* src, bool transformOETF, bool rescaleTo8Bits) +createFromPAM(FILE* src, bool transformOETF, Image::rescale_e rescale) { char line[255]; #define MAX_TUPLETYPE_SIZE 20 @@ -343,7 +343,7 @@ createFromPAM(FILE* src, bool transformOETF, bool rescaleTo8Bits) if (maxval <= 0 || maxval >= (1<<16)) { throw Image::invalid_file("Max color component value must be > 0 && < 65536."); } - if (maxval > 255 && !rescaleTo8Bits) { + if (maxval > 255 && rescale != Image::rescale_e::eAlwaysRescaleTo8Bits) { switch (depth) { case 1: image = new r16image(width, height); diff --git a/tools/toktx/pngimage.cc b/tools/toktx/pngimage.cc index 0144876d37..90d49dc1c2 100644 --- a/tools/toktx/pngimage.cc +++ b/tools/toktx/pngimage.cc @@ -28,7 +28,7 @@ void warning(const char *pFmt, ...); Image* -Image::CreateFromPNG(FILE* src, bool transformOETF, bool rescaleTo8Bits) +Image::CreateFromPNG(FILE* src, bool transformOETF, Image::rescale_e rescale) { // Unfortunately LoadPNG doesn't believe in stdio plus // the function we need only reads from memory. To avoid @@ -104,7 +104,7 @@ Image::CreateFromPNG(FILE* src, bool transformOETF, bool rescaleTo8Bits) case LCT_GREY: componentCount = 1; // TODO: Create 4-bit color type and rescale 1- & 2-bpp to that. - rescaleTo8Bits = true; + rescale = Image::eAlwaysRescaleTo8Bits; break; case LCT_RGB: if (pTrnsChunk != nullptr) { @@ -138,7 +138,9 @@ Image::CreateFromPNG(FILE* src, bool transformOETF, bool rescaleTo8Bits) // To avoid potentially uninitialized variable warning. componentCount = 0; } - if (rescaleTo8Bits) { + if (rescale == eAlwaysRescaleTo8Bits + || (rescale == eRescaleTo8BitsIfLess + && state.info_png.color.bitdepth < 8)) { state.info_raw.bitdepth = 8; if (state.info_png.color.bitdepth != 8) { warning("Rescaling %d-bit image to 8 bits.", diff --git a/tools/toktx/toktx.cc b/tools/toktx/toktx.cc index f3f50a8a4c..de46baa010 100644 --- a/tools/toktx/toktx.cc +++ b/tools/toktx/toktx.cc @@ -721,10 +721,40 @@ toktxApp::main(int argc, _TCHAR *argv[]) Image* image; try { + Image::rescale_e rescale = Image::eNoRescale; + if (options.etc1s || options.bopts.uastc) + rescale = Image::rescale_e::eAlwaysRescaleTo8Bits; + else if (options.astc) + rescale = Image::rescale_e::eRescaleTo8BitsIfLess; + image = Image::CreateFromFile(infile, options.assign_oetf == KHR_DF_TRANSFER_UNSPECIFIED, - options.bcmp || options.bopts.uastc); + rescale); + + // If input is > 8bit and user wants LDR issue quality loss warning + if (options.astc && image->getComponentSize() > 1 + && options.astcopts.mode == KTX_PACK_ASTC_ENCODER_MODE_LDR) { + cerr << name << ": Warning! input file is 16bit but LDR option is specified." + << " Expect quality loss in the output." + << endl; + } + + // If input is < 8bit and user wants HDR issue warning + if (options.astc && image->getComponentSize() <= 1 && + options.astcopts.mode == KTX_PACK_ASTC_ENCODER_MODE_HDR) { + cerr << name << ": Warning! input file is not 16bit but HDR option is specified." + << endl; + } + + // If no astc mode option is specified and + // if input is <= 8bit default to LDR otherwise default to HDR + if (options.astc && options.astcopts.mode == KTX_PACK_ASTC_ENCODER_MODE_DEFAULT) { + if (image->getComponentSize() <= 1) + options.astcopts.mode = KTX_PACK_ASTC_ENCODER_MODE_LDR; + else + options.astcopts.mode = KTX_PACK_ASTC_ENCODER_MODE_HDR; + } if (i == 0) { // First file. @@ -869,8 +899,8 @@ toktxApp::main(int argc, _TCHAR *argv[]) } if (options.inputSwizzle.size() > 0 - // inputSwizzle is handled during BasisU encoding - && !options.bcmp && !options.bopts.uastc) { + // inputSwizzle is handled during BasisU and astc encoding + && !options.etc1s && !options.bopts.uastc && !options.astc) { image->swizzle(options.inputSwizzle); } @@ -1106,7 +1136,7 @@ toktxApp::main(int argc, _TCHAR *argv[]) goto cleanup; } #if TRAVIS_DEBUG - if (options.bcmp) { + if (options.etc1s) { cout << "level = " << level << ", faceSlice = " << faceSlice; cout << ", srcImg = " << hex << (void *)srcImg << dec; cout << ", imageSize = " << imageSize << endl; @@ -1201,7 +1231,7 @@ toktxApp::main(int argc, _TCHAR *argv[]) // Add Swizzle metadata if (options.swizzle.size()) { swizzle = options.swizzle; - } else if (!options.bcmp && !options.bopts.uastc + } else if (!options.etc1s && !options.bopts.uastc && !options.astc && defaultSwizzle.size()) { swizzle = defaultSwizzle; } @@ -1229,7 +1259,7 @@ toktxApp::main(int argc, _TCHAR *argv[]) f = _tfopen(options.outfile.c_str(), "wb"); if (f) { - if (options.bcmp || options.bopts.uastc) { + if (options.etc1s || options.bopts.uastc) { commandOptions::basisOptions& bopts = options.bopts; if (bopts.normalMap && chosenOETF != KHR_DF_TRANSFER_LINEAR) { fprintf(stderr, "%s: --normal_map specified but input file(s) are" @@ -1246,6 +1276,10 @@ toktxApp::main(int argc, _TCHAR *argv[]) options.bopts.inputSwizzle[i] = defaultSwizzle[i]; } } + + bopts.threadCount = options.threadCount; + bopts.normalMap = options.normalMode; + #if TRAVIS_DEBUG bopts.print(); #endif @@ -1257,6 +1291,39 @@ toktxApp::main(int argc, _TCHAR *argv[]) exitCode = 2; goto cleanup; } + } else if (options.astc) { + commandOptions::astcOptions& astcopts = options.astcopts; +#if TRAVIS_DEBUG + astcopts.print(); +#endif + if (options.inputSwizzle.size()) { + for (i = 0; i < options.inputSwizzle.size(); i++) { + astcopts.inputSwizzle[i] = options.inputSwizzle[i]; + } + } else if (defaultSwizzle.size()) { + for (i = 0; i < options.inputSwizzle.size(); i++) { + astcopts.inputSwizzle[i] = defaultSwizzle[i]; + } + } + + if (chosenOETF == KHR_DF_TRANSFER_SRGB) { + astcopts.function = KTX_PACK_ASTC_ENCODER_FUNCTION_SRGB; + } + else { + astcopts.function = KTX_PACK_ASTC_ENCODER_FUNCTION_LINEAR; + } + + astcopts.threadCount = options.threadCount; + astcopts.normalMap = options.normalMode; + + ret = ktxTexture_CompressAstcEx(texture, &astcopts); + if (KTX_SUCCESS != ret) { + fprintf(stderr, "%s failed to compress KTX file \"%s\" to astc; KTX error: %s\n", + name.c_str(), options.outfile.c_str(), + ktxErrorString(ret)); + exitCode = 2; + goto cleanup; + } } else { ret = KTX_SUCCESS; } @@ -1489,7 +1556,7 @@ toktxApp::processOption(argparser& parser, int opt) break; case 1102: std::for_each(parser.optarg.begin(), parser.optarg.end(), [](char & c) { - c = (char)::toupper(c); + c = (char)::toupper(c); }); if (parser.optarg.compare("R") == 0) options.targetType = commandOptions::eR; @@ -1508,7 +1575,7 @@ toktxApp::processOption(argparser& parser, int opt) break; case 1103: std::for_each(parser.optarg.begin(), parser.optarg.end(), [](char & c) { - c = (char)::tolower(c); + c = (char)::tolower(c); }); if (parser.optarg.compare("linear") == 0) options.convert_oetf = KHR_DF_TRANSFER_LINEAR; @@ -1517,7 +1584,7 @@ toktxApp::processOption(argparser& parser, int opt) break; case 1104: std::for_each(parser.optarg.begin(), parser.optarg.end(), [](char & c) { - c = (char)::tolower(c); + c = (char)::tolower(c); }); if (parser.optarg.compare("linear") == 0) options.assign_oetf = KHR_DF_TRANSFER_LINEAR; @@ -1526,7 +1593,7 @@ toktxApp::processOption(argparser& parser, int opt) break; case 1105: std::for_each(parser.optarg.begin(), parser.optarg.end(), [](char & c) { - c = (char)::tolower(c); + c = (char)::tolower(c); }); if (parser.optarg.compare("bt709") == 0) options.assign_primaries = KHR_DF_PRIMARIES_BT709; diff --git a/utils/scapp.h b/utils/scapp.h index 200d05d86a..cc7be657b3 100644 --- a/utils/scapp.h +++ b/utils/scapp.h @@ -6,6 +6,8 @@ #include #include "ktxapp.h" +#include +#include template struct clampedOption @@ -38,142 +40,304 @@ struct clampedOption T max; }; +/** + * @memberof ktxTexture + * @ingroup write + * @~English + * @brief Creates valid ASTC block dimension from string. + * + * @return Valid ktx_pack_astc_block_dimension_e from string + */ +ktx_pack_astc_block_dimension_e +astcBlockDimension(const char* block_size) { + static std::unordered_map + astc_blocks_mapping{{"4x4", KTX_PACK_ASTC_BLOCK_DIMENSION_4x4}, + {"5x4", KTX_PACK_ASTC_BLOCK_DIMENSION_5x4}, + {"5x5", KTX_PACK_ASTC_BLOCK_DIMENSION_5x5}, + {"6x5", KTX_PACK_ASTC_BLOCK_DIMENSION_6x5}, + {"6x6", KTX_PACK_ASTC_BLOCK_DIMENSION_6x6}, + {"8x5", KTX_PACK_ASTC_BLOCK_DIMENSION_8x5}, + {"8x6", KTX_PACK_ASTC_BLOCK_DIMENSION_8x6}, + {"10x5", KTX_PACK_ASTC_BLOCK_DIMENSION_10x5}, + {"10x6", KTX_PACK_ASTC_BLOCK_DIMENSION_10x6}, + {"8x8", KTX_PACK_ASTC_BLOCK_DIMENSION_8x8}, + {"10x8", KTX_PACK_ASTC_BLOCK_DIMENSION_10x8}, + {"10x10", KTX_PACK_ASTC_BLOCK_DIMENSION_10x10}, + {"12x10", KTX_PACK_ASTC_BLOCK_DIMENSION_12x10}, + {"12x12", KTX_PACK_ASTC_BLOCK_DIMENSION_12x12}, + {"3x3x3", KTX_PACK_ASTC_BLOCK_DIMENSION_3x3x3}, + {"4x3x3", KTX_PACK_ASTC_BLOCK_DIMENSION_4x3x3}, + {"4x4x3", KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x3}, + {"4x4x4", KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x4}, + {"5x4x4", KTX_PACK_ASTC_BLOCK_DIMENSION_5x4x4}, + {"5x5x4", KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x4}, + {"5x5x5", KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x5}, + {"6x5x5", KTX_PACK_ASTC_BLOCK_DIMENSION_6x5x5}, + {"6x6x5", KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x5}, + {"6x6x6", KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x6}}; + + auto opt = astc_blocks_mapping.find(block_size); + + if (opt != astc_blocks_mapping.end()) + return opt->second; + + return KTX_PACK_ASTC_BLOCK_DIMENSION_6x6; +} + +/** + * @memberof ktxTexture + * @ingroup write + * @~English + * @brief Creates valid ASTC quality from string. + * + * @return Valid ktx_pack_astc_quality_e from string + */ +ktx_pack_astc_quality_levels_e +astcQualityLevel(const char *quality) { + + static std::unordered_map astc_quality_mapping{ + {"fastest", KTX_PACK_ASTC_QUALITY_LEVEL_FASTEST}, + {"fast", KTX_PACK_ASTC_QUALITY_LEVEL_FAST}, + {"medium", KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM}, + {"thorough", KTX_PACK_ASTC_QUALITY_LEVEL_THOROUGH}, + {"exhaustive", KTX_PACK_ASTC_QUALITY_LEVEL_EXHAUSTIVE} + }; + + auto opt = astc_quality_mapping.find(quality); + + if (opt != astc_quality_mapping.end()) + return opt->second; + + return KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM; +} + +/** + * @memberof ktxTexture + * @ingroup write + * @~English + * @brief Creates valid ASTC mode from string. + * + * @return Valid ktx_pack_astc_mode_e from string + */ +ktx_pack_astc_encoder_mode_e +astcEncoderMode(const char* mode) { + if (strcmp(mode, "ldr") == 0) + return KTX_PACK_ASTC_ENCODER_MODE_LDR; + else if (strcmp(mode, "hdr") == 0) + return KTX_PACK_ASTC_ENCODER_MODE_HDR; + + return KTX_PACK_ASTC_ENCODER_MODE_DEFAULT; +} + /* // Markdown doesn't work in files included by snipped{doc} or include{doc} // so the table below has to be laboriously done in html. //! [scApp options]
-
--bcmp
-
Supercompress the image data with ETC1S / BasisLZ. Implies @b --t2. - RED images will become RGB with RED in each component. RG images will - have R in the RGB part and G in the alpha part of the compressed - texture. When set, the following BasisLZ-related options become valid - otherwise they are ignored. +
--encode <astc|etc1s|uastc>
+
Compress the image data to ASTC, transcodable ETC1S / BasisLZ or + high-quality transcodable UASTC format. Implies @b --t2. + With each encoding option the following encoder specific options + become valid, otherwise they are ignored.
-
--no_multithreading
-
Disable multithreading. Deprecated. For backward compatibility - only. Use @b --threads 1 instead.
-
--threads <count>
-
Explicitly set the number of threads to use during compression. - By default, ETC1S / BasisLZ compression will use the number of threads - reported by @c thread::hardware_concurrency or 1 if value - returned is 0.
-
--clevel <level>
-
ETC1S / BasisLZ compression level, an encoding speed vs. quality - tradeoff. Range is [0,5], default is 1. Higher values are slower, but - give higher quality.
-
--qlevel <level>
-
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 @b --qlevel which defaults to 128 if neither it nor - both of @b --max_endpoints and @b --max_selectors have been set. - - @note 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 @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. -
--max_endpoints <arg>
-
Manually set the maximum number of color endpoint clusters. - Range is [1,16128]. Default is 0, unset. If this is set, - @b --max_selectors must also be set, otherwise the value - will be ignored.
-
--endpoint_rdo_threshold <arg>
-
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.
-
--max_selectors <arg>
-
Manually set the maximum number of color selector clusters. Range - is [1,16128]. Default is 0, unset. If this is set, - @b --max_selectors must also be set, otherwise the value - will be ignored.
-
--selector_rdo_threshold <arg>
-
Set selector RDO quality threshold. The default is 1.5. Lower is - higher quality but less quality per output bit (try [1.0,3.0]. - This will override the value chosen by @b --qlevel.
-
--normal_map
-
Tunes codec parameters for better quality on normal maps (no - selector RDO, no endpoint RDO). Only valid for linear textures.
-
--separate_rg_to_color_alpha
-
Separates the input R and G channels to RGB and A (for tangent - space XY normal maps). Only needed with 3 or 4 component input - images.
-
--no_endpoint_rdo
-
Disable endpoint rate distortion optimizations. Slightly faster, - less noisy output, but lower quality per output bit. Default is - to do endpoint RDO.
-
--no_selector_rdo
-
Disable selector rate distortion optimizations. Slightly faster, - less noisy output, but lower quality per output bit. Default is - to do selector RDO.
+
astc:
+
Create a texture in high-quality ASTC format.
+
--astc_blk_d <XxY|XxYxZ>
+
Specify which block dimension to use for compressing the textures. + e.g. @b --astc_blk_d 6x5 for 2D or @b --astc_blk_d 6x6x6 for 3D. + 6x6 is default for 2D. + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Supported 2D block dimensions are:
4x4 8.00 bpp
5x4 6.40 bpp
5x5 5.12 bpp
6x5 4.27 bpp
6x6 3.56 bpp
8x5 3.20 bpp
8x6 2.67 bpp
10x5 2.56 bpp
10x6 2.13 bpp
8x8 2.00 bpp
10x8 1.60 bpp
10x10 1.28 bpp
12x10 1.07 bpp
12x12 0.89 bpp
Supported 3D block dimensions are:
3x3x3 4.74 bpp
4x3x3 3.56 bpp
4x4x3 2.67 bpp
4x4x4 2.00 bpp
5x4x4 1.60 bpp
5x5x4 1.28 bpp
5x5x5 1.02 bpp
6x5x5 0.85 bpp
6x6x5 0.71 bpp
6x6x6 0.59 bpp
+
--astc_mode <ldr|hdr>
+
Specify which encoding mode to use. LDR is the default unless the input. + image is 16-bit in which case the default is HDR.
+
--astc_quality <level>
+
The quality level configures the quality-performance tradeoff for + the compressor; more complete searches of the search space improve + image quality at the expense of compression time. Default is 'medium' + The quality level can be set to fastest (0) and thorough (100) via the + following fixed quality presets: + + + + + + + +
Level Quality
fastest (equivalent to quality = 0)
fast (equivalent to quality = 10)
medium (equivalent to quality = 60)
thorough (equivalent to quality = 98)
exhaustive (equivalent to quality = 100)
+
- -
--uastc [<level>]
-
Create a texture in high-quality transcodable UASTC format. Implies - @b --t2. The optional parameter @e level selects a speed vs quality - tradeoff as shown in the following table: - - - - - - - - -
Level Speed Quality
0 Fastest 43.45dB
1 Faster 46.49dB
2 Default 47.47dB
3 Slower 48.01dB
4 Very slow 48.24dB
- - You are strongly encouraged to also specify @b --zcmp 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 @b --uastc - is set the following options become available for controlling RDO:
+
etc1s:
+
Supercompress the image data with ETC1S / BasisLZ. + RED images will become RGB with RED in each component. RG images + will have R in the RGB part and G in the alpha part of the + compressed texture. When set, the following BasisLZ-related + options become valid, otherwise they are ignored.
+
--no_multithreading
+
Disable multithreading. Deprecated. For backward compatibility. + Use @b --threads 1 instead.
+
--clevel <level>
+
ETC1S / BasisLZ compression level, an encoding speed vs. quality + tradeoff. Range is [0,5], default is 1. Higher values are slower, + but give higher quality.
+
--qlevel <level>
+
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 + both of @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.
+
--max_endpoints <arg>
+
Manually set the maximum number of color endpoint clusters. Range + is [1,16128]. Default is 0, unset.
+
--endpoint_rdo_threshold <arg>
+
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.
+
--max_selectors <arg>
+
Manually set the maximum number of color selector clusters from + [1,16128]. Default is 0, unset.
+
--selector_rdo_threshold <arg>
+
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.
+
--separate_rg_to_color_alpha
+
Separates the input R and G channels to RGB and A (for tangent + space XY normal maps). Only needed with 3 or 4 component input + images.
+
--no_endpoint_rdo
+
Disable endpoint rate distortion optimizations. Slightly faster, + less noisy output, but lower quality per output bit. Default is + to do endpoint RDO.
+
--no_selector_rdo
+
Disable selector rate distortion optimizations. Slightly faster, + less noisy output, but lower quality per output bit. Default is + to do selector RDO.
+
+
+
uastc:
+
Create a texture in high-quality transcodable UASTC format.
+
--uastc_quality <level>
+
This optional parameter selects a speed vs quality + tradeoff as shown in the following table: + + + + + + + + +
Level Speed Quality
0 Fastest 43.45dB
1 Faster 46.49dB
2 Default 47.47dB
3 Slower 48.01dB
4 Very slow 48.24dB
+ + You are strongly encouraged to also specify @b --zcmp 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:
--uastc_rdo_l [<lambda>]
-
Enable UASTC RDO post-processing and optionally 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 Previous versions used the @b --uastc_rdo_q option which was - removed because the RDO algorithm changed.
+
Enable UASTC RDO post-processing and optionally 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.
--uastc_rdo_d <dictsize>
-
Set UASTC RDO dictionary size in bytes. Default is 4096. Lower - values=faster, but give less compression. Range is [64,65536].
+
Set UASTC RDO dictionary size in bytes. Default is 4096. Lower + values=faster, but give less compression. Range is [64,65536].
--uastc_rdo_b <scale>
-
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.
+
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.
--uastc_rdo_s <deviation>
-
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.
+
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.
--uastc_rdo_f
-
Do not favor simpler UASTC modes in RDO mode.
+
Do not favor simpler UASTC modes in RDO mode.
--uastc_rdo_m
-
Disable RDO multithreading (slightly higher compression, - deterministic).
+
Disable RDO multithreading (slightly higher compression, + deterministic).
-
+
--normal_mode
+
For ASTC encoder '@b --encode astc' assumes the input texture is + a three component linear LDR normal map storing unit length + normals as (R=X, G=Y, B=Z). The output will be a two component + X+Y normal map stored as (RGB=X, A=Y), optimized for angular + error instead of simple PSNR. The Z component can be recovered + programmatically in shader code by using the equation: +
+      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
+                 
+ For ETC1S encoder '@b --encode etc1s' tunes codec parameters for + better quality on normal maps (no selector RDO, no endpoint RDO). + Only valid for linear textures.
--no_sse
-
Forbid use of the SSE instruction set. Ignored if CPU does not - support SSE. Only the Basis Universal compressor uses SSE.
-
--verbose
-
Print encoder/compressor activity status to stdout. Currently only - the Basis Universal compressor emits status.
+
Forbid use of the SSE instruction set. Ignored if CPU does not + support SSE. Only the Basis Universal compressor uses SSE.
+
--bcmp
+
Deprecated. Use '@b --encode etc1s' instead.
+
--uastc [<level>]
+
Deprecated. Use '@b --encode uastc' instead.
--zcmp [<compressionLevel>]
-
Supercompress the data with Zstandard. Implies @b --t2. Can be used - with data in any format except ETC1S / BasisLZ (@b --bcmp). Most - effective with RDO-conditioned UASTC or uncompressed formats. The - optional @e compressionLevel range is 1 - 22 and the default is 3. - Lower values=faster but give less compression. Values above 20 should - be used with caution as they require more memory.
+
Supercompress the data with Zstandard. Implies @b --t2. Can be used + with data in any format except ETC1S / BasisLZ. Most + effective with RDO-conditioned UASTC or uncompressed formats. The + optional compressionLevel range is 1 - 22 and the default is 3. + Lower values=faster but give less compression. Values above 20 + should be used with caution as they require more memory.
+
--threads <count>
+
Explicitly set the number of threads to use during compression. + By default, ETC1S / BasisLZ and ASTC compression will use the number of + threads reported by thread::hardware_concurrency or 1 if value + returned is 0.
+
--verbose
+
Print encoder/compressor activity status to stdout. Currently + only the astc, etc1s and uastc encoders emit status.
@snippet{doc} ktxapp.h ktxApp options @@ -246,16 +410,55 @@ class scApp : public ktxApp { } }; + struct astcOptions : public ktxAstcParams { + clampedOption threadCount; + clampedOption blockDimension; + clampedOption function; + clampedOption mode; + clampedOption qualityLevel; + + astcOptions() : + threadCount(ktxAstcParams::threadCount, 1, 10000), + blockDimension(ktxAstcParams::blockDimension, 0, KTX_PACK_ASTC_BLOCK_DIMENSION_MAX), + function(ktxAstcParams::function, 0, KTX_PACK_ASTC_ENCODER_FUNCTION_MAX), + mode(ktxAstcParams::mode, 0, KTX_PACK_ASTC_ENCODER_MODE_MAX), + qualityLevel(ktxAstcParams::qualityLevel, 0, KTX_PACK_ASTC_QUALITY_LEVEL_MAX) + { + uint32_t tc = thread::hardware_concurrency(); + if (tc == 0) tc = 1; + threadCount.max = tc; + threadCount = tc; + + structSize = sizeof(ktxAstcParams); + blockDimension.clear(); + blockDimension = KTX_PACK_ASTC_BLOCK_DIMENSION_6x6; + function.clear(); + // Default to unknown to have a chance to use color space from file + function = KTX_PACK_ASTC_ENCODER_FUNCTION_UNKNOWN; + mode.clear(); + qualityLevel.clear(); + normalMap = false; + } + }; int ktx2; - int bcmp; + int etc1s; int zcmp; + int astc; + ktx_bool_t normalMode; clamped zcmpLevel; + clamped threadCount; struct basisOptions bopts; + struct astcOptions astcopts; - commandOptions() : zcmpLevel(ZSTD_CLEVEL_DEFAULT, 1U, 22U) { + commandOptions() : + zcmpLevel(ZSTD_CLEVEL_DEFAULT, 1U, 22U), + threadCount(std::max(1U, thread::hardware_concurrency()) , 1U, 10000U) + { ktx2 = false; - bcmp = false; + etc1s = false; zcmp = false; + astc = false; + normalMode = false; } }; @@ -276,10 +479,61 @@ class scApp : public ktxApp { return scparams; } + void setEncoder(string encoding) { + if (encoding == "astc") + options.astc = 1; + else if (encoding == "etc1s") + options.etc1s = 1; + else if (encoding == "uastc") + options.bopts.uastc = 1; + } + void usage() { cerr << - " --bcmp Supercompress the image data with ETC1S / BasisLZ. Implies --t2.\n" + " --encode \n" + " Compress the image data to ASTC, transcodable ETC1S / BasisLZ or\n" + " high-quality transcodable UASTC format. Implies --t2.\n" + " With each encoding option the following encoder specific options\n" + " become valid, otherwise they are ignored.\n\n" + " astc:\n" + " Create a texture in high-quality ASTC format.\n" + " --astc_blk_d \n" + " Specify which block dimension to use for compressing the textures.\n" + " e.g. --astc_blk_d 6x5 for 2D or --astc_blk_d 6x6x6 for 3D.\n" + " 6x6 is default for 2D.\n\n" + " Supported 2D block dimensions are:\n\n" + " 4x4: 8.00 bpp 10x5: 2.56 bpp\n" + " 5x4: 6.40 bpp 10x6: 2.13 bpp\n" + " 5x5: 5.12 bpp 8x8: 2.00 bpp\n" + " 6x5: 4.27 bpp 10x8: 1.60 bpp\n" + " 6x6: 3.56 bpp 10x10: 1.28 bpp\n" + " 8x5: 3.20 bpp 12x10: 1.07 bpp\n" + " 8x6: 2.67 bpp 12x12: 0.89 bpp\n\n" + " Supported 3D block dimensions are:\n\n" + " 3x3x3: 4.74 bpp 5x5x4: 1.28 bpp\n" + " 4x3x3: 3.56 bpp 5x5x5: 1.02 bpp\n" + " 4x4x3: 2.67 bpp 6x5x5: 0.85 bpp\n" + " 4x4x4: 2.00 bpp 6x6x5: 0.71 bpp\n" + " 5x4x4: 1.60 bpp 6x6x6: 0.59 bpp\n" + " --astc_mode \n" + " Specify which encoding mode to use. LDR is the default unless the input.\n" + " image is 16-bit in which case the default is HDR.\n" + " --astc_quality \n" + " The quality level configures the quality-performance tradeoff for\n" + " the compressor; more complete searches of the search space improve\n" + " image quality at the expense of compression time. Default is 'medium'\n" + " The quality level can be set to fastest (0) and thorough (100) via the \n" + " 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)\n" + " etc1s:\n" + " Supercompress the image data with ETC1S / BasisLZ.\n" " RED images will become RGB with RED in each component. RG images\n" " will have R in the RGB part and G in the alpha part of the\n" " compressed texture. When set, the following BasisLZ-related\n" @@ -287,11 +541,6 @@ class scApp : public ktxApp { " --no_multithreading\n" " Disable multithreading. Deprecated. For backward compatibility.\n" " Use --threads 1 instead.\n" - " --threads \n" - " Explicitly set the number of threads to use during compression.\n" - " By default, ETC1S / BasisLZ compression will use the number of\n" - " threads reported by thread::hardware_concurrency or 1 if value\n" - " returned is 0.\n" " --clevel \n" " ETC1S / BasisLZ compression level, an encoding speed vs. quality\n" " tradeoff. Range is [0,5], default is 1. Higher values are slower,\n" @@ -327,9 +576,6 @@ class scApp : public ktxApp { " Set selector RDO quality threshold. The default is 1.25. Lower\n" " is higher quality but less quality per output bit (try\n" " [1.0,3.0]). This will override the value chosen by --qlevel.\n" - " --normal_map\n" - " Tunes codec parameters for better quality on normal maps (no\n" - " selector RDO, no endpoint RDO). Only valid for linear textures.\n" " --separate_rg_to_color_alpha\n" " Separates the input R and G channels to RGB and A (for tangent\n" " space XY normal maps). Only needed with 3 or 4 component input\n" @@ -342,25 +588,26 @@ class scApp : public ktxApp { " Disable selector rate distortion optimizations. Slightly faster,\n" " less noisy output, but lower quality per output bit. Default is\n" " to do selector RDO.\n\n" - " --uastc []\n" + " uastc:\n" " Create a texture in high-quality transcodable UASTC format.\n" - " Implies --t2. The optional parameter selects a speed\n" - " vs quality tradeoff as shown in the following table:\n" + " --uastc_quality \n" + " This optional parameter selects a speed vs quality\n" + " tradeoff as shown in the following table:\n" "\n" - " Level | Speed | Quality\n" - " ----- | ------- | -------\n" - " 0 | Fastest | 43.45dB\n" - " 1 | Faster | 46.49dB\n" - " 2 | Default | 47.47dB\n" - " 3 | Slower | 48.01dB\n" - " 4 | Very slow | 48.24dB\n" + " Level | Speed | Quality\n" + " ----- | --------- | -------\n" + " 0 | Fastest | 43.45dB\n" + " 1 | Faster | 46.49dB\n" + " 2 | Default | 47.47dB\n" + " 3 | Slower | 48.01dB\n" + " 4 | Very slow | 48.24dB\n" "\n" " You are strongly encouraged to also specify --zcmp to losslessly\n" " compress the UASTC data. This and any LZ-style compression can\n" " be made more effective by conditioning the UASTC texture data\n" " using the Rate Distortion Optimization (RDO) post-process stage.\n" - " When --uastc is set the following options become available for\n" - " controlling RDO:\n\n" + " When uastc encoding is set the following options become available\n" + " for controlling RDO:\n\n" " --uastc_rdo_l []\n" " Enable UASTC RDO post-processing and optionally set UASTC RDO\n" " quality scalar (lambda) to @e lambda. Lower values yield higher\n" @@ -387,17 +634,42 @@ class scApp : public ktxApp { " --uastc_rdo_m\n" " Disable RDO multithreading (slightly higher compression,\n" " deterministic).\n\n" - " --no_sse Forbid use of the SSE instruction set. Ignored if CPU does not\n" + " --normal_mode\n" + " For ASTC encoder '--encode astc' assumes the input texture is\n" + " a three component linear LDR normal map storing unit length\n" + " normals as (R=X, G=Y, B=Z). The output will be a two component\n" + " X+Y normal map stored as (RGB=X, A=Y), optimized for angular\n" + " error instead of simple PSNR. The Z component can be recovered\n" + " programmatically in shader code by using the equation:\n\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\n" + " For ETC1S encoder '--encode etc1s' tunes codec parameters for \n" + " better quality on normal maps (no selector RDO, no endpoint RDO).\n" + " Only valid for linear textures.\n" + " --no_sse\n" + " Forbid use of the SSE instruction set. Ignored if CPU does not\n" " support SSE. Only the Basis Universal compressor uses SSE.\n" - " --verbose Print encoder/compressor activity status to stdout. Currently\n" - " only the Basis Universal compressor emits status.\n" + " --bcmp\n" + " Deprecated. Use '--encode etc1s' instead.\n" + " --uastc []\n" + " Deprecated. Use '--encode uastc' instead.\n" " --zcmp []\n" " Supercompress the data with Zstandard. Implies --t2. Can be used\n" - " with data in any format except ETC1S / BasisLZ (--bcmp). Most\n" + " with data in any format except ETC1S / BasisLZ. Most\n" " effective with RDO-conditioned UASTC or uncompressed formats. The\n" " optional compressionLevel range is 1 - 22 and the default is 3.\n" " Lower values=faster but give less compression. Values above 20\n" - " should be used with caution as they require more memory.\n"; + " should be used with caution as they require more memory.\n" + " --threads \n" + " Explicitly set the number of threads to use during compression.\n" + " By default, ETC1S / BasisLZ and ASTC compression will use the number of\n" + " threads reported by thread::hardware_concurrency or 1 if value\n" + " returned is 0.\n" + " --verbose\n" + " Print encoder/compressor activity status to stdout. Currently\n" + " only the astc, etc1s and uastc encoders emit status.\n" + "\n"; ktxApp::usage(); cerr << endl << "In case of ambiguity, such as when the last option is one with an optional\n" @@ -414,7 +686,6 @@ scApp::scApp(string& version, string& defaultVersion, : ktxApp(version, defaultVersion, options), options(options) { argparser::option my_option_list[] = { - { "bcmp", argparser::option::no_argument, NULL, 'b' }, { "zcmp", argparser::option::optional_argument, NULL, 'z' }, { "no_multithreading", argparser::option::no_argument, NULL, 'N' }, { "threads", argparser::option::required_argument, NULL, 't' }, @@ -424,25 +695,32 @@ scApp::scApp(string& version, string& defaultVersion, { "endpoint_rdo_threshold", argparser::option::required_argument, NULL, 'E' }, { "max_selectors", argparser::option::required_argument, NULL, 'u' }, { "selector_rdo_threshold", argparser::option::required_argument, NULL, 'S' }, - { "normal_map", argparser::option::no_argument, NULL, 'n' }, + { "normal_mode", argparser::option::no_argument, NULL, 'n' }, { "separate_rg_to_color_alpha", argparser::option::no_argument, NULL, 1000 }, { "no_endpoint_rdo", argparser::option::no_argument, NULL, 1001 }, { "no_selector_rdo", argparser::option::no_argument, NULL, 1002 }, { "no_sse", argparser::option::no_argument, NULL, 1011 }, - { "uastc", argparser::option::optional_argument, NULL, 1003 }, + { "uastc_quality", argparser::option::required_argument, NULL, 1003 }, { "uastc_rdo_l", argparser::option::optional_argument, NULL, 1004 }, { "uastc_rdo_d", argparser::option::required_argument, NULL, 1005 }, { "uastc_rdo_b", argparser::option::optional_argument, NULL, 1006 }, { "uastc_rdo_s", argparser::option::optional_argument, NULL, 1007 }, { "uastc_rdo_f", argparser::option::no_argument, NULL, 1008 }, { "uastc_rdo_m", argparser::option::no_argument, NULL, 1009 }, - { "verbose", argparser::option::no_argument, NULL, 1010 } + { "verbose", argparser::option::no_argument, NULL, 1010 }, + { "astc_blk_d", argparser::option::required_argument, NULL, 1012 }, + { "astc_mode", argparser::option::required_argument, NULL, 1013 }, + { "astc_quality", argparser::option::required_argument, NULL, 1014 }, + { "encode", argparser::option::required_argument, NULL, 1015 }, + // Deprecated options + { "bcmp", argparser::option::no_argument, NULL, 'b' }, + { "uastc", argparser::option::optional_argument, NULL, 1016 } }; 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 += "bz;Nt:c:q:e:E:u:S:n"; + short_opts += "z;Nt:c:q:e:E:u:S:nb"; } void @@ -482,25 +760,9 @@ scApp::processOption(argparser& parser, int opt) bool capture = true; switch (opt) { - case 'b': - if (options.zcmp) { - cerr << "Only one of --bcmp and --zcmp can be specified." - << endl; - usage(); - exit(1); - } - if (options.bopts.uastc) { - cerr << "Only one of --bcmp and --uastc can be specified." - << endl; - usage(); - exit(1); - } - options.bcmp = 1; - options.ktx2 = 1; - break; case 'z': - if (options.bcmp) { - cerr << "Only one of --bcmp and --zcmp can be specified." + if (options.etc1s) { + cerr << "Only one of '--encode etc1s|--bcmp' and --zcmp can be specified." << endl; usage(); exit(1); @@ -525,11 +787,11 @@ scApp::processOption(argparser& parser, int opt) hasArg = true; break; case 'N': - options.bopts.threadCount = 1; + options.threadCount = 1; capture = false; break; case 'n': - options.bopts.normalMap = 1; + options.normalMode = true; break; case 1001: options.bopts.noEndpointRDO = 1; @@ -553,19 +815,11 @@ scApp::processOption(argparser& parser, int opt) hasArg = true; break; case 't': - options.bopts.threadCount = strtoi(parser.optarg.c_str()); + options.threadCount = strtoi(parser.optarg.c_str()); capture = false; break; case 1003: - if (options.bcmp) { - cerr << "Only one of --bcmp and --uastc can be specified." - << endl; - usage(); - exit(1); - } - options.bopts.uastc = 1; - options.ktx2 = 1; - if (parser.optarg.size() > 0) { + { ktx_uint32_t level = strtoi(parser.optarg.c_str()); level = clamp(level, 0, KTX_PACK_UASTC_MAX_LEVEL); // Ensure the last one wins in case of multiple of these args. @@ -604,12 +858,65 @@ scApp::processOption(argparser& parser, int opt) break; case 1010: options.bopts.verbose = true; + options.astcopts.verbose = true; capture = false; break; case 1011: options.bopts.noSSE = true; capture = true; break; + case 1012: // astc_blk_d + options.astcopts.blockDimension = astcBlockDimension(parser.optarg.c_str()); + hasArg = true; + break; + case 1013: // astc_mode + options.astcopts.mode = astcEncoderMode(parser.optarg.c_str()); + hasArg = true; + break; + case 1014: // astc_quality + options.astcopts.qualityLevel = astcQualityLevel(parser.optarg.c_str()); + hasArg = true; + break; + case 'b': + if (options.zcmp) { + cerr << "Only one of --bcmp and --zcmp can be specified.\n" + << "--bcmp is deprecated, use '--encode etc1s' instead." + << endl; + usage(); + exit(1); + } + if (options.bopts.uastc) { + cerr << "Only one of --bcmp and '--encode etc1s|--uastc' can be specified.\n" + << "--bcmp is deprecated, use '--encode etc1s' instead." + << endl; + usage(); + exit(1); + } + options.etc1s = 1; + options.ktx2 = 1; + break; + case 1015: + setEncoder(parser.optarg); + options.ktx2 = 1; + break; + case 1016: + if (options.etc1s) { + cerr << "Only one of `--encode etc1s|--bcmp` and `--uastc []` can be specified." + << endl; + usage(); + exit(1); + } + options.bopts.uastc = 1; + options.ktx2 = 1; + if (parser.optarg.size() > 0) { + ktx_uint32_t level = strtoi(parser.optarg.c_str()); + level = clamp(level, 0, KTX_PACK_UASTC_MAX_LEVEL); + // Ensure the last one wins in case of multiple of these args. + options.bopts.uastcFlags = (unsigned int)~KTX_PACK_UASTC_LEVEL_MASK; + options.bopts.uastcFlags |= level; + hasArg = true; + } + break; default: return false; } @@ -622,4 +929,3 @@ scApp::processOption(argparser& parser, int opt) return true; } -