diff --git a/.gitattributes b/.gitattributes index d4f71a5dcb..5e75f4048f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -75,3 +75,9 @@ tests/testimages/*.ktx2 filter=lfs -text tests/srcimages/**/*.jpg filter=lfs -text tests/srcimages/**/*.png filter=lfs -text tests/webgl/**/*.ktx2 filter=lfs -text +tests/clitests/input/images/**/*.ktx2 filter=lfs -text +tests/clitests/input/images/**/*.png filter=lfs -text +tests/clitests/input/images/**/*.exr filter=lfs -text +tests/clitests/golden/images/**/*.ktx2 filter=lfs -text +tests/clitests/golden/images/**/*.png filter=lfs -text +tests/clitests/golden/images/**/*.exr filter=lfs -text diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index c768d03836..835fa625a8 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -43,16 +43,18 @@ jobs: run: git fetch -f origin ${{ github.ref }}:${{ github.ref }} - name: Pull test images from Git LFS run: git lfs pull --include=tests/srcimages,tests/testimages + - name: Fetch CTS Submodule + run: git submodule update --init --recursive tests/cts - name: Install Ninja run: choco install ninja --no-progress - name: Configure Mingw x64 - run: cmake -B build -G "Ninja Multi-Config" -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ + run: cmake -B build -G "Ninja Multi-Config" -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DKTX_FEATURE_TOOLS=TRUE -DKTX_FEATURE_TOOLS_CTS=TRUE - name: Build Mingw x64 Debug run: cmake --build build --config Debug - name: Build Mingw x64 Release run: cmake --build build --config Release - name: Test Mingw build - run: ctest --test-dir build -C Release + run: ctest --output-on-failure --test-dir build -C Release - name: Upload test log if: ${{ failure() }} run: ci_scripts/on_failure.ps1 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index faa7b98ad5..4250abec84 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -33,19 +33,19 @@ jobs: arch: [ x64 ] options: [ {config: 'Debug,Release', - doc: ON, jni: ON, loadtests: OpenGL+Vulkan, tests: ON, tools: ON, + doc: ON, jni: ON, loadtests: OpenGL+Vulkan, tests: ON, tools: ON, tools_cts: ON, package: YES, sse: ON, opencl: OFF}, {config: Release, - doc: OFF, loadtests: OFF, tests: OFF, tools: OFF, + doc: OFF, loadtests: OFF, tests: OFF, tools: OFF, tools_cts: OFF, package: NO, sse: OFF, opencl: OFF}, {config: Release, - doc: OFF, loadtests: OFF, tests: OFF, tools: OFF, + doc: OFF, loadtests: OFF, tests: OFF, tools: OFF, tools_cts: OFF, package: NO, sse: OFF, opencl: ON}, {config: Release, - doc: OFF, loadtests: OFF, tests: OFF, tools: OFF, + doc: OFF, loadtests: OFF, tests: OFF, tools: OFF, tools_cts: OFF, package: NO, sse: ON, opencl: ON} ] @@ -56,7 +56,7 @@ jobs: arch: x64 options: { config: 'Debug,Release', - doc: ON, jni: ON, loadtests: OpenGL+Vulkan, tests: ON, tools: ON, + doc: ON, jni: ON, loadtests: OpenGL+Vulkan, tests: ON, tools: ON, tools_cts: ON, package: NO, sse: ON, opencl: OFF } @@ -68,7 +68,7 @@ jobs: # built tests options: { config: 'Debug,Release', - doc: ON, jni: ON, loadtests: OFF, tests: ON, tools: ON, + doc: ON, jni: ON, loadtests: OFF, tests: ON, tools: ON, tools_cts: ON, package: YES } runs-on: ${{ matrix.os }} @@ -90,6 +90,7 @@ jobs: FEATURE_LOADTESTS: ${{ matrix.options.loadtests }} FEATURE_TESTS: ${{ matrix.options.tests }} FEATURE_TOOLS: ${{ matrix.options.tools }} + FEATURE_TOOLS_CTS: ${{ matrix.options.tools_cts }} PACKAGE: ${{ matrix.options.package }} SUPPORT_OPENCL: ${{ matrix.options.opencl }} SUPPORT_SSE: ${{ matrix.options.sse }} @@ -189,7 +190,7 @@ jobs: - name: Test Windows build if: matrix.arch == 'x64' && matrix.options.tests == 'ON' - run: ctest --test-dir $env:BUILD_DIR -C Release + run: ctest --output-on-failure --test-dir $env:BUILD_DIR -C Release - name: Upload test log if: ${{ failure() }} diff --git a/.gitignore b/.gitignore index 1f7c70534e..c751530755 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,9 @@ version.h .vs/ CMakeSettings.json +# Visual Studio Code +.vscode/ + # Build bindings & maven /interface/java_binding/build/ /interface/java_binding/target/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..dd8e8e8c9e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,8 @@ +# Copyright 2022-2023 The Khronos Group Inc. +# Copyright 2022-2023 RasterGrid Kft. +# SPDX-License-Identifier: Apache-2.0 + +[submodule "tests/cts"] + path = tests/cts + url = https://github.com/KhronosGroup/KTX-Software-CTS.git + branch = main diff --git a/.reuse/dep5 b/.reuse/dep5 index e9ec6d4d0d..62ad04dabd 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -204,3 +204,7 @@ License: Apache-2.0 Files: other_projects/fmt/* Copyright: 2012 - present Victor Zverovich License: MIT + +Files: other_projects/cxxopts/* +Copyright: 2014-2022 Jarryd Beck +License: MIT diff --git a/.travis.yml b/.travis.yml index a17c728609..4c70040b4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,35 +43,36 @@ env: # add the list of tests to the cmake test runner. - CONFIGURATION=Debug,Release PLATFORM=macOS ARCHS=x86_64 FEATURE_DOC=ON FEATURE_JNI=ON FEATURE_LOADTESTS=OpenGL+Vulkan FEATURE_TOOLS=ON - SUPPORT_SSE=ON SUPPORT_OPENCL=OFF DEPLOY_DOCS=YES PACKAGE=YES + FEATURE_TOOLS_CTS=ON SUPPORT_SSE=ON SUPPORT_OPENCL=OFF DEPLOY_DOCS=YES PACKAGE=YES - CONFIGURATION=Release PLATFORM=macOS ARCHS=arm64 FEATURE_DOC=ON FEATURE_JNI=ON FEATURE_LOADTESTS=OpenGL+Vulkan FEATURE_TESTS=OFF - FEATURE_TOOLS=ON SUPPORT_SSE=ON SUPPORT_OPENCL=OFF PACKAGE=YES + FEATURE_TOOLS=ON FEATURE_TOOLS_CTS=OFF SUPPORT_SSE=ON SUPPORT_OPENCL=OFF PACKAGE=YES - CONFIGURATION=Release PLATFORM=macOS ARCHS=x86_64 FEATURE_DOC=ON FEATURE_JNI=OFF FEATURE_LOADTESTS=OFF FEATURE_TOOLS=OFF - SUPPORT_SSE=ON SUPPORT_OPENCL=ON + FEATURE_TOOLS_CTS=OFF SUPPORT_SSE=ON SUPPORT_OPENCL=ON - CONFIGURATION=Release PLATFORM=macOS ARCHS=arm64 FEATURE_DOC=ON FEATURE_JNI=OFF FEATURE_LOADTESTS=OFF FEATURE_TESTS=OFF - FEATURE_TOOLS=OFF SUPPORT_SSE=ON SUPPORT_OPENCL=ON + FEATURE_TOOLS=OFF FEATURE_TOOLS_CTS=OFF SUPPORT_SSE=ON SUPPORT_OPENCL=ON - CONFIGURATION=Release PLATFORM=macOS ARCHS=x86_64 FEATURE_DOC=ON FEATURE_JNI=OFF FEATURE_LOADTESTS=OFF FEATURE_TOOLS=OFF - SUPPORT_SSE=OFF SUPPORT_OPENCL=ON + FEATURE_TOOLS_CTS=OFF SUPPORT_SSE=OFF SUPPORT_OPENCL=ON - CONFIGURATION=Release PLATFORM=macOS ARCHS=arm64 FEATURE_DOC=ON FEATURE_JNI=OFF FEATURE_LOADTESTS=OFF FEATURE_TESTS=OFF - FEATURE_TOOLS=OFF SUPPORT_SSE=OFF SUPPORT_OPENCL=ON + FEATURE_TOOLS=OFF FEATURE_TOOLS_CTS=OFF SUPPORT_SSE=OFF SUPPORT_OPENCL=ON - CONFIGURATION=Release PLATFORM=macOS ARCHS=x86_64 FEATURE_DOC=ON FEATURE_JNI=OFF FEATURE_LOADTESTS=OFF FEATURE_TOOLS=OFF - SUPPORT_SSE=OFF SUPPORT_OPENCL=OFF + FEATURE_TOOLS_CTS=OFF SUPPORT_SSE=OFF SUPPORT_OPENCL=OFF - CONFIGURATION=Release PLATFORM=macOS ARCHS=arm64 FEATURE_DOC=ON FEATURE_JNI=OFF FEATURE_LOADTESTS=OFF FEATURE_TESTS=OFF - FEATURE_TOOLS=OFF SUPPORT_SSE=OFF SUPPORT_OPENCL=OFF + FEATURE_TOOLS=OFF FEATURE_TOOLS_CTS=OFF SUPPORT_SSE=OFF + SUPPORT_OPENCL=OFF - CONFIGURATION=Debug,Release PLATFORM=iOS FEATURE_DOC=OFF FEATURE_JNI=OFF FEATURE_LOADTESTS=OpenGL+Vulkan FEATURE_TOOLS=OFF - SUPPORT_SSE=OFF SUPPORT_OPENCL=OFF PACKAGE=YES + FEATURE_TOOLS_CTS=OFF SUPPORT_SSE=OFF SUPPORT_OPENCL=OFF PACKAGE=YES jobs: include: @@ -110,7 +111,8 @@ jobs: - VULKAN_SDK_VER: "1.3.243" - CMAKE_GEN: Ninja - CONFIGURATION=Release - FEATURE_DOC=ON FEATURE_JNI=ON FEATURE_LOADTESTS=OpenGL FEATURE_TOOLS=ON + FEATURE_DOC=ON FEATURE_JNI=ON FEATURE_LOADTESTS=OpenGL + FEATURE_TOOLS=ON FEATURE_TOOLS_CTS=OFF SUPPORT_SSE=OFF SUPPORT_OPENCL=OFF PACKAGE=YES - os: linux dist: jammy @@ -118,7 +120,8 @@ jobs: env: - CMAKE_GEN: Ninja - CONFIGURATION=Release - FEATURE_DOC=OFF FEATURE_JNI=OFF FEATURE_LOADTESTS=OFF FEATURE_TOOLS=OFF + FEATURE_DOC=OFF FEATURE_JNI=OFF FEATURE_LOADTESTS=OFF + FEATURE_TOOLS=OFF FEATURE_TOOLS_CTS=OFF SUPPORT_SSE=ON SUPPORT_OPENCL=ON PACKAGE=NO - os: linux dist: jammy @@ -127,7 +130,7 @@ jobs: - CMAKE_GEN: Ninja - CONFIGURATION=Release FEATURE_DOC=OFF FEATURE_JNI=OFF FEATURE_LOADTESTS=OFF - FEATURE_TOOLS=OFF + FEATURE_TOOLS=OFF FEATURE_TOOLS_CTS=OFF SUPPORT_SSE=OFF SUPPORT_OPENCL=ON PACKAGE=NO - os: linux dist: jammy @@ -136,7 +139,7 @@ jobs: - CMAKE_GEN: Ninja - CONFIGURATION=Release FEATURE_DOC=OFF FEATURE_JNI=OFF FEATURE_LOADTESTS=OFF - FEATURE_TOOLS=OFF + FEATURE_TOOLS=OFF FEATURE_TOOLS_CTS=OFF SUPPORT_SSE=OFF SUPPORT_OPENCL=OFF PACKAGE=NO - os: linux dist: jammy diff --git a/BUILDING.md b/BUILDING.md index 5095e0266f..4f7662d100 100755 --- a/BUILDING.md +++ b/BUILDING.md @@ -51,6 +51,10 @@ If you need the library to be static, add `-D KTX_FEATURE_STATIC_LIBRARY=ON` to > define `KHRONOS_STATIC` before including KTX header files. > This is especially important on Windows. +If you want to run the CTS tests (recommended only during KTX development) +add `-D KTX_FEATURE_TOOLS_CTS=ON` to the CMake configure command and fetch +the CTS submodule. For more information see [Conformance Test Suite](#conformance-test-suite). + If you want the Basis Universal encoders in `libktx` to use OpenCL add `-D BASISU_SUPPORT_OPENCL=ON` to the CMake configure command. @@ -421,6 +425,48 @@ cmake --build "build-android" > Note: SSE has to be disabled currently (for ABIs x86 and x86_64) due to [an issue](https://github.com/BinomialLLC/basis_universal/pull/233). +Conformance Test Suite +------------ + +The submodule of [CTS Repository](https://github.com/KhronosGroup/KTX-Software-CTS/) is optional and +only required for running the CTS tests during KTX development. If the CTS test suit is desired it +can be fetched during cloning with the additional `--recurse-submodules` git clone flag: +```bash +git clone --recurse-submodules git@github.com:KhronosGroup/KTX-Software.git +``` +If the repository was already cloned or whenever the submodule ref changes the submodule has to be +updated with: +```bash +git submodule update --init --recursive tests/cts +``` +(For more information on submodules see the [git documentation](https://git-scm.com/book/en/v2/Git-Tools-Submodules).) + +Once the submodule is fetched the CTS tests can be enabled with the `KTX_FEATURE_TOOLS_CTS` +cmake option during cmake configuration. Please note that for `KTX_FEATURE_TOOLS_CTS` to take +effect both `KTX_FEATURE_TESTS` and `KTX_FEATURE_TOOLS` has to be also enabled. +The CTS integrates into `ctest` so running `ctest` will also execute the CTS tests too. +The test cases can be limited to the CTS tests with `ctest -R ktxToolTests`. + +Example for development workflow with CTS testing: +```bash +# Git clone and submodule fetch +git clone git@github.com:KhronosGroup/KTX-Software.git +cd KTX-Software/ +git submodule update --init --recursive tests/cts +# Configure +mkdir build +cmake -B build . -DKTX_FEATURE_DOC=ON -DKTX_FEATURE_STATIC_LIBRARY=ON -DKTX_FEATURE_TOOLS_CTS=ON -DKTX_FEATURE_TESTS=ON -DKTX_FEATURE_TOOLS_CTS=ON +# Build everything (depending on workflow its better to build the specific target like 'ktxtools'): +cmake --build build --target all +# Run every test case: +ctest --test-dir build +# Run only the CTS test cases: +ctest --test-dir build -R ktxToolTests +``` + +To create and update CTS test cases and about their specific features and usages +see the [CTS documentation](https://github.com/KhronosGroup/KTX-Software-CTS/blob/main/README.md). + Dependencies ------------ diff --git a/CMakeLists.txt b/CMakeLists.txt index 907310f704..3ba2edc2a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ -# Copyright 2015-2020 The Khronos Group Inc. +# Copyright 2015-2023 The Khronos Group Inc. +# Copyright 2022-2023 RasterGrid Kft. # SPDX-License-Identifier: Apache-2.0 cmake_minimum_required(VERSION 3.15) @@ -38,8 +39,17 @@ CMAKE_DEPENDENT_OPTION( KTX_FEATURE_TOOLS ) option( KTX_FEATURE_DOC "Create KTX documentation" OFF ) option( KTX_FEATURE_STATIC_LIBRARY "Create static libraries (shared otherwise)" ${LIB_TYPE_DEFAULT} ) -option( KTX_FEATURE_TESTS "Create unit tests" ON ) option( KTX_FEATURE_JNI "Create Java bindings for libktx" OFF ) +option( KTX_FEATURE_TESTS "Create unit tests" ON ) +option( KTX_FEATURE_TOOLS_CTS "Enable KTX CLI Tools CTS tests (requires CTS submodule)" OFF ) + +if(KTX_FEATURE_TOOLS_CTS AND NOT KTX_FEATURE_TOOLS) + message(WARNING "KTX_FEATURE_TOOLS is not set -> disabling KTX_FEATURE_TOOLS_CTS") + set(KTX_FEATURE_TOOLS_CTS "OFF") +elseif(KTX_FEATURE_TOOLS_CTS AND NOT KTX_FEATURE_TESTS) + message(WARNING "KTX_FEATURE_TESTS is not set -> disabling KTX_FEATURE_TOOLS_CTS") + set(KTX_FEATURE_TOOLS_CTS "OFF") +endif() if(POLICY CMP0127) # cmake_dependent_option() supports full Condition Syntax. Introduced in @@ -239,11 +249,14 @@ else() message(FATAL_ERROR "${CMAKE_CXX_COMPILER_ID} not yet supported.") endif() +set(KTX_BUILD_DIR "${CMAKE_BINARY_DIR}") + set(KTX_MAIN_SRC include/KHR/khr_df.h include/ktx.h lib/basis_sgd.h lib/basis_transcode.cpp + lib/miniz_wrapper.cpp lib/basisu/transcoder/basisu_containers.h lib/basisu/transcoder/basisu_containers_impl.h lib/basisu/transcoder/basisu_file_headers.h @@ -288,6 +301,46 @@ set(KTX_MAIN_SRC lib/vkformat_str.c ) +set(BASISU_ENCODER_CXX_SRC + lib/basisu/encoder/basisu_backend.cpp + lib/basisu/encoder/basisu_backend.h + lib/basisu/encoder/basisu_basis_file.cpp + lib/basisu/encoder/basisu_basis_file.h + lib/basisu/encoder/basisu_bc7enc.cpp + lib/basisu/encoder/basisu_bc7enc.h + lib/basisu/encoder/basisu_comp.cpp + lib/basisu/encoder/basisu_comp.h + lib/basisu/encoder/basisu_enc.cpp + lib/basisu/encoder/basisu_enc.h + lib/basisu/encoder/basisu_etc.cpp + lib/basisu/encoder/basisu_etc.h + lib/basisu/encoder/basisu_frontend.cpp + lib/basisu/encoder/basisu_frontend.h + lib/basisu/encoder/basisu_gpu_texture.cpp + lib/basisu/encoder/basisu_gpu_texture.h + lib/basisu/encoder/basisu_kernels_declares.h + lib/basisu/encoder/basisu_kernels_imp.h + lib/basisu/encoder/basisu_kernels_sse.cpp + lib/basisu/encoder/basisu_miniz.h + lib/basisu/encoder/basisu_opencl.cpp + lib/basisu/encoder/basisu_opencl.h + lib/basisu/encoder/basisu_pvrtc1_4.cpp + lib/basisu/encoder/basisu_pvrtc1_4.h + lib/basisu/encoder/basisu_resample_filters.cpp + lib/basisu/encoder/basisu_resampler_filters.h + lib/basisu/encoder/basisu_resampler.cpp + lib/basisu/encoder/basisu_resampler.h + lib/basisu/encoder/basisu_ssim.cpp + lib/basisu/encoder/basisu_ssim.h + lib/basisu/encoder/basisu_uastc_enc.cpp + lib/basisu/encoder/basisu_uastc_enc.h + lib/basisu/encoder/cppspmd_flow.h + lib/basisu/encoder/cppspmd_math.h + lib/basisu/encoder/cppspmd_math_declares.h + lib/basisu/encoder/cppspmd_sse.h + lib/basisu/encoder/cppspmd_type_aliases.h + ) + if(KTX_FEATURE_GL_UPLOAD) list(APPEND KTX_MAIN_SRC lib/gl_funcs.c @@ -296,6 +349,20 @@ if(KTX_FEATURE_GL_UPLOAD) ) endif() +if(WIN32) + # By wrapping in generator expression we force multi configuration + # generators (like Visual Studio) to take the exact path and not + # change it. + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${KTX_BUILD_DIR}/$>) +elseif(APPLE) + if(NOT IOS) + # Set a common RUNTIME_OUTPUT_DIR for all targets, so that + # INSTALL RPATH is functional in build directory as well. + # BUILD_WITH_INSTALL_RPATH is necessary for working code signing. + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${KTX_BUILD_DIR}/$) + endif() +endif() + # Main library add_library( ktx ${LIB_TYPE} ${KTX_MAIN_SRC} @@ -306,14 +373,14 @@ add_library( ktx_read ${LIB_TYPE} ${KTX_MAIN_SRC} ) -macro(commom_lib_settings lib write) +macro(common_libktx_settings target enable_write library_type) if(TARGET mkvk) # Creating vulkan headers is only required when Vulkan SDK updates. - add_dependencies(${lib} mkvk) + add_dependencies(${target} mkvk) endif() - set_target_properties(${lib} PROPERTIES + set_target_properties(${target} PROPERTIES PUBLIC_HEADER # "${CMAKE_CURRENT_SOURCE_DIR}/include/ktx.h;${CMAKE_CURRENT_SOURCE_DIR}/include/KHR/khr_df.h" # Omit khr_df.h. Its installation has to be handled separately to @@ -324,15 +391,21 @@ macro(commom_lib_settings lib write) XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES" ) if(IOS) - set_target_properties(${lib} PROPERTIES + set_target_properties(${target} PROPERTIES FRAMEWORK TRUE ) endif() - set_code_sign(${lib} "NOPPS") + if( NOT ${library_type} STREQUAL STATIC ) + # Must not call this macro for static libs on Windows. To keep + # the if test simple, never call it for static libs. On macOS + # and iOS Xcode knows libs aren't signed so it would ignore the + # settings made by this macro. + set_code_sign(${target} "NOPPS") + endif() target_compile_definitions( - ${lib} + ${target} PUBLIC "$<$:_DEBUG;DEBUG>" PRIVATE @@ -340,16 +413,16 @@ macro(commom_lib_settings lib write) ) # C/C++ Standard - target_compile_features(${lib} PUBLIC c_std_99 cxx_std_11) + target_compile_features(${target} PUBLIC c_std_99 cxx_std_11) # Compiler Warning Flags if(EMSCRIPTEN) - target_compile_options(${lib} PRIVATE + target_compile_options(${target} PRIVATE -Wno-nested-anon-types -Wno-gnu-anonymous-struct ) else() - target_compile_options(${lib} PRIVATE + target_compile_options(${target} PRIVATE # clang options $<$: -Wno-nested-anon-types @@ -366,7 +439,7 @@ macro(commom_lib_settings lib write) endif() target_include_directories( - ${lib} + ${target} PUBLIC $ $ @@ -384,12 +457,12 @@ macro(commom_lib_settings lib write) $ ) - if( LIB_TYPE STREQUAL STATIC ) - target_compile_definitions(${lib} PUBLIC KHRONOS_STATIC) + if( ${library_type} STREQUAL STATIC ) + target_compile_definitions(${target} PUBLIC KHRONOS_STATIC) endif() # To reduce size, don't support transcoding to ancient formats. - target_compile_definitions(${lib} PRIVATE BASISD_SUPPORT_FXT1=0) + target_compile_definitions(${target} PRIVATE BASISD_SUPPORT_FXT1=0) # TODO: make options for all formats and good per-platform defaults # - BASISD_SUPPORT_UASTC @@ -408,10 +481,10 @@ macro(commom_lib_settings lib write) if(WIN32) target_compile_definitions( - ${lib} + ${target} PRIVATE # Only set dllexport when building a shared library. - $<$:KTX_API=__declspec\(dllexport\)> + $<$:KTX_API=__declspec\(dllexport\)> PUBLIC # only for basisu_c_binding. BASISU_NO_ITERATOR_DEBUG_LEVEL ) @@ -420,27 +493,27 @@ macro(commom_lib_settings lib write) # The def files that we add have a different syntax depending on the ABI if(MINGW) target_sources( - ${lib} + ${target} PRIVATE lib/internalexport_mingw.def - $<${write}:lib/internalexport_write_mingw.def> + $<${enable_write}:lib/internalexport_write_mingw.def> ) # Need these flags if mingw happens to target the ucrt (new) rather # than the legacy msvcrt. Otherwise tests will fail to run because # the necessary dlls will be missing. If we statically link # them instead it's fine. This does not cause any abberations if # the mingw toolchain targets msvcrt instead. - target_link_options(${lib} PUBLIC -static-libgcc -static-libstdc++) + target_link_options(${target} PUBLIC -static-libgcc -static-libstdc++) else() target_sources( - ${lib} + ${target} PRIVATE lib/internalexport.def - $<${write}:lib/internalexport_write.def> + $<${enable_write}:lib/internalexport_write.def> ) endif() elseif(EMSCRIPTEN) - target_compile_definitions(${lib} PRIVATE + target_compile_definitions(${target} PRIVATE # To reduce size, don't support transcoding to formats not # supported # by WebGL. BASISD_SUPPORT_ATC=0 @@ -452,9 +525,9 @@ macro(commom_lib_settings lib write) endif() if(KTX_FEATURE_KTX1) - target_compile_definitions(${lib} PUBLIC KTX_FEATURE_KTX1) + target_compile_definitions(${target} PUBLIC KTX_FEATURE_KTX1) target_sources( - ${lib} + ${target} PRIVATE lib/texture1.c lib/texture1.h @@ -462,39 +535,28 @@ macro(commom_lib_settings lib write) endif() if(KTX_FEATURE_KTX2) - target_compile_definitions(${lib} PUBLIC KTX_FEATURE_KTX2) + target_compile_definitions(${target} PUBLIC KTX_FEATURE_KTX2) endif() if(WIN32) - # By wrapping in generator expression we force multi configuration - # generators (like Visual Studio) to take the exact path and not - # change it. - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${KTX_BUILD_DIR}/$>) - if(MINGW) # Check if the Threads package is provided; if using Mingw it MIGHT be find_package(Threads) if(Threads_FOUND AND CMAKE_USE_PTHREADS_INIT) - target_compile_definitions(ktx PRIVATE WIN32_HAS_PTHREADS) - target_link_libraries(ktx PRIVATE Threads::Threads) + target_compile_definitions(${target} PRIVATE WIN32_HAS_PTHREADS) + target_link_libraries(${target} PRIVATE Threads::Threads) endif() endif() elseif(APPLE) if(KTX_EMBED_BITCODE) - target_compile_options(${lib} PRIVATE "-fembed-bitcode") - endif() - if(NOT IOS) - # Set a common RUNTIME_OUTPUT_DIR for all targets, so that - # INSTALL RPATH is functional in build directory as well. - # BUILD_WITH_INSTALL_RPATH is necessary for working code signing. - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${KTX_BUILD_DIR}/$) + target_compile_options(${target} PRIVATE "-fembed-bitcode") endif() elseif(LINUX) find_package(Threads REQUIRED) target_link_libraries( - ${lib} + ${target} PUBLIC dl Threads::Threads @@ -503,7 +565,7 @@ macro(commom_lib_settings lib write) if(KTX_FEATURE_VK_UPLOAD) target_sources( - ${lib} + ${target} PRIVATE include/ktxvulkan.h lib/vk_funcs.c @@ -511,27 +573,74 @@ macro(commom_lib_settings lib write) lib/vkloader.c ) target_include_directories( - ${lib} + ${target} PRIVATE $ $ ) - get_target_property( KTX_PUBLIC_HEADER ${lib} PUBLIC_HEADER ) + get_target_property( KTX_PUBLIC_HEADER ${target} PUBLIC_HEADER ) list(APPEND KTX_PUBLIC_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/include/ktxvulkan.h) - set_target_properties(${lib} PROPERTIES + set_target_properties(${target} PROPERTIES PUBLIC_HEADER "${KTX_PUBLIC_HEADER}" ) else() - target_compile_definitions( ${lib} PRIVATE KTX_OMIT_VULKAN=1 ) + target_compile_definitions( ${target} PRIVATE KTX_OMIT_VULKAN=1 ) endif() -endmacro(commom_lib_settings) + # Adding write capability to target ktx + if(${enable_write}) + target_sources( + ${target} + PRIVATE + lib/basis_encode.cpp + lib/astc_encode.cpp + ${BASISU_ENCODER_C_SRC} + ${BASISU_ENCODER_CXX_SRC} + lib/writer1.c + lib/writer2.c + ) -set(KTX_BUILD_DIR "${CMAKE_BINARY_DIR}") + target_include_directories( + ${target} + PRIVATE + $ + $ + $<$:${OpenCL_INCLUDE_DIRS}> + ) + target_compile_definitions( + ${target} + PUBLIC + KTX_FEATURE_WRITE + PRIVATE + # BASISD_SUPPORT_KTX2 has to be 1 to compile the encoder. We + # don't use it. Hopefully it doesn't add too much code. We're using + # the zstd encoder in basisu by explicitly including the file in our + # source list. We don't need the related code in the encoder. + BASISD_SUPPORT_KTX2_ZSTD=0 + BASISD_SUPPORT_KTX2=1 + $<$:BASISU_SUPPORT_SSE=1> + $<$>:BASISU_SUPPORT_SSE=0> + $<$:BASISU_SUPPORT_OPENCL=1> + $<$>:BASISU_SUPPORT_OPENCL=0> + ) + target_compile_options( + ${target} + PRIVATE + $<$,$>: + -msse4.1 + > + ) + target_link_libraries( + ${target} + PRIVATE + $<$:${OpenCL_LIBRARIES}> + ) + endif() +endmacro(common_libktx_settings) -commom_lib_settings(ktx 1) -commom_lib_settings(ktx_read 0) +common_libktx_settings(ktx 1 ${LIB_TYPE}) +common_libktx_settings(ktx_read 0 ${LIB_TYPE}) if(KTX_FEATURE_JNI) add_subdirectory(interface/java_binding) @@ -548,59 +657,6 @@ PRIVATE BASISD_SUPPORT_KTX2=0 ) -# Adding write capability to target ktx - -set(BASISU_ENCODER_CXX_SRC - lib/basisu/encoder/basisu_backend.cpp - lib/basisu/encoder/basisu_backend.h - lib/basisu/encoder/basisu_basis_file.cpp - lib/basisu/encoder/basisu_basis_file.h - lib/basisu/encoder/basisu_bc7enc.cpp - lib/basisu/encoder/basisu_bc7enc.h - lib/basisu/encoder/basisu_comp.cpp - lib/basisu/encoder/basisu_comp.h - lib/basisu/encoder/basisu_enc.cpp - lib/basisu/encoder/basisu_enc.h - lib/basisu/encoder/basisu_etc.cpp - lib/basisu/encoder/basisu_etc.h - lib/basisu/encoder/basisu_frontend.cpp - lib/basisu/encoder/basisu_frontend.h - lib/basisu/encoder/basisu_gpu_texture.cpp - lib/basisu/encoder/basisu_gpu_texture.h - lib/basisu/encoder/basisu_kernels_declares.h - lib/basisu/encoder/basisu_kernels_imp.h - lib/basisu/encoder/basisu_kernels_sse.cpp - lib/basisu/encoder/basisu_miniz.h - lib/basisu/encoder/basisu_opencl.cpp - lib/basisu/encoder/basisu_opencl.h - lib/basisu/encoder/basisu_pvrtc1_4.cpp - lib/basisu/encoder/basisu_pvrtc1_4.h - lib/basisu/encoder/basisu_resample_filters.cpp - lib/basisu/encoder/basisu_resampler_filters.h - lib/basisu/encoder/basisu_resampler.cpp - lib/basisu/encoder/basisu_resampler.h - lib/basisu/encoder/basisu_ssim.cpp - lib/basisu/encoder/basisu_ssim.h - lib/basisu/encoder/basisu_uastc_enc.cpp - lib/basisu/encoder/basisu_uastc_enc.h - lib/basisu/encoder/cppspmd_flow.h - lib/basisu/encoder/cppspmd_math.h - lib/basisu/encoder/cppspmd_math_declares.h - lib/basisu/encoder/cppspmd_sse.h - lib/basisu/encoder/cppspmd_type_aliases.h -) - -target_sources( - ktx -PRIVATE - lib/basis_encode.cpp - lib/astc_encode.cpp - ${BASISU_ENCODER_C_SRC} - ${BASISU_ENCODER_CXX_SRC} - lib/writer1.c - lib/writer2.c -) - # Turn off these warnings until Rich fixes the occurences. # It it not clear to me if generator expressions can be used here # hence the long-winded way. @@ -676,12 +732,12 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") PROPERTIES COMPILE_OPTIONS "-Wno-unused-parameter" ) endif() - if (${clang_version} VERSION_GREATER_EQUAL "15.0") + if (${clang_version} VERSION_GREATER_EQUAL "14.0") # These are for Emscripten which is ahead of xcode in its clang # version. Also future proofing for when xcode catches up. set_source_files_properties( ${BASISU_ENCODER_CXX_SRC} - PROPERTIES COMPILE_OPTIONS "-Wno-sign-compare;-Wno-unused-variable;-Wno-unused-parameter" + PROPERTIES COMPILE_OPTIONS "-Wno-sign-compare;-Wno-unused-variable;-Wno-unused-parameter;-Wno-deprecated-copy-with-user-provided-copy" ) set_source_files_properties( lib/basisu/transcoder/basisu_transcoder.cpp @@ -706,42 +762,6 @@ get_source_file_property(transcoder_options ) get_source_file_property(zstd_options lib/basisu/zstd/zstd.c COMPILE_OPTIONS) -target_include_directories( - ktx -PRIVATE - $ - $ - $<$:${OpenCL_INCLUDE_DIRS}> -) -target_compile_definitions( - ktx -PUBLIC - KTX_FEATURE_WRITE -PRIVATE - # BASISD_SUPPORT_KTX2 has to be 1 to compile the encoder. We - # don't use it. Hopefully it doesn't add too much code. We're using - # the zstd encoder in basisu by explicitly including the file in our - # source list. We don't need the related code in the encoder. - BASISD_SUPPORT_KTX2_ZSTD=0 - BASISD_SUPPORT_KTX2=1 - $<$:BASISU_SUPPORT_SSE=1> - $<$>:BASISU_SUPPORT_SSE=0> - $<$:BASISU_SUPPORT_OPENCL=1> - $<$>:BASISU_SUPPORT_OPENCL=0> -) -target_compile_options( - ktx -PRIVATE - $<$,$>: - -msse4.1 - > -) -target_link_libraries( - ktx -PRIVATE - $<$:${OpenCL_LIBRARIES}> -) - if(EMSCRIPTEN) set( KTX_EMC_LINK_FLAGS @@ -934,8 +954,9 @@ else() target_link_libraries(ktx PRIVATE ${ASTCENC_LIB_TARGET}) endif() -# FMT +# Other external projects add_subdirectory(other_projects/fmt) +add_subdirectory(other_projects/cxxopts) # Tools if(KTX_FEATURE_TOOLS) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca6abb2d69..f731226df1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,8 @@ 1. Make sure you have a GitHub account. 2. Fork the repository on GitHub. 3. Make changes to your clone of the repository. -4. Update or supplement the tests as necessary. +4. Update or supplement the tests as necessary in this +repository and in the [Conformance Test Suite](https://github.com/KhronosGroup/KTX-Software-CTS/). 5. Submit a pull request against _main_. If you will be generating documentation with or preparing diff --git a/README.md b/README.md index 9fb2c2f9b9..85162f0dac 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The Official Khronos KTX Software Repository This is the official home of the source code for the Khronos KTX library and tools. -KTX (Khronos Texture) is a lightweight container for textures for OpenGL®, Vulkan® and other GPU APIs. KTX files contain all the parameters needed for texture loading. A single file can contain anything from a simple base-level 2D texture through to a cubemap array texture with mipmaps. Contained textures can be in a Basis Universal format, in any of the block-compressed formats supported by OpenGL family and Vulkan APIs and extensions or in an uncompressed single-plane format. Basis Universal currently encompasses two formats that can be quickly transcoded to any GPU-supported format: LZ/ETC1S, which combines block-compression and supercompression, and UASTC, a block-compressed format. Formats other than LZ/ETC1S can be supercompressed with Zstd. +KTX (Khronos Texture) is a lightweight container for textures for OpenGL®, Vulkan® and other GPU APIs. KTX files contain all the parameters needed for texture loading. A single file can contain anything from a simple base-level 2D texture through to a cubemap array texture with mipmaps. Contained textures can be in a Basis Universal format, in any of the block-compressed formats supported by OpenGL family and Vulkan APIs and extensions or in an uncompressed single-plane format. Basis Universal currently encompasses two formats that can be quickly transcoded to any GPU-supported format: LZ/ETC1S, which combines block-compression and supercompression, and UASTC, a block-compressed format. Formats other than LZ/ETC1S can be supercompressed with Zstd and ZLIB. Download [KTX Software Releases](https://github.com/KhronosGroup/KTX-Software/releases) to get binary packages of the tools, library and development headers @@ -33,6 +33,14 @@ Javascript wrapper. [`interface/js_binding`](https://github.com/KhronosGroup/KTX Javascript wrapper for Basis Universal formats. For use with KTX parsers written in Javascript. [`interface/js_binding`](https://github.com/KhronosGroup/KTX-Software/tree/main/interface/js_binding) - *libktx.jar, libktx-jni* - Java wrapper and native interface library. [`interface/java_binding`](https://github.com/KhronosGroup/KTX-Software/tree/main/interface/java_binding) +- *ktx* - a generic command line tool for managing KTX2 files with subcommands.[`tools/ktx`](https://github.com/KhronosGroup/KTX-Software/tree/main/tools/ktx) + - *ktx create* - Create a KTX2 file from various input files + - *ktx extract* - Export selected images from a KTX2 file + - *ktx encode* - Encode a KTX2 file + - *ktx transcode* - Transcode a KTX2 file + - *ktx info* - Prints information about a KTX2 file + - *ktx validate* - Validate a KTX2 file + - *ktx help* - Display help information about the ktx tools - *ktx2check* - a tool for validating KTX Version 2 format files. [`tools/ktx2check`](https://github.com/KhronosGroup/KTX-Software/tree/main/tools/ktx2check) - *ktx2ktx2* - a tool for converting a KTX Version 1 file to a KTX Version 2 file. [`tools/ktx2ktx2`](https://github.com/KhronosGroup/KTX-Software/tree/main/tools/ktx2ktx2) @@ -74,6 +82,14 @@ A few files have `$Date$` keywords. If you care about having the proper dates shown or will be generating the documentation or preparing distribution archives, you **must** follow the instructions below. +#### KTX-Software-CTS - Conformance Test Suite + +The tests and test files for the generic command line `ktx` tool can be found in a separate +[CTS Repository](https://github.com/KhronosGroup/KTX-Software-CTS/). To save space and bandwidth this repository +is included with git submodule and by default it is not required for building the libraries or the tools. +For more information about building, running and extending the CTS tests see [BUILDING](BUILDING.md#Conformance-Test-Suite) +and [CTS README](https://github.com/KhronosGroup/KTX-Software-CTS/blob/main/README.md). + #### $Date$ keyword expansion $Date$ keywords are expanded via smudge & clean filters. To install @@ -103,4 +119,3 @@ local git config file `.git/config`, i.e. the one in your clone of the repo. `.gitconfig` contains the config of the "keyworder" filter. The remaining commands force a new checkout of the affected files to smudge them with the date. These two are unnecessary if you plan to edit these files. - diff --git a/ci_scripts/build_linux.sh b/ci_scripts/build_linux.sh index dd4b06b3fe..47b7f42554 100755 --- a/ci_scripts/build_linux.sh +++ b/ci_scripts/build_linux.sh @@ -39,6 +39,7 @@ else fi FEATURE_TESTS=${FEATURE_TESTS:-ON} FEATURE_TOOLS=${FEATURE_TOOLS:-ON} +FEATURE_TOOLS_CTS=${FEATURE_TOOLS_CTS:-ON} FEATURE_GL_UPLOAD=${FEATURE_GL_UPLOAD:-ON} FEATURE_VK_UPLOAD=${FEATURE_VK_UPLOAD:-ON} PACKAGE=${PACKAGE:-NO} @@ -67,6 +68,10 @@ fi mkdir -p $BUILD_DIR +if [ "$FEATURE_TOOLS_CTS" = "ON" ]; then + git submodule update --init --recursive tests/cts +fi + cmake_args=("-G" "$CMAKE_GEN" "-B" $BUILD_DIR \ "-D" "CMAKE_BUILD_TYPE=$CONFIGURATION" \ @@ -75,6 +80,7 @@ cmake_args=("-G" "$CMAKE_GEN" "-D" "KTX_FEATURE_LOADTEST_APPS=$FEATURE_LOADTESTS" \ "-D" "KTX_FEATURE_TESTS=$FEATURE_TESTS" \ "-D" "KTX_FEATURE_TOOLS=$FEATURE_TOOLS" \ + "-D" "KTX_FEATURE_TOOLS_CTS=$FEATURE_TOOLS_CTS" \ "-D" "KTX_FEATURE_GL_UPLOAD=$FEATURE_GL_UPLOAD" \ "-D" "KTX_FEATURE_VK_UPLOAD=$FEATURE_VK_UPLOAD" \ "-D" "BASISU_SUPPORT_OPENCL=$SUPPORT_OPENCL" \ @@ -109,7 +115,7 @@ do cmake --build . --config $config if [ "$ARCH" = "$(uname -m)" ]; then echo "Test KTX-Software (Linux $ARCH $config)" - ctest -C $config #--verbose + ctest --output-on-failure -C $config #--verbose fi if [ "$config" = "Release" -a "$PACKAGE" = "YES" ]; then echo "Pack KTX-Software (Linux $ARCH $config)" diff --git a/ci_scripts/build_macos.sh b/ci_scripts/build_macos.sh index 601a989094..7b79678140 100755 --- a/ci_scripts/build_macos.sh +++ b/ci_scripts/build_macos.sh @@ -25,8 +25,9 @@ CONFIGURATION=${CONFIGURATION:-Release} FEATURE_DOC=${FEATURE_DOC:-OFF} FEATURE_JNI=${FEATURE_JNI:-OFF} FEATURE_LOADTESTS=${FEATURE_LOADTESTS:-OpenGL+Vulkan} -FEATURE_TOOLS=${FEATURE_TOOLS:-ON} FEATURE_TESTS=${FEATURE_TESTS:-ON} +FEATURE_TOOLS=${FEATURE_TOOLS:-ON} +FEATURE_TOOLS_CTS=${FEATURE_TOOLS_CTS:-ON} PACKAGE=${PACKAGE:-NO} SUPPORT_SSE=${SUPPORT_SSE:-ON} SUPPORT_OPENCL=${SUPPORT_OPENCL:-OFF} @@ -58,6 +59,10 @@ else } fi +if [ "$FEATURE_TOOLS_CTS" = "ON" ]; then + git submodule update --init --recursive tests/cts +fi + cmake_args=("-G" "Xcode" \ "-B" $BUILD_DIR \ "-D" "CMAKE_OSX_ARCHITECTURES=$ARCHS" \ @@ -66,6 +71,7 @@ cmake_args=("-G" "Xcode" \ "-D" "KTX_FEATURE_LOADTEST_APPS=$FEATURE_LOADTESTS" \ "-D" "KTX_FEATURE_TESTS=$FEATURE_TESTS" \ "-D" "KTX_FEATURE_TOOLS=$FEATURE_TOOLS" \ + "-D" "KTX_FEATURE_TOOLS_CTS=$FEATURE_TOOLS_CTS" \ "-D" "BASISU_SUPPORT_OPENCL=$SUPPORT_OPENCL" \ "-D" "BASISU_SUPPORT_SSE=$SUPPORT_SSE" ) @@ -112,7 +118,7 @@ do # Rosetta 2 should let x86_64 tests run on an Apple Silicon Mac hence the -o. if [ "$ARCHS" = "$(uname -m)" -o "$ARCHS" = "x64_64" ]; then echo "Test KTX-Software (macOS $ARCHS $config)" - ctest -C $config # --verbose + ctest --output-on-failure -C $config # --verbose fi if [ "$config" = "Release" -a "$PACKAGE" = "YES" ]; then diff --git a/ci_scripts/build_win.ps1 b/ci_scripts/build_win.ps1 index 1c87416052..d192acfcfc 100644 --- a/ci_scripts/build_win.ps1 +++ b/ci_scripts/build_win.ps1 @@ -46,8 +46,9 @@ $CMAKE_TOOLSET = Set-ConfigVariable CMAKE_TOOLSET "" $FEATURE_DOC = Set-ConfigVariable FEATURE_DOC "OFF" $FEATURE_JNI = Set-ConfigVariable FEATURE_JNI "OFF" $FEATURE_LOADTESTS = Set-ConfigVariable FEATURE_LOADTESTS "OpenGL+Vulkan" -$FEATURE_TOOLS = Set-ConfigVariable FEATURE_TOOLS "ON" $FEATURE_TESTS = Set-ConfigVariable FEATURE_TESTS "ON" +$FEATURE_TOOLS = Set-ConfigVariable FEATURE_TOOLS "ON" +$FEATURE_TOOLS_CTS = Set-ConfigVariable FEATURE_TOOLS_CTS "ON" $PACKAGE = Set-ConfigVariable PACKAGE "NO" $SUPPORT_SSE = Set-ConfigVariable SUPPORT_SSE "ON" $SUPPORT_OPENCL = Set-ConfigVariable SUPPORT_OPENCL "OFF" @@ -70,6 +71,10 @@ if (($PACKAGE -eq "YES") -and ($FEATURE_TOOLS -eq "OFF")) { exit 2 } +if (($FEATURE_TOOLS_CTS -eq "ON")) { + git submodule update --init --recursive tests/cts +} + $cmake_args = @( "-G", "$CMAKE_GEN" "-A", "$ARCH" @@ -84,8 +89,9 @@ $cmake_args += @( "-D", "KTX_FEATURE_DOC=$FEATURE_DOC" "-D", "KTX_FEATURE_JNI=$FEATURE_JNI" "-D", "KTX_FEATURE_LOADTEST_APPS=$FEATURE_LOADTESTS" - "-D", "KTX_FEATURE_TOOLS=$FEATURE_TOOLS" "-D", "KTX_FEATURE_TESTS=$FEATURE_TESTS" + "-D", "KTX_FEATURE_TOOLS=$FEATURE_TOOLS" + "-D", "KTX_FEATURE_TOOLS_CTS=$FEATURE_TOOLS_CTS" "-D", "BASISU_SUPPORT_SSE=$SUPPORT_SSE" "-D", "BASISU_SUPPORT_OPENCL=$SUPPORT_OPENCL" "-D", "CODE_SIGN_KEY_VAULT=$CODE_SIGN_KEY_VAULT" diff --git a/ci_scripts/on_failure.sh b/ci_scripts/on_failure.sh index 3bafc7dddc..d0b4e772f2 100755 --- a/ci_scripts/on_failure.sh +++ b/ci_scripts/on_failure.sh @@ -26,4 +26,3 @@ fi echo "Now uploading the test log" rurl=$(curl --upload-file $build_dir/Testing/Temporary/LastTest.log https://transfer.sh/ktx-failed-tests.log) echo $rurl - diff --git a/cmake/docs.cmake b/cmake/docs.cmake index c0a2ea0840..39e20ec701 100644 --- a/cmake/docs.cmake +++ b/cmake/docs.cmake @@ -97,6 +97,7 @@ function( CreateDocLibKTX ) lib/astc_encode.cpp lib/basis_encode.cpp lib/basis_transcode.cpp + lib/miniz_wrapper.cpp lib/strings.c lib/glloader.c lib/hashlist.c @@ -120,7 +121,7 @@ function( CreateDocKTXTools ) set( DOXYGEN_SHOW_FILES NO ) set( DOXYGEN_FILE_PATTERNS *.cpp ) set( DOXYGEN_RECURSIVE YES ) - set( DOXYGEN_EXAMPLE_PATH utils ) + set( DOXYGEN_EXAMPLE_PATH utils tools ) set( DOXYGEN_HTML_OUTPUT ktxtools ) set( DOXYGEN_MAN_EXTENSION .1 ) set( DOXYGEN_GENERATE_TAGFILE ${docdest}/ktxtools.tag ) @@ -132,6 +133,14 @@ function( CreateDocKTXTools ) tools/ktx2ktx2/ktx2ktx2.cpp tools/ktxsc/ktxsc.cpp tools/toktx/toktx.cc + tools/ktx/ktx_main.cpp + tools/ktx/command_create.cpp + tools/ktx/command_encode.cpp + tools/ktx/command_extract.cpp + tools/ktx/command_help.cpp + tools/ktx/command_info.cpp + tools/ktx/command_transcode.cpp + tools/ktx/command_validate.cpp ) add_docs_cmake(ktxtools.doc) endfunction() @@ -175,12 +184,11 @@ install( COMPONENT tools ) -install( DIRECTORY - ${docdest}/man/man1 +install( + DIRECTORY ${docdest}/man/man1 ## Omit those, since at the moment they contain a lot of undesired files generated by Doxygen # ${docdest}/man/man3 # ${docdest}/man/ktx/man3 -DESTINATION - "${CMAKE_INSTALL_MANDIR}" -COMPONENT tools + DESTINATION "${CMAKE_INSTALL_MANDIR}" + COMPONENT tools ) diff --git a/include/ktx.h b/include/ktx.h index 0af87f2519..81377c80d0 100644 --- a/include/ktx.h +++ b/include/ktx.h @@ -184,7 +184,9 @@ typedef enum ktx_error_code_e { KTX_UNSUPPORTED_TEXTURE_TYPE, /*!< The KTX file specifies an unsupported texture type. */ KTX_UNSUPPORTED_FEATURE, /*!< Feature not included in in-use library or not yet implemented. */ KTX_LIBRARY_NOT_LINKED, /*!< Library dependency (OpenGL or Vulkan) not linked into application. */ - KTX_ERROR_MAX_ENUM = KTX_LIBRARY_NOT_LINKED /*!< For safety checks. */ + KTX_DECOMPRESS_LENGTH_ERROR, /*!< Decompressed byte count does not match expected byte size */ + KTX_DECOMPRESS_CHECKSUM_ERROR, /*!< Checksum mismatch when decompressing */ + KTX_ERROR_MAX_ENUM = KTX_DECOMPRESS_CHECKSUM_ERROR /*!< For safety checks. */ } ktx_error_code_e; /** * @deprecated @@ -672,8 +674,9 @@ typedef enum ktxSupercmpScheme { KTX_SS_NONE = 0, /*!< No supercompression. */ KTX_SS_BASIS_LZ = 1, /*!< Basis LZ supercompression. */ KTX_SS_ZSTD = 2, /*!< ZStd supercompression. */ + KTX_SS_ZLIB = 3, /*!< ZLIB supercompression. */ KTX_SS_BEGIN_RANGE = KTX_SS_NONE, - KTX_SS_END_RANGE = KTX_SS_ZSTD, + KTX_SS_END_RANGE = KTX_SS_ZLIB, KTX_SS_BEGIN_VENDOR_RANGE = 0x10000, KTX_SS_END_VENDOR_RANGE = 0x1ffff, KTX_SS_BEGIN_RESERVED = 0x20000, @@ -766,9 +769,12 @@ enum ktxTextureCreateFlagBits { KTX_TEXTURE_CREATE_RAW_KVDATA_BIT = 0x02, /*!< Load the raw key-value data instead of creating a @c ktxHashList from it. */ - KTX_TEXTURE_CREATE_SKIP_KVDATA_BIT = 0x04 + KTX_TEXTURE_CREATE_SKIP_KVDATA_BIT = 0x04, /*!< Skip any key-value data. This overrides the RAW_KVDATA_BIT. */ + KTX_TEXTURE_CREATE_CHECK_GLTF_BASISU_BIT = 0x08 + /*!< Load texture compatible with the rules + of KHR_texture_basisu glTF extension */ }; /** * @memberof ktxTexture @@ -1053,6 +1059,9 @@ ktxTexture2_CompressBasis(ktxTexture2* This, ktx_uint32_t quality); KTX_API KTX_error_code KTX_APIENTRY ktxTexture2_DeflateZstd(ktxTexture2* This, ktx_uint32_t level); +KTX_API KTX_error_code KTX_APIENTRY +ktxTexture2_DeflateZLIB(ktxTexture2* This, ktx_uint32_t level); + KTX_API void KTX_APIENTRY ktxTexture2_GetComponentInfo(ktxTexture2* This, ktx_uint32_t* numComponents, ktx_uint32_t* componentByteLength); @@ -1682,6 +1691,19 @@ KTX_API KTX_error_code KTX_APIENTRY ktxPrintInfoForStdioStream(FILE* stdioStream KTX_API KTX_error_code KTX_APIENTRY ktxPrintInfoForNamedFile(const char* const filename); KTX_API KTX_error_code KTX_APIENTRY ktxPrintInfoForMemory(const ktx_uint8_t* bytes, ktx_size_t size); +/*===========================================================* + * Utilities for printing info about a KTX2 file. * + *===========================================================*/ + +KTX_API KTX_error_code KTX_APIENTRY ktxPrintKTX2InfoTextForMemory(const ktx_uint8_t* bytes, ktx_size_t size); +KTX_API KTX_error_code KTX_APIENTRY ktxPrintKTX2InfoTextForNamedFile(const char* const filename); +KTX_API KTX_error_code KTX_APIENTRY ktxPrintKTX2InfoTextForStdioStream(FILE* stdioStream); +KTX_API KTX_error_code KTX_APIENTRY ktxPrintKTX2InfoTextForStream(ktxStream* stream); +KTX_API KTX_error_code KTX_APIENTRY ktxPrintKTX2InfoJSONForMemory(const ktx_uint8_t* bytes, ktx_size_t size, ktx_uint32_t base_indent, ktx_uint32_t indent_width, bool minified); +KTX_API KTX_error_code KTX_APIENTRY ktxPrintKTX2InfoJSONForNamedFile(const char* const filename, ktx_uint32_t base_indent, ktx_uint32_t indent_width, bool minified); +KTX_API KTX_error_code KTX_APIENTRY ktxPrintKTX2InfoJSONForStdioStream(FILE* stdioStream, ktx_uint32_t base_indent, ktx_uint32_t indent_width, bool minified); +KTX_API KTX_error_code KTX_APIENTRY ktxPrintKTX2InfoJSONForStream(ktxStream* stream, ktx_uint32_t base_indent, ktx_uint32_t indent_width, bool minified); + #ifdef __cplusplus } #endif diff --git a/interface/java_binding/src/main/java/org/khronos/ktx/KtxErrorCode.java b/interface/java_binding/src/main/java/org/khronos/ktx/KtxErrorCode.java index cdb822cf4f..f25527bdc5 100644 --- a/interface/java_binding/src/main/java/org/khronos/ktx/KtxErrorCode.java +++ b/interface/java_binding/src/main/java/org/khronos/ktx/KtxErrorCode.java @@ -26,5 +26,7 @@ public class KtxErrorCode { public static final int UNSUPPORTED_TEXTURE_TYPE = 16; public static final int UNSUPPORTED_FEATURE = 17; public static final int LIBRARY_NOT_LINKED = 18; - public static final int ERROR_MAX_ENUM = LIBRARY_NOT_LINKED; + public static final int DECOMPRESS_LENGTH_ERROR = 19; + public static final int DECOMPRESS_CHECKSUM_ERROR = 20; + public static final int ERROR_MAX_ENUM = DECOMPRESS_CHECKSUM_ERROR; } diff --git a/interface/java_binding/src/main/java/org/khronos/ktx/KtxSupercmpScheme.java b/interface/java_binding/src/main/java/org/khronos/ktx/KtxSupercmpScheme.java index 62db88cbc5..fa62fd07aa 100644 --- a/interface/java_binding/src/main/java/org/khronos/ktx/KtxSupercmpScheme.java +++ b/interface/java_binding/src/main/java/org/khronos/ktx/KtxSupercmpScheme.java @@ -9,4 +9,5 @@ public class KtxSupercmpScheme { public static final int NONE = 0; public static final int BASIS_LZ = 1; public static final int ZSTD = 2; + public static final int ZLIB = 3; } diff --git a/interface/js_binding/ktx_wrapper.cpp b/interface/js_binding/ktx_wrapper.cpp index 6dc9aed294..9274eb426e 100644 --- a/interface/js_binding/ktx_wrapper.cpp +++ b/interface/js_binding/ktx_wrapper.cpp @@ -284,6 +284,7 @@ enum SupercmpScheme { "NONE", "BASIS_LZ", "ZSTD" + "ZLIB" }; @endcode @@ -427,6 +428,8 @@ EMSCRIPTEN_BINDINGS(ktx) .value("UNSUPPORTED_TEXTURE_TYPE", KTX_UNSUPPORTED_TEXTURE_TYPE) .value("UNSUPPORTED_FEATURE", KTX_UNSUPPORTED_FEATURE) .value("LIBRARY_NOT_LINKED", KTX_LIBRARY_NOT_LINKED) + .value("DECOMPRESS_LENGTH_ERROR", KTX_DECOMPRESS_LENGTH_ERROR) + .value("DECOMPRESS_CHECKSUM_ERROR", KTX_DECOMPRESS_CHECKSUM_ERROR) ; enum_("TranscodeTarget") @@ -473,6 +476,7 @@ EMSCRIPTEN_BINDINGS(ktx) .value("NONE", KTX_SS_NONE) .value("BASIS_LZ", KTX_SS_BASIS_LZ) .value("ZSTD", KTX_SS_ZSTD) + .value("ZLIB", KTX_SS_ZLIB) ; value_object("Orientation") diff --git a/lib/basis_sgd.h b/lib/basis_sgd.h index 6c55909652..15804e8d26 100644 --- a/lib/basis_sgd.h +++ b/lib/basis_sgd.h @@ -28,7 +28,7 @@ extern "C" { // This must be the same value as cSliceDescFlagsFrameIsIFrame so we can just // invert the bit when passing back & forth. As FrameIsIFrame is within // a C namespace it can't easily be accessed from a c header. -enum bu_image_flags__bits_e { eBUImageIsPframe = 0x02 }; +enum bu_image_flags__bits_e { ETC1S_P_FRAME = 0x02 }; typedef uint32_t buFlags; diff --git a/lib/basis_transcode.cpp b/lib/basis_transcode.cpp index 9e4eeca5ed..ca68545e4a 100644 --- a/lib/basis_transcode.cpp +++ b/lib/basis_transcode.cpp @@ -372,6 +372,12 @@ ktxTexture2_transcodeUastc(ktxTexture2* This, This->dataSize = prototype->dataSize; prototype->pData = 0; prototype->dataSize = 0; + // Free SGD data + This->_private->_sgdByteLength = 0; + if (This->_private->_supercompressionGlobalData) { + free(This->_private->_supercompressionGlobalData); + This->_private->_supercompressionGlobalData = NULL; + } } ktxTexture2_Destroy(prototype); return result; diff --git a/lib/checkheader.c b/lib/checkheader.c index ee6f7be4d4..cee47ce341 100644 --- a/lib/checkheader.c +++ b/lib/checkheader.c @@ -27,6 +27,10 @@ #include "ktx.h" #include "ktxint.h" +#include "vkformat_enum.h" + +bool isProhibitedFormat(VkFormat format); +bool isValidFormat(VkFormat format); /** * @internal @@ -112,7 +116,7 @@ KTX_error_code ktxCheckHeader1_(KTX_header* pHeader, if (pHeader->numberOfArrayElements > 0) { /* No 3D array textures yet. */ - return KTX_UNSUPPORTED_TEXTURE_TYPE; + return KTX_UNSUPPORTED_FEATURE; } pSuppInfo->textureDimension = 3; } @@ -192,6 +196,20 @@ KTX_error_code ktxCheckHeader2_(KTX_header2* pHeader, return KTX_UNKNOWN_FILE_FORMAT; } + /* Check format */ + if (isProhibitedFormat(pHeader->vkFormat)) + { + return KTX_FILE_DATA_ERROR; + } + if (!isValidFormat(pHeader->vkFormat)) + { + return KTX_UNSUPPORTED_FEATURE; + } + if (pHeader->supercompressionScheme == KTX_SS_BASIS_LZ && pHeader->vkFormat != VK_FORMAT_UNDEFINED) + { + return KTX_FILE_DATA_ERROR; + } + /* Check texture dimensions. KTX files can store 8 types of textures: 1D, 2D, 3D, cube, and array variants of these. There is currently no extension for 3D array textures in any 3D API. */ @@ -208,7 +226,7 @@ KTX_error_code ktxCheckHeader2_(KTX_header2* pHeader, if (pHeader->layerCount > 0) { /* No 3D array textures yet. */ - return KTX_UNSUPPORTED_TEXTURE_TYPE; + return KTX_UNSUPPORTED_FEATURE; } pSuppInfo->textureDimension = 3; } @@ -228,6 +246,16 @@ KTX_error_code ktxCheckHeader2_(KTX_header2* pHeader, /* cube map needs 2D faces */ return KTX_FILE_DATA_ERROR; } + if (pHeader->pixelDepth != 0) + { + /* cube map cannot have depth */ + return KTX_FILE_DATA_ERROR; + } + if (pHeader->pixelWidth != pHeader->pixelHeight) + { + /* cube map needs square faces */ + return KTX_FILE_DATA_ERROR; + } } else if (pHeader->faceCount != 1) { @@ -246,6 +274,18 @@ KTX_error_code ktxCheckHeader2_(KTX_header2* pHeader, pSuppInfo->generateMipmaps = 0; } + // Check supercompression + switch (pHeader->supercompressionScheme) { + case KTX_SS_NONE: + case KTX_SS_BASIS_LZ: + case KTX_SS_ZSTD: + case KTX_SS_ZLIB: + break; + default: + // Unsupported supercompression + return KTX_UNSUPPORTED_FEATURE; + } + // This test works for arrays too because height or depth will be 0. max_dim = MAX(MAX(pHeader->pixelWidth, pHeader->pixelHeight), pHeader->pixelDepth); if (max_dim < ((ktx_uint32_t)1 << (pHeader->levelCount - 1))) diff --git a/lib/dfdutils/colourspaces.c b/lib/dfdutils/colourspaces.c index 0d998a71aa..8e934746bf 100644 --- a/lib/dfdutils/colourspaces.c +++ b/lib/dfdutils/colourspaces.c @@ -36,7 +36,7 @@ sPrimaryMapping primaryMap[] = { * @param[in] latitude tolerance to use while matching. A suitable value might be 0.002 * but it depends on the application. */ -khr_df_primaries_e findMapping(Primaries *p, float latitude) { +khr_df_primaries_e findMapping(const Primaries *p, float latitude) { unsigned int i; for (i = 0; i < sizeof(primaryMap)/sizeof(sPrimaryMapping); ++i) { if (primaryMap[i].primaries.Rx - p->Rx <= latitude && p->Rx - primaryMap[i].primaries.Rx <= latitude && @@ -49,3 +49,23 @@ khr_df_primaries_e findMapping(Primaries *p, float latitude) { /* No match */ return KHR_DF_PRIMARIES_UNSPECIFIED; } + +/** + * @brief Get the primaries corresponding to a KDFS primaries enum. + * + * @param[in] primaries the enum identifying the KDFS primaries. + * @param[out] p pointer to a Primaries struct that will + * be filled with the primary values. + */ +bool getPrimaries(khr_df_primaries_e primaries, Primaries *p) { + unsigned int i; + for (i = 0; i < sizeof(primaryMap)/sizeof(sPrimaryMapping); ++i) { + if (primaryMap[i].dfPrimaryEnum == primaries) { + *p = primaryMap[i].primaries; + return true; + } + } + + /* No match */ + return false; +} diff --git a/lib/dfdutils/createdfdtest.c b/lib/dfdutils/createdfdtest.c index c9c49e9b22..75486e495d 100644 --- a/lib/dfdutils/createdfdtest.c +++ b/lib/dfdutils/createdfdtest.c @@ -37,6 +37,6 @@ int main() int channels[] = {0,1,2}; uint32_t *DFD = createDFDPacked(1, 3, bits, channels, s_UNORM); #endif - printDFD(DFD); + printDFD(DFD, *DFD); return 0; } diff --git a/lib/dfdutils/dfd.h b/lib/dfdutils/dfd.h index 3ba0249dbb..5ddf387adc 100644 --- a/lib/dfdutils/dfd.h +++ b/lib/dfdutils/dfd.h @@ -19,6 +19,7 @@ #define _DFD_H_ #include +#include #ifdef __cplusplus extern "C" { @@ -126,8 +127,47 @@ enum InterpretDFDResult interpretDFD(const uint32_t *DFD, InterpretedDFDChannel *A, uint32_t *wordBytes); +/* Returns the string representation. + * If there is no direct match or the value is invalid returns NULL */ +const char* dfdToStringVendorID(khr_df_vendorid_e value); + +/* Returns the string representation. + * If there is no direct match or the value is invalid returns NULL */ +const char* dfdToStringDescriptorType(khr_df_khr_descriptortype_e value); + +/* Returns the string representation. + * If there is no direct match or the value is invalid returns NULL */ +const char* dfdToStringVersionNumber(khr_df_versionnumber_e value); + +/* Returns the string representation of a bit in a khr_df_flags_e. + * If there is no direct match or the value is invalid returns NULL */ +const char* dfdToStringFlagsBit(uint32_t bit_index, bool bit_value); + +/* Returns the string representation. + * If there is no direct match or the value is invalid returns NULL */ +const char* dfdToStringTransferFunction(khr_df_transfer_e value); + +/* Returns the string representation. + * If there is no direct match or the value is invalid returns NULL */ +const char* dfdToStringColorPrimaries(khr_df_primaries_e value); + +/* Returns the string representation. + * If there is no direct match or the value is invalid returns NULL */ +const char* dfdToStringColorModel(khr_df_model_e value); + +/* Returns the string representation of a bit in a khr_df_sample_datatype_qualifiers_e. + * If there is no direct match or the value is invalid returns NULL */ +const char* dfdToStringSampleDatatypeQualifiersBit(uint32_t bit_index, bool bit_value); + +/* Returns the string representation. + * If there is no direct match or the value is invalid returns NULL */ +const char* dfdToStringChannelId(khr_df_model_e model, khr_df_model_channels_e value); + /* Print a human-readable interpretation of a data format descriptor. */ -void printDFD(uint32_t *DFD); +void printDFD(uint32_t *DFD, uint32_t dataSize); + +/* Print a JSON interpretation of a data format descriptor. */ +void printDFDJSON(uint32_t *DFD, uint32_t dataSize, uint32_t base_indent, uint32_t indent_width, bool minified); /* Get the number of components & component size from a DFD for an * unpacked format. @@ -161,7 +201,8 @@ typedef struct _Primaries { float Wy; /*!< White y. */ } Primaries; -khr_df_primaries_e findMapping(Primaries *p, float latitude); +khr_df_primaries_e findMapping(const Primaries *p, float latitude); +bool getPrimaries(khr_df_primaries_e primaries, Primaries *p); #ifdef __cplusplus } diff --git a/lib/dfdutils/interpretdfd.c b/lib/dfdutils/interpretdfd.c index 273410a18c..d7b6f4a655 100644 --- a/lib/dfdutils/interpretdfd.c +++ b/lib/dfdutils/interpretdfd.c @@ -25,8 +25,8 @@ described as 32-bit words in native endianness. Note that this is the whole descriptor, not just the basic descriptor block. - * @param R Information about the decoded red channel, if any. - * @param G Information about the decoded green channel, if any. + * @param R Information about the decoded red channel or the depth channel, if any. + * @param G Information about the decoded green channel or the stencil channel, if any. * @param B Information about the decoded blue channel, if any. * @param A Information about the decoded alpha channel, if any. * @param wordBytes Byte size of the channels (unpacked) or total size (packed). @@ -99,8 +99,8 @@ enum InterpretDFDResult interpretDFD(const uint32_t *DFD, if (KHR_DFDSVAL(BDFDB, sampleCounter, QUALIFIERS) & KHR_DF_SAMPLE_DATATYPE_FLOAT) { result |= i_FLOAT_FORMAT_BIT; - determinedFloatness = 1; } + determinedFloatness = 1; } else { /* Check whether we disagree with our predetermined floatness. */ /* Note that this could justifiably happen with (say) D24S8. */ @@ -115,8 +115,8 @@ enum InterpretDFDResult interpretDFD(const uint32_t *DFD, if (KHR_DFDSVAL(BDFDB, sampleCounter, QUALIFIERS) & KHR_DF_SAMPLE_DATATYPE_SIGNED) { result |= i_SIGNED_FORMAT_BIT; - determinedSignedness = 1; } + determinedSignedness = 1; } else { /* Check whether we disagree with our predetermined signedness. */ if (KHR_DFDSVAL(BDFDB, sampleCounter, QUALIFIERS) @@ -207,6 +207,12 @@ enum InterpretDFDResult interpretDFD(const uint32_t *DFD, case KHR_DF_CHANNEL_RGBSDA_BLUE: sampleChannelPtr = B; break; + case KHR_DF_CHANNEL_RGBSDA_DEPTH: + sampleChannelPtr = R; + break; + case KHR_DF_CHANNEL_RGBSDA_STENCIL: + sampleChannelPtr = G; + break; case KHR_DF_CHANNEL_RGBSDA_ALPHA: sampleChannelPtr = A; break; @@ -286,6 +292,12 @@ enum InterpretDFDResult interpretDFD(const uint32_t *DFD, case KHR_DF_CHANNEL_RGBSDA_BLUE: sampleChannelPtr = B; break; + case KHR_DF_CHANNEL_RGBSDA_DEPTH: + sampleChannelPtr = R; + break; + case KHR_DF_CHANNEL_RGBSDA_STENCIL: + sampleChannelPtr = G; + break; case KHR_DF_CHANNEL_RGBSDA_ALPHA: sampleChannelPtr = A; break; diff --git a/lib/dfdutils/interpretdfdtest.c b/lib/dfdutils/interpretdfdtest.c index 8e9bf29dd9..67a0cc7314 100644 --- a/lib/dfdutils/interpretdfdtest.c +++ b/lib/dfdutils/interpretdfdtest.c @@ -228,7 +228,7 @@ int main() uint32_t *d = createDFDUnpacked(0, 3, 1, 0, s_UNORM); #endif - printDFD(d); + printDFD(d, *d); t = interpretDFD(d, &R, &G, &B, &A, &wordSize); #endif diff --git a/lib/dfdutils/printdfd.c b/lib/dfdutils/printdfd.c index 36d3d2c50d..0787cedd4b 100644 --- a/lib/dfdutils/printdfd.c +++ b/lib/dfdutils/printdfd.c @@ -15,83 +15,1011 @@ * Author: Andrew Garrard */ +#include +#include #include +#include #include #include "dfd.h" +enum { + // These constraints are not mandated by the spec and only used as a + // reasonable upper limit to stop parsing garbage data during print + MAX_NUM_BDFD_SAMPLES = 16, + MAX_NUM_DFD_BLOCKS = 10, +}; + +const char* dfdToStringVendorID(khr_df_vendorid_e value) { + switch (value) { + case KHR_DF_VENDORID_KHRONOS: + return "KHR_DF_VENDORID_KHRONOS"; + + case KHR_DF_VENDORID_MAX: + // These enum values are not meant for string representation. Ignore + break; + } + return NULL; +} + +const char* dfdToStringDescriptorType(khr_df_khr_descriptortype_e value) { + switch (value) { + case KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT: + return "KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT"; + case KHR_DF_KHR_DESCRIPTORTYPE_ADDITIONAL_PLANES: + return "KHR_DF_KHR_DESCRIPTORTYPE_ADDITIONAL_PLANES"; + case KHR_DF_KHR_DESCRIPTORTYPE_ADDITIONAL_DIMENSIONS: + return "KHR_DF_KHR_DESCRIPTORTYPE_ADDITIONAL_DIMENSIONS"; + + case KHR_DF_KHR_DESCRIPTORTYPE_NEEDED_FOR_WRITE_BIT: + case KHR_DF_KHR_DESCRIPTORTYPE_NEEDED_FOR_DECODE_BIT: + case KHR_DF_KHR_DESCRIPTORTYPE_MAX: + // These enum values are not meant for string representation. Ignore + break; + } + return NULL; +} + +const char* dfdToStringVersionNumber(khr_df_versionnumber_e value) { + switch (value) { + case KHR_DF_VERSIONNUMBER_1_1: + // case KHR_DF_VERSIONNUMBER_1_0: // Fallthrough, Matching values + return "KHR_DF_VERSIONNUMBER_1_1"; + case KHR_DF_VERSIONNUMBER_1_2: + return "KHR_DF_VERSIONNUMBER_1_2"; + case KHR_DF_VERSIONNUMBER_1_3: + return "KHR_DF_VERSIONNUMBER_1_3"; + + // case KHR_DF_VERSIONNUMBER_LATEST: // Fallthrough, Matching values + case KHR_DF_VERSIONNUMBER_MAX: + // These enum values are not meant for string representation. Ignore + break; + } + return NULL; +} + +const char* dfdToStringFlagsBit(uint32_t bit_index, bool bit_value) { + switch (bit_index) { + case 0: + return bit_value ? "KHR_DF_FLAG_ALPHA_PREMULTIPLIED" : "KHR_DF_FLAG_ALPHA_STRAIGHT"; + default: + return NULL; + } +} + +const char* dfdToStringTransferFunction(khr_df_transfer_e value) { + switch (value) { + case KHR_DF_TRANSFER_UNSPECIFIED: + return "KHR_DF_TRANSFER_UNSPECIFIED"; + case KHR_DF_TRANSFER_LINEAR: + return "KHR_DF_TRANSFER_LINEAR"; + case KHR_DF_TRANSFER_SRGB: + return "KHR_DF_TRANSFER_SRGB"; + case KHR_DF_TRANSFER_ITU: + return "KHR_DF_TRANSFER_ITU"; + case KHR_DF_TRANSFER_NTSC: + // case KHR_DF_TRANSFER_SMTPE170M: // Fallthrough, Matching values + return "KHR_DF_TRANSFER_NTSC"; + case KHR_DF_TRANSFER_SLOG: + return "KHR_DF_TRANSFER_SLOG"; + case KHR_DF_TRANSFER_SLOG2: + return "KHR_DF_TRANSFER_SLOG2"; + case KHR_DF_TRANSFER_BT1886: + return "KHR_DF_TRANSFER_BT1886"; + case KHR_DF_TRANSFER_HLG_OETF: + return "KHR_DF_TRANSFER_HLG_OETF"; + case KHR_DF_TRANSFER_HLG_EOTF: + return "KHR_DF_TRANSFER_HLG_EOTF"; + case KHR_DF_TRANSFER_PQ_EOTF: + return "KHR_DF_TRANSFER_PQ_EOTF"; + case KHR_DF_TRANSFER_PQ_OETF: + return "KHR_DF_TRANSFER_PQ_OETF"; + case KHR_DF_TRANSFER_DCIP3: + return "KHR_DF_TRANSFER_DCIP3"; + case KHR_DF_TRANSFER_PAL_OETF: + return "KHR_DF_TRANSFER_PAL_OETF"; + case KHR_DF_TRANSFER_PAL625_EOTF: + return "KHR_DF_TRANSFER_PAL625_EOTF"; + case KHR_DF_TRANSFER_ST240: + return "KHR_DF_TRANSFER_ST240"; + case KHR_DF_TRANSFER_ACESCC: + return "KHR_DF_TRANSFER_ACESCC"; + case KHR_DF_TRANSFER_ACESCCT: + return "KHR_DF_TRANSFER_ACESCCT"; + case KHR_DF_TRANSFER_ADOBERGB: + return "KHR_DF_TRANSFER_ADOBERGB"; + + case KHR_DF_TRANSFER_MAX: + // These enum values are not meant for string representation. Ignore + break; + } + return NULL; +} + +const char* dfdToStringColorPrimaries(khr_df_primaries_e value) { + switch (value) { + case KHR_DF_PRIMARIES_UNSPECIFIED: + return "KHR_DF_PRIMARIES_UNSPECIFIED"; + case KHR_DF_PRIMARIES_BT709: + // case KHR_DF_PRIMARIES_SRGB: // Fallthrough, Matching values + return "KHR_DF_PRIMARIES_BT709"; + case KHR_DF_PRIMARIES_BT601_EBU: + return "KHR_DF_PRIMARIES_BT601_EBU"; + case KHR_DF_PRIMARIES_BT601_SMPTE: + return "KHR_DF_PRIMARIES_BT601_SMPTE"; + case KHR_DF_PRIMARIES_BT2020: + return "KHR_DF_PRIMARIES_BT2020"; + case KHR_DF_PRIMARIES_CIEXYZ: + return "KHR_DF_PRIMARIES_CIEXYZ"; + case KHR_DF_PRIMARIES_ACES: + return "KHR_DF_PRIMARIES_ACES"; + case KHR_DF_PRIMARIES_ACESCC: + return "KHR_DF_PRIMARIES_ACESCC"; + case KHR_DF_PRIMARIES_NTSC1953: + return "KHR_DF_PRIMARIES_NTSC1953"; + case KHR_DF_PRIMARIES_PAL525: + return "KHR_DF_PRIMARIES_PAL525"; + case KHR_DF_PRIMARIES_DISPLAYP3: + return "KHR_DF_PRIMARIES_DISPLAYP3"; + case KHR_DF_PRIMARIES_ADOBERGB: + return "KHR_DF_PRIMARIES_ADOBERGB"; + + case KHR_DF_PRIMARIES_MAX: + // These enum values are not meant for string representation. Ignore + break; + } + return NULL; +} + +const char* dfdToStringColorModel(khr_df_model_e value) { + switch (value) { + case KHR_DF_MODEL_UNSPECIFIED: + return "KHR_DF_MODEL_UNSPECIFIED"; + case KHR_DF_MODEL_RGBSDA: + return "KHR_DF_MODEL_RGBSDA"; + case KHR_DF_MODEL_YUVSDA: + return "KHR_DF_MODEL_YUVSDA"; + case KHR_DF_MODEL_YIQSDA: + return "KHR_DF_MODEL_YIQSDA"; + case KHR_DF_MODEL_LABSDA: + return "KHR_DF_MODEL_LABSDA"; + case KHR_DF_MODEL_CMYKA: + return "KHR_DF_MODEL_CMYKA"; + case KHR_DF_MODEL_XYZW: + return "KHR_DF_MODEL_XYZW"; + case KHR_DF_MODEL_HSVA_ANG: + return "KHR_DF_MODEL_HSVA_ANG"; + case KHR_DF_MODEL_HSLA_ANG: + return "KHR_DF_MODEL_HSLA_ANG"; + case KHR_DF_MODEL_HSVA_HEX: + return "KHR_DF_MODEL_HSVA_HEX"; + case KHR_DF_MODEL_HSLA_HEX: + return "KHR_DF_MODEL_HSLA_HEX"; + case KHR_DF_MODEL_YCGCOA: + return "KHR_DF_MODEL_YCGCOA"; + case KHR_DF_MODEL_YCCBCCRC: + return "KHR_DF_MODEL_YCCBCCRC"; + case KHR_DF_MODEL_ICTCP: + return "KHR_DF_MODEL_ICTCP"; + case KHR_DF_MODEL_CIEXYZ: + return "KHR_DF_MODEL_CIEXYZ"; + case KHR_DF_MODEL_CIEXYY: + return "KHR_DF_MODEL_CIEXYY"; + case KHR_DF_MODEL_BC1A: + // case KHR_DF_MODEL_DXT1A: // Fallthrough, Matching values + return "KHR_DF_MODEL_BC1A"; + case KHR_DF_MODEL_BC2: + // case KHR_DF_MODEL_DXT2: // Fallthrough, Matching values + // case KHR_DF_MODEL_DXT3: // Fallthrough, Matching values + return "KHR_DF_MODEL_BC2"; + case KHR_DF_MODEL_BC3: + // case KHR_DF_MODEL_DXT4: // Fallthrough, Matching values + // case KHR_DF_MODEL_DXT5: // Fallthrough, Matching values + return "KHR_DF_MODEL_BC3"; + case KHR_DF_MODEL_BC4: + return "KHR_DF_MODEL_BC4"; + case KHR_DF_MODEL_BC5: + return "KHR_DF_MODEL_BC5"; + case KHR_DF_MODEL_BC6H: + return "KHR_DF_MODEL_BC6H"; + case KHR_DF_MODEL_BC7: + return "KHR_DF_MODEL_BC7"; + case KHR_DF_MODEL_ETC1: + return "KHR_DF_MODEL_ETC1"; + case KHR_DF_MODEL_ETC2: + return "KHR_DF_MODEL_ETC2"; + case KHR_DF_MODEL_ASTC: + return "KHR_DF_MODEL_ASTC"; + case KHR_DF_MODEL_ETC1S: + return "KHR_DF_MODEL_ETC1S"; + case KHR_DF_MODEL_PVRTC: + return "KHR_DF_MODEL_PVRTC"; + case KHR_DF_MODEL_PVRTC2: + return "KHR_DF_MODEL_PVRTC2"; + case KHR_DF_MODEL_UASTC: + return "KHR_DF_MODEL_UASTC"; + + case KHR_DF_MODEL_MAX: + // These enum values are not meant for string representation. Ignore + break; + } + return NULL; +} + +const char* dfdToStringSampleDatatypeQualifiers(uint32_t bit_index, bool bit_value) { + if (!bit_value) + return NULL; + + switch (1u << bit_index) { + case KHR_DF_SAMPLE_DATATYPE_LINEAR: + return "KHR_DF_SAMPLE_DATATYPE_LINEAR"; + case KHR_DF_SAMPLE_DATATYPE_EXPONENT: + return "KHR_DF_SAMPLE_DATATYPE_EXPONENT"; + case KHR_DF_SAMPLE_DATATYPE_SIGNED: + return "KHR_DF_SAMPLE_DATATYPE_SIGNED"; + case KHR_DF_SAMPLE_DATATYPE_FLOAT: + return "KHR_DF_SAMPLE_DATATYPE_FLOAT"; + } + return NULL; +} + +const char* dfdToStringChannelId(khr_df_model_e model, khr_df_model_channels_e value) { + switch (model) { + case KHR_DF_MODEL_RGBSDA: + switch (value) { + case KHR_DF_CHANNEL_RGBSDA_RED: + return "KHR_DF_CHANNEL_RGBSDA_RED"; + case KHR_DF_CHANNEL_RGBSDA_GREEN: + return "KHR_DF_CHANNEL_RGBSDA_GREEN"; + case KHR_DF_CHANNEL_RGBSDA_BLUE: + return "KHR_DF_CHANNEL_RGBSDA_BLUE"; + case KHR_DF_CHANNEL_RGBSDA_STENCIL: + return "KHR_DF_CHANNEL_RGBSDA_STENCIL"; + case KHR_DF_CHANNEL_RGBSDA_DEPTH: + return "KHR_DF_CHANNEL_RGBSDA_DEPTH"; + case KHR_DF_CHANNEL_RGBSDA_ALPHA: + return "KHR_DF_CHANNEL_RGBSDA_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_YUVSDA: + switch (value) { + case KHR_DF_CHANNEL_YUVSDA_Y: + return "KHR_DF_CHANNEL_YUVSDA_Y"; + case KHR_DF_CHANNEL_YUVSDA_U: + return "KHR_DF_CHANNEL_YUVSDA_U"; + case KHR_DF_CHANNEL_YUVSDA_V: + return "KHR_DF_CHANNEL_YUVSDA_V"; + case KHR_DF_CHANNEL_YUVSDA_STENCIL: + return "KHR_DF_CHANNEL_YUVSDA_STENCIL"; + case KHR_DF_CHANNEL_YUVSDA_DEPTH: + return "KHR_DF_CHANNEL_YUVSDA_DEPTH"; + case KHR_DF_CHANNEL_YUVSDA_ALPHA: + return "KHR_DF_CHANNEL_YUVSDA_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_YIQSDA: + switch (value) { + case KHR_DF_CHANNEL_YIQSDA_Y: + return "KHR_DF_CHANNEL_YIQSDA_Y"; + case KHR_DF_CHANNEL_YIQSDA_I: + return "KHR_DF_CHANNEL_YIQSDA_I"; + case KHR_DF_CHANNEL_YIQSDA_Q: + return "KHR_DF_CHANNEL_YIQSDA_Q"; + case KHR_DF_CHANNEL_YIQSDA_STENCIL: + return "KHR_DF_CHANNEL_YIQSDA_STENCIL"; + case KHR_DF_CHANNEL_YIQSDA_DEPTH: + return "KHR_DF_CHANNEL_YIQSDA_DEPTH"; + case KHR_DF_CHANNEL_YIQSDA_ALPHA: + return "KHR_DF_CHANNEL_YIQSDA_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_LABSDA: + switch (value) { + case KHR_DF_CHANNEL_LABSDA_L: + return "KHR_DF_CHANNEL_LABSDA_L"; + case KHR_DF_CHANNEL_LABSDA_A: + return "KHR_DF_CHANNEL_LABSDA_A"; + case KHR_DF_CHANNEL_LABSDA_B: + return "KHR_DF_CHANNEL_LABSDA_B"; + case KHR_DF_CHANNEL_LABSDA_STENCIL: + return "KHR_DF_CHANNEL_LABSDA_STENCIL"; + case KHR_DF_CHANNEL_LABSDA_DEPTH: + return "KHR_DF_CHANNEL_LABSDA_DEPTH"; + case KHR_DF_CHANNEL_LABSDA_ALPHA: + return "KHR_DF_CHANNEL_LABSDA_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_CMYKA: + switch (value) { + case KHR_DF_CHANNEL_CMYKSDA_CYAN: + return "KHR_DF_CHANNEL_CMYKSDA_CYAN"; + case KHR_DF_CHANNEL_CMYKSDA_MAGENTA: + return "KHR_DF_CHANNEL_CMYKSDA_MAGENTA"; + case KHR_DF_CHANNEL_CMYKSDA_YELLOW: + return "KHR_DF_CHANNEL_CMYKSDA_YELLOW"; + case KHR_DF_CHANNEL_CMYKSDA_BLACK: + return "KHR_DF_CHANNEL_CMYKSDA_BLACK"; + case KHR_DF_CHANNEL_CMYKSDA_ALPHA: + return "KHR_DF_CHANNEL_CMYKSDA_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_XYZW: + switch (value) { + case KHR_DF_CHANNEL_XYZW_X: + return "KHR_DF_CHANNEL_XYZW_X"; + case KHR_DF_CHANNEL_XYZW_Y: + return "KHR_DF_CHANNEL_XYZW_Y"; + case KHR_DF_CHANNEL_XYZW_Z: + return "KHR_DF_CHANNEL_XYZW_Z"; + case KHR_DF_CHANNEL_XYZW_W: + return "KHR_DF_CHANNEL_XYZW_W"; + default: + return NULL; + } + + case KHR_DF_MODEL_HSVA_ANG: + switch (value) { + case KHR_DF_CHANNEL_HSVA_ANG_VALUE: + return "KHR_DF_CHANNEL_HSVA_ANG_VALUE"; + case KHR_DF_CHANNEL_HSVA_ANG_SATURATION: + return "KHR_DF_CHANNEL_HSVA_ANG_SATURATION"; + case KHR_DF_CHANNEL_HSVA_ANG_HUE: + return "KHR_DF_CHANNEL_HSVA_ANG_HUE"; + case KHR_DF_CHANNEL_HSVA_ANG_ALPHA: + return "KHR_DF_CHANNEL_HSVA_ANG_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_HSLA_ANG: + switch (value) { + case KHR_DF_CHANNEL_HSLA_ANG_LIGHTNESS: + return "KHR_DF_CHANNEL_HSLA_ANG_LIGHTNESS"; + case KHR_DF_CHANNEL_HSLA_ANG_SATURATION: + return "KHR_DF_CHANNEL_HSLA_ANG_SATURATION"; + case KHR_DF_CHANNEL_HSLA_ANG_HUE: + return "KHR_DF_CHANNEL_HSLA_ANG_HUE"; + case KHR_DF_CHANNEL_HSLA_ANG_ALPHA: + return "KHR_DF_CHANNEL_HSLA_ANG_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_HSVA_HEX: + switch (value) { + case KHR_DF_CHANNEL_HSVA_HEX_VALUE: + return "KHR_DF_CHANNEL_HSVA_HEX_VALUE"; + case KHR_DF_CHANNEL_HSVA_HEX_SATURATION: + return "KHR_DF_CHANNEL_HSVA_HEX_SATURATION"; + case KHR_DF_CHANNEL_HSVA_HEX_HUE: + return "KHR_DF_CHANNEL_HSVA_HEX_HUE"; + case KHR_DF_CHANNEL_HSVA_HEX_ALPHA: + return "KHR_DF_CHANNEL_HSVA_HEX_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_HSLA_HEX: + switch (value) { + case KHR_DF_CHANNEL_HSLA_HEX_LIGHTNESS: + return "KHR_DF_CHANNEL_HSLA_HEX_LIGHTNESS"; + case KHR_DF_CHANNEL_HSLA_HEX_SATURATION: + return "KHR_DF_CHANNEL_HSLA_HEX_SATURATION"; + case KHR_DF_CHANNEL_HSLA_HEX_HUE: + return "KHR_DF_CHANNEL_HSLA_HEX_HUE"; + case KHR_DF_CHANNEL_HSLA_HEX_ALPHA: + return "KHR_DF_CHANNEL_HSLA_HEX_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_YCGCOA: + switch (value) { + case KHR_DF_CHANNEL_YCGCOA_Y: + return "KHR_DF_CHANNEL_YCGCOA_Y"; + case KHR_DF_CHANNEL_YCGCOA_CG: + return "KHR_DF_CHANNEL_YCGCOA_CG"; + case KHR_DF_CHANNEL_YCGCOA_CO: + return "KHR_DF_CHANNEL_YCGCOA_CO"; + case KHR_DF_CHANNEL_YCGCOA_ALPHA: + return "KHR_DF_CHANNEL_YCGCOA_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_CIEXYZ: + switch (value) { + case KHR_DF_CHANNEL_CIEXYZ_X: + return "KHR_DF_CHANNEL_CIEXYZ_X"; + case KHR_DF_CHANNEL_CIEXYZ_Y: + return "KHR_DF_CHANNEL_CIEXYZ_Y"; + case KHR_DF_CHANNEL_CIEXYZ_Z: + return "KHR_DF_CHANNEL_CIEXYZ_Z"; + default: + return NULL; + } + + case KHR_DF_MODEL_CIEXYY: + switch (value) { + case KHR_DF_CHANNEL_CIEXYY_X: + return "KHR_DF_CHANNEL_CIEXYY_X"; + case KHR_DF_CHANNEL_CIEXYY_YCHROMA: + return "KHR_DF_CHANNEL_CIEXYY_YCHROMA"; + case KHR_DF_CHANNEL_CIEXYY_YLUMA: + return "KHR_DF_CHANNEL_CIEXYY_YLUMA"; + default: + return NULL; + } + + case KHR_DF_MODEL_BC1A: + switch (value) { + case KHR_DF_CHANNEL_BC1A_COLOR: + return "KHR_DF_CHANNEL_BC1A_COLOR"; + case KHR_DF_CHANNEL_BC1A_ALPHA: + return "KHR_DF_CHANNEL_BC1A_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_BC2: + switch (value) { + case KHR_DF_CHANNEL_BC2_COLOR: + return "KHR_DF_CHANNEL_BC2_COLOR"; + case KHR_DF_CHANNEL_BC2_ALPHA: + return "KHR_DF_CHANNEL_BC2_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_BC3: + switch (value) { + case KHR_DF_CHANNEL_BC3_COLOR: + return "KHR_DF_CHANNEL_BC3_COLOR"; + case KHR_DF_CHANNEL_BC3_ALPHA: + return "KHR_DF_CHANNEL_BC3_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_BC4: + switch (value) { + case KHR_DF_CHANNEL_BC4_DATA: + return "KHR_DF_CHANNEL_BC4_DATA"; + default: + return NULL; + } + + case KHR_DF_MODEL_BC5: + switch (value) { + case KHR_DF_CHANNEL_BC5_RED: + return "KHR_DF_CHANNEL_BC5_RED"; + case KHR_DF_CHANNEL_BC5_GREEN: + return "KHR_DF_CHANNEL_BC5_GREEN"; + default: + return NULL; + } + + case KHR_DF_MODEL_BC6H: + switch (value) { + case KHR_DF_CHANNEL_BC6H_COLOR: + return "KHR_DF_CHANNEL_BC6H_COLOR"; + default: + return NULL; + } + + case KHR_DF_MODEL_BC7: + switch (value) { + case KHR_DF_CHANNEL_BC7_COLOR: + return "KHR_DF_CHANNEL_BC7_COLOR"; + default: + return NULL; + } + + case KHR_DF_MODEL_ETC1: + switch (value) { + case KHR_DF_CHANNEL_ETC1_COLOR: + return "KHR_DF_CHANNEL_ETC1_COLOR"; + default: + return NULL; + } + + case KHR_DF_MODEL_ETC2: + switch (value) { + case KHR_DF_CHANNEL_ETC2_RED: + return "KHR_DF_CHANNEL_ETC2_RED"; + case KHR_DF_CHANNEL_ETC2_GREEN: + return "KHR_DF_CHANNEL_ETC2_GREEN"; + case KHR_DF_CHANNEL_ETC2_COLOR: + return "KHR_DF_CHANNEL_ETC2_COLOR"; + case KHR_DF_CHANNEL_ETC2_ALPHA: + return "KHR_DF_CHANNEL_ETC2_ALPHA"; + default: + return NULL; + } + + case KHR_DF_MODEL_ASTC: + switch (value) { + case KHR_DF_CHANNEL_ASTC_DATA: + return "KHR_DF_CHANNEL_ASTC_DATA"; + default: + return NULL; + } + + case KHR_DF_MODEL_ETC1S: + switch (value) { + case KHR_DF_CHANNEL_ETC1S_RGB: + return "KHR_DF_CHANNEL_ETC1S_RGB"; + case KHR_DF_CHANNEL_ETC1S_RRR: + return "KHR_DF_CHANNEL_ETC1S_RRR"; + case KHR_DF_CHANNEL_ETC1S_GGG: + return "KHR_DF_CHANNEL_ETC1S_GGG"; + case KHR_DF_CHANNEL_ETC1S_AAA: + return "KHR_DF_CHANNEL_ETC1S_AAA"; + default: + return NULL; + } + + case KHR_DF_MODEL_PVRTC: + switch (value) { + case KHR_DF_CHANNEL_PVRTC_COLOR: + return "KHR_DF_CHANNEL_PVRTC_COLOR"; + default: + return NULL; + } + + case KHR_DF_MODEL_PVRTC2: + switch (value) { + case KHR_DF_CHANNEL_PVRTC2_COLOR: + return "KHR_DF_CHANNEL_PVRTC2_COLOR"; + default: + return NULL; + } + + case KHR_DF_MODEL_UASTC: + switch (value) { + case KHR_DF_CHANNEL_UASTC_RGB: + return "KHR_DF_CHANNEL_UASTC_RGB"; + case KHR_DF_CHANNEL_UASTC_RGBA: + return "KHR_DF_CHANNEL_UASTC_RGBA"; + case KHR_DF_CHANNEL_UASTC_RRR: + return "KHR_DF_CHANNEL_UASTC_RRR"; + case KHR_DF_CHANNEL_UASTC_RRRG: + return "KHR_DF_CHANNEL_UASTC_RRRG"; + case KHR_DF_CHANNEL_UASTC_RG: + return "KHR_DF_CHANNEL_UASTC_RG"; + default: + return NULL; + } + + default: + break; + } + + switch (value) { + case KHR_DF_CHANNEL_UNSPECIFIED_0: + return "KHR_DF_CHANNEL_UNSPECIFIED_0"; + case KHR_DF_CHANNEL_UNSPECIFIED_1: + return "KHR_DF_CHANNEL_UNSPECIFIED_1"; + case KHR_DF_CHANNEL_UNSPECIFIED_2: + return "KHR_DF_CHANNEL_UNSPECIFIED_2"; + case KHR_DF_CHANNEL_UNSPECIFIED_3: + return "KHR_DF_CHANNEL_UNSPECIFIED_3"; + case KHR_DF_CHANNEL_UNSPECIFIED_4: + return "KHR_DF_CHANNEL_UNSPECIFIED_4"; + case KHR_DF_CHANNEL_UNSPECIFIED_5: + return "KHR_DF_CHANNEL_UNSPECIFIED_5"; + case KHR_DF_CHANNEL_UNSPECIFIED_6: + return "KHR_DF_CHANNEL_UNSPECIFIED_6"; + case KHR_DF_CHANNEL_UNSPECIFIED_7: + return "KHR_DF_CHANNEL_UNSPECIFIED_7"; + case KHR_DF_CHANNEL_UNSPECIFIED_8: + return "KHR_DF_CHANNEL_UNSPECIFIED_8"; + case KHR_DF_CHANNEL_UNSPECIFIED_9: + return "KHR_DF_CHANNEL_UNSPECIFIED_9"; + case KHR_DF_CHANNEL_UNSPECIFIED_10: + return "KHR_DF_CHANNEL_UNSPECIFIED_10"; + case KHR_DF_CHANNEL_UNSPECIFIED_11: + return "KHR_DF_CHANNEL_UNSPECIFIED_11"; + case KHR_DF_CHANNEL_UNSPECIFIED_12: + return "KHR_DF_CHANNEL_UNSPECIFIED_12"; + case KHR_DF_CHANNEL_UNSPECIFIED_13: + return "KHR_DF_CHANNEL_UNSPECIFIED_13"; + case KHR_DF_CHANNEL_UNSPECIFIED_14: + return "KHR_DF_CHANNEL_UNSPECIFIED_14"; + case KHR_DF_CHANNEL_UNSPECIFIED_15: + return "KHR_DF_CHANNEL_UNSPECIFIED_15"; + default: + break; + } + + return NULL; +} + +/** + * @internal + */ +static void printFlagBits(uint32_t flags, const char*(*toStringFn)(uint32_t, bool)) { + bool first = true; + for (uint32_t bit_index = 0; bit_index < 32; ++bit_index) { + uint32_t bit_mask = 1u << bit_index; + bool bit_value = (bit_mask & (uint32_t) flags) != 0; + + const char* comma = first ? "" : ", "; + const char* str = toStringFn(bit_index, bit_value); + if (str) { + printf("%s%s", comma, str); + first = false; + } else if (bit_value) { + printf("%s%u", comma, bit_mask); + first = false; + } + } +} + +/** + * @internal + */ +static void printFlagBitsJSON(uint32_t indent, const char* nl, uint32_t flags, const char*(*toStringFn)(uint32_t, bool)) { + bool first = true; + for (uint32_t bit_index = 0; bit_index < 32; ++bit_index) { + uint32_t bit_mask = 1u << bit_index; + bool bit_value = (bit_mask & (uint32_t) flags) != 0; + + const char* str = toStringFn(bit_index, bit_value); + if (str) { + printf("%s%s%*s\"%s\"", first ? "" : ",", first ? "" : nl, indent, "", str); + first = false; + } else if (bit_value) { + printf("%s%s%*s%u", first ? "" : ",", first ? "" : nl, indent, "", bit_mask); + first = false; + } + } + if (!first) + printf("%s", nl); +} + /** * @~English * @brief Print a human-readable interpretation of a data format descriptor. * * @param DFD Pointer to a data format descriptor. + * @param dataSize The maximum size that can be considered as part of the DFD. + **/ +void printDFD(uint32_t *DFD, uint32_t dataSize) +{ +#define PRINT_ENUM(VALUE, TO_STRING_FN) { \ + int value = VALUE; \ + const char* str = TO_STRING_FN(value); \ + if (str) \ + printf("%s", str); \ + else \ + printf("%u", value); \ + } + + const uint32_t sizeof_dfdTotalSize = sizeof(uint32_t); + const uint32_t sizeof_DFDBHeader = sizeof(uint32_t) * 2; + const uint32_t sizeof_BDFD = sizeof(uint32_t) * KHR_DF_WORD_SAMPLESTART; + const uint32_t sizeof_BDFDSample = sizeof(uint32_t) * KHR_DF_WORD_SAMPLEWORDS; + + uint32_t dfdTotalSize = dataSize >= sizeof_dfdTotalSize ? DFD[0] : 0; + uint32_t remainingSize = dfdTotalSize < dataSize ? dfdTotalSize : dataSize; + if (remainingSize < sizeof_dfdTotalSize) + return; // Invalid DFD: dfdTotalSize must be present + + uint32_t* block = DFD + 1; + remainingSize -= sizeof_dfdTotalSize; + + printf("DFD total bytes: %u\n", dfdTotalSize); + + for (int i = 0; i < MAX_NUM_DFD_BLOCKS; ++i) { // At most only iterate MAX_NUM_DFD_BLOCKS block + if (remainingSize < sizeof_DFDBHeader) + break; // Invalid DFD: Missing or partial block header + + const khr_df_vendorid_e vendorID = KHR_DFDVAL(block, VENDORID); + const khr_df_khr_descriptortype_e descriptorType = KHR_DFDVAL(block, DESCRIPTORTYPE); + const khr_df_versionnumber_e versionNumber = KHR_DFDVAL(block, VERSIONNUMBER); + const uint32_t blockSize = KHR_DFDVAL(block, DESCRIPTORBLOCKSIZE); + + printf("Vendor ID: "); + PRINT_ENUM(vendorID, dfdToStringVendorID); + printf("\nDescriptor type: "); + PRINT_ENUM(descriptorType, dfdToStringDescriptorType); + printf("\nVersion: "); + PRINT_ENUM(versionNumber, dfdToStringVersionNumber); + printf("\nDescriptor block size: %u", blockSize); + printf("\n"); + + if (vendorID == KHR_DF_VENDORID_KHRONOS && descriptorType == KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT) { + if (remainingSize < sizeof_BDFD) + break; // Invalid DFD: Missing or partial basic DFD block + + const int model = KHR_DFDVAL(block, MODEL); + + khr_df_flags_e flags = KHR_DFDVAL(block, FLAGS); + printf("Flags: 0x%X (", flags); + printFlagBits(flags, dfdToStringFlagsBit); + printf(")\nTransfer: "); + PRINT_ENUM(KHR_DFDVAL(block, TRANSFER), dfdToStringTransferFunction); + printf("\nPrimaries: "); + PRINT_ENUM(KHR_DFDVAL(block, PRIMARIES), dfdToStringColorPrimaries); + printf("\nModel: "); + PRINT_ENUM(model, dfdToStringColorModel); + printf("\n"); + + printf("Dimensions: %u, %u, %u, %u\n", + KHR_DFDVAL(block, TEXELBLOCKDIMENSION0) + 1, + KHR_DFDVAL(block, TEXELBLOCKDIMENSION1) + 1, + KHR_DFDVAL(block, TEXELBLOCKDIMENSION2) + 1, + KHR_DFDVAL(block, TEXELBLOCKDIMENSION3) + 1); + printf("Plane bytes: %u, %u, %u, %u, %u, %u, %u, %u\n", + KHR_DFDVAL(block, BYTESPLANE0), + KHR_DFDVAL(block, BYTESPLANE1), + KHR_DFDVAL(block, BYTESPLANE2), + KHR_DFDVAL(block, BYTESPLANE3), + KHR_DFDVAL(block, BYTESPLANE4), + KHR_DFDVAL(block, BYTESPLANE5), + KHR_DFDVAL(block, BYTESPLANE6), + KHR_DFDVAL(block, BYTESPLANE7)); + + int samples = (blockSize - sizeof_BDFD) / sizeof_BDFDSample; + if (samples > MAX_NUM_BDFD_SAMPLES) + samples = MAX_NUM_BDFD_SAMPLES; // Too many BDFD samples + for (int sample = 0; sample < samples; ++sample) { + if (remainingSize < sizeof_BDFD + (sample + 1) * sizeof_BDFDSample) + break; // Invalid DFD: Missing or partial basic DFD sample + + khr_df_model_channels_e channelType = KHR_DFDSVAL(block, sample, CHANNELID); + printf("Sample %u:\n", sample); + + khr_df_sample_datatype_qualifiers_e qualifiers = KHR_DFDSVAL(block, sample, QUALIFIERS); + printf(" Qualifiers: 0x%X (", qualifiers); + printFlagBits(qualifiers, dfdToStringSampleDatatypeQualifiers); + printf(")\n"); + printf(" Channel Type: 0x%X", channelType); + { + const char* str = dfdToStringChannelId(model, channelType); + if (str) + printf(" (%s)\n", str); + else + printf(" (%u)\n", channelType); + } + printf(" Length: %u bits Offset: %u\n", + KHR_DFDSVAL(block, sample, BITLENGTH) + 1, + KHR_DFDSVAL(block, sample, BITOFFSET)); + printf(" Position: %u, %u, %u, %u\n", + KHR_DFDSVAL(block, sample, SAMPLEPOSITION0), + KHR_DFDSVAL(block, sample, SAMPLEPOSITION1), + KHR_DFDSVAL(block, sample, SAMPLEPOSITION2), + KHR_DFDSVAL(block, sample, SAMPLEPOSITION3)); + printf(" Lower: 0x%08x\n Upper: 0x%08x\n", + KHR_DFDSVAL(block, sample, SAMPLELOWER), + KHR_DFDSVAL(block, sample, SAMPLEUPPER)); + } + } else if (vendorID == KHR_DF_VENDORID_KHRONOS && descriptorType == KHR_DF_KHR_DESCRIPTORTYPE_ADDITIONAL_DIMENSIONS) { + // TODO Tools P5: Implement DFD print for ADDITIONAL_DIMENSIONS + } else if (vendorID == KHR_DF_VENDORID_KHRONOS && descriptorType == KHR_DF_KHR_DESCRIPTORTYPE_ADDITIONAL_PLANES) { + // TODO Tools P5: Implement DFD print for ADDITIONAL_PLANES + } else { + printf("Unknown block\n"); + } + + const uint32_t advance = sizeof_DFDBHeader > blockSize ? sizeof_DFDBHeader : blockSize; + if (advance > remainingSize) + break; + remainingSize -= advance; + block += advance / 4; + } +#undef PRINT_ENUM +} + +/** + * @~English + * @brief Print a JSON interpretation of a data format descriptor. + * + * @param DFD Pointer to a data format descriptor. + * @param dataSize The maximum size that can be considered as part of the DFD. + * @param base_indent The number of indentations to include at the front of every line + * @param indent_width The number of spaces to add with each nested scope + * @param minified Specifies whether the JSON output should be minified **/ -void printDFD(uint32_t *DFD) +void printDFDJSON(uint32_t* DFD, uint32_t dataSize, uint32_t base_indent, uint32_t indent_width, bool minified) { - uint32_t *BDB = DFD+1; - int samples = (KHR_DFDVAL(BDB, DESCRIPTORBLOCKSIZE) - 4 * KHR_DF_WORD_SAMPLESTART) / (4 * KHR_DF_WORD_SAMPLEWORDS); - int sample; - int model = KHR_DFDVAL(BDB, MODEL); - printf("DFD total bytes: %d\n", DFD[0]); - printf("BDB descriptor type 0x%04x vendor id = 0x%05x\n", - KHR_DFDVAL(BDB, DESCRIPTORTYPE), - KHR_DFDVAL(BDB, VENDORID)); - printf("Descriptor block size %d (%d samples) versionNumber = 0x%04x\n", - KHR_DFDVAL(BDB, DESCRIPTORBLOCKSIZE), - samples, - KHR_DFDVAL(BDB, VERSIONNUMBER)); - printf("Flags 0x%02x Xfer %02d Primaries %02d Model %03d\n", - KHR_DFDVAL(BDB, FLAGS), - KHR_DFDVAL(BDB, TRANSFER), - KHR_DFDVAL(BDB, PRIMARIES), - model); - printf("Dimensions: %d,%d,%d,%d\n", - KHR_DFDVAL(BDB, TEXELBLOCKDIMENSION0) + 1, - KHR_DFDVAL(BDB, TEXELBLOCKDIMENSION1) + 1, - KHR_DFDVAL(BDB, TEXELBLOCKDIMENSION2) + 1, - KHR_DFDVAL(BDB, TEXELBLOCKDIMENSION3) + 1); - printf("Plane bytes: %d,%d,%d,%d,%d,%d,%d,%d\n", - KHR_DFDVAL(BDB, BYTESPLANE0), - KHR_DFDVAL(BDB, BYTESPLANE1), - KHR_DFDVAL(BDB, BYTESPLANE2), - KHR_DFDVAL(BDB, BYTESPLANE3), - KHR_DFDVAL(BDB, BYTESPLANE4), - KHR_DFDVAL(BDB, BYTESPLANE5), - KHR_DFDVAL(BDB, BYTESPLANE6), - KHR_DFDVAL(BDB, BYTESPLANE7)); - for (sample = 0; sample < samples; ++sample) { - int channelId = KHR_DFDSVAL(BDB, sample, CHANNELID); - printf(" Sample %d\n", sample); - printf("Qualifiers %x", KHR_DFDSVAL(BDB, sample, QUALIFIERS) >> 4); - printf(" Channel 0x%x", channelId); - if (model == KHR_DF_MODEL_UASTC) { - printf(" (%s)", - channelId == KHR_DF_CHANNEL_UASTC_RRRG ? "RRRG" - : channelId == KHR_DF_CHANNEL_UASTC_RGBA ? "RGBA" - : channelId == KHR_DF_CHANNEL_UASTC_RRR ? "RRR" - : channelId == KHR_DF_CHANNEL_UASTC_RGB ? "RGB" - : channelId == KHR_DF_CHANNEL_UASTC_RG ? "RG" - : "unknown"); - } else if (model == KHR_DF_MODEL_ETC1S) { - printf(" (%s)", - channelId == KHR_DF_CHANNEL_ETC1S_AAA ? "AAA" - : channelId == KHR_DF_CHANNEL_ETC1S_GGG ? "GGG" - : channelId == KHR_DF_CHANNEL_ETC1S_RRR ? "RRR" - : channelId == KHR_DF_CHANNEL_ETC1S_RGB ? "RGB" - : "unknown"); + if (minified) { + base_indent = 0; + indent_width = 0; + } + const char* space = minified ? "" : " "; + const char* nl = minified ? "" : "\n"; + +#define LENGTH_OF_INDENT(INDENT) ((base_indent + INDENT) * indent_width) + +/** Prints an enum as string or number */ +#define PRINT_ENUM(INDENT, NAME, VALUE, TO_STRING_FN, COMMA) { \ + int value = VALUE; \ + printf("%*s\"" NAME "\":%s", LENGTH_OF_INDENT(INDENT), "", space); \ + const char* str = TO_STRING_FN(value); \ + if (str) \ + printf("\"%s\"", str); \ + else \ + printf("%u", value); \ + printf(COMMA "%s", nl); \ + } + +/** Prints an enum as string or number if the to string function fails with a trailing comma*/ +#define PRINT_ENUM_C(INDENT, NAME, VALUE, TO_STRING_FN) \ + PRINT_ENUM(INDENT, NAME, VALUE, TO_STRING_FN, ",") + +/** Prints an enum as string or number if the to string function fails without a trailing comma*/ +#define PRINT_ENUM_E(INDENT, NAME, VALUE, TO_STRING_FN) \ + PRINT_ENUM(INDENT, NAME, VALUE, TO_STRING_FN, "") + +#define PRINT_INDENT(INDENT, FMT, ...) { \ + printf("%*s" FMT, LENGTH_OF_INDENT(INDENT), "", __VA_ARGS__); \ + } + +#define PRINT_INDENT_NOARG(INDENT, FMT) { \ + printf("%*s" FMT, LENGTH_OF_INDENT(INDENT), ""); \ + } + + const uint32_t sizeof_dfdTotalSize = sizeof(uint32_t); + const uint32_t sizeof_DFDBHeader = sizeof(uint32_t) * 2; + const uint32_t sizeof_BDFD = sizeof(uint32_t) * KHR_DF_WORD_SAMPLESTART; + const uint32_t sizeof_BDFDSample = sizeof(uint32_t) * KHR_DF_WORD_SAMPLEWORDS; + + uint32_t dfdTotalSize = dataSize >= sizeof_dfdTotalSize ? DFD[0] : 0; + PRINT_INDENT(0, "\"totalSize\":%s%u,%s", space, dfdTotalSize, nl) + + uint32_t remainingSize = dfdTotalSize < dataSize ? dfdTotalSize : dataSize; + if (remainingSize < sizeof_dfdTotalSize) { + PRINT_INDENT(0, "\"blocks\":%s[]%s", space, nl) // Print empty blocks to confirm to the json scheme + return; // Invalid DFD: dfdTotalSize must be present + } + + uint32_t* block = DFD + 1; + remainingSize -= sizeof_dfdTotalSize; + PRINT_INDENT(0, "\"blocks\":%s[", space) + + for (int i = 0; i < MAX_NUM_DFD_BLOCKS; ++i) { // At most only iterate MAX_NUM_DFD_BLOCKS block + if (remainingSize < sizeof_DFDBHeader) + break; // Invalid DFD: Missing or partial block header + + const khr_df_vendorid_e vendorID = KHR_DFDVAL(block, VENDORID); + const khr_df_khr_descriptortype_e descriptorType = KHR_DFDVAL(block, DESCRIPTORTYPE); + const khr_df_versionnumber_e versionNumber = KHR_DFDVAL(block, VERSIONNUMBER); + const uint32_t blockSize = KHR_DFDVAL(block, DESCRIPTORBLOCKSIZE); + + const int model = KHR_DFDVAL(block, MODEL); + + if (i == 0) { + printf("%s", nl); } else { - printf(" (%c)", - "RGB3456789abcdeA"[channelId]); - } - printf(" Length %d bits Offset %d\n", - KHR_DFDSVAL(BDB, sample, BITLENGTH) + 1, - KHR_DFDSVAL(BDB, sample, BITOFFSET)); - printf("Position: %d,%d,%d,%d\n", - KHR_DFDSVAL(BDB, sample, SAMPLEPOSITION0), - KHR_DFDSVAL(BDB, sample, SAMPLEPOSITION1), - KHR_DFDSVAL(BDB, sample, SAMPLEPOSITION2), - KHR_DFDSVAL(BDB, sample, SAMPLEPOSITION3)); - printf("Lower 0x%08x\nUpper 0x%08x\n", - KHR_DFDSVAL(BDB, sample, SAMPLELOWER), - KHR_DFDSVAL(BDB, sample, SAMPLEUPPER)); + printf(",%s", nl); + } + PRINT_INDENT(1, "{%s", nl) + PRINT_ENUM_C(2, "vendorId", vendorID, dfdToStringVendorID); + PRINT_ENUM_C(2, "descriptorType", descriptorType, dfdToStringDescriptorType); + PRINT_ENUM_C(2, "versionNumber", versionNumber, dfdToStringVersionNumber); + PRINT_INDENT(2, "\"descriptorBlockSize\":%s%u", space, blockSize) + + if (vendorID == KHR_DF_VENDORID_KHRONOS && descriptorType == KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT) { + if (remainingSize < sizeof_BDFD) { + // Invalid DFD: Missing or partial basic DFD block + printf("%s", nl); + PRINT_INDENT(1, "}%s", nl) // End of block + break; + } + + printf(",%s", nl); + PRINT_INDENT(2, "\"flags\":%s[%s", space, nl) + khr_df_flags_e flags = KHR_DFDVAL(block, FLAGS); + printFlagBitsJSON(LENGTH_OF_INDENT(3), nl, flags, dfdToStringFlagsBit); + PRINT_INDENT(2, "],%s", nl) + + PRINT_ENUM_C(2, "transferFunction", KHR_DFDVAL(block, TRANSFER), dfdToStringTransferFunction); + PRINT_ENUM_C(2, "colorPrimaries", KHR_DFDVAL(block, PRIMARIES), dfdToStringColorPrimaries); + PRINT_ENUM_C(2, "colorModel", model, dfdToStringColorModel); + PRINT_INDENT(2, "\"texelBlockDimension\":%s[%u,%s%u,%s%u,%s%u],%s", space, + KHR_DFDVAL(block, TEXELBLOCKDIMENSION0) + 1, space, + KHR_DFDVAL(block, TEXELBLOCKDIMENSION1) + 1, space, + KHR_DFDVAL(block, TEXELBLOCKDIMENSION2) + 1, space, + KHR_DFDVAL(block, TEXELBLOCKDIMENSION3) + 1, nl) + PRINT_INDENT(2, "\"bytesPlane\":%s[%u,%s%u,%s%u,%s%u,%s%u,%s%u,%s%u,%s%u],%s", space, + KHR_DFDVAL(block, BYTESPLANE0), space, + KHR_DFDVAL(block, BYTESPLANE1), space, + KHR_DFDVAL(block, BYTESPLANE2), space, + KHR_DFDVAL(block, BYTESPLANE3), space, + KHR_DFDVAL(block, BYTESPLANE4), space, + KHR_DFDVAL(block, BYTESPLANE5), space, + KHR_DFDVAL(block, BYTESPLANE6), space, + KHR_DFDVAL(block, BYTESPLANE7), nl) + + PRINT_INDENT(2, "\"samples\":%s[%s", space, nl) + int samples = (blockSize - sizeof_BDFD) / sizeof_BDFDSample; + if (samples > MAX_NUM_BDFD_SAMPLES) + samples = MAX_NUM_BDFD_SAMPLES; + for (int sample = 0; sample < samples; ++sample) { + if (remainingSize < sizeof_BDFD + (sample + 1) * sizeof_BDFDSample) + break; // Invalid DFD: Missing or partial basic DFD sample + + if (sample != 0) + printf(",%s", nl); + PRINT_INDENT(3, "{%s", nl) + + khr_df_sample_datatype_qualifiers_e qualifiers = KHR_DFDSVAL(block, sample, QUALIFIERS); + if (qualifiers == 0) { + PRINT_INDENT(4, "\"qualifiers\":%s[],%s", space, nl) + + } else { + PRINT_INDENT(4, "\"qualifiers\":%s[%s", space, nl) + printFlagBitsJSON(LENGTH_OF_INDENT(5), nl, qualifiers, dfdToStringSampleDatatypeQualifiers); + PRINT_INDENT(4, "],%s", nl) + } + + khr_df_model_channels_e channelType = KHR_DFDSVAL(block, sample, CHANNELID); + const char* channelStr = dfdToStringChannelId(model, channelType); + if (channelStr) + PRINT_INDENT(4, "\"channelType\":%s\"%s\",%s", space, channelStr, nl) + else + PRINT_INDENT(4, "\"channelType\":%s%u,%s", space, channelType, nl) + + PRINT_INDENT(4, "\"bitLength\":%s%u,%s", space, KHR_DFDSVAL(block, sample, BITLENGTH), nl) + PRINT_INDENT(4, "\"bitOffset\":%s%u,%s", space, KHR_DFDSVAL(block, sample, BITOFFSET), nl) + PRINT_INDENT(4, "\"samplePosition\":%s[%u,%s%u,%s%u,%s%u],%s", space, + KHR_DFDSVAL(block, sample, SAMPLEPOSITION0), space, + KHR_DFDSVAL(block, sample, SAMPLEPOSITION1), space, + KHR_DFDSVAL(block, sample, SAMPLEPOSITION2), space, + KHR_DFDSVAL(block, sample, SAMPLEPOSITION3), nl) + + if (qualifiers & KHR_DF_SAMPLE_DATATYPE_SIGNED) { + PRINT_INDENT(4, "\"sampleLower\":%s%d,%s", space, KHR_DFDSVAL(block, sample, SAMPLELOWER), nl) + PRINT_INDENT(4, "\"sampleUpper\":%s%d%s", space, KHR_DFDSVAL(block, sample, SAMPLEUPPER), nl) + } else { + PRINT_INDENT(4, "\"sampleLower\":%s%u,%s", space, (unsigned int) KHR_DFDSVAL(block, sample, SAMPLELOWER), nl) + PRINT_INDENT(4, "\"sampleUpper\":%s%u%s", space, (unsigned int) KHR_DFDSVAL(block, sample, SAMPLEUPPER), nl) + } + + PRINT_INDENT_NOARG(3, "}") + } + printf("%s", nl); + PRINT_INDENT(2, "]%s", nl) // End of samples + } else if (vendorID == KHR_DF_VENDORID_KHRONOS && descriptorType == KHR_DF_KHR_DESCRIPTORTYPE_ADDITIONAL_DIMENSIONS) { + printf("%s", nl); + // printf(",%s", nl); // If there is extra member printed + // TODO Tools P5: Implement DFD print for ADDITIONAL_DIMENSIONS + } else if (vendorID == KHR_DF_VENDORID_KHRONOS && descriptorType == KHR_DF_KHR_DESCRIPTORTYPE_ADDITIONAL_PLANES) { + printf("%s", nl); + // printf(",%s", nl); // If there is extra member printed + // TODO Tools P5: Implement DFD print for ADDITIONAL_PLANES + } else { + printf("%s", nl); + // printf(",%s", nl); // If there is extra member printed + // TODO Tools P5: What to do with unknown blocks for json? + // Unknown block data in binary? + } + + PRINT_INDENT_NOARG(1, "}") // End of block + + const uint32_t advance = sizeof_DFDBHeader > blockSize ? sizeof_DFDBHeader : blockSize; + if (advance > remainingSize) + break; + remainingSize -= advance; + block += advance / 4; } + printf("%s", nl); + PRINT_INDENT(0, "]%s", nl) // End of blocks + +#undef PRINT_ENUM +#undef PRINT_ENUM_C +#undef PRINT_ENUM_E +#undef PRINT_INDENT +#undef PRINT_INDENT_NOARG } diff --git a/lib/filestream.c b/lib/filestream.c index b1e0eba7c6..31f964b9fc 100644 --- a/lib/filestream.c +++ b/lib/filestream.c @@ -307,15 +307,17 @@ KTX_error_code ktxFileStream_getsize(ktxStream* str, ktx_size_t* size) assert(str->type == eStreamTypeFile); - // Need to flush so that fstat will return the current size. - // Can ignore return value. The only error that can happen is to tell you - // it was a NOP because the file is read only. + // Need to flush so that fstat will return the current size. + // Can ignore return value. The only error that can happen is to tell you + // it was a NOP because the file is read only. #if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(__MINGW64__) && !defined(_UCRT) - // Bug in VS2013 msvcrt. fflush on FILE open for READ changes file offset - // to 4096. - if (str->data.file->_flag & _IOWRT) -#endif + // Bug in VS2013 msvcrt. fflush on FILE open for READ changes file offset + // to 4096. + if (str->data.file->_flag & _IOWRT) + (void)fflush(str->data.file); +#else (void)fflush(str->data.file); +#endif statret = fstat(fileno(str->data.file), &statbuf); if (statret < 0) { switch (errno) { diff --git a/lib/hashlist.c b/lib/hashlist.c index 0ca89fc561..acfe41e5fc 100644 --- a/lib/hashlist.c +++ b/lib/hashlist.c @@ -525,14 +525,38 @@ ktxHashList_Deserialize(ktxHashList* pHead, unsigned int kvdLen, void* pKvd) result = KTX_SUCCESS; while (result == KTX_SUCCESS && src < (char *)pKvd + kvdLen) { + if (src + 6 > (char *)pKvd + kvdLen) { + // Not enough space for another entry + return KTX_FILE_DATA_ERROR; + } + char* key; unsigned int keyLen, valueLen; void* value; ktx_uint32_t keyAndValueByteSize = *((ktx_uint32_t*)src); + if (src + 4 + keyAndValueByteSize > (char *)pKvd + kvdLen) { + // Not enough space for this entry + return KTX_FILE_DATA_ERROR; + } + src += sizeof(keyAndValueByteSize); key = src; - keyLen = (unsigned int)strlen(key) + 1; + keyLen = 0; + + while (keyLen < keyAndValueByteSize && key[keyLen] != '\0') keyLen++; + + if (key[keyLen] != '\0') { + // Missing NULL terminator + return KTX_FILE_DATA_ERROR; + } + + if (keyLen >= 3 && key[0] == '\xEF' && key[1] == '\xBB' && key[2] == '\xBF') { + // Forbidden BOM + return KTX_FILE_DATA_ERROR; + } + + keyLen += 1; value = key + keyLen; valueLen = keyAndValueByteSize - keyLen; diff --git a/lib/info.c b/lib/info.c index d70e0ca1e2..bde958453d 100644 --- a/lib/info.c +++ b/lib/info.c @@ -41,6 +41,87 @@ * Common Utilities for version 1 and 2. * *===========================================================*/ +enum { + // These constraints are not mandated by the spec and only used as a + // reasonable upper limit to stop parsing garbage data during print + MAX_NUM_KVD_ENTRIES = 100, + MAX_NUM_LEVELS = 64, +}; + +/** @internal */ +#define LENGTH_OF_INDENT(INDENT) ((base_indent + INDENT) * indent_width) + +/** @internal */ +#define PRINT_INDENT(INDENT, FMT, ...) { \ + printf("%*s" FMT, LENGTH_OF_INDENT(INDENT), "", __VA_ARGS__); \ + } + +/** @internal */ +#define PRINT_INDENT_NOARG(INDENT, FMT) { \ + printf("%*s" FMT, LENGTH_OF_INDENT(INDENT), ""); \ + } + +/** @internal */ +static void printFlagBitsJSON(uint32_t indent, const char* nl, uint32_t flags, const char*(*toStringFn)(uint32_t, bool)) { + bool first = true; + for (uint32_t bit_index = 0; bit_index < 32; ++bit_index) { + uint32_t bit_mask = 1u << bit_index; + bool bit_value = (bit_mask & (uint32_t) flags) != 0; + + const char* str = toStringFn(bit_index, bit_value); + if (str) { + printf("%s%s%*s\"%s\"", first ? "" : ",", first ? "" : nl, indent, "", str); + first = false; + } else if (bit_value) { + printf("%s%s%*s%u", first ? "" : ",", first ? "" : nl, indent, "", bit_mask); + first = false; + } + } + if (!first) + printf("%s", nl); +} + +/** @internal */ +bool isKnownKeyValueUINT32(const char* key) { + if (strcmp(key, "KTXdxgiFormat__") == 0) + return true; + if (strcmp(key, "KTXmetalPixelFormat") == 0) + return true; + + return false; +} + +/** @internal */ +bool isKnownKeyValueString(const char* key) { + if (strcmp(key, "KTXorientation") == 0) + return true; + if (strcmp(key, "KTXswizzle") == 0) + return true; + if (strcmp(key, "KTXwriter") == 0) + return true; + if (strcmp(key, "KTXwriterScParams") == 0) + return true; + if (strcmp(key, "KTXastcDecodeMode") == 0) + return true; + + return false; +} + +/** @internal */ +bool isKnownKeyValue(const char* key) { + if (isKnownKeyValueUINT32(key)) + return true; + if (isKnownKeyValueString(key)) + return true; + if (strcmp(key, "KTXglFormat") == 0) + return true; + if (strcmp(key, "KTXanimData") == 0) + return true; + if (strcmp(key, "KTXcubemapIncomplete") == 0) + return true; + return false; +} + /** * @internal * @~English @@ -57,84 +138,263 @@ printKVData(ktx_uint8_t* pKvd, ktx_uint32_t kvdLen) assert(pKvd != NULL && kvdLen > 0); - result = ktxHashList_Deserialize(&kvDataHead, - kvdLen, pKvd); - if (result == KTX_SUCCESS) { - if (kvDataHead == NULL) { - fprintf(stdout, "None\n"); + result = ktxHashList_Deserialize(&kvDataHead, kvdLen, pKvd); + if (result != KTX_SUCCESS) { + fprintf(stdout, "Failed to parse or not enough memory to build list of key/value pairs.\n"); + return; + } + + if (kvDataHead == NULL) + return; + + int entryIndex = 0; + ktxHashListEntry* entry = kvDataHead; + for (; entry != NULL && entryIndex < MAX_NUM_KVD_ENTRIES; entry = ktxHashList_Next(entry), ++entryIndex) { + char* key; + char* value; + ktx_uint32_t keyLen, valueLen; + + ktxHashListEntry_GetKey(entry, &keyLen, &key); + ktxHashListEntry_GetValue(entry, &valueLen, (void**)&value); + // Keys must be NUL terminated. + fprintf(stdout, "%s:", key); + if (!value) { + fprintf(stdout, " null\n"); + } else { - ktxHashListEntry* entry; - for (entry = kvDataHead; entry != NULL; entry = ktxHashList_Next(entry)) { - char* key; - char* value; // XXX May be a binary value. How to tell? - ktx_uint32_t keyLen, valueLen; - - ktxHashListEntry_GetKey(entry, &keyLen, &key); - ktxHashListEntry_GetValue(entry, &valueLen, (void**)&value); - // Keys must be NUL terminated. - fprintf(stdout, "%s: ", key); - // XXX How to tell if a value is binary and how to output it? - // valueLen includes the terminating NUL, if any. - if (value) { - if (value[valueLen-1] == '\0') { - fprintf(stdout, "%s", value); - } else { - for (ktx_uint32_t i=0; i < valueLen; i++) { - fputc(value[i], stdout); - } - } + if (strcmp(key, "KTXglFormat") == 0) { + if (valueLen == 3 * sizeof(ktx_uint32_t)) { + ktx_uint32_t glInternalformat = *(const ktx_uint32_t*) (value + 0); + ktx_uint32_t glFormat = *(const ktx_uint32_t*) (value + 4); + ktx_uint32_t glType = *(const ktx_uint32_t*) (value + 8); + fprintf(stdout, "\n"); + fprintf(stdout, " glInternalformat: 0x%08X\n", glInternalformat); + fprintf(stdout, " glFormat: 0x%08X\n", glFormat); + fprintf(stdout, " glType: 0x%08X\n", glType); + } + + } else if (strcmp(key, "KTXanimData") == 0) { + if (valueLen == 3 * sizeof(ktx_uint32_t)) { + ktx_uint32_t duration = *(const ktx_uint32_t*) (value + 0); + ktx_uint32_t timescale = *(const ktx_uint32_t*) (value + 4); + ktx_uint32_t loopCount = *(const ktx_uint32_t*) (value + 8); + fprintf(stdout, "\n"); + fprintf(stdout, " duration: %u\n", duration); + fprintf(stdout, " timescale: %u\n", timescale); + fprintf(stdout, " loopCount: %u%s\n", loopCount, loopCount == 0 ? " (infinite)" : ""); + } + + } else if (strcmp(key, "KTXcubemapIncomplete") == 0) { + if (valueLen == sizeof(ktx_uint8_t)) { + ktx_uint8_t faces = *value; + fprintf(stdout, "\n"); + fprintf(stdout, " positiveX: %s\n", faces & 1u << 0u ? "true" : "false"); + fprintf(stdout, " negativeX: %s\n", faces & 1u << 1u ? "true" : "false"); + fprintf(stdout, " positiveY: %s\n", faces & 1u << 2u ? "true" : "false"); + fprintf(stdout, " negativeY: %s\n", faces & 1u << 3u ? "true" : "false"); + fprintf(stdout, " positiveZ: %s\n", faces & 1u << 4u ? "true" : "false"); + fprintf(stdout, " negativeZ: %s\n", faces & 1u << 5u ? "true" : "false"); + } + + } else if (isKnownKeyValueUINT32(key)) { + if (valueLen == sizeof(ktx_uint32_t)) { + ktx_uint32_t number = *(const ktx_uint32_t*) value; + fprintf(stdout, " %u\n", number); + } + + } else if (isKnownKeyValueString(key)) { + if (value[valueLen-1] == '\0') { + fprintf(stdout, " %s\n", value); } - fputc('\n', stdout); + + } else { + fprintf(stdout, " ["); + for (ktx_uint32_t i = 0; i < valueLen; i++) + fprintf(stdout, "%d%s", (int) value[i], i + 1 == valueLen ? "" : ", "); + fprintf(stdout, "]\n"); } } - ktxHashList_Destruct(&kvDataHead); - } else { - fprintf(stdout, - "Not enough memory to build list of key/value pairs.\n"); } + + ktxHashList_Destruct(&kvDataHead); } +/** + * @internal + * @~English + * @brief Prints a list of the keys & values found in a KTX2 file. + * + * @param [in] pKvd pointer to serialized key/value data. + * @param [in] kvdLen length of the serialized key/value data. + * @param [in] base_indent The number of indentations to include at the front of every line + * @param [in] indent_width The number of spaces to add with each nested scope + * @param [in] minified Specifies whether the JSON output should be minified + */ +void +printKVDataJSON(ktx_uint8_t* pKvd, ktx_uint32_t kvdLen, ktx_uint32_t base_indent, ktx_uint32_t indent_width, bool minified) +{ + const char* space = minified ? "" : " "; + const char* nl = minified ? "" : "\n"; + + KTX_error_code result; + ktxHashList kvDataHead = 0; + + assert(pKvd != NULL && kvdLen > 0); + + result = ktxHashList_Deserialize(&kvDataHead, kvdLen, pKvd); + if (result != KTX_SUCCESS) { + // Logging while printing JSON is not possible, we rely on the validation step to provide meaningful errors + // fprintf(stdout, "Failed to parse or not enough memory to build list of key/value pairs.\n"); + return; + } + + if (kvDataHead == NULL) + return; + + int entryIndex = 0; + ktxHashListEntry* entry = kvDataHead; + bool firstPrint = true; // Marks if the first print did not occur yet (first print != first entry) + for (; entry != NULL && entryIndex < MAX_NUM_KVD_ENTRIES; entry = ktxHashList_Next(entry), ++entryIndex) { + char* key; + char* value; + ktx_uint32_t keyLen, valueLen; + + ktxHashListEntry_GetKey(entry, &keyLen, &key); + ktxHashListEntry_GetValue(entry, &valueLen, (void**)&value); + // Keys must be NUL terminated. + if (!value) { + if (!isKnownKeyValue(key)) { + // Known keys are not be printed with null + if (!firstPrint) + fprintf(stdout, ",%s", nl); + firstPrint = false; + PRINT_INDENT(0, "\"%s\":%snull", key, space) + } + } else { + if (strcmp(key, "KTXglFormat") == 0) { + if (valueLen == 3 * sizeof(ktx_uint32_t)) { + if (!firstPrint) + fprintf(stdout, ",%s", nl); + firstPrint = false; + ktx_uint32_t glInternalformat = *(const ktx_uint32_t*) (value + 0); + ktx_uint32_t glFormat = *(const ktx_uint32_t*) (value + 4); + ktx_uint32_t glType = *(const ktx_uint32_t*) (value + 8); + PRINT_INDENT(0, "\"%s\":%s{%s", key, space, nl) + PRINT_INDENT(1, "\"glInternalformat\":%s%u,%s", space, glInternalformat, nl) + PRINT_INDENT(1, "\"glFormat\":%s%u,%s", space, glFormat, nl) + PRINT_INDENT(1, "\"glType\":%s%u%s", space, glType, nl) + PRINT_INDENT_NOARG(0, "}") + } + } else if (strcmp(key, "KTXanimData") == 0) { + if (valueLen == 3 * sizeof(ktx_uint32_t)) { + if (!firstPrint) + fprintf(stdout, ",%s", nl); + firstPrint = false; + ktx_uint32_t duration = *(const ktx_uint32_t*) (value + 0); + ktx_uint32_t timescale = *(const ktx_uint32_t*) (value + 4); + ktx_uint32_t loopCount = *(const ktx_uint32_t*) (value + 8); + PRINT_INDENT(0, "\"%s\":%s{%s", key, space, nl) + PRINT_INDENT(1, "\"duration\":%s%u,%s", space, duration, nl) + PRINT_INDENT(1, "\"timescale\":%s%u,%s", space, timescale, nl) + PRINT_INDENT(1, "\"loopCount\":%s%u%s", space, loopCount, nl) + PRINT_INDENT_NOARG(0, "}") + } + } else if (strcmp(key, "KTXcubemapIncomplete") == 0) { + if (valueLen == sizeof(ktx_uint8_t)) { + if (!firstPrint) + fprintf(stdout, ",%s", nl); + firstPrint = false; + ktx_uint8_t faces = *value; + PRINT_INDENT(0, "\"%s\":%s{%s", key, space, nl) + PRINT_INDENT(1, "\"positiveX\":%s%s,%s", space, faces & 1u << 0u ? "true" : "false", nl) + PRINT_INDENT(1, "\"negativeX\":%s%s,%s", space, faces & 1u << 1u ? "true" : "false", nl) + PRINT_INDENT(1, "\"positiveY\":%s%s,%s", space, faces & 1u << 2u ? "true" : "false", nl) + PRINT_INDENT(1, "\"negativeY\":%s%s,%s", space, faces & 1u << 3u ? "true" : "false", nl) + PRINT_INDENT(1, "\"positiveZ\":%s%s,%s", space, faces & 1u << 4u ? "true" : "false", nl) + PRINT_INDENT(1, "\"negativeZ\":%s%s%s", space, faces & 1u << 5u ? "true" : "false", nl) + PRINT_INDENT_NOARG(0, "}") + } + } else if (isKnownKeyValueUINT32(key)) { + if (valueLen == sizeof(ktx_uint32_t)) { + if (!firstPrint) + fprintf(stdout, ",%s", nl); + firstPrint = false; + ktx_uint32_t number = *(const ktx_uint32_t*) value; + PRINT_INDENT(0, "\"%s\":%s%u", key, space, number) + } + } else if (isKnownKeyValueString(key)) { + if (value[valueLen-1] == '\0') { + if (!firstPrint) + fprintf(stdout, ",%s", nl); + firstPrint = false; + PRINT_INDENT(0, "\"%s\":%s\"%s\"", key, space, value) + } + } else { + if (!firstPrint) + fprintf(stdout, ",%s", nl); + firstPrint = false; + PRINT_INDENT(0, "\"%s\":%s[", key, space) + for (ktx_uint32_t i = 0; i < valueLen; i++) + fprintf(stdout, "%d%s", (int) value[i], i + 1 == valueLen ? "" : ", "); + fprintf(stdout, "]"); + } + } + } + fprintf(stdout, "%s", nl); + + ktxHashList_Destruct(&kvDataHead); +} + +/** + * @internal + * @~English + * @brief Print the KTX 1/2 file identifier. + * + * @param [in] identifier + * @param [in] json specifies if "\x1A" should be escaped as "\u001A" to not break most json tools + */ void -printIdentifier(const ktx_uint8_t identifier[12]) +printIdentifier(const ktx_uint8_t identifier[12], bool json) { // Convert identifier for better display. uint32_t idlen = 0; - char u8identifier[30]; + char u8identifier[30]; for (uint32_t i = 0; i < 12 && idlen < sizeof(u8identifier); i++, idlen++) { // Convert the angle brackets to utf-8 for better printing. The // conversion below only works for characters whose msb's are 10. if (identifier[i] == U'\xAB') { - u8identifier[idlen++] = '\xc2'; - u8identifier[idlen] = identifier[i]; + u8identifier[idlen++] = '\xc2'; + u8identifier[idlen] = identifier[i]; } else if (identifier[i] == U'\xBB') { - u8identifier[idlen++] = '\xc2'; - u8identifier[idlen] = identifier[i]; - } else if (identifier[i] < '\x20') { - uint32_t nchars; - switch (identifier[i]) { - case '\n': - u8identifier[idlen++] = '\\'; - u8identifier[idlen] = 'n'; - break; - case '\r': - u8identifier[idlen++] = '\\'; - u8identifier[idlen] = 'r'; - break; - default: - nchars = snprintf(&u8identifier[idlen], + u8identifier[idlen++] = '\xc2'; + u8identifier[idlen] = identifier[i]; + } else if (identifier[i] < '\x20') { + uint32_t nchars; + switch (identifier[i]) { + case '\n': + u8identifier[idlen++] = '\\'; + u8identifier[idlen] = 'n'; + break; + case '\r': + u8identifier[idlen++] = '\\'; + u8identifier[idlen] = 'r'; + break; + default: + nchars = snprintf(&u8identifier[idlen], sizeof(u8identifier) - idlen, - "\\x%02X", identifier[i]); - idlen += nchars - 1; - } - } else { - u8identifier[idlen] = identifier[i]; + json ? "\\u%04X" : "\\x%02X", + identifier[i]); + idlen += nchars - 1; + } + } else { + u8identifier[idlen] = identifier[i]; } } #if defined(_WIN32) - if (_isatty(_fileno(stdout))) - SetConsoleOutputCP(CP_UTF8); + if (_isatty(_fileno(stdout))) + SetConsoleOutputCP(CP_UTF8); #endif - fprintf(stdout, "identifier: %.*s\n", idlen, u8identifier); + fprintf(stdout, "%.*s", idlen, u8identifier); } /*===========================================================* @@ -151,22 +411,24 @@ printIdentifier(const ktx_uint8_t identifier[12]) void printKTXHeader(KTX_header* pHeader) { - printIdentifier(pHeader->identifier); + fprintf(stdout, "identifier: "); + printIdentifier(pHeader->identifier, false); + fprintf(stdout, "\n"); fprintf(stdout, "endianness: %#x\n", pHeader->endianness); fprintf(stdout, "glType: %#x\n", pHeader->glType); - fprintf(stdout, "glTypeSize: %d\n", pHeader->glTypeSize); + fprintf(stdout, "glTypeSize: %u\n", pHeader->glTypeSize); fprintf(stdout, "glFormat: %#x\n", pHeader->glFormat); fprintf(stdout, "glInternalformat: %#x\n", pHeader->glInternalformat); fprintf(stdout, "glBaseInternalformat: %#x\n", pHeader->glBaseInternalformat); - fprintf(stdout, "pixelWidth: %d\n", pHeader->pixelWidth); - fprintf(stdout, "pixelHeight: %d\n", pHeader->pixelHeight); - fprintf(stdout, "pixelDepth: %d\n", pHeader->pixelDepth); - fprintf(stdout, "numberOfArrayElements: %d\n", + fprintf(stdout, "pixelWidth: %u\n", pHeader->pixelWidth); + fprintf(stdout, "pixelHeight: %u\n", pHeader->pixelHeight); + fprintf(stdout, "pixelDepth: %u\n", pHeader->pixelDepth); + fprintf(stdout, "numberOfArrayElements: %u\n", pHeader->numberOfArrayElements); - fprintf(stdout, "numberOfFaces: %d\n", pHeader->numberOfFaces); - fprintf(stdout, "numberOfMipLevels: %d\n", pHeader->numberOfMipLevels); - fprintf(stdout, "bytesOfKeyValueData: %d\n", pHeader->bytesOfKeyValueData); + fprintf(stdout, "numberOfFaces: %u\n", pHeader->numberOfFaces); + fprintf(stdout, "numberOfMipLevels: %u\n", pHeader->numberOfMipLevels); + fprintf(stdout, "bytesOfKeyValueData: %u\n", pHeader->bytesOfKeyValueData); } /** @@ -203,8 +465,8 @@ printKTXInfo2(ktxStream* stream, KTX_header* pHeader) fprintf(stdout, " it has invalid data such as bad glTypeSize, improper dimensions,\n" "improper number of faces or too many levels.\n"); break; - case KTX_UNSUPPORTED_TEXTURE_TYPE: - fprintf(stdout, " it describes a 3D array that is unsupported\n"); + case KTX_UNSUPPORTED_FEATURE: + fprintf(stdout, " it describes an unsupported feature or format\n"); break; default: ; // _ktxCheckHeader returns only the above 2 errors. @@ -248,9 +510,9 @@ printKTXInfo2(ktxStream* stream, KTX_header* pHeader) } result = stream->skip(stream, lodSize); dataSize += lodSize; - fprintf(stdout, "Level %d: %d\n", level, lodSize); + fprintf(stdout, "Level %u: %u\n", level, lodSize); } - fprintf(stdout, "\nTotal: %"PRIu64"\n", dataSize); + fprintf(stdout, "\nTotal: %" PRId64 "\n", dataSize); } /** @@ -278,7 +540,9 @@ printKTXInfo(ktxStream* stream) extern const char* vkFormatString(VkFormat format); -extern const char * ktxSupercompressionSchemeString(ktxSupercmpScheme scheme); +extern const char* ktxSupercompressionSchemeString(ktxSupercmpScheme scheme); + +const char* ktxBUImageFlagsBitString(ktx_uint32_t bit_index, bool bit_value); /** * @internal @@ -290,24 +554,35 @@ extern const char * ktxSupercompressionSchemeString(ktxSupercmpScheme scheme); void printKTX2Header(KTX_header2* pHeader) { - printIdentifier(pHeader->identifier); - fprintf(stdout, "vkFormat: %s\n", vkFormatString(pHeader->vkFormat)); - fprintf(stdout, "typeSize: %d\n", pHeader->typeSize); - fprintf(stdout, "pixelWidth: %d\n", pHeader->pixelWidth); - fprintf(stdout, "pixelHeight: %d\n", pHeader->pixelHeight); - fprintf(stdout, "pixelDepth: %d\n", pHeader->pixelDepth); - fprintf(stdout, "layerCount: %d\n", + fprintf(stdout, "identifier: "); + printIdentifier(pHeader->identifier, false); + fprintf(stdout, "\n"); + const char* vkFormatStr = vkFormatString(pHeader->vkFormat); + if (strcmp(vkFormatStr, "VK_UNKNOWN_FORMAT") == 0) + fprintf(stdout, "vkFormat: 0x%08X\n", (uint32_t) pHeader->vkFormat); + else + fprintf(stdout, "vkFormat: %s\n", vkFormatStr); + fprintf(stdout, "typeSize: %u\n", pHeader->typeSize); + fprintf(stdout, "pixelWidth: %u\n", pHeader->pixelWidth); + fprintf(stdout, "pixelHeight: %u\n", pHeader->pixelHeight); + fprintf(stdout, "pixelDepth: %u\n", pHeader->pixelDepth); + fprintf(stdout, "layerCount: %u\n", pHeader->layerCount); - fprintf(stdout, "faceCount: %d\n", pHeader->faceCount); - fprintf(stdout, "levelCount: %d\n", pHeader->levelCount); - fprintf(stdout, "supercompressionScheme: %s\n", - ktxSupercompressionSchemeString(pHeader->supercompressionScheme)); + fprintf(stdout, "faceCount: %u\n", pHeader->faceCount); + fprintf(stdout, "levelCount: %u\n", pHeader->levelCount); + const char* scSchemeStr = ktxSupercompressionSchemeString(pHeader->supercompressionScheme); + if (strcmp(scSchemeStr, "Invalid scheme value") == 0) + fprintf(stdout, "supercompressionScheme: Invalid scheme (0x%X)\n", (uint32_t) pHeader->supercompressionScheme); + else if (strcmp(scSchemeStr, "Vendor or reserved scheme") == 0) + fprintf(stdout, "supercompressionScheme: Vendor or reserved scheme (0x%X)\n", (uint32_t) pHeader->supercompressionScheme); + else + fprintf(stdout, "supercompressionScheme: %s\n", scSchemeStr); fprintf(stdout, "dataFormatDescriptor.byteOffset: %#x\n", pHeader->dataFormatDescriptor.byteOffset); - fprintf(stdout, "dataFormatDescriptor.byteLength: %d\n", + fprintf(stdout, "dataFormatDescriptor.byteLength: %u\n", pHeader->dataFormatDescriptor.byteLength); fprintf(stdout, "keyValueData.byteOffset: %#x\n", pHeader->keyValueData.byteOffset); - fprintf(stdout, "keyValueData.byteLength: %d\n", pHeader->keyValueData.byteLength); + fprintf(stdout, "keyValueData.byteLength: %u\n", pHeader->keyValueData.byteLength); fprintf(stdout, "supercompressionGlobalData.byteOffset: %#" PRIx64 "\n", pHeader->supercompressionGlobalData.byteOffset); fprintf(stdout, "supercompressionGlobalData.byteLength: %" PRId64 "\n", @@ -325,12 +600,13 @@ printKTX2Header(KTX_header2* pHeader) void printLevelIndex(ktxLevelIndexEntry levelIndex[], ktx_uint32_t numLevels) { + numLevels = MIN(MAX_NUM_LEVELS, numLevels); // Print at most 64 levels to stop parsing garbage for (ktx_uint32_t level = 0; level < numLevels; level++) { - fprintf(stdout, "Level%d.byteOffset: %#" PRIx64 "\n", level, + fprintf(stdout, "Level%u.byteOffset: %#" PRIx64 "\n", level, levelIndex[level].byteOffset); - fprintf(stdout, "Level%d.byteLength: %" PRId64 "\n", level, + fprintf(stdout, "Level%u.byteLength: %" PRId64 "\n", level, levelIndex[level].byteLength); - fprintf(stdout, "Level%d.uncompressedByteLength: %" PRId64 "\n", level, + fprintf(stdout, "Level%u.uncompressedByteLength: %" PRId64 "\n", level, levelIndex[level].uncompressedByteLength); } } @@ -348,21 +624,25 @@ printBasisSGDInfo(ktx_uint8_t* bgd, ktx_uint64_t byteLength, ktx_uint32_t numImages) { ktxBasisLzGlobalHeader* bgdh = (ktxBasisLzGlobalHeader*)(bgd); - UNUSED(byteLength); + if (byteLength < sizeof(ktxBasisLzGlobalHeader)) + return; - fprintf(stdout, "endpointCount: %d\n", bgdh->endpointCount); - fprintf(stdout, "selectorCount: %d\n", bgdh->selectorCount); - fprintf(stdout, "endpointsByteLength: %d\n", bgdh->endpointsByteLength); - fprintf(stdout, "selectorsByteLength: %d\n", bgdh->selectorsByteLength); - fprintf(stdout, "tablesByteLength: %d\n", bgdh->tablesByteLength); - fprintf(stdout, "extendedByteLength: %d\n", bgdh->extendedByteLength); + fprintf(stdout, "endpointCount: %u\n", bgdh->endpointCount); + fprintf(stdout, "selectorCount: %u\n", bgdh->selectorCount); + fprintf(stdout, "endpointsByteLength: %u\n", bgdh->endpointsByteLength); + fprintf(stdout, "selectorsByteLength: %u\n", bgdh->selectorsByteLength); + fprintf(stdout, "tablesByteLength: %u\n", bgdh->tablesByteLength); + fprintf(stdout, "extendedByteLength: %u\n", bgdh->extendedByteLength); ktxBasisLzEtc1sImageDesc* slices = (ktxBasisLzEtc1sImageDesc*)(bgd + sizeof(ktxBasisLzGlobalHeader)); for (ktx_uint32_t i = 0; i < numImages; i++) { + if (byteLength < (i + 1) * sizeof(ktxBasisLzEtc1sImageDesc) + sizeof(ktxBasisLzGlobalHeader)) + break; + fprintf(stdout, "\nimageFlags: %#x\n", slices[i].imageFlags); - fprintf(stdout, "rgbSliceByteLength: %d\n", slices[i].rgbSliceByteLength); + fprintf(stdout, "rgbSliceByteLength: %u\n", slices[i].rgbSliceByteLength); fprintf(stdout, "rgbSliceByteOffset: %#x\n", slices[i].rgbSliceByteOffset); - fprintf(stdout, "alphaSliceByteLength: %d\n", slices[i].alphaSliceByteLength); + fprintf(stdout, "alphaSliceByteLength: %u\n", slices[i].alphaSliceByteLength); fprintf(stdout, "alphaSliceByteOffset: %#x\n", slices[i].alphaSliceByteOffset); } } @@ -377,14 +657,23 @@ printBasisSGDInfo(ktx_uint8_t* bgd, ktx_uint64_t byteLength, * @param [in] stream pointer to the ktxStream reading the file. * @param [in] pHeader pointer to the header to print. */ -void +KTX_error_code printKTX2Info2(ktxStream* stream, KTX_header2* pHeader) { + const bool hasDFD = + pHeader->dataFormatDescriptor.byteOffset != 0 && + pHeader->dataFormatDescriptor.byteLength != 0; + const bool hasKVD = + pHeader->keyValueData.byteOffset != 0 && + pHeader->keyValueData.byteLength != 0; + const bool hasSGD = + pHeader->supercompressionGlobalData.byteOffset != 0 && + pHeader->supercompressionGlobalData.byteLength != 0; + ktx_uint32_t numLevels; ktxLevelIndexEntry* levelIndex; ktx_uint32_t levelIndexSize; - ktx_uint32_t* DFD; - ktx_uint8_t* metadata; + KTX_error_code ec = KTX_SUCCESS; fprintf(stdout, "Header\n\n"); printKTX2Header(pHeader); @@ -393,32 +682,65 @@ printKTX2Info2(ktxStream* stream, KTX_header2* pHeader) numLevels = MAX(1, pHeader->levelCount); levelIndexSize = sizeof(ktxLevelIndexEntry) * numLevels; levelIndex = (ktxLevelIndexEntry*)malloc(levelIndexSize); - stream->read(stream, levelIndex, levelIndexSize); + if (levelIndex == NULL) + return KTX_OUT_OF_MEMORY; + ec = stream->read(stream, levelIndex, levelIndexSize); + if (ec != KTX_SUCCESS) { + free(levelIndex); + return ec; + } printLevelIndex(levelIndex, numLevels); free(levelIndex); - fprintf(stdout, "\nData Format Descriptor\n\n"); - DFD = (ktx_uint32_t*)malloc(pHeader->dataFormatDescriptor.byteLength); - stream->read(stream, DFD, pHeader->dataFormatDescriptor.byteLength); - printDFD(DFD); - free(DFD); + if (hasDFD) { + fprintf(stdout, "\nData Format Descriptor\n\n"); + ktx_uint32_t* dfd = (ktx_uint32_t*)malloc(pHeader->dataFormatDescriptor.byteLength); + if (dfd == NULL) + return KTX_OUT_OF_MEMORY; + ec = stream->read(stream, dfd, pHeader->dataFormatDescriptor.byteLength); + if (ec != KTX_SUCCESS) { + free(dfd); + return ec; + } + if (*dfd != pHeader->dataFormatDescriptor.byteLength) { + free(dfd); + return KTX_FILE_DATA_ERROR; + } + printDFD(dfd, pHeader->dataFormatDescriptor.byteLength); + free(dfd); + } - if (pHeader->keyValueData.byteLength) { + if (hasKVD) { fprintf(stdout, "\nKey/Value Data\n\n"); - metadata = malloc(pHeader->keyValueData.byteLength); - stream->read(stream, metadata, pHeader->keyValueData.byteLength); - printKVData(metadata, pHeader->keyValueData.byteLength); - free(metadata); + ktx_uint8_t* kvd = malloc(pHeader->keyValueData.byteLength); + if (kvd == NULL) + return KTX_OUT_OF_MEMORY; + ec = stream->read(stream, kvd, pHeader->keyValueData.byteLength); + if (ec != KTX_SUCCESS) { + free(kvd); + return ec; + } + printKVData(kvd, pHeader->keyValueData.byteLength); + free(kvd); } else { fprintf(stdout, "\nNo Key/Value data.\n"); } - if (pHeader->supercompressionGlobalData.byteOffset != 0 - && pHeader->supercompressionGlobalData.byteLength != 0) { + if (hasSGD) { if (pHeader->supercompressionScheme == KTX_SS_BASIS_LZ) { ktx_uint8_t* sgd = malloc(pHeader->supercompressionGlobalData.byteLength); - stream->setpos(stream, pHeader->supercompressionGlobalData.byteOffset); - stream->read(stream, sgd, pHeader->supercompressionGlobalData.byteLength); + if (sgd == NULL) + return KTX_OUT_OF_MEMORY; + ec = stream->setpos(stream, pHeader->supercompressionGlobalData.byteOffset); + if (ec != KTX_SUCCESS) { + free(sgd); + return ec; + } + ec = stream->read(stream, sgd, pHeader->supercompressionGlobalData.byteLength); + if (ec != KTX_SUCCESS) { + free(sgd); + return ec; + } // // Calculate number of images // @@ -431,10 +753,258 @@ printKTX2Info2(ktxStream* stream, KTX_header2* pHeader) uint32_t numImages = layersFaces * layerPixelDepth; fprintf(stdout, "\nBasis Supercompression Global Data\n\n"); printBasisSGDInfo(sgd, pHeader->supercompressionGlobalData.byteLength, numImages); + free(sgd); } else { - fprintf(stdout, "\nUnrecognized supercompressionScheme."); + fprintf(stdout, "\nUnrecognized supercompressionScheme.\n"); + } + } + + return ec; +} + +/** + * @internal + * @~English + * @brief Print information about a KTX 2 file. + * + * The stream's read pointer should be immediately following the header. + * + * @param [in] stream pointer to the ktxStream reading the file. + * @param [in] pHeader pointer to the header to print. + * @param [in] base_indent The number of indentations to include at the front of every line + * @param [in] indent_width The number of spaces to add with each nested scope + * @param [in] minified Specifies whether the JSON output should be minified + */ +KTX_error_code +printKTX2Info2JSON(ktxStream* stream, KTX_header2* pHeader, ktx_uint32_t base_indent, ktx_uint32_t indent_width, bool minified) +{ + if (minified) { + base_indent = 0; + indent_width = 0; + } + const char* space = minified ? "" : " "; + const char* nl = minified ? "" : "\n"; + + const bool hasDFD = + pHeader->dataFormatDescriptor.byteOffset != 0 && + pHeader->dataFormatDescriptor.byteLength != 0; + const bool hasKVD = + pHeader->keyValueData.byteOffset != 0 && + pHeader->keyValueData.byteLength != 0; + const bool hasSGD = + pHeader->supercompressionGlobalData.byteOffset != 0 && + pHeader->supercompressionGlobalData.byteLength != 0; + + ktx_uint32_t numLevels; + ktxLevelIndexEntry* levelIndex; + ktx_uint32_t levelIndexSize; + + KTX_error_code ec = KTX_SUCCESS; + + PRINT_INDENT(0, "\"header\":%s{%s", space, nl) + PRINT_INDENT(1, "\"identifier\":%s\"", space) + printIdentifier(pHeader->identifier, true); + printf("\",%s", nl); + const char* vkFormatStr = vkFormatString(pHeader->vkFormat); + if (strcmp(vkFormatStr, "VK_UNKNOWN_FORMAT") == 0) + PRINT_INDENT(1, "\"vkFormat\":%s%u,%s", space, (uint32_t) pHeader->vkFormat, nl) + else + PRINT_INDENT(1, "\"vkFormat\":%s\"%s\",%s", space, vkFormatStr, nl) + PRINT_INDENT(1, "\"typeSize\":%s%u,%s", space, pHeader->typeSize, nl); + PRINT_INDENT(1, "\"pixelWidth\":%s%u,%s", space, pHeader->pixelWidth, nl); + PRINT_INDENT(1, "\"pixelHeight\":%s%u,%s", space, pHeader->pixelHeight, nl); + PRINT_INDENT(1, "\"pixelDepth\":%s%u,%s", space, pHeader->pixelDepth, nl); + PRINT_INDENT(1, "\"layerCount\":%s%u,%s", space, pHeader->layerCount, nl); + PRINT_INDENT(1, "\"faceCount\":%s%u,%s", space, pHeader->faceCount, nl); + PRINT_INDENT(1, "\"levelCount\":%s%u,%s", space, pHeader->levelCount, nl); + const char* scSchemeStr = ktxSupercompressionSchemeString(pHeader->supercompressionScheme); + if (strcmp(scSchemeStr, "Invalid scheme value") == 0 || strcmp(scSchemeStr, "Vendor or reserved scheme") == 0) + PRINT_INDENT(1, "\"supercompressionScheme\":%s%u%s", space, (uint32_t) pHeader->supercompressionScheme, nl) + else + PRINT_INDENT(1, "\"supercompressionScheme\":%s\"%s\"%s", space, scSchemeStr, nl) + PRINT_INDENT_NOARG(0, "}") + + numLevels = MAX(1, pHeader->levelCount); + levelIndexSize = sizeof(ktxLevelIndexEntry) * numLevels; + levelIndex = (ktxLevelIndexEntry*)malloc(levelIndexSize); + if (levelIndex == NULL) + return KTX_OUT_OF_MEMORY; + ec = stream->read(stream, levelIndex, levelIndexSize); + if (ec != KTX_SUCCESS) { + printf("%s", nl); + free(levelIndex); + return ec; + } + + printf(",%s", nl); + PRINT_INDENT(0, "\"index\":%s{%s", space, nl) + + PRINT_INDENT(1, "\"dataFormatDescriptor\":%s{%s", space, nl) + PRINT_INDENT(2, "\"byteOffset\":%s%u,%s", space, pHeader->dataFormatDescriptor.byteOffset, nl); + PRINT_INDENT(2, "\"byteLength\":%s%u%s", space, pHeader->dataFormatDescriptor.byteLength, nl); + PRINT_INDENT(1, "},%s", nl) + PRINT_INDENT(1, "\"keyValueData\":%s{%s", space, nl) + PRINT_INDENT(2, "\"byteOffset\":%s%u,%s", space, pHeader->keyValueData.byteOffset, nl); + PRINT_INDENT(2, "\"byteLength\":%s%u%s", space, pHeader->keyValueData.byteLength, nl); + PRINT_INDENT(1, "},%s", nl) + PRINT_INDENT(1, "\"supercompressionGlobalData\":%s{%s", space, nl) + PRINT_INDENT(2, "\"byteOffset\":%s%" PRId64 ",%s", space, pHeader->supercompressionGlobalData.byteOffset, nl); + PRINT_INDENT(2, "\"byteLength\":%s%" PRId64 "%s", space, pHeader->supercompressionGlobalData.byteLength, nl); + PRINT_INDENT(1, "},%s", nl) + + PRINT_INDENT(1, "\"levels\":%s[%s", space, nl) + numLevels = MIN(MAX_NUM_LEVELS, numLevels); // Print at most 64 levels to stop parsing garbage + for (ktx_uint32_t level = 0; level < numLevels; level++) { + PRINT_INDENT(2, "{%s", nl); + PRINT_INDENT(3, "\"byteOffset\":%s%" PRId64 ",%s", space, levelIndex[level].byteOffset, nl); + PRINT_INDENT(3, "\"byteLength\":%s%" PRId64 ",%s", space, levelIndex[level].byteLength, nl); + PRINT_INDENT(3, "\"uncompressedByteLength\":%s%" PRId64 "%s", space, levelIndex[level].uncompressedByteLength, nl); + PRINT_INDENT(2, "}%s%s", level + 1 == numLevels ? "" : ",", nl); + } + PRINT_INDENT(1, "]%s", nl) // End of levels + + free(levelIndex); + PRINT_INDENT_NOARG(0, "}") // End of index + + if (hasDFD) { + ktx_uint32_t* dfd = (ktx_uint32_t*)malloc(pHeader->dataFormatDescriptor.byteLength); + if (dfd == NULL) + return KTX_OUT_OF_MEMORY; + ec = stream->read(stream, dfd, pHeader->dataFormatDescriptor.byteLength); + if (ec != KTX_SUCCESS) { + printf("%s", nl); + free(dfd); + return ec; + } + printf(",%s", nl); + PRINT_INDENT(0, "\"dataFormatDescriptor\":%s{%s", space, nl) + printDFDJSON(dfd, pHeader->dataFormatDescriptor.byteLength, base_indent + 1, indent_width, minified); + free(dfd); + PRINT_INDENT_NOARG(0, "}") + } + + if (hasKVD) { + ktx_uint8_t* kvd = malloc(pHeader->keyValueData.byteLength); + if (kvd == NULL) + return KTX_OUT_OF_MEMORY; + ec = stream->read(stream, kvd, pHeader->keyValueData.byteLength); + if (ec != KTX_SUCCESS) { + printf("%s", nl); + free(kvd); + return ec; + } + printf(",%s", nl); + PRINT_INDENT(0, "\"keyValueData\":%s{%s", space, nl) + printKVDataJSON(kvd, pHeader->keyValueData.byteLength, base_indent + 1, indent_width, minified); + free(kvd); + PRINT_INDENT_NOARG(0, "}") + } + + if (hasSGD) { + printf(",%s", nl); + PRINT_INDENT(0, "\"supercompressionGlobalData\":%s{%s", space, nl) + + switch (pHeader->supercompressionScheme) { + case KTX_SS_NONE: { + PRINT_INDENT(1, "\"type\":%s\"%s\"%s", space, "KTX_SS_NONE", nl) + break; + } + case KTX_SS_BASIS_LZ: { + PRINT_INDENT(1, "\"type\":%s\"%s\"", space, "KTX_SS_BASIS_LZ") + ktx_size_t sgdByteLength = pHeader->supercompressionGlobalData.byteLength; + ktx_uint8_t* sgd = malloc(sgdByteLength); + if (sgd == NULL) + return KTX_OUT_OF_MEMORY; + ec = stream->setpos(stream, pHeader->supercompressionGlobalData.byteOffset); + if (ec != KTX_SUCCESS) { + printf("%s", nl); + PRINT_INDENT(0, "}%s", nl) + free(sgd); + return ec; + } + ec = stream->read(stream, sgd, sgdByteLength); + if (ec != KTX_SUCCESS) { + printf("%s", nl); + PRINT_INDENT(0, "}%s", nl) + free(sgd); + return ec; + } + + // Calculate number of images + uint32_t layersFaces = MAX(pHeader->layerCount, 1) * pHeader->faceCount; + uint32_t layerPixelDepth = MAX(pHeader->pixelDepth, 1); + for (uint32_t level = 1; level < MAX(pHeader->levelCount, 1); level++) + layerPixelDepth += MAX(MAX(pHeader->pixelDepth, 1) >> level, 1U); + // NOTA BENE: faceCount * layerPixelDepth is only reasonable because + // faceCount and depth can't both be > 1. I.e there are no 3d cubemaps. + uint32_t numImages = layersFaces * layerPixelDepth; + ktxBasisLzGlobalHeader* bgdh = (ktxBasisLzGlobalHeader*)(sgd); + + if (sgdByteLength < sizeof(ktxBasisLzGlobalHeader)) { + printf("%s", nl); + PRINT_INDENT(0, "}%s", nl) + free(sgd); + return ec; + } + printf(",%s", nl); + PRINT_INDENT(1, "\"endpointCount\":%s%u,%s", space, bgdh->endpointCount, nl) + PRINT_INDENT(1, "\"selectorCount\":%s%u,%s", space, bgdh->selectorCount, nl) + PRINT_INDENT(1, "\"endpointsByteLength\":%s%u,%s", space, bgdh->endpointsByteLength, nl) + PRINT_INDENT(1, "\"selectorsByteLength\":%s%u,%s", space, bgdh->selectorsByteLength, nl) + PRINT_INDENT(1, "\"tablesByteLength\":%s%u,%s", space, bgdh->tablesByteLength, nl) + PRINT_INDENT(1, "\"extendedByteLength\":%s%u,%s", space, bgdh->extendedByteLength, nl) + PRINT_INDENT(1, "\"images\":%s[", space) + + ktxBasisLzEtc1sImageDesc* slices = (ktxBasisLzEtc1sImageDesc*)(sgd + sizeof(ktxBasisLzGlobalHeader)); + for (ktx_uint32_t i = 0; i < numImages; i++) { + if (sgdByteLength < (i + 1) * sizeof(ktxBasisLzEtc1sImageDesc) + sizeof(ktxBasisLzGlobalHeader)) + break; + + if (i == 0) + printf("%s", nl); + else + printf(",%s", nl); + + PRINT_INDENT(2, "{%s", nl) + + buFlags imageFlags = slices[i].imageFlags; + if (imageFlags == 0) { + PRINT_INDENT(3, "\"imageFlags\":%s[],%s", space, nl) + } else { + PRINT_INDENT(3, "\"imageFlags\":%s[%s", space, nl) + printFlagBitsJSON(LENGTH_OF_INDENT(4), nl, imageFlags, ktxBUImageFlagsBitString); + PRINT_INDENT(3, "],%s", nl) + } + + PRINT_INDENT(3, "\"rgbSliceByteLength\":%s%u,%s", space, slices[i].rgbSliceByteLength, nl) + PRINT_INDENT(3, "\"rgbSliceByteOffset\":%s%u,%s", space, slices[i].rgbSliceByteOffset, nl) + PRINT_INDENT(3, "\"alphaSliceByteLength\":%s%u,%s", space, slices[i].alphaSliceByteLength, nl) + PRINT_INDENT(3, "\"alphaSliceByteOffset\":%s%u%s", space, slices[i].alphaSliceByteOffset, nl) + PRINT_INDENT_NOARG(2, "}") + } + printf("%s", nl); + PRINT_INDENT(1, "]%s", nl) + + free(sgd); + break; + } + case KTX_SS_ZSTD: { + PRINT_INDENT(1, "\"type\":%s\"%s\"%s", space, "KTX_SS_ZSTD", nl) + break; + } + case KTX_SS_ZLIB: { + PRINT_INDENT(1, "\"type\":%s\"%s\"%s", space, "KTX_SS_ZLIB", nl) + break; } + default: + PRINT_INDENT(1, "\"type\":%s%u%s", space, pHeader->supercompressionScheme, nl) + break; + } + PRINT_INDENT_NOARG(0, "}") } + printf("%s", nl); + + return ec; } /** @@ -499,15 +1069,87 @@ ktxPrintInfoForStream(ktxStream* stream) KTX_HEADER_SIZE - sizeof(ktx_ident_ref)); printKTXInfo2(stream, &header.ktx); } else { - // Read rest of header. - result = stream->read(stream, &header.ktx2.vkFormat, + // Read rest of header. + result = stream->read(stream, &header.ktx2.vkFormat, KTX2_HEADER_SIZE - sizeof(ktx2_ident_ref)); - printKTX2Info2(stream, &header.ktx2); + if (result != KTX_SUCCESS) + return result; + result = printKTX2Info2(stream, &header.ktx2); } } return result; } +/** + * @internal + * @~English + * @brief Print information about a KTX2 file. + * + * The stream's read pointer should be at the start of the file. + * + * @param [in] stream pointer to the ktxStream reading the file. + */ +KTX_error_code +ktxPrintKTX2InfoJSONForStream(ktxStream* stream, ktx_uint32_t base_indent, ktx_uint32_t indent_width, bool minified) +{ + ktx_uint8_t ktx2_ident_ref[12] = KTX2_IDENTIFIER_REF; + KTX_header2 header; + KTX_error_code result; + + assert(stream != NULL); + + result = stream->read(stream, &header, sizeof(ktx2_ident_ref)); + if (result != KTX_SUCCESS) + return result; + + // Compare identifier, is this a KTX2 file? + if (memcmp(header.identifier, ktx2_ident_ref, 12)) + return KTX_UNKNOWN_FILE_FORMAT; + + // Read rest of header. + result = stream->read(stream, &header.vkFormat, KTX2_HEADER_SIZE - sizeof(ktx2_ident_ref)); + if (result != KTX_SUCCESS) + return result; + + result = printKTX2Info2JSON(stream, &header, base_indent, indent_width, minified); + return result; +} + +/** + * @internal + * @~English + * @brief Print information about a KTX2 file. + * + * The stream's read pointer should be at the start of the file. + * + * @param [in] stream pointer to the ktxStream reading the file. + */ +KTX_error_code +ktxPrintKTX2InfoTextForStream(ktxStream* stream) +{ + ktx_uint8_t ktx2_ident_ref[12] = KTX2_IDENTIFIER_REF; + KTX_header2 header; + KTX_error_code result; + + assert(stream != NULL); + + result = stream->read(stream, &header, sizeof(ktx2_ident_ref)); + if (result != KTX_SUCCESS) + return result; + + // Compare identifier, is this a KTX2 file? + if (memcmp(header.identifier, ktx2_ident_ref, 12)) + return KTX_UNKNOWN_FILE_FORMAT; + + // Read rest of header. + result = stream->read(stream, &header.vkFormat, KTX2_HEADER_SIZE - sizeof(ktx2_ident_ref)); + if (result != KTX_SUCCESS) + return result; + + result = printKTX2Info2(stream, &header); + return result; +} + /** * @~English * @brief Print information about a KTX file on a stdioStream. @@ -568,3 +1210,148 @@ ktxPrintInfoForMemory(const ktx_uint8_t* bytes, ktx_size_t size) result = ktxPrintInfoForStream(&stream); return result; } + +/** + * @~English + * @brief Print information about a KTX2 file on a stdioStream in JSON format. + * + * The stdioStream's read pointer should be at the start of the file. + * + * @param [in] stream pointer to the ktxStream reading the file. + * @param [in] base_indent The number of indentations to include at the front of every line. + * @param [in] indent_width The number of spaces to add with each nested scope. + * @param [in] minified Specifies whether the JSON output should be minified. + */ +KTX_error_code +ktxPrintKTX2InfoJSONForStdioStream(FILE* stdioStream, ktx_uint32_t base_indent, ktx_uint32_t indent_width, bool minified) +{ + KTX_error_code result; + ktxStream stream; + + if (stdioStream == NULL) + return KTX_INVALID_VALUE; + + result = ktxFileStream_construct(&stream, stdioStream, KTX_FALSE); + if (result == KTX_SUCCESS) + result = ktxPrintKTX2InfoJSONForStream(&stream, base_indent, indent_width, minified); + return result; +} + +/** + * @~English + * @brief Print information about a KTX2 file in JSON format. + * + * @param [in] filename Filepath of the KTX2 file. + * @param [in] base_indent The number of indentations to include at the front of every line. + * @param [in] indent_width The number of spaces to add with each nested scope. + * @param [in] minified Specifies whether the JSON output should be minified. + */ +KTX_error_code +ktxPrintKTX2InfoJSONForNamedFile(const char* const filename, ktx_uint32_t base_indent, ktx_uint32_t indent_width, bool minified) +{ + FILE* file = NULL; + +#ifdef _WIN32 + fopen_s(&file, filename, "rb"); +#else + file = fopen(filename, "rb"); +#endif + + if (!file) + return KTX_FILE_OPEN_FAILED; + + KTX_error_code result = ktxPrintKTX2InfoJSONForStdioStream(file, base_indent, indent_width, minified); + + fclose(file); + return result; +} + +/** + * @~English + * @brief Print information about a KTX2 file in memory in JSON format. + * + * @param [in] bytes pointer to the memory holding the KTX file. + * @param [in] size length of the KTX file in bytes. + * @param [in] base_indent The number of indentations to include at the front of every line. + * @param [in] indent_width The number of spaces to add with each nested scope. + * @param [in] minified Specifies whether the JSON output should be minified. + */ +KTX_error_code +ktxPrintKTX2InfoJSONForMemory(const ktx_uint8_t* bytes, ktx_size_t size, ktx_uint32_t base_indent, ktx_uint32_t indent_width, bool minified) +{ + KTX_error_code result; + ktxStream stream; + + result = ktxMemStream_construct_ro(&stream, bytes, size); + if (result == KTX_SUCCESS) + result = ktxPrintKTX2InfoJSONForStream(&stream, base_indent, indent_width, minified); + return result; +} + +/** + * @~English + * @brief Print information about a KTX2 file on a stdioStream in textual format. + * + * The stdioStream's read pointer should be at the start of the file. + * + * @param [in] stream pointer to the ktxStream reading the file. + */ +KTX_error_code +ktxPrintKTX2InfoTextForStdioStream(FILE* stdioStream) +{ + KTX_error_code result; + ktxStream stream; + + if (stdioStream == NULL) + return KTX_INVALID_VALUE; + + result = ktxFileStream_construct(&stream, stdioStream, KTX_FALSE); + if (result == KTX_SUCCESS) + result = ktxPrintKTX2InfoTextForStream(&stream); + return result; +} + +/** + * @~English + * @brief Print information about a KTX2 file in JSON format. + * + * @param [in] filename Filepath of the KTX2 file. + */ +KTX_error_code +ktxPrintKTX2InfoTextForNamedFile(const char* const filename) +{ + FILE* file = NULL; + +#ifdef _WIN32 + fopen_s(&file, filename, "rb"); +#else + file = fopen(filename, "rb"); +#endif + + if (!file) + return KTX_FILE_OPEN_FAILED; + + KTX_error_code result = ktxPrintKTX2InfoTextForStdioStream(file); + + fclose(file); + return result; +} + +/** + * @~English + * @brief Print information about a KTX2 file in memory in textual format. + * + * @param [in] bytes pointer to the memory holding the KTX file. + * @param [in] size length of the KTX file in bytes. + */ +KTX_error_code +ktxPrintKTX2InfoTextForMemory(const ktx_uint8_t* bytes, ktx_size_t size) +{ + KTX_error_code result; + ktxStream stream; + + result = ktxMemStream_construct_ro(&stream, bytes, size); + if (result == KTX_SUCCESS) + result = ktxPrintKTX2InfoTextForStream(&stream); + return result; +} diff --git a/lib/internalexport.def b/lib/internalexport.def index 5b22dff647..aef3aeca45 100644 --- a/lib/internalexport.def +++ b/lib/internalexport.def @@ -23,6 +23,7 @@ EXPORTS createDFDUnpacked createDFDPacked findMapping + getPrimaries interpretDFD isProhibitedFormat isValidFormat diff --git a/lib/internalexport_mingw.def b/lib/internalexport_mingw.def index 1a61a2bc89..eb21740a87 100644 --- a/lib/internalexport_mingw.def +++ b/lib/internalexport_mingw.def @@ -23,6 +23,7 @@ EXPORTS createDFDUnpacked createDFDPacked findMapping + getPrimaries interpretDFD isProhibitedFormat isValidFormat diff --git a/lib/internalexport_write.def b/lib/internalexport_write.def index 48ed522576..3d847a5bb1 100644 --- a/lib/internalexport_write.def +++ b/lib/internalexport_write.def @@ -12,6 +12,16 @@ EXPORTS ??0Resampler@basisu@@QEAA@HHHHW4Boundary_Op@01@MMPEBDPEAUContrib_List@01@2MMMM@Z ?put_line@Resampler@basisu@@QEAA_NPEBM@Z ?get_line@Resampler@basisu@@QEAAPEBMXZ + ?calc@image_metrics@basisu@@QEAAXAEBVimage@2@0II_N1@Z + ?compute_ssim@basisu@@YA?AV?$vec@$03M@1@AEBVimage@1@0_N1@Z + ?increase_capacity@elemental_vector@basisu@@QEAA_NI_NIP6AXPEAX1I@Z0@Z ?swizzle_to_rgba@@YAXPEAE0I_KQEAW4swizzle_e@@@Z appendLibId - + dfdToStringChannelId + dfdToStringColorModel + dfdToStringColorPrimaries + dfdToStringDescriptorType + dfdToStringTransferFunction + dfdToStringVendorID + dfdToStringVersionNumber + ktxTexture2_constructCopy diff --git a/lib/internalexport_write_mingw.def b/lib/internalexport_write_mingw.def index 45c46fa990..7b75fa0b3c 100644 --- a/lib/internalexport_write_mingw.def +++ b/lib/internalexport_write_mingw.def @@ -12,6 +12,16 @@ EXPORTS _ZN6basisu9ResamplerC1EiiiiNS0_11Boundary_OpEffPKcPNS0_12Contrib_ListES5_ffff _ZN6basisu9Resampler8put_lineEPKf _ZN6basisu9Resampler8get_lineEv + _ZN6basisu13image_metrics4calcERKNS_5imageES3_jjbb + _ZN6basisu12compute_ssimERKNS_5imageES2_bb + _ZN6basisu16elemental_vector17increase_capacityEjbjPFvPvS1_jEb _Z15swizzle_to_rgbaPhS_jyP9swizzle_e appendLibId - + dfdToStringChannelId + dfdToStringColorModel + dfdToStringColorPrimaries + dfdToStringDescriptorType + dfdToStringTransferFunction + dfdToStringVendorID + dfdToStringVersionNumber + ktxTexture2_constructCopy diff --git a/lib/ktxint.h b/lib/ktxint.h index e36ad79b5e..7656aa9e48 100644 --- a/lib/ktxint.h +++ b/lib/ktxint.h @@ -29,6 +29,9 @@ #ifndef MAX #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #endif +#ifndef MIN +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#endif #define QUOTE(x) #x #define STR(x) QUOTE(x) @@ -211,6 +214,37 @@ KTX_error_code _ktxUnpackETC(const GLubyte* srcETC, const GLenum srcFormat, GLenum* format, GLenum* internalFormat, GLenum* type, GLint R16Formats, GLboolean supportsSRGB); +/* + * @internal + * ktxCompressZLIBBounds + * + * Returns upper bound for compresses data using miniz (ZLIB) + */ +ktx_size_t ktxCompressZLIBBounds(ktx_size_t srcLength); + +/* + * @internal + * ktxCompressZLIBInt + * + * Compresses data using miniz (ZLIB) + */ +KTX_error_code ktxCompressZLIBInt(unsigned char* pDest, + ktx_size_t* pDestLength, + const unsigned char* pSrc, + ktx_size_t srcLength, + ktx_uint32_t level); + +/* + * @internal + * ktxUncompressZLIBInt + * + * Uncompresses data using miniz (ZLIB) + */ +KTX_error_code ktxUncompressZLIBInt(unsigned char* pDest, + ktx_size_t* pDestLength, + const unsigned char* pSrc, + ktx_size_t srcLength); + /* * Pad nbytes to next multiple of n */ @@ -257,7 +291,7 @@ KTX_error_code _ktxUnpackETC(const GLubyte* srcETC, const GLenum srcFormat, ====================================== */ -void printKTX2Info2(ktxStream* src, KTX_header2* header); +KTX_error_code printKTX2Info2(ktxStream* src, KTX_header2* header); #ifdef __cplusplus } diff --git a/lib/mainpage.md b/lib/mainpage.md index bc832f3aad..b9458c90bc 100644 --- a/lib/mainpage.md +++ b/lib/mainpage.md @@ -10,9 +10,9 @@ Introduction {#mainpage} libktx is a small library of functions for creating and reading KTX (Khronos TeXture) files, version 1 and 2 and instantiating OpenGL® and OpenGL® ES textures and Vulkan images from them. KTX version 2 files can contain images -supercompressed with ZStd. They can also contain images in the Basis Universal -formats. libktx can deflate and inflate ZStd compressed images and can encode -and transcode the Basis Universal formats. +supercompressed with ZStd or ZLIB. They can also contain images in the Basis +Universal formats. libktx can deflate and inflate ZStd and ZLIB compressed +images and can encode and transcode the Basis Universal formats. For information about the KTX format see the diff --git a/lib/miniz_wrapper.cpp b/lib/miniz_wrapper.cpp new file mode 100644 index 0000000000..07920c4809 --- /dev/null +++ b/lib/miniz_wrapper.cpp @@ -0,0 +1,149 @@ +/* -*- tab-width: 4; -*- */ +/* vi: set sw=2 ts=4 expandtab: */ + +/* + * Copyright 2023-2023 The Khronos Group Inc. + * Copyright 2023-2023 RasterGrid Kft. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @internal + * @file miniz_wrapper.c + * @~English + * + * @brief Wrapper functions for ZLIB compression/decompression using miniz. + * + * @author Daniel Rakos, RasterGrid + */ + +#include "ktx.h" +#include "ktxint.h" + +#include + +#if !KTX_FEATURE_WRITE +// The reader does not link with the basisu components that already include a +// definition of miniz so we include it here explicitly. +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wextra" +#pragma GCC diagnostic ignored "-Wmisleading-indentation" +#endif +#include "basisu/encoder/basisu_miniz.h" +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#else +// Otherwise we only declare the interfaces and link with the basisu version. +// This is needed because while miniz is defined as a header in basisu it's +// not declaring the functions as static or inline, hence causing multiple +// conflicting definitions at link-time. +namespace buminiz { + typedef unsigned long mz_ulong; + enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; + mz_ulong mz_compressBound(mz_ulong source_len); + int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +} +#endif + +using namespace buminiz; + +extern "C" { + +/** + * @internal + * @~English + * @brief Returns upper bound for compresses data using miniz (ZLIB). + * + * @param srcLength source data length + * + * @author Daniel Rakos, RasterGrid + */ +ktx_size_t ktxCompressZLIBBounds(ktx_size_t srcLength) { + return mz_compressBound((mz_ulong)srcLength); +} + +/** + * @internal + * @~English + * @brief Compresses data using miniz (ZLIB) + * + * @param pDest destination data buffer + * @param pDestLength destination data buffer size + * (filled with written byte count on success) + * @param pSrc source data buffer + * @param srcLength source data size + * @param level compression level (between 1 and 9) + * + * @author Daniel Rakos, RasterGrid + */ +KTX_error_code ktxCompressZLIBInt(unsigned char* pDest, + ktx_size_t* pDestLength, + const unsigned char* pSrc, + ktx_size_t srcLength, + ktx_uint32_t level) { + if ((srcLength | *pDestLength) > 0xFFFFFFFFU) return KTX_INVALID_VALUE; + mz_ulong mzCompressedSize = (mz_ulong)*pDestLength; + int status = mz_compress2(pDest, &mzCompressedSize, pSrc, (mz_ulong)srcLength, level); + switch (status) { + case MZ_OK: + *pDestLength = mzCompressedSize; + return KTX_SUCCESS; + case MZ_PARAM_ERROR: + return KTX_INVALID_VALUE; + case MZ_BUF_ERROR: +#ifdef DEBUG + assert(false && "Deflate dstSize too small."); +#endif + return KTX_OUT_OF_MEMORY; + case MZ_MEM_ERROR: +#ifdef DEBUG + assert(false && "Deflate workspace too small."); +#endif + return KTX_OUT_OF_MEMORY; + default: + // The remaining errors look like they should only + // occur during decompression but just in case. +#ifdef DEBUG + assert(true); +#endif + return KTX_INVALID_OPERATION; + } +} + +/** + * @internal + * @~English + * @brief Uncompresses data using miniz (ZLIB) + * + * @param pDest destination data buffer + * @param pDestLength destination data buffer size + * (filled with written byte count on success) + * @param pSrc source data buffer + * @param srcLength source data size + * + * @author Daniel Rakos, RasterGrid + */ +KTX_error_code ktxUncompressZLIBInt(unsigned char* pDest, + ktx_size_t* pDestLength, + const unsigned char* pSrc, + ktx_size_t srcLength) { + if ((srcLength | *pDestLength) > 0xFFFFFFFFU) return KTX_INVALID_VALUE; + mz_ulong mzUncompressedSize = (mz_ulong)*pDestLength; + int status = mz_uncompress(pDest, &mzUncompressedSize, pSrc, (mz_ulong)srcLength); + switch (status) { + case MZ_OK: + *pDestLength = mzUncompressedSize; + return KTX_SUCCESS; + case MZ_BUF_ERROR: + return KTX_DECOMPRESS_LENGTH_ERROR; // buffer too small + case MZ_MEM_ERROR: + return KTX_OUT_OF_MEMORY; + default: + return KTX_FILE_DATA_ERROR; + } +} + +} diff --git a/lib/mkvkformatfiles b/lib/mkvkformatfiles index c972f0be3f..86b1d27c57 100755 --- a/lib/mkvkformatfiles +++ b/lib/mkvkformatfiles @@ -30,7 +30,8 @@ BEGIN { prohibited = prohibited "bool\nisProhibitedFormat(VkFormat format)\n{\n" prohibited = prohibited " switch (format) {\n"; valid = "bool\nisValidFormat(VkFormat format)\n{\n" - valid = valid " if (format <= VK_FORMAT_MAX_STANDARD_ENUM)\n" + valid = valid " // On MSVC VkFormat can be a signed integer\n" + valid = valid " if ((uint32_t) format <= VK_FORMAT_MAX_STANDARD_ENUM)\n" valid = valid " return true;\n else switch(format) {\n" if (ARGC == 2) { output_dir = ARGV[1] "/"; @@ -89,7 +90,7 @@ $2 == "VK_HEADER_VERSION" { /.*SCALED|A8B8G8R8_.*_PACK32/ { prohibited = prohibited " case " $1 ":\n"; } #/A8B8G8R8_.*_PACK32/ { prohibited = prohibited " case " $1 ":\n"; } # Multiplane formats. -/VK_FORMAT_[^F]/ && (/PLANE/ || /422/ || /420/) { +/VK_FORMAT_[^F]/ && (/PLANE/ || /420/) { # Avoid values defined as existing values and avoid the MAX_ENUM value. if ($3 !~ /VK_FORMAT_.*/ && $1 !~ /.*MAX_ENUM/) { prohibited = prohibited " case " $1 ":\n"; diff --git a/lib/strings.c b/lib/strings.c index 767fbf7f66..c635201161 100644 --- a/lib/strings.c +++ b/lib/strings.c @@ -16,6 +16,7 @@ */ #include "ktx.h" +#include "basis_sgd.h" static const char* const errorStrings[] = { "Operation succeeded.", /* KTX_SUCCESS */ @@ -34,9 +35,11 @@ static const char* const errorStrings[] = { "Out of memory.", /* KTX_OUT_OF_MEMORY */ "Transcoding of block compressed texture failed.",/* KTX_TRANSCODE_FAILED */ "Not a KTX file.", /* KTX_UNKNOWN_FILE_FORMAT */ - "Texture type not supported.", /* KTX_UNSUPPORTED_TEXTURE_TYPE */ + "Texture type not supported.", /* KTX_UNSUPPORTED_TEXTURE_TYPE */ "Feature not included in in-use library or not yet implemented.", /* KTX_UNSUPPORTED_FEATURE */ - "Library dependency (OpenGL or Vulkan) not linked into application." /* KTX_LIBRARY_NOT_LINKED */ + "Library dependency (OpenGL or Vulkan) not linked into application.", /* KTX_LIBRARY_NOT_LINKED */ + "Decompressed byte count does not match expected byte size", /* KTX_DECOMPRESS_LENGTH_ERROR */ + "Checksum mismatch when decompressing" /* KTX_DECOMPRESS_CHECKSUM_ERROR */ }; /* This will cause compilation to fail if number of messages and codes doesn't match */ typedef int errorStrings_SIZE_ASSERT[sizeof(errorStrings) / sizeof(char*) - 1 == KTX_ERROR_MAX_ENUM]; @@ -118,11 +121,34 @@ ktxSupercompressionSchemeString(ktxSupercmpScheme scheme) case KTX_SS_NONE: return "KTX_SS_NONE"; case KTX_SS_BASIS_LZ: return "KTX_SS_BASIS_LZ"; case KTX_SS_ZSTD: return "KTX_SS_ZSTD"; + case KTX_SS_ZLIB: return "KTX_SS_ZLIB"; default: if (scheme < KTX_SS_BEGIN_VENDOR_RANGE || scheme >= KTX_SS_BEGIN_RESERVED) return "Invalid scheme value"; else - return "Vendor scheme"; + return "Vendor or reserved scheme"; + } +} + +/** +* @~English +* @brief Return a string corresponding to a bu_image_flags bit. +* +* @param bit_index the bu_image_flag bit to test. +* @param bit_value the bu_image_flag bit value. +* +* @return pointer to the message string or NULL otherwise. +* +* @internal Use UTF-8 for translated message strings. +*/ +const char* ktxBUImageFlagsBitString(ktx_uint32_t bit_index, bool bit_value) +{ + if (!bit_value) + return NULL; + + switch (1u << bit_index) { + case ETC1S_P_FRAME: return "ETC1S_P_FRAME"; + default: return NULL; } } diff --git a/lib/texture2.c b/lib/texture2.c index afbe7dcbfe..a0c1a4146e 100644 --- a/lib/texture2.c +++ b/lib/texture2.c @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -419,7 +420,7 @@ ktxTexture2_construct(ktxTexture2* This, ktxTextureCreateInfo* createInfo, if (!This->pDfd) return KTX_OUT_OF_MEMORY; memcpy(This->pDfd, createInfo->pDfd, *createInfo->pDfd); - if (ktxFormatSize_initFromDfd(&formatSize, This->pDfd)) { + if (!ktxFormatSize_initFromDfd(&formatSize, This->pDfd)) { result = KTX_UNSUPPORTED_TEXTURE_TYPE; goto cleanup; } @@ -437,15 +438,26 @@ ktxTexture2_construct(ktxTexture2* This, ktxTextureCreateInfo* createInfo, // Ideally we'd set all these things in ktxFormatSize_initFromDfd // but This->_protected is not allocated until ktxTexture_construct; - if (This->isCompressed) + if (This->isCompressed) { This->_protected->_typeSize = 1; - else if (formatSize.flags & KTX_FORMAT_SIZE_PACKED_BIT) - This->_protected->_typeSize = formatSize.blockSizeInBits / 8; - else if (formatSize.flags & (KTX_FORMAT_SIZE_DEPTH_BIT | KTX_FORMAT_SIZE_STENCIL_BIT)) { - if (createInfo->vkFormat == VK_FORMAT_D16_UNORM_S8_UINT) + } else if (formatSize.flags & (KTX_FORMAT_SIZE_DEPTH_BIT | KTX_FORMAT_SIZE_STENCIL_BIT)) { + switch (createInfo->vkFormat) { + case VK_FORMAT_S8_UINT: + This->_protected->_typeSize = 1; + break; + case VK_FORMAT_D16_UNORM: // [[fallthrough]]; + case VK_FORMAT_D16_UNORM_S8_UINT: This->_protected->_typeSize = 2; - else + break; + case VK_FORMAT_X8_D24_UNORM_PACK32: // [[fallthrough]]; + case VK_FORMAT_D24_UNORM_S8_UINT: // [[fallthrough]]; + case VK_FORMAT_D32_SFLOAT: // [[fallthrough]]; + case VK_FORMAT_D32_SFLOAT_S8_UINT: This->_protected->_typeSize = 4; + break; + } + } else if (formatSize.flags & KTX_FORMAT_SIZE_PACKED_BIT) { + This->_protected->_typeSize = formatSize.blockSizeInBits / 8; } else { // Unpacked and uncompressed uint32_t numComponents; @@ -503,7 +515,7 @@ ktxTexture2_construct(ktxTexture2* This, ktxTextureCreateInfo* createInfo, * * @exception KTX_OUT_OF_MEMORY Not enough memory for the texture data. */ -static KTX_error_code +KTX_error_code ktxTexture2_constructCopy(ktxTexture2* This, ktxTexture2* orig) { KTX_error_code result; @@ -648,6 +660,7 @@ ktxTexture2_constructFromStreamAndHeader(ktxTexture2* This, ktxStream* pStream, KTX_error_code result; KTX_supplemental_info suppInfo; ktxStream* stream; + struct BDFD* pBDFD; ktx_size_t levelIndexSize; assert(pHeader != NULL && pStream != NULL); @@ -721,9 +734,21 @@ ktxTexture2_constructFromStreamAndHeader(ktxTexture2* This, ktxStream* pStream, for (ktx_uint32_t level = 0; level < This->numLevels; level++) { private->_levelIndex[level].byteOffset -= private->_firstLevelFileOffset; + if (This->supercompressionScheme == KTX_SS_NONE && + private->_levelIndex[level].byteLength != private->_levelIndex[level].uncompressedByteLength) { + // For non-supercompressed files the levels must have matching byte lengths + result = KTX_FILE_DATA_ERROR; + } } + if (result != KTX_SUCCESS) + goto cleanup; // Read DFD + if (pHeader->dataFormatDescriptor.byteOffset == 0 || pHeader->dataFormatDescriptor.byteLength < 16) { + // Missing or too small DFD + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } This->pDfd = (ktx_uint32_t*)malloc(pHeader->dataFormatDescriptor.byteLength); if (!This->pDfd) { @@ -735,19 +760,87 @@ ktxTexture2_constructFromStreamAndHeader(ktxTexture2* This, ktxStream* pStream, if (result != KTX_SUCCESS) goto cleanup; + if (pHeader->dataFormatDescriptor.byteLength != This->pDfd[0]) { + // DFD byteLength does not match dfdTotalSize + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } + pBDFD = (struct BDFD*)(This->pDfd + 1); + if (pBDFD->descriptorBlockSize < 24 || (pBDFD->descriptorBlockSize - 24) % 16 != 0) { + // BDFD has invalid size + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } + if (pBDFD->transfer != KHR_DF_TRANSFER_LINEAR && pBDFD->transfer != KHR_DF_TRANSFER_SRGB) { + // Unsupported transfer function + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } + if (!ktxFormatSize_initFromDfd(&This->_protected->_formatSize, This->pDfd)) { result = KTX_UNSUPPORTED_TEXTURE_TYPE; goto cleanup; } This->isCompressed = (This->_protected->_formatSize.flags & KTX_FORMAT_SIZE_COMPRESSED_BIT); - if (This->supercompressionScheme == KTX_SS_BASIS_LZ - && KHR_DFDVAL(This->pDfd + 1, MODEL) != KHR_DF_MODEL_ETC1S) - { + if (This->supercompressionScheme == KTX_SS_BASIS_LZ && pBDFD->model != KHR_DF_MODEL_ETC1S) { result = KTX_FILE_DATA_ERROR; goto cleanup; } + // Check compatibility with the KHR_texture_basisu glTF extension, if needed. + if (createFlags & KTX_TEXTURE_CREATE_CHECK_GLTF_BASISU_BIT) { + uint32_t max_dim = MAX(MAX(pHeader->pixelWidth, pHeader->pixelHeight), pHeader->pixelDepth); + uint32_t full_mip_pyramid_level_count = 1 + (uint32_t)log2(max_dim); + if (pHeader->levelCount != 1 && pHeader->levelCount != full_mip_pyramid_level_count) { + // KHR_texture_basisu requires full mip pyramid or single mip level + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } + if (This->numDimensions != 2 || This->isArray || This->isCubemap) { + // KHR_texture_basisu requires 2D textures. + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } + if ((This->baseWidth % 4) != 0 || (This->baseHeight % 4) != 0) { + // KHR_texture_basisu requires width and height to be a multiple of 4. + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } + if (pBDFD->model != KHR_DF_MODEL_ETC1S && pBDFD->model != KHR_DF_MODEL_UASTC) { + // KHR_texture_basisu requires BasisLZ or UASTC + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } + if (pBDFD->model == KHR_DF_MODEL_UASTC && + This->supercompressionScheme != KTX_SS_NONE && + This->supercompressionScheme != KTX_SS_ZSTD) { + // KHR_texture_basisu only allows NONE and ZSTD supercompression for UASTC + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } + } + + uint32_t sampleCount = KHR_DFDSAMPLECOUNT(This->pDfd + 1); + if (sampleCount == 0) { + // Invalid sample count + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } + if (pBDFD->model == KHR_DF_MODEL_ETC1S || pBDFD->model == KHR_DF_MODEL_UASTC) { + if (sampleCount < 1 || sampleCount > 2 || (sampleCount == 2 && pBDFD->model == KHR_DF_MODEL_UASTC)) { + // Invalid sample count + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } + if (pBDFD->texelBlockDimension0 != 3 || pBDFD->texelBlockDimension1 != 3 || + pBDFD->texelBlockDimension2 != 0 || pBDFD->texelBlockDimension3 != 0) { + // Texel block dimension must be 4x4x1x1 (offset by one) + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } + } + This->_private->_requiredLevelAlignment = ktxTexture2_calcRequiredLevelAlignment(This); @@ -755,6 +848,12 @@ ktxTexture2_constructFromStreamAndHeader(ktxTexture2* This, ktxStream* pStream, ktxHashList_Construct(&This->kvDataHead); // Load KVData. if (pHeader->keyValueData.byteLength > 0) { + uint32_t expectedOffset = pHeader->dataFormatDescriptor.byteOffset + pHeader->dataFormatDescriptor.byteLength; + expectedOffset = (expectedOffset + 3) & ~0x3; // 4 byte aligned + if (pHeader->keyValueData.byteOffset != expectedOffset) { + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } if (!(createFlags & KTX_TEXTURE_CREATE_SKIP_KVDATA_BIT)) { ktx_uint32_t kvdLen = pHeader->keyValueData.byteLength; ktx_uint8_t* pKvd; @@ -850,9 +949,30 @@ ktxTexture2_constructFromStreamAndHeader(ktxTexture2* This, ktxStream* pStream, } else { stream->skip(stream, pHeader->keyValueData.byteLength); } + } else if (pHeader->keyValueData.byteOffset != 0) { + // Non-zero KVD byteOffset with zero byteLength + result = KTX_FILE_DATA_ERROR; + goto cleanup; } if (pHeader->supercompressionGlobalData.byteLength > 0) { + switch (This->supercompressionScheme) { + case KTX_SS_BASIS_LZ: + break; + case KTX_SS_NONE: + case KTX_SS_ZSTD: + case KTX_SS_ZLIB: + // In these cases SGD is not allowed + result = KTX_FILE_DATA_ERROR; + break; + default: + // We don't support other supercompression schemes + result = KTX_UNSUPPORTED_FEATURE; + break; + } + if (result != KTX_SUCCESS) + goto cleanup; + // There could be padding here so seek to the next item. (void)stream->setpos(stream, pHeader->supercompressionGlobalData.byteOffset); @@ -871,6 +991,14 @@ ktxTexture2_constructFromStreamAndHeader(ktxTexture2* This, ktxStream* pStream, if (result != KTX_SUCCESS) goto cleanup; + } else if (pHeader->supercompressionGlobalData.byteOffset != 0) { + // Non-zero SGD byteOffset with zero byteLength + result = KTX_FILE_DATA_ERROR; + goto cleanup; + } else if (This->supercompressionScheme == KTX_SS_BASIS_LZ) { + // SGD is required for BasisLZ + result = KTX_FILE_DATA_ERROR; + goto cleanup; } // Calculate size of the image data. Level 0 is the last level in the data. @@ -1581,7 +1709,7 @@ ktxTexture2_calcPostInflationLevelAlignment(ktxTexture2* This) // Should actually work for none supercompressed but don't want to // encourage use of it. - assert(This->supercompressionScheme >= KTX_SS_ZSTD); + assert(This->supercompressionScheme != KTX_SS_NONE && This->supercompressionScheme != KTX_SS_BASIS_LZ); if (This->vkFormat != VK_FORMAT_UNDEFINED) alignment = lcm4(This->_protected->_formatSize.blockSizeInBits / 8); @@ -1838,7 +1966,7 @@ ktxTexture2_NeedsTranscoding(ktxTexture2* This) * * If supercompressionScheme == KTX_SS_NONE or * KTX_SS_BASIS_LZ, returns the value of @c This->dataSize - * else if supercompressionScheme == KTX_SS_ZSTD, it returns the + * else if supercompressionScheme == KTX_SS_ZSTD or KTX_SS_ZLIB, it returns the * sum of the uncompressed sizes of each mip level plus space for the level padding. With no * supercompression the data size and uncompressed data size are the same. For Basis * supercompression the uncompressed size cannot be known until the data is transcoded @@ -1854,6 +1982,7 @@ ktxTexture2_GetDataSizeUncompressed(ktxTexture2* This) case KTX_SS_NONE: return This->dataSize; case KTX_SS_ZSTD: + case KTX_SS_ZLIB: { ktx_size_t uncompressedSize = 0; ktx_uint32_t uncompressedLevelAlignment; @@ -1984,7 +2113,7 @@ ktxTexture2_IterateLevels(ktxTexture2* This, PFNKTXITERCB iterCb, void* userdata * * This operates similarly to ktxTexture_IterateLevelFaces() except that it * loads the images from the ktxTexture2's source to a temporary buffer - * while iterating. If supercompressionScheme == KTX_SS_ZSTD, + * while iterating. If supercompressionScheme == KTX_SS_ZSTD or KTX_SS_ZLIB, * it will inflate the data before passing it to the callback. The callback function * must copy the image data if it wishes to preserve it as the temporary buffer * is reused for each level and is freed when this function exits. @@ -1992,8 +2121,8 @@ ktxTexture2_IterateLevels(ktxTexture2* This, PFNKTXITERCB iterCb, void* userdata * This function is helpful for reducing memory usage when uploading the data * to a graphics API. * - * Intended for use only when supercompressionScheme == SUPERCOMPRESSION_NONE - * or SUPERCOMPRESSION_ZSTD. As there is no access to the ktxTexture's data on + * Intended for use only when supercompressionScheme == KTX_SS_NONE, + * KTX_SS_ZSTD or KTX_SS_ZLIB. As there is no access to the ktxTexture's data on * conclusion of this function, destroying the texture on completion is recommended. * * @param[in] This pointer to the ktxTexture2 object of interest. @@ -2013,8 +2142,9 @@ ktxTexture2_IterateLevels(ktxTexture2* This, PFNKTXITERCB iterCb, void* userdata * this ktxTexture2's images have already * been loaded. * @exception KTX_INVALID_OPERATION - * supercompressionScheme != SUPERCOMPRESSION_NONE. - * and supercompressionScheme != SUPERCOMPRESSION_ZSTD. + * supercompressionScheme != KTX_SS_NONE, + * supercompressionScheme != KTX_SS_ZSTD, and + * supercompressionScheme != KTX_SS_ZLIB. * @exception KTX_INVALID_VALUE @p This is @c NULL or @p iterCb is @c NULL. * @exception KTX_OUT_OF_MEMORY not enough memory to allocate a block to * hold the base level image. @@ -2040,7 +2170,8 @@ ktxTexture2_IterateLoadLevelFaces(ktxTexture2* This, PFNKTXITERCB iterCb, return KTX_INVALID_OPERATION; if (This->supercompressionScheme != KTX_SS_NONE && - This->supercompressionScheme != KTX_SS_ZSTD) + This->supercompressionScheme != KTX_SS_ZSTD && + This->supercompressionScheme != KTX_SS_ZLIB) return KTX_INVALID_OPERATION; if (iterCb == NULL) @@ -2057,14 +2188,16 @@ ktxTexture2_IterateLoadLevelFaces(ktxTexture2* This, PFNKTXITERCB iterCb, dataBuf = malloc(dataSize); if (!dataBuf) return KTX_OUT_OF_MEMORY; - if (This->supercompressionScheme == KTX_SS_ZSTD) { + if (This->supercompressionScheme == KTX_SS_ZSTD || This->supercompressionScheme == KTX_SS_ZLIB) { uncompressedDataSize = levelIndex[0].uncompressedByteLength; uncompressedDataBuf = malloc(uncompressedDataSize); if (!uncompressedDataBuf) { result = KTX_OUT_OF_MEMORY; goto cleanup; } - dctx = ZSTD_createDCtx(); + if (This->supercompressionScheme == KTX_SS_ZSTD) { + dctx = ZSTD_createDCtx(); + } pData = uncompressedDataBuf; } else { pData = dataBuf; @@ -2107,21 +2240,34 @@ ktxTexture2_IterateLoadLevelFaces(ktxTexture2* This, PFNKTXITERCB iterCb, ZSTD_ErrorCode error = ZSTD_getErrorCode(levelSize); switch(error) { case ZSTD_error_dstSize_tooSmall: - return KTX_INVALID_VALUE; // inflatedDataCapacity too small. + return KTX_DECOMPRESS_LENGTH_ERROR; // inflatedDataCapacity too small. + case ZSTD_error_checksum_wrong: + return KTX_DECOMPRESS_CHECKSUM_ERROR; case ZSTD_error_memory_allocation: return KTX_OUT_OF_MEMORY; default: return KTX_FILE_DATA_ERROR; } } + // We don't fix up the texture's dataSize, levelIndex or // _requiredAlignment because after this function completes there // is no way to get at the texture's data. //nindex[level].byteOffset = levelOffset; //nindex[level].uncompressedByteLength = nindex[level].byteLength = //levelByteLength; + } else if (This->supercompressionScheme == KTX_SS_ZLIB) { + result = ktxUncompressZLIBInt(uncompressedDataBuf, + &uncompressedDataSize, + dataBuf, + levelSize); + if (result != KTX_SUCCESS) + return result; } + if (levelIndex[level].uncompressedByteLength != levelSize) + return KTX_DECOMPRESS_LENGTH_ERROR; + #if IS_BIG_ENDIAN switch (prtctd->_typeSize) { case 2: @@ -2188,16 +2334,23 @@ KTX_error_code ktxTexture2_inflateZstdInt(ktxTexture2* This, ktx_uint8_t* pDeflatedData, ktx_uint8_t* pInflatedData, ktx_size_t inflatedDataCapacity); + +KTX_error_code +ktxTexture2_inflateZLIBInt(ktxTexture2* This, ktx_uint8_t* pDeflatedData, + ktx_uint8_t* pInflatedData, + ktx_size_t inflatedDataCapacity); + /** * @memberof ktxTexture2 * @~English * @brief Load all the image data from the ktxTexture2's source. * - * The data will be inflated if supercompressionScheme == SUPERCOMPRESSION_ZSTD. + * The data will be inflated if supercompressionScheme == KTX_SS_ZSTD or + * KTX_SS_ZLIB. * The data is loaded into the provided buffer or to an internally allocated * buffer, if @p pBuffer is @c NULL. Callers providing their own buffer must * ensure the buffer large enough to hold the inflated data for files deflated - * with Zstd. See ktxTexture2_GetDataSizeUncompressed(). + * with Zstd or ZLIB. See ktxTexture2_GetDataSizeUncompressed(). * * The texture's levelIndex, dataSize, DFD and supercompressionScheme will * all be updated after successful inflation to reflect the inflated data. @@ -2248,7 +2401,7 @@ ktxTexture2_LoadImageData(ktxTexture2* This, pDest = pBuffer; } - if (This->supercompressionScheme == KTX_SS_ZSTD) { + if (This->supercompressionScheme == KTX_SS_ZSTD || This->supercompressionScheme == KTX_SS_ZLIB) { // Create buffer to hold deflated data. pDeflatedData = malloc(This->dataSize); if (pDeflatedData == NULL) @@ -2271,10 +2424,15 @@ ktxTexture2_LoadImageData(ktxTexture2* This, if (result != KTX_SUCCESS) return result; - if (This->supercompressionScheme == KTX_SS_ZSTD) { + if (This->supercompressionScheme == KTX_SS_ZSTD || This->supercompressionScheme == KTX_SS_ZLIB) { assert(pDeflatedData != NULL); - result = ktxTexture2_inflateZstdInt(This, pDeflatedData, pDest, - inflatedDataCapacity); + if (This->supercompressionScheme == KTX_SS_ZSTD) { + result = ktxTexture2_inflateZstdInt(This, pDeflatedData, pDest, + inflatedDataCapacity); + } else if (This->supercompressionScheme == KTX_SS_ZLIB) { + result = ktxTexture2_inflateZLIBInt(This, pDeflatedData, pDest, + inflatedDataCapacity); + } free(pDeflatedData); if (result != KTX_SUCCESS) { if (pBuffer == NULL) { @@ -2388,13 +2546,19 @@ ktxTexture2_inflateZstdInt(ktxTexture2* This, ktx_uint8_t* pDeflatedData, ZSTD_ErrorCode error = ZSTD_getErrorCode(levelByteLength); switch(error) { case ZSTD_error_dstSize_tooSmall: - return KTX_INVALID_VALUE; // inflatedDataCapacity too small. + return KTX_DECOMPRESS_LENGTH_ERROR; // inflatedDataCapacity too small. + case ZSTD_error_checksum_wrong: + return KTX_DECOMPRESS_CHECKSUM_ERROR; case ZSTD_error_memory_allocation: return KTX_OUT_OF_MEMORY; default: return KTX_FILE_DATA_ERROR; } } + + if (This->_private->_levelIndex[level].uncompressedByteLength != levelByteLength) + return KTX_DECOMPRESS_LENGTH_ERROR; + nindex[level].byteOffset = levelOffset; nindex[level].uncompressedByteLength = nindex[level].byteLength = levelByteLength; @@ -2421,6 +2585,89 @@ ktxTexture2_inflateZstdInt(ktxTexture2* This, ktx_uint8_t* pDeflatedData, return KTX_SUCCESS; } +/** + * @memberof ktxTexture2 @private + * @~English + * @brief Inflate the data in a ktxTexture2 object using miniz (ZLIB). + * + * The texture's levelIndex, dataSize, DFD and supercompressionScheme will + * all be updated after successful inflation to reflect the inflated data. + * + * @param[in] This pointer to the ktxTexture2 object of interest. + * @param[in] pDeflatedData pointer to a buffer containing the deflated + * data of the entire texture. + * @param[in,out] pInflatedData pointer to a buffer in which to write the + * inflated data. + * @param[in] inflatedDataCapacity capacity of the buffer pointed at by + * @p pInflatedData. + */ +KTX_error_code +ktxTexture2_inflateZLIBInt(ktxTexture2* This, ktx_uint8_t* pDeflatedData, + ktx_uint8_t* pInflatedData, + ktx_size_t inflatedDataCapacity) +{ + DECLARE_PROTECTED(ktxTexture); + ktx_uint32_t levelIndexByteLength = + This->numLevels * sizeof(ktxLevelIndexEntry); + uint64_t levelOffset = 0; + ktxLevelIndexEntry* cindex = This->_private->_levelIndex; + ktxLevelIndexEntry* nindex; + ktx_uint32_t uncompressedLevelAlignment; + + if (pDeflatedData == NULL) + return KTX_INVALID_VALUE; + + if (pInflatedData == NULL) + return KTX_INVALID_VALUE; + + if (This->supercompressionScheme != KTX_SS_ZLIB) + return KTX_INVALID_OPERATION; + + nindex = malloc(levelIndexByteLength); + if (nindex == NULL) + return KTX_OUT_OF_MEMORY; + + uncompressedLevelAlignment = + ktxTexture2_calcPostInflationLevelAlignment(This); + + ktx_size_t inflatedByteLength = 0; + for (int32_t level = This->numLevels - 1; level >= 0; level--) { + size_t levelByteLength = inflatedDataCapacity; + KTX_error_code result = ktxUncompressZLIBInt(pInflatedData + levelOffset, + &levelByteLength, + &pDeflatedData[cindex[level].byteOffset], + cindex[level].byteLength); + if (result != KTX_SUCCESS) + return result; + + if (This->_private->_levelIndex[level].uncompressedByteLength != levelByteLength) + return KTX_DECOMPRESS_LENGTH_ERROR; + + nindex[level].byteOffset = levelOffset; + nindex[level].uncompressedByteLength = nindex[level].byteLength = + levelByteLength; + ktx_size_t paddedLevelByteLength + = _KTX_PADN(uncompressedLevelAlignment, levelByteLength); + inflatedByteLength += paddedLevelByteLength; + levelOffset += paddedLevelByteLength; + inflatedDataCapacity -= paddedLevelByteLength; + } + + // Now modify the texture. + + This->dataSize = inflatedByteLength; + This->supercompressionScheme = KTX_SS_NONE; + memcpy(cindex, nindex, levelIndexByteLength); // Update level index + free(nindex); + This->_private->_requiredLevelAlignment = uncompressedLevelAlignment; + // Set bytesPlane as we're now sized. + uint32_t* bdb = This->pDfd + 1; + // blockSizeInBits was set to the inflated size on file load. + bdb[KHR_DF_WORD_BYTESPLANE0] = prtctd->_formatSize.blockSizeInBits / 8; + + return KTX_SUCCESS; +} + #if !KTX_FEATURE_WRITE /* diff --git a/lib/texture2.h b/lib/texture2.h index 14e4115ecf..08507804d9 100644 --- a/lib/texture2.h +++ b/lib/texture2.h @@ -50,6 +50,8 @@ KTX_error_code ktxTexture2_LoadImageData(ktxTexture2* This, ktx_uint8_t* pBuffer, ktx_size_t bufSize); +KTX_error_code +ktxTexture2_constructCopy(ktxTexture2* This, ktxTexture2* orig); KTX_error_code ktxTexture2_constructFromStreamAndHeader(ktxTexture2* This, ktxStream* pStream, KTX_header2* pHeader, diff --git a/lib/vkformat_check.c b/lib/vkformat_check.c index fbd645b830..98f0e63e16 100644 --- a/lib/vkformat_check.c +++ b/lib/vkformat_check.c @@ -49,29 +49,21 @@ isProhibitedFormat(VkFormat format) case VK_FORMAT_R16G16B16_SSCALED: case VK_FORMAT_R16G16B16A16_USCALED: case VK_FORMAT_R16G16B16A16_SSCALED: - case VK_FORMAT_G8B8G8R8_422_UNORM: - case VK_FORMAT_B8G8R8G8_422_UNORM: case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM: case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM: case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM: case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM: case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM: - case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16: - case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16: case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16: case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16: case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16: case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16: case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16: - case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16: - case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16: case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16: case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16: case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16: case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16: case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16: - case VK_FORMAT_G16B16G16R16_422_UNORM: - case VK_FORMAT_B16G16R16G16_422_UNORM: case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM: case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM: case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM: @@ -86,7 +78,8 @@ isProhibitedFormat(VkFormat format) bool isValidFormat(VkFormat format) { - if (format <= VK_FORMAT_MAX_STANDARD_ENUM) + // On MSVC VkFormat can be a signed integer + if ((uint32_t) format <= VK_FORMAT_MAX_STANDARD_ENUM) return true; else switch(format) { case VK_FORMAT_R10X6_UNORM_PACK16: diff --git a/lib/writer2.c b/lib/writer2.c index ab998b5ab6..6b0e990f63 100644 --- a/lib/writer2.c +++ b/lib/writer2.c @@ -858,5 +858,87 @@ ktxTexture2_DeflateZstd(ktxTexture2* This, ktx_uint32_t compressionLevel) return KTX_SUCCESS; } +/** + * @memberof ktxTexture2 + * @~English + * @brief Deflate the data in a ktxTexture2 object using miniz (ZLIB). + * + * The texture's levelIndex, dataSize, DFD and supercompressionScheme will + * all be updated after successful deflation to reflect the deflated data. + * + * @param[in] This pointer to the ktxTexture2 object of interest. + * @param[in] compressionLevel set speed vs compression ratio trade-off. Values + * between 1 and 9 are accepted. The lower the level the faster. + */ +KTX_error_code +ktxTexture2_DeflateZLIB(ktxTexture2* This, ktx_uint32_t compressionLevel) +{ + ktx_uint32_t levelIndexByteLength = + This->numLevels * sizeof(ktxLevelIndexEntry); + ktx_uint8_t* workBuf; + ktx_uint8_t* cmpData; + ktx_size_t dstRemainingByteLength = 0; + ktx_size_t byteLengthCmp = 0; + ktx_size_t levelOffset = 0; + ktxLevelIndexEntry* cindex = This->_private->_levelIndex; + ktxLevelIndexEntry* nindex; + ktx_uint8_t* pCmpDst; + + if (This->supercompressionScheme != KTX_SS_NONE) + return KTX_INVALID_OPERATION; + + // On rare occasions the deflated data can be a few bytes larger than + // the source data. Calculating the dst buffer size using + // mz_deflateBound provides a conservative size to account for that. + for (int32_t level = This->numLevels - 1; level >= 0; level--) { + dstRemainingByteLength += ktxCompressZLIBBounds(cindex[level].byteLength); + } + + workBuf = malloc(dstRemainingByteLength + levelIndexByteLength); + if (workBuf == NULL) + return KTX_OUT_OF_MEMORY; + nindex = (ktxLevelIndexEntry*)workBuf; + pCmpDst = &workBuf[levelIndexByteLength]; + + for (int32_t level = This->numLevels - 1; level >= 0; level--) { + size_t levelByteLengthCmp = dstRemainingByteLength; + KTX_error_code result = ktxCompressZLIBInt(pCmpDst + levelOffset, + &levelByteLengthCmp, + &This->pData[cindex[level].byteOffset], + cindex[level].byteLength, + compressionLevel); + if (result != KTX_SUCCESS) + return result; + + nindex[level].byteOffset = levelOffset; + nindex[level].uncompressedByteLength = cindex[level].byteLength; + nindex[level].byteLength = levelByteLengthCmp; + byteLengthCmp += levelByteLengthCmp; + levelOffset += levelByteLengthCmp; + dstRemainingByteLength -= levelByteLengthCmp; + } + + // Move the compressed data into a correctly sized buffer. + cmpData = malloc(byteLengthCmp); + if (cmpData == NULL) { + free(workBuf); + return KTX_OUT_OF_MEMORY; + } + // Now modify the texture. + memcpy(cmpData, pCmpDst, byteLengthCmp); // Copy data to sized buffer. + memcpy(cindex, nindex, levelIndexByteLength); // Update level index + free(workBuf); + free(This->pData); + This->pData = cmpData; + This->dataSize = byteLengthCmp; + This->supercompressionScheme = KTX_SS_ZLIB; + This->_private->_requiredLevelAlignment = 1; + // Clear bytesPlane to indicate we're now unsized. + uint32_t* bdb = This->pDfd + 1; + bdb[KHR_DF_WORD_BYTESPLANE0] = 0; /* bytesPlane3..0 = 0 */ + + return KTX_SUCCESS; +} + /** @} */ diff --git a/libktx.doxy b/libktx.doxy index 31c0b8f320..c3dbb6395a 100644 --- a/libktx.doxy +++ b/libktx.doxy @@ -762,6 +762,7 @@ INPUT = LICENSE.md \ include \ lib/basis_encode.cpp \ lib/basis_transcode.cpp \ + lib/miniz_wrapper.cpp\ lib/strings.c \ lib/mainpage.md \ lib/glloader.c \ diff --git a/other_projects/cxxopts/CMakeLists.txt b/other_projects/cxxopts/CMakeLists.txt new file mode 100644 index 0000000000..4c1a686069 --- /dev/null +++ b/other_projects/cxxopts/CMakeLists.txt @@ -0,0 +1,82 @@ +# Copyright (c) 2014 Jarryd Beck +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +cmake_minimum_required(VERSION 3.1...3.19) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") +include(cxxopts) +set("PROJECT_DESCRIPTION" "A header-only lightweight C++ command line option parser") +set("PROJECT_HOMEPAGE_URL" "https://github.com/jarro2783/cxxopts") + +# Get the version of the library +cxxopts_getversion(VERSION) + +project(cxxopts + VERSION "${VERSION}" + LANGUAGES CXX +) + +# Must include after the project call due to GNUInstallDirs requiring a language be enabled (IE. CXX) +include(GNUInstallDirs) + +# Determine whether this is a standalone project or included by other projects +set(CXXOPTS_STANDALONE_PROJECT OFF) +if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(CXXOPTS_STANDALONE_PROJECT ON) +endif() + +# Establish the project options +option(CXXOPTS_BUILD_EXAMPLES "Set to ON to build examples" ${CXXOPTS_STANDALONE_PROJECT}) +option(CXXOPTS_BUILD_TESTS "Set to ON to build tests" ${CXXOPTS_STANDALONE_PROJECT}) +option(CXXOPTS_ENABLE_INSTALL "Generate the install target" ${CXXOPTS_STANDALONE_PROJECT}) +option(CXXOPTS_ENABLE_WARNINGS "Add warnings to CMAKE_CXX_FLAGS" ${CXXOPTS_STANDALONE_PROJECT}) +option(CXXOPTS_USE_UNICODE_HELP "Use ICU Unicode library" OFF) + +if (CXXOPTS_STANDALONE_PROJECT) + cxxopts_set_cxx_standard() +endif() + +if (CXXOPTS_ENABLE_WARNINGS) + cxxopts_enable_warnings() +endif() + +add_library(cxxopts INTERFACE) +add_library(cxxopts::cxxopts ALIAS cxxopts) +add_subdirectory(include) + +# Link against the ICU library when requested +if(CXXOPTS_USE_UNICODE_HELP) + cxxopts_use_unicode() +endif() + +# Install cxxopts when requested by the user +if (CXXOPTS_ENABLE_INSTALL) + cxxopts_install_logic() +endif() + +# Build examples when requested by the user +if (CXXOPTS_BUILD_EXAMPLES) + add_subdirectory(src) +endif() + +# Enable testing when requested by the user +if (CXXOPTS_BUILD_TESTS) + enable_testing() + add_subdirectory(test) +endif() diff --git a/other_projects/cxxopts/cmake/cxxopts.cmake b/other_projects/cxxopts/cmake/cxxopts.cmake new file mode 100644 index 0000000000..0ead5434fc --- /dev/null +++ b/other_projects/cxxopts/cmake/cxxopts.cmake @@ -0,0 +1,163 @@ +# Copyright (c) 2014 Jarryd Beck +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +if (CMAKE_VERSION VERSION_GREATER 3.10 OR CMAKE_VERSION VERSION_EQUAL 3.10) + # Use include_guard() added in cmake 3.10 + include_guard() +endif() + +include(CMakePackageConfigHelpers) + +function(cxxopts_getversion version_arg) + # Parse the current version from the cxxopts header + file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/cxxopts.hpp" cxxopts_version_defines + REGEX "#define CXXOPTS__VERSION_(MAJOR|MINOR|PATCH)") + foreach(ver ${cxxopts_version_defines}) + if(ver MATCHES "#define CXXOPTS__VERSION_(MAJOR|MINOR|PATCH) +([^ ]+)$") + set(CXXOPTS__VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}" CACHE INTERNAL "") + endif() + endforeach() + set(VERSION ${CXXOPTS__VERSION_MAJOR}.${CXXOPTS__VERSION_MINOR}.${CXXOPTS__VERSION_PATCH}) + + # Give feedback to the user. Prefer DEBUG when available since large projects tend to have a lot + # going on already + if (CMAKE_VERSION VERSION_GREATER 3.15 OR CMAKE_VERSION VERSION_EQUAL 3.15) + message(DEBUG "cxxopts version ${VERSION}") + else() + message(STATUS "cxxopts version ${VERSION}") + endif() + + # Return the information to the caller + set(${version_arg} ${VERSION} PARENT_SCOPE) +endfunction() + +# Optionally, enable unicode support using the ICU library +function(cxxopts_use_unicode) + find_package(PkgConfig) + pkg_check_modules(ICU REQUIRED icu-uc) + + target_link_libraries(cxxopts INTERFACE ${ICU_LDFLAGS}) + target_compile_options(cxxopts INTERFACE ${ICU_CFLAGS}) + target_compile_definitions(cxxopts INTERFACE CXXOPTS_USE_UNICODE) +endfunction() + +# Request C++11 without gnu extension for the whole project and enable more warnings +macro(cxxopts_set_cxx_standard) + if (CXXOPTS_CXX_STANDARD) + set(CMAKE_CXX_STANDARD ${CXXOPTS_CXX_STANDARD}) + else() + set(CMAKE_CXX_STANDARD 11) + endif() + + set(CMAKE_CXX_EXTENSIONS OFF) +endmacro() + +# Helper function to enable warnings +function(cxxopts_enable_warnings) + if(MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W2") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if (${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER_EQUAL 5.0) + set(COMPILER_SPECIFIC_FLAGS "-Wsuggest-override") + endif() + endif() + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Wextra -Wshadow -Weffc++ -Wsign-compare -Wshadow -Wwrite-strings -Wpointer-arith -Winit-self -Wconversion -Wno-sign-conversion ${COMPILER_SPECIFIC_FLAGS}") + endif() + + set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} PARENT_SCOPE) +endfunction() + +# Helper function to ecapsulate install logic +function(cxxopts_install_logic) + if(CMAKE_LIBRARY_ARCHITECTURE) + string(REPLACE "/${CMAKE_LIBRARY_ARCHITECTURE}" "" CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}") + else() + # On some systems (e.g. NixOS), `CMAKE_LIBRARY_ARCHITECTURE` can be empty + set(CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}") + endif() + set(CXXOPTS_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR_ARCHIND}/cmake/cxxopts" CACHE STRING "Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.") + set(version_config "${PROJECT_BINARY_DIR}/cxxopts-config-version.cmake") + set(project_config "${PROJECT_BINARY_DIR}/cxxopts-config.cmake") + set(targets_export_name cxxopts-targets) + set(PackagingTemplatesDir "${PROJECT_SOURCE_DIR}/packaging") + + + if(${CMAKE_VERSION} VERSION_GREATER "3.14") + set(OPTIONAL_ARCH_INDEPENDENT "ARCH_INDEPENDENT") + endif() + + # Generate the version, config and target files into the build directory. + write_basic_package_version_file( + ${version_config} + VERSION ${VERSION} + COMPATIBILITY AnyNewerVersion + ${OPTIONAL_ARCH_INDEPENDENT} + ) + configure_package_config_file( + ${PackagingTemplatesDir}/cxxopts-config.cmake.in + ${project_config} + INSTALL_DESTINATION ${CXXOPTS_CMAKE_DIR}) + export(TARGETS cxxopts NAMESPACE cxxopts:: + FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) + + # Install version, config and target files. + install( + FILES ${project_config} ${version_config} + DESTINATION ${CXXOPTS_CMAKE_DIR}) + install(EXPORT ${targets_export_name} DESTINATION ${CXXOPTS_CMAKE_DIR} + NAMESPACE cxxopts::) + + # Install the header file and export the target + install(TARGETS cxxopts EXPORT ${targets_export_name} DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(FILES ${PROJECT_SOURCE_DIR}/include/cxxopts.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") + set(CPACK_PACKAGE_VENDOR "cxxopt developers") + set(CPACK_PACKAGE_DESCRIPTION "${PROJECT_DESCRIPTION}") + set(CPACK_DEBIAN_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_RPM_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_PACKAGE_HOMEPAGE_URL "${PROJECT_HOMEPAGE_URL}") + set(CPACK_PACKAGE_MAINTAINER "${CPACK_PACKAGE_VENDOR}") + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_MAINTAINER}") + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + + set(CPACK_DEBIAN_PACKAGE_NAME "lib${PROJECT_NAME}-dev") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6-dev") + set(CPACK_DEBIAN_PACKAGE_SUGGESTS "cmake, pkg-config, pkg-conf") + + set(CPACK_RPM_PACKAGE_NAME "lib${PROJECT_NAME}-devel") + set(CPACK_RPM_PACKAGE_SUGGESTS "${CPACK_DEBIAN_PACKAGE_SUGGESTS}") + + set(CPACK_DEB_COMPONENT_INSTALL ON) + set(CPACK_RPM_COMPONENT_INSTALL ON) + set(CPACK_NSIS_COMPONENT_INSTALL ON) + set(CPACK_DEBIAN_COMPRESSION_TYPE "xz") + + set(PKG_CONFIG_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc") + configure_file("${PackagingTemplatesDir}/pkgconfig.pc.in" "${PKG_CONFIG_FILE_NAME}" @ONLY) + install(FILES "${PKG_CONFIG_FILE_NAME}" + DESTINATION "${CMAKE_INSTALL_LIBDIR_ARCHIND}/pkgconfig" + ) + + include(CPack) +endfunction() diff --git a/other_projects/cxxopts/include/CMakeLists.txt b/other_projects/cxxopts/include/CMakeLists.txt new file mode 100644 index 0000000000..1123c5a11f --- /dev/null +++ b/other_projects/cxxopts/include/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (c) 2014 Jarryd Beck +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +target_include_directories(cxxopts INTERFACE + $ + $ +) diff --git a/other_projects/cxxopts/include/cxxopts.hpp b/other_projects/cxxopts/include/cxxopts.hpp new file mode 100644 index 0000000000..a397600250 --- /dev/null +++ b/other_projects/cxxopts/include/cxxopts.hpp @@ -0,0 +1,2837 @@ +/* + +Copyright (c) 2014-2022 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// vim: ts=2:sw=2:expandtab + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CXXOPTS_NO_EXCEPTIONS +#include +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 +# define CXXOPTS_NO_REGEX true +# endif +#endif +#if defined(_MSC_VER) && !defined(__clang__) +#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern +#define CXXOPTS_LINKONCE __declspec(selectany) extern +#else +#define CXXOPTS_LINKONCE_CONST +#define CXXOPTS_LINKONCE +#endif + +#ifndef CXXOPTS_NO_REGEX +# include +#endif // CXXOPTS_NO_REGEX + +// Nonstandard before C++17, which is coincidentally what we also need for +#ifdef __has_include +# if __has_include() +# include +# ifdef __cpp_lib_optional +# define CXXOPTS_HAS_OPTIONAL +# endif +# endif +#endif + +#if __cplusplus >= 201603L +#define CXXOPTS_NODISCARD [[nodiscard]] +#else +#define CXXOPTS_NODISCARD +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 3 +#define CXXOPTS__VERSION_MINOR 1 +#define CXXOPTS__VERSION_PATCH 1 + +#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 + #define CXXOPTS_NULL_DEREF_IGNORE +#endif + +#if defined(__GNUC__) +#define DO_PRAGMA(x) _Pragma(#x) +#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push) +#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop) +#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x) +#else +// define other compilers here if needed +#define CXXOPTS_DIAGNOSTIC_PUSH +#define CXXOPTS_DIAGNOSTIC_POP +#define CXXOPTS_IGNORE_WARNING(x) +#endif + +#ifdef CXXOPTS_NO_RTTI +#define CXXOPTS_RTTI_CAST static_cast +#else +#define CXXOPTS_RTTI_CAST dynamic_cast +#endif + +namespace cxxopts { +static constexpr struct { + uint8_t major, minor, patch; +} version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH +}; +} // namespace cxxopts + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts { + +using String = icu::UnicodeString; + +inline +String +toLocalString(std::string s) +{ + return icu::UnicodeString::fromUTF8(std::move(s)); +} + +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: +// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") +// This will be ignored under other compilers like LLVM clang. +class UnicodeStringIterator +{ + public: + + using iterator_category = std::forward_iterator_tag; + using value_type = int32_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; +}; +CXXOPTS_DIAGNOSTIC_POP + +inline +String& +stringAppend(String&s, String a) +{ + return s.append(std::move(a)); +} + +inline +String& +stringAppend(String& s, std::size_t n, UChar32 c) +{ + for (std::size_t i = 0; i != n; ++i) + { + s.append(c); + } + + return s; +} + +template +String& +stringAppend(String& s, Iterator begin, Iterator end) +{ + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; +} + +inline +std::size_t +stringLength(const String& s) +{ + return s.length(); +} + +inline +std::string +toUTF8String(const String& s) +{ + std::string result; + s.toUTF8String(result); + + return result; +} + +inline +bool +empty(const String& s) +{ + return s.isEmpty(); +} + +} // namespace cxxopts + +namespace std { + +inline +cxxopts::UnicodeStringIterator +begin(const icu::UnicodeString& s) +{ + return cxxopts::UnicodeStringIterator(&s, 0); +} + +inline +cxxopts::UnicodeStringIterator +end(const icu::UnicodeString& s) +{ + return cxxopts::UnicodeStringIterator(&s, s.length()); +} + +} // namespace std + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts { + +using String = std::string; + +template +T +toLocalString(T&& t) +{ + return std::forward(t); +} + +inline +std::size_t +stringLength(const String& s) +{ + return s.length(); +} + +inline +String& +stringAppend(String&s, const String& a) +{ + return s.append(a); +} + +inline +String& +stringAppend(String& s, std::size_t n, char c) +{ + return s.append(n, c); +} + +template +String& +stringAppend(String& s, Iterator begin, Iterator end) +{ + return s.append(begin, end); +} + +template +std::string +toUTF8String(T&& t) +{ + return std::forward(t); +} + +inline +bool +empty(const std::string& s) +{ + return s.empty(); +} + +} // namespace cxxopts + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts { + +namespace { +// Altered source code for KTX Tools: +// For deterministic and portable behaviour the code is changed to always use ' as quote marks +// +// --- Start of original source code: +// #ifdef _WIN32 +// CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); +// CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); +// #else +// CXXOPTS_LINKONCE_CONST std::string LQUOTE("‘"); +// CXXOPTS_LINKONCE_CONST std::string RQUOTE("’"); +// #endif +// --- End of original source code +// --- Start of altered source code +CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); +CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); +// --- End of altered source code +} // namespace + +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we +// want to silence it: warning: base class 'class +// std::enable_shared_from_this' has accessible non-virtual +// destructor This will be ignored under other compilers like LLVM clang. +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") + +// some older versions of GCC warn under this warning +CXXOPTS_IGNORE_WARNING("-Weffc++") +class Value : public std::enable_shared_from_this +{ + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; +}; + +CXXOPTS_DIAGNOSTIC_POP + +namespace exceptions { + +class exception : public std::exception +{ + public: + explicit exception(std::string message) + : m_message(std::move(message)) + { + } + + CXXOPTS_NODISCARD + const char* + what() const noexcept override + { + return m_message.c_str(); + } + + private: + std::string m_message; +}; + +class specification : public exception +{ + public: + + explicit specification(const std::string& message) + : exception(message) + { + } +}; + +class parsing : public exception +{ + public: + explicit parsing(const std::string& message) + : exception(message) + { + } +}; + +class option_already_exists : public specification +{ + public: + explicit option_already_exists(const std::string& option) + : specification("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } +}; + +class invalid_option_format : public specification +{ + public: + explicit invalid_option_format(const std::string& format) + : specification("Invalid option format " + LQUOTE + format + RQUOTE) + { + } +}; + +class invalid_option_syntax : public parsing { + public: + explicit invalid_option_syntax(const std::string& text) + : parsing("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } +}; + +class no_such_option : public parsing +{ + public: + explicit no_such_option(const std::string& option) + : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } +}; + +class missing_argument : public parsing +{ + public: + explicit missing_argument(const std::string& option) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } +}; + +class option_requires_argument : public parsing +{ + public: + explicit option_requires_argument(const std::string& option) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } +}; + +class gratuitous_argument_for_option : public parsing +{ + public: + gratuitous_argument_for_option + ( + const std::string& option, + const std::string& arg + ) + : parsing( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } +}; + +class requested_option_not_present : public parsing +{ + public: + explicit requested_option_not_present(const std::string& option) + : parsing("Option " + LQUOTE + option + RQUOTE + " not present") + { + } +}; + +class option_has_no_value : public exception +{ + public: + explicit option_has_no_value(const std::string& option) + : exception( + !option.empty() ? + ("Option " + LQUOTE + option + RQUOTE + " has no value") : + "Option has no value") + { + } +}; + +class incorrect_argument_type : public parsing +{ + public: + explicit incorrect_argument_type + ( + const std::string& arg + ) + : parsing( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } +}; + +} // namespace exceptions + + +template +void throw_or_mimic(const std::string& text) +{ + static_assert(std::is_base_of::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); + +#ifndef CXXOPTS_NO_EXCEPTIONS + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{text}; +#else + // Otherwise manually instantiate the exception, print what() to stderr, + // and exit + T exception{text}; + std::cerr << exception.what() << std::endl; + std::exit(EXIT_FAILURE); +#endif +} + +using OptionNames = std::vector; + +namespace values { + +namespace parser_tool { + +struct IntegerDesc +{ + std::string negative = ""; + std::string base = ""; + std::string value = ""; +}; +struct ArguDesc { + std::string arg_name = ""; + bool grouping = false; + bool set_value = false; + std::string value = ""; +}; + +#ifdef CXXOPTS_NO_REGEX +inline IntegerDesc SplitInteger(const std::string &text) +{ + if (text.empty()) + { + throw_or_mimic(text); + } + IntegerDesc desc; + const char *pdata = text.c_str(); + if (*pdata == '-') + { + pdata += 1; + desc.negative = "-"; + } + if (strncmp(pdata, "0x", 2) == 0) + { + pdata += 2; + desc.base = "0x"; + } + if (*pdata != '\0') + { + desc.value = std::string(pdata); + } + else + { + throw_or_mimic(text); + } + return desc; +} + +inline bool IsTrueText(const std::string &text) +{ + const char *pdata = text.c_str(); + if (*pdata == 't' || *pdata == 'T') + { + pdata += 1; + if (strncmp(pdata, "rue\0", 4) == 0) + { + return true; + } + } + else if (strncmp(pdata, "1\0", 2) == 0) + { + return true; + } + return false; +} + +inline bool IsFalseText(const std::string &text) +{ + const char *pdata = text.c_str(); + if (*pdata == 'f' || *pdata == 'F') + { + pdata += 1; + if (strncmp(pdata, "alse\0", 5) == 0) + { + return true; + } + } + else if (strncmp(pdata, "0\0", 2) == 0) + { + return true; + } + return false; +} + +inline OptionNames split_option_names(const std::string &text) +{ + OptionNames split_names; + + std::string::size_type token_start_pos = 0; + auto length = text.length(); + + if (length == 0) + { + throw_or_mimic(text); + } + + while (token_start_pos < length) { + const auto &npos = std::string::npos; + auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos); + if (next_non_space_pos == npos) { + throw_or_mimic(text); + } + token_start_pos = next_non_space_pos; + auto next_delimiter_pos = text.find(',', token_start_pos); + if (next_delimiter_pos == token_start_pos) { + throw_or_mimic(text); + } + if (next_delimiter_pos == npos) { + next_delimiter_pos = length; + } + auto token_length = next_delimiter_pos - token_start_pos; + // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/ + { + const char* option_name_valid_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "_-.?"; + + if (!std::isalnum(text[token_start_pos], std::locale::classic()) || + text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) { + throw_or_mimic(text); + } + } + split_names.emplace_back(text.substr(token_start_pos, token_length)); + token_start_pos = next_delimiter_pos + 1; + } + return split_names; +} + +inline ArguDesc ParseArgument(const char *arg, bool &matched) +{ + ArguDesc argu_desc; + const char *pdata = arg; + matched = false; + if (strncmp(pdata, "--", 2) == 0) + { + pdata += 2; + if (isalnum(*pdata, std::locale::classic())) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_') + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + if (argu_desc.arg_name.length() > 1) + { + if (*pdata == '=') + { + argu_desc.set_value = true; + pdata += 1; + if (*pdata != '\0') + { + argu_desc.value = std::string(pdata); + } + matched = true; + } + else if (*pdata == '\0') + { + matched = true; + } + } + } + } + else if (strncmp(pdata, "-", 1) == 0) + { + pdata += 1; + argu_desc.grouping = true; + while (isalnum(*pdata, std::locale::classic())) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + matched = !argu_desc.arg_name.empty() && *pdata == '\0'; + } + return argu_desc; +} + +#else // CXXOPTS_NO_REGEX + +namespace { +CXXOPTS_LINKONCE +std::basic_regex integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); +CXXOPTS_LINKONCE +std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); +CXXOPTS_LINKONCE +std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); +CXXOPTS_LINKONCE +std::basic_regex option_matcher + ("--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"); +CXXOPTS_LINKONCE +std::basic_regex option_specifier + ("([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"); +CXXOPTS_LINKONCE +std::basic_regex option_specifier_separator(", *"); + +} // namespace + +inline IntegerDesc SplitInteger(const std::string &text) +{ + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw_or_mimic(text); + } + + IntegerDesc desc; + desc.negative = match[1]; + desc.base = match[2]; + desc.value = match[3]; + + if (match.length(4) > 0) + { + desc.base = match[5]; + desc.value = "0"; + return desc; + } + + return desc; +} + +inline bool IsTrueText(const std::string &text) +{ + std::smatch result; + std::regex_match(text, result, truthy_pattern); + return !result.empty(); +} + +inline bool IsFalseText(const std::string &text) +{ + std::smatch result; + std::regex_match(text, result, falsy_pattern); + return !result.empty(); +} + +// Gets the option names specified via a single, comma-separated string, +// and returns the separate, space-discarded, non-empty names +// (without considering which or how many are single-character) +inline OptionNames split_option_names(const std::string &text) +{ + if (!std::regex_match(text.c_str(), option_specifier)) + { + throw_or_mimic(text); + } + + OptionNames split_names; + + constexpr int use_non_matches { -1 }; + auto token_iterator = std::sregex_token_iterator( + text.begin(), text.end(), option_specifier_separator, use_non_matches); + std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names)); + return split_names; +} + +inline ArguDesc ParseArgument(const char *arg, bool &matched) +{ + std::match_results result; + std::regex_match(arg, result, option_matcher); + matched = !result.empty(); + + ArguDesc argu_desc; + if (matched) { + argu_desc.arg_name = result[1].str(); + argu_desc.set_value = result[2].length() > 0; + argu_desc.value = result[3].str(); + if (result[4].length() > 0) + { + argu_desc.grouping = true; + argu_desc.arg_name = result[4].str(); + } + } + + return argu_desc; +} + +#endif // CXXOPTS_NO_REGEX +#undef CXXOPTS_NO_REGEX +} // namespace parser_tool + +namespace detail { + +template +struct SignedCheck; + +template +struct SignedCheck +{ + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw_or_mimic(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw_or_mimic(text); + } + } + } +}; + +template +struct SignedCheck +{ + template + void + operator()(bool, U, const std::string&) const {} +}; + +template +void +check_signed_range(bool negative, U value, const std::string& text) +{ + SignedCheck::is_signed>()(negative, value, text); +} + +} // namespace detail + +template +void +checked_negate(R& r, T&& t, const std::string&, std::true_type) +{ + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + r = static_cast(-static_cast(t-1)-1); +} + +template +void +checked_negate(R&, T&&, const std::string& text, std::false_type) +{ + throw_or_mimic(text); +} + +template +void +integer_parser(const std::string& text, T& value) +{ + parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); + + using US = typename std::make_unsigned::type; + constexpr bool is_signed = std::numeric_limits::is_signed; + + const bool negative = int_desc.negative.length() > 0; + const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; + const std::string & value_match = int_desc.value; + + US result = 0; + + for (char ch : value_match) + { + US digit = 0; + + if (ch >= '0' && ch <= '9') + { + digit = static_cast(ch - '0'); + } + else if (base == 16 && ch >= 'a' && ch <= 'f') + { + digit = static_cast(ch - 'a' + 10); + } + else if (base == 16 && ch >= 'A' && ch <= 'F') + { + digit = static_cast(ch - 'A' + 10); + } + else + { + throw_or_mimic(text); + } + + const US next = static_cast(result * base + digit); + if (result > next) + { + throw_or_mimic(text); + } + + result = next; + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + checked_negate(value, result, text, std::integral_constant()); + } + else + { + value = static_cast(result); + } +} + +template +void stringstream_parser(const std::string& text, T& value) +{ + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic(text); + } +} + +template ::value>::type* = nullptr + > +void parse_value(const std::string& text, T& value) +{ + integer_parser(text, value); +} + +inline +void +parse_value(const std::string& text, bool& value) +{ + if (parser_tool::IsTrueText(text)) + { + value = true; + return; + } + + if (parser_tool::IsFalseText(text)) + { + value = false; + return; + } + + throw_or_mimic(text); +} + +inline +void +parse_value(const std::string& text, std::string& value) +{ + value = text; +} + +// The fallback parser. It uses the stringstream parser to parse all types +// that have not been overloaded explicitly. It has to be placed in the +// source code before all other more specialized templates. +template ::value>::type* = nullptr + > +void +parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); +} + +template +void +parse_value(const std::string& text, std::vector& value) +{ + if (text.empty()) { + T v; + parse_value(text, v); + value.emplace_back(std::move(v)); + return; + } + std::stringstream in(text); + std::string token; + while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } +} + +#ifdef CXXOPTS_HAS_OPTIONAL +template +void +parse_value(const std::string& text, std::optional& value) +{ + T result; + parse_value(text, result); + value = std::move(result); +} +#endif + +inline +void parse_value(const std::string& text, char& c) +{ + if (text.length() != 1) + { + throw_or_mimic(text); + } + + c = text[0]; +} + +template +struct type_is_container +{ + static constexpr bool value = false; +}; + +template +struct type_is_container> +{ + static constexpr bool value = true; +}; + +template +class abstract_value : public Value +{ + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + explicit abstract_value(T* t) + : m_store(t) + { + } + + ~abstract_value() override = default; + + abstract_value& operator=(const abstract_value&) = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + parse(const std::string& text) const override + { + parse_value(text, *m_store); + } + + bool + is_container() const override + { + return type_is_container::value; + } + + void + parse() const override + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const override + { + return m_default; + } + + bool + has_implicit() const override + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) override + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) override + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() override + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const override + { + return m_default_value; + } + + std::string + get_implicit_value() const override + { + return m_implicit_value; + } + + bool + is_boolean() const override + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + return *m_store; + } + + protected: + std::shared_ptr m_result{}; + T* m_store{}; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value{}; + std::string m_implicit_value{}; +}; + +template +class standard_value : public abstract_value +{ + public: + using abstract_value::abstract_value; + + CXXOPTS_NODISCARD + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } +}; + +template <> +class standard_value : public abstract_value +{ + public: + ~standard_value() override = default; + + standard_value() + { + set_default_and_implicit(); + } + + explicit standard_value(bool* b) + : abstract_value(b) + { + m_implicit = true; + m_implicit_value = "true"; + } + + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } +}; + +} // namespace values + +template +std::shared_ptr +value() +{ + return std::make_shared>(); +} + +template +std::shared_ptr +value(T& t) +{ + return std::make_shared>(&t); +} + +class OptionAdder; + +CXXOPTS_NODISCARD +inline +const std::string& +first_or_empty(const OptionNames& long_names) +{ + static const std::string empty{""}; + return long_names.empty() ? empty : long_names.front(); +} + +class OptionDetails +{ + public: + OptionDetails + ( + std::string short_, + OptionNames long_, + String desc, + std::shared_ptr val + ) + : m_short(std::move(short_)) + , m_long(std::move(long_)) + , m_desc(std::move(desc)) + , m_value(std::move(val)) + , m_count(0) + { + m_hash = std::hash{}(first_long_name() + m_short); + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_value(rhs.m_value->clone()) + , m_count(rhs.m_count) + { + } + + OptionDetails(OptionDetails&& rhs) = default; + + CXXOPTS_NODISCARD + const String& + description() const + { + return m_desc; + } + + CXXOPTS_NODISCARD + const Value& + value() const { + return *m_value; + } + + CXXOPTS_NODISCARD + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + CXXOPTS_NODISCARD + const std::string& + short_name() const + { + return m_short; + } + + CXXOPTS_NODISCARD + const std::string& + first_long_name() const + { + return first_or_empty(m_long); + } + + CXXOPTS_NODISCARD + const std::string& + essential_name() const + { + return m_long.empty() ? m_short : m_long.front(); + } + + CXXOPTS_NODISCARD + const OptionNames & + long_names() const + { + return m_long; + } + + std::size_t + hash() const + { + return m_hash; + } + + private: + std::string m_short{}; + OptionNames m_long{}; + String m_desc{}; + std::shared_ptr m_value{}; + int m_count; + + std::size_t m_hash{}; +}; + +struct HelpOptionDetails +{ + std::string s; + OptionNames l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; +}; + +struct HelpGroupDetails +{ + std::string name{}; + std::string description{}; + std::vector options{}; +}; + +class OptionValue +{ + public: + void + parse + ( + const std::shared_ptr& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + m_long_names = &details->long_names(); + } + + void + parse_default(const std::shared_ptr& details) + { + ensure_value(details); + m_default = true; + m_long_names = &details->long_names(); + m_value->parse(); + } + + void + parse_no_value(const std::shared_ptr& details) + { + m_long_names = &details->long_names(); + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnull-dereference") +#endif + + CXXOPTS_NODISCARD + std::size_t + count() const noexcept + { + return m_count; + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +CXXOPTS_DIAGNOSTIC_POP +#endif + + // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD + bool + has_default() const noexcept + { + return m_default; + } + + template + const T& + as() const + { + if (m_value == nullptr) { + throw_or_mimic( + m_long_names == nullptr ? "" : first_or_empty(*m_long_names)); + } + + return CXXOPTS_RTTI_CAST&>(*m_value).get(); + } + + private: + void + ensure_value(const std::shared_ptr& details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + + const OptionNames * m_long_names = nullptr; + // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, + // where the key has the string we point to. + std::shared_ptr m_value{}; + std::size_t m_count = 0; + bool m_default = false; +}; + +class KeyValue +{ + public: + KeyValue(std::string key_, std::string value_) noexcept + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + CXXOPTS_NODISCARD + const std::string& + key() const + { + return m_key; + } + + CXXOPTS_NODISCARD + const std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; +}; + +using ParsedHashMap = std::unordered_map; +using NameHashMap = std::unordered_map; + +class ParseResult +{ + public: + class Iterator + { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = KeyValue; + using difference_type = void; + using pointer = const KeyValue*; + using reference = const KeyValue&; + + Iterator() = default; + Iterator(const Iterator&) = default; + +// GCC complains about m_iter not being initialised in the member +// initializer list +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Weffc++") + Iterator(const ParseResult *pr, bool end=false) + : m_pr(pr) + { + if (end) + { + m_sequential = false; + m_iter = m_pr->m_defaults.end(); + } + else + { + m_sequential = true; + m_iter = m_pr->m_sequential.begin(); + + if (m_iter == m_pr->m_sequential.end()) + { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + } + } + } +CXXOPTS_DIAGNOSTIC_POP + + Iterator& operator++() + { + ++m_iter; + if(m_sequential && m_iter == m_pr->m_sequential.end()) + { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + return *this; + } + return *this; + } + + Iterator operator++(int) + { + Iterator retval = *this; + ++(*this); + return retval; + } + + bool operator==(const Iterator& other) const + { + return (m_sequential == other.m_sequential) && (m_iter == other.m_iter); + } + + bool operator!=(const Iterator& other) const + { + return !(*this == other); + } + + const KeyValue& operator*() + { + return *m_iter; + } + + const KeyValue* operator->() + { + return m_iter.operator->(); + } + + private: + const ParseResult* m_pr; + std::vector::const_iterator m_iter; + bool m_sequential = true; + }; + + ParseResult() = default; + ParseResult(const ParseResult&) = default; + + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, + std::vector default_opts, std::vector&& unmatched_args) + : m_keys(std::move(keys)) + , m_values(std::move(values)) + , m_sequential(std::move(sequential)) + , m_defaults(std::move(default_opts)) + , m_unmatched(std::move(unmatched_args)) + { + } + + ParseResult& operator=(ParseResult&&) = default; + ParseResult& operator=(const ParseResult&) = default; + + Iterator + begin() const + { + return Iterator(this); + } + + Iterator + end() const + { + return Iterator(this, true); + } + + std::size_t + count(const std::string& o) const + { + auto iter = m_keys.find(o); + if (iter == m_keys.end()) + { + return 0; + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + return 0; + } + + return viter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_keys.find(option); + + if (iter == m_keys.end()) + { + throw_or_mimic(option); + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + throw_or_mimic(option); + } + + return viter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + const std::vector& + unmatched() const + { + return m_unmatched; + } + + const std::vector& + defaults() const + { + return m_defaults; + } + + const std::string + arguments_string() const + { + std::string result; + for(const auto& kv: m_sequential) + { + result += kv.key() + " = " + kv.value() + "\n"; + } + for(const auto& kv: m_defaults) + { + result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; + } + return result; + } + + private: + NameHashMap m_keys{}; + ParsedHashMap m_values{}; + std::vector m_sequential{}; + std::vector m_defaults{}; + std::vector m_unmatched{}; +}; + +struct Option +{ + Option + ( + std::string opts, + std::string desc, + std::shared_ptr value = ::cxxopts::value(), + std::string arg_help = "" + ) + : opts_(std::move(opts)) + , desc_(std::move(desc)) + , value_(std::move(value)) + , arg_help_(std::move(arg_help)) + { + } + + std::string opts_; + std::string desc_; + std::shared_ptr value_; + std::string arg_help_; +}; + +using OptionMap = std::unordered_map>; +using PositionalList = std::vector; +using PositionalListIterator = PositionalList::const_iterator; + +class OptionParser +{ + public: + OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) + : m_options(options) + , m_positional(positional) + , m_allow_unrecognised(allow_unrecognised) + { + } + + ParseResult + parse(int argc, const char* const* argv); + + bool + consume_positional(const std::string& a, PositionalListIterator& next); + + void + checked_parse_arg + ( + int argc, + const char* const* argv, + int& current, + const std::shared_ptr& value, + const std::string& name + ); + + void + add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); + + void + parse_option + ( + const std::shared_ptr& value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(const std::shared_ptr& details); + + void + parse_no_value(const std::shared_ptr& details); + + private: + + void finalise_aliases(); + + const OptionMap& m_options; + const PositionalList& m_positional; + + std::vector m_sequential{}; + std::vector m_defaults{}; + bool m_allow_unrecognised; + + ParsedHashMap m_parsed{}; + NameHashMap m_keys{}; +}; + +class Options +{ + public: + + explicit Options(std::string program_name, std::string help_string = "") + : m_program(std::move(program_name)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_width(76) + , m_tab_expansion(false) + , m_options(std::make_shared()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + Options& + set_width(std::size_t width) + { + m_width = width; + return *this; + } + + Options& + set_tab_expansion(bool expansion=true) + { + m_tab_expansion = expansion; + return *this; + } + + ParseResult + parse(int argc, const char* const* argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_options + ( + const std::string& group, + std::initializer_list