From a6abf2ff12a385fccca63657fcb006d35260d9b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cs=C3=A1sz=C3=A1r=20M=C3=A1ty=C3=A1s?= Date: Tue, 13 Jun 2023 02:00:13 +0200 Subject: [PATCH] Merge ktxtools into main (#714) PR to merge the contents of the ktxtools branch into main. This PR should be functionally identical to the ktxtools branch. Briefs: Rebase ktxtools onto main Update the CTS golden files to pickup the changes in ASTC and ZSTD versions Add CTS testing to MinGW and Windows CI builds Pickup changes in image.hpp from main to ktxtools's image.hpp Fix any remaining warnings --- .gitattributes | 6 + .github/workflows/mingw.yml | 6 +- .github/workflows/windows.yml | 15 +- .gitignore | 3 + .gitmodules | 8 + .reuse/dep5 | 4 + .travis.yml | 29 +- BUILDING.md | 46 + CMakeLists.txt | 305 +- CONTRIBUTING.md | 3 +- README.md | 19 +- ci_scripts/build_linux.sh | 8 +- ci_scripts/build_macos.sh | 10 +- ci_scripts/build_win.ps1 | 10 +- ci_scripts/on_failure.sh | 1 - cmake/docs.cmake | 20 +- include/ktx.h | 28 +- .../java/org/khronos/ktx/KtxErrorCode.java | 4 +- .../org/khronos/ktx/KtxSupercmpScheme.java | 1 + interface/js_binding/ktx_wrapper.cpp | 4 + lib/basis_sgd.h | 2 +- lib/basis_transcode.cpp | 6 + lib/checkheader.c | 44 +- lib/dfdutils/colourspaces.c | 22 +- lib/dfdutils/createdfdtest.c | 2 +- lib/dfdutils/dfd.h | 45 +- lib/dfdutils/interpretdfd.c | 20 +- lib/dfdutils/interpretdfdtest.c | 2 +- lib/dfdutils/printdfd.c | 1060 +++++- lib/filestream.c | 16 +- lib/hashlist.c | 26 +- lib/info.c | 1023 +++++- lib/internalexport.def | 1 + lib/internalexport_mingw.def | 1 + lib/internalexport_write.def | 12 +- lib/internalexport_write_mingw.def | 12 +- lib/ktxint.h | 36 +- lib/mainpage.md | 6 +- lib/miniz_wrapper.cpp | 149 + lib/mkvkformatfiles | 5 +- lib/strings.c | 32 +- lib/texture2.c | 305 +- lib/texture2.h | 2 + lib/vkformat_check.c | 11 +- lib/writer2.c | 82 + libktx.doxy | 1 + other_projects/cxxopts/CMakeLists.txt | 82 + other_projects/cxxopts/cmake/cxxopts.cmake | 163 + other_projects/cxxopts/include/CMakeLists.txt | 23 + other_projects/cxxopts/include/cxxopts.hpp | 2837 +++++++++++++++++ tests/CMakeLists.txt | 6 + tests/cts | 1 + tests/transcodetests/CMakeLists.txt | 5 +- tools/CMakeLists.txt | 3 + tools/imageio/exr.imageio/exrinput.cc | 4 +- tools/imageio/npbm.imageio/npbminput.cc | 3 +- tools/ktx/CMakeLists.txt | 68 + tools/ktx/command.cpp | 46 + tools/ktx/command.h | 327 ++ tools/ktx/command_create.cpp | 2136 +++++++++++++ tools/ktx/command_encode.cpp | 226 ++ tools/ktx/command_extract.cpp | 1166 +++++++ tools/ktx/command_help.cpp | 198 ++ tools/ktx/command_info.cpp | 225 ++ tools/ktx/command_transcode.cpp | 296 ++ tools/ktx/command_validate.cpp | 191 ++ tools/ktx/compress_utils.h | 70 + tools/ktx/encode_utils.h | 521 +++ tools/ktx/format_descriptor.h | 65 + tools/ktx/formats.h | 1190 +++++++ tools/ktx/image.hpp | 1344 ++++++++ tools/ktx/ktx_main.cpp | 205 ++ tools/ktx/metrics_utils.h | 195 ++ tools/ktx/schema/info.json | 890 ++++++ tools/ktx/schema/validate.json | 38 + tools/ktx/transcode_utils.cpp | 75 + tools/ktx/transcode_utils.h | 90 + tools/ktx/utility.h | 628 ++++ tools/ktx/validate.cpp | 1871 +++++++++++ tools/ktx/validate.h | 38 + tools/ktx/validation_messages.h | 793 +++++ tools/ktx2check/ktx2check.cpp | 2 +- utils/stdafx.h | 6 +- 83 files changed, 19036 insertions(+), 444 deletions(-) create mode 100644 .gitmodules create mode 100644 lib/miniz_wrapper.cpp create mode 100644 other_projects/cxxopts/CMakeLists.txt create mode 100644 other_projects/cxxopts/cmake/cxxopts.cmake create mode 100644 other_projects/cxxopts/include/CMakeLists.txt create mode 100644 other_projects/cxxopts/include/cxxopts.hpp create mode 160000 tests/cts create mode 100644 tools/ktx/CMakeLists.txt create mode 100644 tools/ktx/command.cpp create mode 100644 tools/ktx/command.h create mode 100644 tools/ktx/command_create.cpp create mode 100644 tools/ktx/command_encode.cpp create mode 100644 tools/ktx/command_extract.cpp create mode 100644 tools/ktx/command_help.cpp create mode 100644 tools/ktx/command_info.cpp create mode 100644 tools/ktx/command_transcode.cpp create mode 100644 tools/ktx/command_validate.cpp create mode 100644 tools/ktx/compress_utils.h create mode 100644 tools/ktx/encode_utils.h create mode 100644 tools/ktx/format_descriptor.h create mode 100644 tools/ktx/formats.h create mode 100644 tools/ktx/image.hpp create mode 100644 tools/ktx/ktx_main.cpp create mode 100644 tools/ktx/metrics_utils.h create mode 100644 tools/ktx/schema/info.json create mode 100644 tools/ktx/schema/validate.json create mode 100644 tools/ktx/transcode_utils.cpp create mode 100644 tools/ktx/transcode_utils.h create mode 100644 tools/ktx/utility.h create mode 100644 tools/ktx/validate.cpp create mode 100644 tools/ktx/validate.h create mode 100644 tools/ktx/validation_messages.h 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