From 3bc79d8ec5ccea87c60a431d9a1d90edea990ade Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sat, 16 Oct 2021 19:17:05 +0200 Subject: [PATCH 01/76] Basis{ImageConverter,Importer}: update to basis_universal v1_15_update2 --- doc/building-plugins.dox | 2 +- modules/FindBasisUniversal.cmake | 26 ++++++++++++-- package/archlinux/magnum-plugins-git/PKGBUILD | 4 +-- package/ci/appveyor.yml | 5 +-- package/ci/basisu-msvc2019-16.6.patch | 12 ------- package/ci/circleci.yml | 3 +- package/ci/travis.yml | 2 +- package/homebrew/magnum-plugins.rb | 15 +++++--- .../BasisImageConverter.cpp | 34 ++++++++++++++++--- .../BasisImporter/BasisImporter.cpp | 2 +- 10 files changed, 69 insertions(+), 36 deletions(-) delete mode 100644 package/ci/basisu-msvc2019-16.6.patch diff --git a/doc/building-plugins.dox b/doc/building-plugins.dox index 4083dab84..273d4be56 100644 --- a/doc/building-plugins.dox +++ b/doc/building-plugins.dox @@ -227,7 +227,7 @@ enable them, do the following: - For @ref Trade::BasisImporter "BasisImporter" or @ref Trade::BasisImageConverter "BasisImageConverter", [download commit - `2f43afcc` of the Basis Universal repo](https://github.com/BinomialLLC/basis_universal/archive/2f43afcc97d0a5dafdb73b4e24e123cf9687a418.tar.gz), + `77b7df8e` of the Basis Universal repo](https://github.com/BinomialLLC/basis_universal/archive/77b7df8e5df3532a42ef3c76de0c14cc005d0f65.tar.gz), extract it into `src/external/basis-universal` (note the dash instead of an underscore) and set `WITH_BASISIMPORTER` / `WITH_BASISIMAGECONVERTER` to `ON` in `package/debian/rules` diff --git a/modules/FindBasisUniversal.cmake b/modules/FindBasisUniversal.cmake index a914e45bc..b7020f47e 100644 --- a/modules/FindBasisUniversal.cmake +++ b/modules/FindBasisUniversal.cmake @@ -58,6 +58,12 @@ if(${_index} GREATER -1) endif() macro(_basis_setup_source_file source) + # Compile any .c files as C++ since we can't guarantee that C is enabled + # in the calling scope, either inside project() or with enable_language(). + # Otherwise, they won't get compiled at all, leading to undefined symbols. + set_property(SOURCE ${source} PROPERTY LANGUAGE + CXX) + # Basis shouldn't override the MSVC iterator debug level as it would make # it inconsistent with the rest of the code if(CORRADE_TARGET_WINDOWS) @@ -73,7 +79,7 @@ macro(_basis_setup_source_file source) " -w") # Clang supports -w, but it doesn't have any effect on all the # -Wall -Wold-style-cast etc flags specified before. -Wno-everything does. - # Funnily enough this is not an issue on Emscripten.; + # Funnily enough this is not an issue on Emscripten. elseif(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang" AND NOT CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC") set_property(SOURCE ${source} APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-everything") @@ -126,19 +132,24 @@ foreach(_component ${BasisUniversal_FIND_COMPONENTS}) endif() set(BasisUniversalEncoder_SOURCES + ${BasisUniversalEncoder_DIR}/apg_bmp.c ${BasisUniversalEncoder_DIR}/basisu_astc_decomp.cpp ${BasisUniversalEncoder_DIR}/basisu_backend.cpp ${BasisUniversalEncoder_DIR}/basisu_basis_file.cpp + ${BasisUniversalEncoder_DIR}/basisu_bc7enc.cpp ${BasisUniversalEncoder_DIR}/basisu_comp.cpp ${BasisUniversalEncoder_DIR}/basisu_enc.cpp ${BasisUniversalEncoder_DIR}/basisu_etc.cpp ${BasisUniversalEncoder_DIR}/basisu_frontend.cpp ${BasisUniversalEncoder_DIR}/basisu_global_selector_palette_helpers.cpp ${BasisUniversalEncoder_DIR}/basisu_gpu_texture.cpp + ${BasisUniversalEncoder_DIR}/basisu_kernels_sse.cpp ${BasisUniversalEncoder_DIR}/basisu_pvrtc1_4.cpp ${BasisUniversalEncoder_DIR}/basisu_resampler.cpp ${BasisUniversalEncoder_DIR}/basisu_resample_filters.cpp ${BasisUniversalEncoder_DIR}/basisu_ssim.cpp + ${BasisUniversalEncoder_DIR}/basisu_uastc_enc.cpp + ${BasisUniversalEncoder_DIR}/jpgd.cpp ${BasisUniversalEncoder_DIR}/lodepng.cpp) foreach(_file ${BasisUniversalEncoder_SOURCES}) @@ -173,8 +184,6 @@ foreach(_component ${BasisUniversal_FIND_COMPONENTS}) # The rest is documented in the BasisImageConverter plugin itself. set_property(TARGET BasisUniversal::Encoder APPEND PROPERTY INTERFACE_LINK_LIBRARIES BasisUniversal::Transcoder) - set_property(TARGET BasisUniversal::Encoder APPEND PROPERTY - INTERFACE_COMPILE_DEFINITIONS "BASISU_NO_ITERATOR_DEBUG_LEVEL") endif() else() set(BasisUniversal_Encoder_FOUND TRUE) @@ -227,6 +236,17 @@ foreach(_component ${BasisUniversal_FIND_COMPONENTS}) set(BasisUniversalTranscoder_SOURCES ${BasisUniversalTranscoder_DIR}/basisu_transcoder.cpp) + # Not linking to zstddeclib.c because that leads to multiple + # definition errors in BasisUniversal::Encoder which links to + # BasisUniversal::Transcoder + find_path(BasisUniversalZstd_DIR NAMES zstd.c + HINTS "${BASIS_UNIVERSAL_DIR}/zstd" "${BASIS_UNIVERSAL_DIR}" + NO_CMAKE_FIND_ROOT_PATH) + if(BasisUniversalZstd_DIR) + list(APPEND BasisUniversalTranscoder_SOURCES + ${BasisUniversalZstd_DIR}/zstd.c) + endif() + foreach(_file ${BasisUniversalTranscoder_SOURCES}) _basis_setup_source_file(${_file}) endforeach() diff --git a/package/archlinux/magnum-plugins-git/PKGBUILD b/package/archlinux/magnum-plugins-git/PKGBUILD index f0fc76f02..533c820ff 100644 --- a/package/archlinux/magnum-plugins-git/PKGBUILD +++ b/package/archlinux/magnum-plugins-git/PKGBUILD @@ -1,7 +1,7 @@ # Author: mosra pkgname=magnum-plugins-git pkgver=2020.06.r119.g15b8cac9 -_basis_pkgver=2f43afcc97d0a5dafdb73b4e24e123cf9687a418 +_basis_pkgver=v1_15_update2 pkgrel=1 pkgdesc="Plugins for the Magnum C++11/C++14 graphics engine (Git version)" arch=('i686' 'x86_64') @@ -12,8 +12,6 @@ makedepends=('cmake' 'git' 'ninja') provides=('magnum-plugins') conflicts=('magnum-plugins') source=("git+git://github.com/mosra/magnum-plugins.git" - # A commit that's before the UASTC support (which is not implemented - # yet, because latest versions crash even on trivial tests) "https://github.com/BinomialLLC/basis_universal/archive/${_basis_pkgver}.tar.gz") sha1sums=('SKIP' 'b8d3995292c2c0bbedea943250087b0a9a92ca96') diff --git a/package/ci/appveyor.yml b/package/ci/appveyor.yml index 8f9eccc08..db343368f 100644 --- a/package/ci/appveyor.yml +++ b/package/ci/appveyor.yml @@ -82,12 +82,9 @@ install: - IF "%TARGET%" == "desktop" IF "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2017" IF "%COMPILER:~0,4%" == "msvc" appveyor DownloadFile https://ci.magnum.graphics/freetype-2.10.4-windows-2016.zip && 7z x freetype-2.10.4-windows-2016.zip -o%APPVEYOR_BUILD_FOLDER%\deps # Basis Universal -- set BASIS_VERSION=8565af680d1bd2ad56ab227ca7d96c56dfbe93ed +- set BASIS_VERSION=v1_15_update2 - IF NOT EXIST %APPVEYOR_BUILD_FOLDER%\basis_universal-%BASIS_VERSION%.zip appveyor DownloadFile https://github.com/BinomialLLC/basis_universal/archive/%BASIS_VERSION%.zip - 7z x %BASIS_VERSION%.zip && ren basis_universal-%BASIS_VERSION% basis_universal -# https://github.com/BinomialLLC/basis_universal/pull/106 -# TODO: remove once we update to UASTC-enabled Basis -- bash -c "cd basis_universal && patch -p1 < ../package/ci/basisu-msvc2019-16.6.patch" # SPIRV-Tools, for MSVC 2019, 2017 and clang-cl only # This line REQUIRES the COMPILER variable to be set on rt builds, otherwise it diff --git a/package/ci/basisu-msvc2019-16.6.patch b/package/ci/basisu-msvc2019-16.6.patch deleted file mode 100644 index 564d087a1..000000000 --- a/package/ci/basisu-msvc2019-16.6.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/basisu_enc.h b/basisu_enc.h -index c2b9133..0a0c3c6 100644 ---- a/basisu_enc.h -+++ b/basisu_enc.h -@@ -22,6 +22,7 @@ - #include - #include - #include -+#include - - #if !defined(_WIN32) || defined(__MINGW32__) - #include diff --git a/package/ci/circleci.yml b/package/ci/circleci.yml index 807450ec3..b91ed41ac 100644 --- a/package/ci/circleci.yml +++ b/package/ci/circleci.yml @@ -148,9 +148,8 @@ commands: steps: - run: name: Install Basis Universal - # Version before UASTC is a thing, as our tests crash with the new one command: | - export BASIS_VERSION=8565af680d1bd2ad56ab227ca7d96c56dfbe93ed + export BASIS_VERSION=v1_15_update2 mkdir -p $HOME/basis_universal && cd $HOME/basis_universal wget -nc https://github.com/BinomialLLC/basis_universal/archive/$BASIS_VERSION.tar.gz tar --strip-components 1 -xzf $BASIS_VERSION.tar.gz diff --git a/package/ci/travis.yml b/package/ci/travis.yml index ef2b05496..ade1e8d71 100644 --- a/package/ci/travis.yml +++ b/package/ci/travis.yml @@ -196,7 +196,7 @@ install: - if [ "$TRAVIS_OS_NAME" == "linux" ] && [ "$TARGET" == "desktop-sanitizers" ]; then cd $HOME ; wget https://ci.magnum.graphics/spirv-tools-2020.4-ubuntu-16.04-gcc5.zip && cd $HOME/deps && unzip $HOME/spirv-tools-2020.4-ubuntu-16.04-gcc5.zip && cd $TRAVIS_BUILD_DIR ; fi # Basis Universal -- export BASIS_VERSION=8565af680d1bd2ad56ab227ca7d96c56dfbe93ed && wget -nc https://github.com/BinomialLLC/basis_universal/archive/$BASIS_VERSION.zip && unzip -q $BASIS_VERSION; mv basis_universal-$BASIS_VERSION $HOME/basis_universal +- export BASIS_VERSION=v1_15_update2 && wget -nc https://github.com/BinomialLLC/basis_universal/archive/$BASIS_VERSION.zip && unzip -q $BASIS_VERSION; mv basis_universal-$BASIS_VERSION $HOME/basis_universal script: - if [ "$TRAVIS_OS_NAME" == "linux" ] && ( [ "$TARGET" == "desktop" ] || [ "$TARGET" == "desktop-sanitizers" ] ); then ./package/ci/unix-desktop.sh; fi diff --git a/package/homebrew/magnum-plugins.rb b/package/homebrew/magnum-plugins.rb index acc31cd2d..2cb146c4b 100644 --- a/package/homebrew/magnum-plugins.rb +++ b/package/homebrew/magnum-plugins.rb @@ -23,11 +23,16 @@ class MagnumPlugins < Formula depends_on "spirv-tools" => :recommended def install - # Bundle Basis Universal, a commit that's before the UASTC support (which - # is not implemented yet). The repo has massive useless files in its - # history, so we're downloading just a snapshot instead of a git clone. - # Also, WHY THE FUCK curl needs -L and -o?! why can't it just work?! - system "curl", "-L", "https://github.com/BinomialLLC/basis_universal/archive/2f43afcc97d0a5dafdb73b4e24e123cf9687a418.tar.gz", "-o", "src/external/basis-universal.tar.gz" + # Bundle Basis Universal, v1_15_update2 for HEAD builds, a commit that's + # before the UASTC support (which was not implemented yet) on 2020.06. + # The repo has massive useless files in its history, so we're downloading + # just a snapshot instead of a git clone. Also, WHY THE FUCK curl needs -L + # and -o?! why can't it just work?! + if build.head? + system "curl", "-L", "https://github.com/BinomialLLC/basis_universal/archive/v1_15_update2.tar.gz", "-o", "src/external/basis-universal.tar.gz" + else + system "curl", "-L", "https://github.com/BinomialLLC/basis_universal/archive/2f43afcc97d0a5dafdb73b4e24e123cf9687a418.tar.gz", "-o", "src/external/basis-universal.tar.gz" + end cd "src/external" do system "mkdir", "basis-universal" system "tar", "xzvf", "basis-universal.tar.gz", "-C", "basis-universal", "--strip-components=1" diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index fadf9a122..9bdd90cf4 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -31,10 +31,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include @@ -81,7 +81,33 @@ Containers::Array BasisImageConverter::doConvertToData(const ImageView2D& PARAM_CONFIG(y_flip, bool); PARAM_CONFIG(check_for_alpha, bool); PARAM_CONFIG(force_alpha, bool); - PARAM_CONFIG_FIX_NAME(seperate_rg_to_color_alpha, bool, "separate_rg_to_color_alpha"); + + std::string swizzle = configuration().value("swizzle"); + /* swizzle has precedence in the basisu commandline tool, do the same */ + if(swizzle.empty() && configuration().value("separate_rg_to_color_alpha")) + swizzle = "rrrg"; + + if(!swizzle.empty()) { + if(swizzle.size() != 4) { + Error{} << "Trade::BasisImageConverter::convertToData(): invalid swizzle length, expected 4 but got" << swizzle.size(); + return {}; + } + + if(swizzle.find_first_not_of("rgba") != std::string::npos) { + Error{} << "Trade::BasisImageConverter::convertToData(): invalid characters in swizzle" << swizzle; + return {}; + } + + for(std::size_t i = 0; i != 4; ++i) { + switch(swizzle[i]) { + case 'r': params.m_swizzle[i] = 0; break; + case 'g': params.m_swizzle[i] = 1; break; + case 'b': params.m_swizzle[i] = 2; break; + case 'a': params.m_swizzle[i] = 3; break; + default: CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } + } UnsignedInt threadCount = configuration().value("threads"); if(threadCount == 0) threadCount = std::thread::hardware_concurrency(); @@ -133,7 +159,7 @@ Containers::Array BasisImageConverter::doConvertToData(const ImageView2D& basis image from existing data as it is based on a std::vector, moreover we need to tightly pack it and flip Y. The `dst` is an Y-flipped view already to make the following loops simpler. */ - params.m_source_images.emplace_back(image.size().x(), image.size().y()); + params.m_source_images.push_back({uint32_t(image.size().x()), uint32_t(image.size().y())}); auto dst = Containers::arrayCast(Containers::StridedArrayView2D({params.m_source_images.back().get_ptr(), params.m_source_images.back().get_total_pixels()}, {std::size_t(image.size().y()), std::size_t(image.size().x())})).flipped<0>(); /* basis image is always RGBA, fill in alpha if necessary */ @@ -202,7 +228,7 @@ Containers::Array BasisImageConverter::doConvertToData(const ImageView2D& const basisu::uint8_vec& out = basis.get_output_basis_file(); Containers::Array fileData{NoInit, out.size()}; - Utility::copy(Containers::arrayCast(out), fileData); + Utility::copy(Containers::arrayView(out.data(), out.size()), fileData); return fileData; } diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 191687b9d..71d4343e9 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -269,7 +269,7 @@ Containers::Optional BasisImporter::doImage2D(const UnsignedInt id, rowStride = 0; /* left up to Basis to calculate */ outputRowsInPixels = 0; /* not used for compressed data */ outputSizeInBlocksOrPixels = totalBlocks; - dataSize = basis_get_bytes_per_block(format)*totalBlocks; + dataSize = basis_get_bytes_per_block_or_pixel(format)*totalBlocks; } Containers::Array dest{DefaultInit, dataSize}; if(!_state->transcoder->transcode_image_level(_state->in.data(), _state->in.size(), id, level, dest.data(), outputSizeInBlocksOrPixels, basist::transcoder_texture_format(format), flags, rowStride, nullptr, outputRowsInPixels)) { From d83eb5ba0b073e0784b6dfa27783168b355c995a Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 17 Oct 2021 01:08:16 +0200 Subject: [PATCH 02/76] BasisImporter: import KTX2 files TODO: tests --- .../BasisImporter/BasisImporter.cpp | 221 ++++++++++++++---- .../BasisImporter/Test/BasisImporterTest.cpp | 4 +- 2 files changed, 173 insertions(+), 52 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 71d4343e9..8bfa79332 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -26,22 +26,32 @@ #include "BasisImporter.h" -#include - +#include #include #include #include #include #include #include +#include #include #include #include +#include + namespace Magnum { namespace Trade { namespace { -/* Map BasisImporter::TargetFormat to CompressedPixelFormat. See the +/* Map BasisImporter::TargetFormat to (Compressed)PixelFormat. See the TargetFormat enum for details. */ +PixelFormat pixelFormat(BasisImporter::TargetFormat type) { + switch(type) { + case BasisImporter::TargetFormat::RGBA8: + return PixelFormat::RGBA8Unorm; + default: CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } +} + CompressedPixelFormat compressedPixelFormat(BasisImporter::TargetFormat type) { switch(type) { /** @todo sRGB once https://github.com/BinomialLLC/basis_universal/issues/66 @@ -125,9 +135,20 @@ namespace Magnum { namespace Trade { struct BasisImporter::State { /* There is only this type of codebook */ basist::etc1_global_selector_codebook codebook; - Containers::Optional transcoder; + + Containers::Optional basisTranscoder; + #if BASISD_SUPPORT_KTX2 + Containers::Optional ktx2Transcoder; + #else + Containers::Optional ktx2Transcoder; + #endif + Containers::Array in; - basist::basisu_file_info fileInfo; + + UnsignedInt numImages; + Containers::Array numLevels; + basist::basis_tex_format compressionType; + bool isYFlipped; bool noTranscodeFormatWarningPrinted = false; @@ -162,14 +183,15 @@ BasisImporter::~BasisImporter() = default; ImporterFeatures BasisImporter::doFeatures() const { return ImporterFeature::OpenData; } bool BasisImporter::doIsOpened() const { - /* Both the transcoder and then input data have to be present or both + /* Both a transcoder and the input data have to be present or both have to be empty */ - CORRADE_INTERNAL_ASSERT(!_state->transcoder == !_state->in); + CORRADE_INTERNAL_ASSERT(!(_state->basisTranscoder || _state->ktx2Transcoder) == !_state->in); return _state->in; } void BasisImporter::doClose() { - _state->transcoder = Containers::NullOpt; + _state->basisTranscoder = Containers::NullOpt; + _state->ktx2Transcoder = Containers::NullOpt; _state->in = nullptr; } @@ -186,35 +208,98 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { return; } - _state->transcoder.emplace(&_state->codebook); - Containers::ScopeGuard transcoderGuard{&_state->transcoder, [](Containers::Optional* o) { - *o = Containers::NullOpt; + /* Check if this is a KTX2 file. There's basist::g_ktx2_file_identifier but + that's hidden behind BASISD_SUPPORT_KTX2 so define it ourselves, taken + from KtxImporter/KtxHeader.h */ + constexpr char KtxFileIdentifier[12]{'\xab', 'K', 'T', 'X', ' ', '2', '0', '\xbb', '\r', '\n', '\x1a', '\n'}; + const bool isKTX2 = data.size() >= sizeof(KtxFileIdentifier) && + std::memcmp(data.begin(), KtxFileIdentifier, sizeof(KtxFileIdentifier)) == 0; + + /* Keep a copy of the data. We have to do this first because transcoders + may hold on to the pointer we pass into init(). While basis_transcoder + currently doesn't keep the pointer around, it might in the future and + ktx2_transcoder already does. */ + _state->in = Containers::Array{NoInit, data.size()}; + Utility::copy(data, _state->in); + + Containers::ScopeGuard resourceGuard{_state.get(), [](BasisImporter::State* state) { + state->basisTranscoder = Containers::NullOpt; + state->ktx2Transcoder = Containers::NullOpt; + state->in = nullptr; }}; - if(!_state->transcoder->validate_header(data.data(), data.size())) { - Error() << "Trade::BasisImporter::openData(): invalid header"; - return; - } - /* Save the global file info to avoid calling that again each time we check - for image count and whatnot; start transcoding */ - if(!_state->transcoder->get_file_info(data.data(), data.size(), _state->fileInfo) || - !_state->transcoder->start_transcoding(data.data(), data.size())) { - Error() << "Trade::BasisImporter::openData(): bad basis file"; + if(isKTX2) { + #if !BASISD_SUPPORT_KTX2 + Error() << "Trade::BasisImporter::openData(): opening a KTX2 file but Basis Universal was compiled without KTX2 support"; return; + #else + _state->ktx2Transcoder.emplace(&_state->codebook); + + /* Init handles all the validation checks, there's no extra function */ + if(!_state->ktx2Transcoder->init(_state->in.data(), _state->in.size())) { + Error() << "Trade::BasisImporter::openData(): invalid KTX2 header"; + return; + } + + /* Start transcoding */ + if(!_state->ktx2Transcoder->start_transcoding()) { + Error() << "Trade::BasisImporter::openData(): bad KTX2 file"; + return; + } + + /* Save some global file info we need later */ + + /* KTX2 files only ever contain one image */ + _state->numImages = 1; + _state->numLevels = Containers::Array{DirectInit, _state->ktx2Transcoder->get_levels()}; + + _state->compressionType = _state->ktx2Transcoder->get_format(); + const basisu::uint8_vec* orientation = _state->ktx2Transcoder->find_key("KTXorientation"); + /* The default without orientation key is Y-down. Y-up = flipped. */ + _state->isYFlipped = orientation && orientation->size() >= 2 && (*orientation)[1] == 'u'; + #endif + } else { + _state->basisTranscoder.emplace(&_state->codebook); + + if(!_state->basisTranscoder->validate_header(_state->in.data(), _state->in.size())) { + Error() << "Trade::BasisImporter::openData(): invalid basis header"; + return; + } + + /* Start transcoding */ + basist::basisu_file_info fileInfo; + if(!_state->basisTranscoder->get_file_info(_state->in.data(), _state->in.size(), fileInfo) || + !_state->basisTranscoder->start_transcoding(_state->in.data(), _state->in.size())) { + Error() << "Trade::BasisImporter::openData(): bad basis file"; + return; + } + + /* Save some global file info we need later. We can't save fileInfo + directly because that's specific to .basis files, and there's no + equivalent in ktx2_transcoder. */ + _state->numImages = fileInfo.m_total_images; + _state->numLevels = Containers::Array{NoInit, _state->numImages}; + Utility::copy(Containers::arrayView(fileInfo.m_image_mipmap_levels.begin(), fileInfo.m_image_mipmap_levels.size()), _state->numLevels); + + _state->compressionType = fileInfo.m_tex_format; + _state->isYFlipped = fileInfo.m_y_flipped; } - /* All good, release the transcoder guard and keep a copy of the data */ - transcoderGuard.release(); - _state->in = Containers::Array{NoInit, data.size()}; - Utility::copy(data, _state->in); + /* There has to be exactly one transcoder */ + CORRADE_INTERNAL_ASSERT(!_state->ktx2Transcoder != !_state->basisTranscoder); + /* Can't have a KTX2 transcoder without KTX2 support compiled into basisu */ + CORRADE_INTERNAL_ASSERT(BASISD_SUPPORT_KTX2 || !_state->ktx2Transcoder); + + /* All good, release the resource guard */ + resourceGuard.release(); } UnsignedInt BasisImporter::doImage2DCount() const { - return _state->fileInfo.m_total_images; + return _state->numImages; } UnsignedInt BasisImporter::doImage2DLevelCount(const UnsignedInt id) { - return _state->fileInfo.m_image_mipmap_levels[id]; + return _state->numLevels[id]; } Containers::Optional BasisImporter::doImage2D(const UnsignedInt id, const UnsignedInt level) { @@ -229,56 +314,92 @@ Containers::Optional BasisImporter::doImage2D(const UnsignedInt id, targetFormat = configuration().value("format"); if(UnsignedInt(targetFormat) == ~UnsignedInt{}) { Error() << "Trade::BasisImporter::image2D(): invalid transcoding target format" - << targetFormatStr.data() << Debug::nospace << ", expected to be one of EacR, EacRG, Etc1RGB, Etc2RGBA, Bc1RGB, Bc3RGBA, Bc4R, Bc5RG, Bc7RGB, Bc7RGBA, Pvrtc1RGB4bpp, Pvrtc1RGBA4bpp, Astc4x4RGBA, RGBA8"; + << targetFormatStr << Debug::nospace << ", expected to be one of EacR, EacRG, Etc1RGB, Etc2RGBA, Bc1RGB, Bc3RGBA, Bc4R, Bc5RG, Bc7RGB, Bc7RGBA, Pvrtc1RGB4bpp, Pvrtc1RGBA4bpp, Astc4x4RGBA, RGBA8"; return Containers::NullOpt; } } + const auto format = basist::transcoder_texture_format(Int(targetFormat)); + const bool isUncompressed = basist::basis_transcoder_format_is_uncompressed(format); + + /* Some target formats may be unsupported, either because support wasn't + compiled in or UASTC doesn't support a certain format. + None of the formats in TargetFormat are currently affected by UASTC. */ + if(!basist::basis_is_format_supported(format, _state->compressionType)) { + /** @todo Test */ + /** @todo Mention that some formats may not be compiled in? */ + Error{} << "Trade::BasisImporter::image2D(): unsupported transcoding target format" + << targetFormatStr << "for a" << (_state->compressionType == basist::basis_tex_format::cUASTC4x4 ? "UASTC" : "ETC1S") << "image"; + return Containers::NullOpt; + } - basist::basisu_image_info info; - /* Header validation etc. is already done in doOpenData() and id is - bounds-checked against doImage2DCount() by AbstractImporter, so by - looking at the code there's nothing else that could fail and wasn't - already caught before. That means we also can't craft any file to cover - an error path, so turning this into an assert. When this blows up for - someome, we'd most probably need to harden doOpenData() to catch that, - not turning this into a graceful error. */ - CORRADE_INTERNAL_ASSERT_OUTPUT(_state->transcoder->get_image_info(_state->in.data(), _state->in.size(), info, id)); + /** @todo Don't decode to PVRTC if width/height (not origWidth/origHeight?) + is not a power-of-two: + https://github.com/BinomialLLC/basis_universal/blob/77b7df8e5df3532a42ef3c76de0c14cc005d0f65/basisu_tool.cpp#L1458 */ UnsignedInt origWidth, origHeight, totalBlocks; - /* Same as above, it checks for state we already verified before. If this - blows up for someone, we can reconsider. */ - CORRADE_INTERNAL_ASSERT_OUTPUT(_state->transcoder->get_image_level_desc(_state->in.data(), _state->in.size(), id, level, origWidth, origHeight, totalBlocks)); + if(_state->ktx2Transcoder) { + #if BASISD_SUPPORT_KTX2 + basist::ktx2_image_level_info levelInfo; + /* Header validation etc. is already done in doOpenData() and id is + bounds-checked against doImage2DCount() by AbstractImporter, so by + looking at the code there's nothing else that could fail and wasn't + already caught before. That means we also can't craft any file to + cover an error path, so turning this into an assert. When this blows + up for someome, we'd most probably need to harden doOpenData() to + catch that, not turning this into a graceful error. + Layer/face index doesn't affect the output we're interested in, so + 0, 0 is enough here. */ + CORRADE_INTERNAL_ASSERT_OUTPUT(_state->ktx2Transcoder->get_image_level_info(levelInfo, level, 0, 0)); + + origWidth = levelInfo.m_orig_width; + origHeight = levelInfo.m_orig_height; + totalBlocks = levelInfo.m_total_blocks; + #endif + } else { + /* Same as above, it checks for state we already verified before. If this + blows up for someone, we can reconsider. */ + CORRADE_INTERNAL_ASSERT_OUTPUT(_state->basisTranscoder->get_image_level_desc(_state->in.data(), _state->in.size(), id, level, origWidth, origHeight, totalBlocks)); + } /* No flags used by transcode_image_level() by default */ const std::uint32_t flags = 0; - if(!_state->fileInfo.m_y_flipped) { + if(!_state->isYFlipped) { /** @todo replace with the flag once the PR is submitted */ Warning{} << "Trade::BasisImporter::image2D(): the image was not encoded Y-flipped, imported data will have wrong orientation"; //flags |= basist::basisu_transcoder::cDecodeFlagsFlipY; } - Vector2i size{Int(origWidth), Int(origHeight)}; - UnsignedInt dataSize, rowStride, outputSizeInBlocksOrPixels, outputRowsInPixels; - if(targetFormat == BasisImporter::TargetFormat::RGBA8) { + const Vector2i size{Int(origWidth), Int(origHeight)}; + UnsignedInt rowStride, outputRowsInPixels, outputSizeInBlocksOrPixels; + if(isUncompressed) { rowStride = size.x(); outputRowsInPixels = size.y(); outputSizeInBlocksOrPixels = size.product(); - dataSize = 4*outputSizeInBlocksOrPixels; } else { rowStride = 0; /* left up to Basis to calculate */ outputRowsInPixels = 0; /* not used for compressed data */ outputSizeInBlocksOrPixels = totalBlocks; - dataSize = basis_get_bytes_per_block_or_pixel(format)*totalBlocks; } + const UnsignedInt dataSize = basis_get_bytes_per_block_or_pixel(format)*outputSizeInBlocksOrPixels; Containers::Array dest{DefaultInit, dataSize}; - if(!_state->transcoder->transcode_image_level(_state->in.data(), _state->in.size(), id, level, dest.data(), outputSizeInBlocksOrPixels, basist::transcoder_texture_format(format), flags, rowStride, nullptr, outputRowsInPixels)) { - Error{} << "Trade::BasisImporter::image2D(): transcoding failed"; - return Containers::NullOpt; + + if(_state->ktx2Transcoder) { + #if BASISD_SUPPORT_KTX2 + if(!_state->ktx2Transcoder->transcode_image_level(level, 0, 0, dest.data(), outputSizeInBlocksOrPixels, format, flags, rowStride, outputRowsInPixels)) { + Error{} << "Trade::BasisImporter::image2D(): transcoding failed"; + return Containers::NullOpt; + } + #endif + } else { + if(!_state->basisTranscoder->transcode_image_level(_state->in.data(), _state->in.size(), id, level, dest.data(), outputSizeInBlocksOrPixels, format, flags, rowStride, nullptr, outputRowsInPixels)) { + Error{} << "Trade::BasisImporter::image2D(): transcoding failed"; + return Containers::NullOpt; + } } - if(targetFormat == BasisImporter::TargetFormat::RGBA8) - return Trade::ImageData2D{PixelFormat::RGBA8Unorm, size, std::move(dest)}; + if(isUncompressed) + return Trade::ImageData2D{pixelFormat(targetFormat), size, std::move(dest)}; else return Trade::ImageData2D{compressedPixelFormat(targetFormat), size, std::move(dest)}; } diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index 966e8b1f4..f9fd05f82 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -154,7 +154,7 @@ void BasisImporterTest::invalid() { Error redirectError{&out}; CORRADE_VERIFY(!importer->openData("NotABasisFile")); - CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): invalid header\n"); + CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): invalid basis header\n"); } void BasisImporterTest::unconfigured() { @@ -217,7 +217,7 @@ void BasisImporterTest::fileTooShort() { CORRADE_VERIFY(!importer->openData(basisData)); CORRADE_COMPARE(out.str(), - "Trade::BasisImporter::openData(): invalid header\n" + "Trade::BasisImporter::openData(): invalid basis header\n" "Trade::BasisImporter::openData(): bad basis file\n"); } From 09f13ddfc393b72ab1079a894790d63fe2710e49 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 17 Oct 2021 01:10:06 +0200 Subject: [PATCH 03/76] BasisImporter: import sRGB images with the appropriate format TODO: tests, docs --- .../BasisImporter/BasisImporter.cpp | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 8bfa79332..017e42a73 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -44,44 +44,42 @@ namespace Magnum { namespace Trade { namespace { /* Map BasisImporter::TargetFormat to (Compressed)PixelFormat. See the TargetFormat enum for details. */ -PixelFormat pixelFormat(BasisImporter::TargetFormat type) { +PixelFormat pixelFormat(BasisImporter::TargetFormat type, bool isSrgb) { switch(type) { case BasisImporter::TargetFormat::RGBA8: - return PixelFormat::RGBA8Unorm; + return isSrgb ? PixelFormat::RGBA8Srgb : PixelFormat::RGBA8Unorm; default: CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } } -CompressedPixelFormat compressedPixelFormat(BasisImporter::TargetFormat type) { +CompressedPixelFormat compressedPixelFormat(BasisImporter::TargetFormat type, bool isSrgb) { switch(type) { - /** @todo sRGB once https://github.com/BinomialLLC/basis_universal/issues/66 - is fixed */ case BasisImporter::TargetFormat::Etc1RGB: - return CompressedPixelFormat::Etc2RGB8Unorm; + return isSrgb ? CompressedPixelFormat::Etc2RGB8Srgb : CompressedPixelFormat::Etc2RGB8Unorm; case BasisImporter::TargetFormat::Etc2RGBA: - return CompressedPixelFormat::Etc2RGBA8Unorm; + return isSrgb ? CompressedPixelFormat::Etc2RGBA8Srgb : CompressedPixelFormat::Etc2RGBA8Unorm; case BasisImporter::TargetFormat::Bc1RGB: - return CompressedPixelFormat::Bc1RGBUnorm; + return isSrgb ? CompressedPixelFormat::Bc1RGBSrgb : CompressedPixelFormat::Bc1RGBUnorm; case BasisImporter::TargetFormat::Bc3RGBA: - return CompressedPixelFormat::Bc3RGBAUnorm; - /** @todo use bc7/bc4/bc5 based on channel count? needs a bit from the - above issue as well */ + return isSrgb ? CompressedPixelFormat::Bc3RGBASrgb : CompressedPixelFormat::Bc3RGBAUnorm; + /** @todo use bc7/bc4/bc5 based on channel count? needs a bit from + https://github.com/BinomialLLC/basis_universal/issues/66 */ case BasisImporter::TargetFormat::Bc4R: return CompressedPixelFormat::Bc4RUnorm; case BasisImporter::TargetFormat::Bc5RG: return CompressedPixelFormat::Bc5RGUnorm; case BasisImporter::TargetFormat::Bc7RGB: - return CompressedPixelFormat::Bc7RGBAUnorm; + return isSrgb ? CompressedPixelFormat::Bc7RGBASrgb : CompressedPixelFormat::Bc7RGBAUnorm; case BasisImporter::TargetFormat::Bc7RGBA: - return CompressedPixelFormat::Bc7RGBAUnorm; + return isSrgb ? CompressedPixelFormat::Bc7RGBASrgb : CompressedPixelFormat::Bc7RGBAUnorm; case BasisImporter::TargetFormat::PvrtcRGB4bpp: - return CompressedPixelFormat::PvrtcRGB4bppUnorm; + return isSrgb ? CompressedPixelFormat::PvrtcRGB4bppSrgb : CompressedPixelFormat::PvrtcRGB4bppUnorm; case BasisImporter::TargetFormat::PvrtcRGBA4bpp: - return CompressedPixelFormat::PvrtcRGBA4bppUnorm; + return isSrgb ? CompressedPixelFormat::PvrtcRGBA4bppSrgb : CompressedPixelFormat::PvrtcRGBA4bppUnorm; case BasisImporter::TargetFormat::Astc4x4RGBA: - return CompressedPixelFormat::Astc4x4RGBAUnorm; + return isSrgb ? CompressedPixelFormat::Astc4x4RGBASrgb : CompressedPixelFormat::Astc4x4RGBAUnorm; /** @todo use etc2/eacR/eacRG based on channel count? needs a bit from - the above issue as well */ + https://github.com/BinomialLLC/basis_universal/issues/66 */ case BasisImporter::TargetFormat::EacR: return CompressedPixelFormat::EacR11Unorm; case BasisImporter::TargetFormat::EacRG: @@ -149,6 +147,7 @@ struct BasisImporter::State { Containers::Array numLevels; basist::basis_tex_format compressionType; bool isYFlipped; + bool isSrgb; bool noTranscodeFormatWarningPrinted = false; @@ -257,6 +256,7 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { const basisu::uint8_vec* orientation = _state->ktx2Transcoder->find_key("KTXorientation"); /* The default without orientation key is Y-down. Y-up = flipped. */ _state->isYFlipped = orientation && orientation->size() >= 2 && (*orientation)[1] == 'u'; + _state->isSrgb = _state->ktx2Transcoder->get_dfd_transfer_func() == basist::KTX2_KHR_DF_TRANSFER_SRGB; #endif } else { _state->basisTranscoder.emplace(&_state->codebook); @@ -283,6 +283,11 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { _state->compressionType = fileInfo.m_tex_format; _state->isYFlipped = fileInfo.m_y_flipped; + + /* For some reason cBASISHeaderFlagSRGB is not exposed in basisu_file_info, + get it from the header directly */ + const basist::basis_file_header& pHeader = *reinterpret_cast(_state->in.data()); + _state->isSrgb = pHeader.m_flags & basist::basis_header_flags::cBASISHeaderFlagSRGB; } /* There has to be exactly one transcoder */ @@ -399,9 +404,9 @@ Containers::Optional BasisImporter::doImage2D(const UnsignedInt id, } if(isUncompressed) - return Trade::ImageData2D{pixelFormat(targetFormat), size, std::move(dest)}; + return Trade::ImageData2D{pixelFormat(targetFormat, _state->isSrgb), size, std::move(dest)}; else - return Trade::ImageData2D{compressedPixelFormat(targetFormat), size, std::move(dest)}; + return Trade::ImageData2D{compressedPixelFormat(targetFormat, _state->isSrgb), size, std::move(dest)}; } void BasisImporter::setTargetFormat(TargetFormat format) { From 75e47e7089e62f13cb7fb44beb136dfc7a83fa7e Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 17 Oct 2021 12:44:23 +0200 Subject: [PATCH 04/76] BasisImporter: handle missing zstd gracefully If zstd can't be found by FindBasisUniversal.cmake, compile with Zstandard (de)compression support disabled. At runtime, detect this and print a useful error (instead of "transcoding failed"). --- modules/FindBasisUniversal.cmake | 18 +++++++++--- .../BasisImporter/BasisImporter.cpp | 28 +++++++++++++------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/modules/FindBasisUniversal.cmake b/modules/FindBasisUniversal.cmake index b7020f47e..5577aef45 100644 --- a/modules/FindBasisUniversal.cmake +++ b/modules/FindBasisUniversal.cmake @@ -236,15 +236,25 @@ foreach(_component ${BasisUniversal_FIND_COMPONENTS}) set(BasisUniversalTranscoder_SOURCES ${BasisUniversalTranscoder_DIR}/basisu_transcoder.cpp) - # Not linking to zstddeclib.c because that leads to multiple - # definition errors in BasisUniversal::Encoder which links to - # BasisUniversal::Transcoder + set(BasisUniversalTranscoder_DEFINITIONS "BASISU_NO_ITERATOR_DEBUG_LEVEL") + + # Not linking to zstddeclib.c because together with Encoder + # linking to zstd.c this would lead to duplicate symbol + # errors. + # @todo Unused functions *should* be removed by LTO but is + # there a better way? find_path(BasisUniversalZstd_DIR NAMES zstd.c HINTS "${BASIS_UNIVERSAL_DIR}/zstd" "${BASIS_UNIVERSAL_DIR}" NO_CMAKE_FIND_ROOT_PATH) if(BasisUniversalZstd_DIR) list(APPEND BasisUniversalTranscoder_SOURCES ${BasisUniversalZstd_DIR}/zstd.c) + else() + # If zstd wasn't found, disable Zstandard supercompression + # support at compile time. The zstd.h include is hidden + # behind this definition as well. + list(APPEND BasisUniversalTranscoder_DEFINITIONS + "BASISD_SUPPORT_KTX2_ZSTD=0") endif() foreach(_file ${BasisUniversalTranscoder_SOURCES}) @@ -263,7 +273,7 @@ foreach(_component ${BasisUniversal_FIND_COMPONENTS}) set_property(TARGET BasisUniversal::Transcoder APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${BasisUniversalTranscoder_INCLUDE_DIR}) set_property(TARGET BasisUniversal::Transcoder APPEND PROPERTY - INTERFACE_COMPILE_DEFINITIONS "BASISU_NO_ITERATOR_DEBUG_LEVEL") + INTERFACE_COMPILE_DEFINITIONS ${BasisUniversalTranscoder_DEFINITIONS}) set_property(TARGET BasisUniversal::Transcoder APPEND PROPERTY INTERFACE_SOURCES "${BasisUniversalTranscoder_SOURCES}") endif() diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 017e42a73..0c42fd0af 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -228,18 +228,30 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { }}; if(isKTX2) { - #if !BASISD_SUPPORT_KTX2 - Error() << "Trade::BasisImporter::openData(): opening a KTX2 file but Basis Universal was compiled without KTX2 support"; - return; - #else + if(!basist::basisu_transcoder_supports_ktx2()) { + Error() << "Trade::BasisImporter::openData(): opening a KTX2 file but Basis Universal was compiled without KTX2 support"; + return; + } + + #if BASISD_SUPPORT_KTX2 _state->ktx2Transcoder.emplace(&_state->codebook); - /* Init handles all the validation checks, there's no extra function */ + /* Init handles all the validation checks, there's no extra function + for that */ if(!_state->ktx2Transcoder->init(_state->in.data(), _state->in.size())) { Error() << "Trade::BasisImporter::openData(): invalid KTX2 header"; return; } + /* Check for supercompression and print a useful error if basisu was + compiled without Zstandard support. Not exposed in ktx2_transcoder, + get it from the KTX2 header directly. */ + const basist::ktx2_header& header = *reinterpret_cast(_state->in.data()); + if(header.m_supercompression_scheme == basist::KTX2_SS_ZSTANDARD && !basist::basisu_transcoder_supports_ktx2_zstd()) { + Error() << "Trade::BasisImporter::openData(): file uses Zstandard supercompression but Basis Universal was compiled without Zstandard support"; + return; + } + /* Start transcoding */ if(!_state->ktx2Transcoder->start_transcoding()) { Error() << "Trade::BasisImporter::openData(): bad KTX2 file"; @@ -285,9 +297,9 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { _state->isYFlipped = fileInfo.m_y_flipped; /* For some reason cBASISHeaderFlagSRGB is not exposed in basisu_file_info, - get it from the header directly */ - const basist::basis_file_header& pHeader = *reinterpret_cast(_state->in.data()); - _state->isSrgb = pHeader.m_flags & basist::basis_header_flags::cBASISHeaderFlagSRGB; + get it from the basis header directly */ + const basist::basis_file_header& header = *reinterpret_cast(_state->in.data()); + _state->isSrgb = header.m_flags & basist::basis_header_flags::cBASISHeaderFlagSRGB; } /* There has to be exactly one transcoder */ From 7b9d26a45f5848a73fda165b1943cbbd91256bfb Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 17 Oct 2021 18:43:23 +0200 Subject: [PATCH 05/76] BasisImporter: add KtxImporter alias --- src/MagnumPlugins/BasisImporter/BasisImporter.conf | 1 + src/MagnumPlugins/BasisImporter/BasisImporter.h | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.conf b/src/MagnumPlugins/BasisImporter/BasisImporter.conf index a7865a7e0..bce60f6f5 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.conf +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.conf @@ -12,6 +12,7 @@ provides=BasisImporterPvrtcRGB4bpp provides=BasisImporterPvrtcRGBA4bpp provides=BasisImporterAstc4x4RGBA provides=BasisImporterRGBA8 +provides=KtxImporter # [configuration_] [configuration] diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index adf47e101..547ef1a7e 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -59,18 +59,19 @@ namespace Magnum { namespace Trade { @m_keywords{BasisImporterEacR BasisImporterEacRG BasisImporterEtc1RGB} @m_keywords{BasisImporterEtc2RGBA BasisImporterBc1RGB BasisImporterBc3RGBA} @m_keywords{BasisImporterBc4R BasisImporterBc5RG BasisImporterBc7RGB} @m_keywords{BasisImporterBc7RGBA BasisImporterPvrtc1RGB4bpp} @m_keywords{BasisImporterPvrtc1RGBA4bpp BasisImporterAstc4x4RGBA} -@m_keywords{BasisImporterRGBA8} +@m_keywords{BasisImporterRGBA8 KtxImporter} Supports [Basis Universal](https://github.com/binomialLLC/basis_universal) -(`*.basis`) compressed images by parsing and transcoding files into an -explicitly specified GPU format (see @ref Trade-BasisImporter-target-format). +compressed images (`*.basis` or `.ktx2`) by parsing and transcoding files into +an explicitly specified GPU format (see @ref Trade-BasisImporter-target-format). You can use @ref BasisImageConverter to transcode images into this format. This plugin provides `BasisImporterEacR`, `BasisImporterEacRG`, `BasisImporterEtc1RGB`, `BasisImporterEtc2RGBA`, `BasisImporterBc1RGB`, `BasisImporterBc3RGBA`, `BasisImporterBc4R`, `BasisImporterBc5RG`, `BasisImporterBc7RGB`, `BasisImporterBc7RGBA`, `BasisImporterPvrtc1RGB4bpp`, -`BasisImporterPvrtc1RGBA4bpp`, `BasisImporterAstc4x4RGBA`, `BasisImporterRGBA8`. +`BasisImporterPvrtc1RGBA4bpp`, `BasisImporterAstc4x4RGBA`, `BasisImporterRGBA8`, +`KtxImporter`. @m_class{m-block m-success} From aec8e3dceaeba25cdc5458bb9175d2a31417f56c Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 17 Oct 2021 21:19:19 +0200 Subject: [PATCH 06/76] BasisImageConverter: support sRGB formats This sets the correct basis parameters depending on the image format. Can still be overridden by config values, if present. TODO tests --- .../BasisImageConverter.conf | 10 +++-- .../BasisImageConverter.cpp | 38 ++++++++++++++----- .../BasisImageConverter/BasisImageConverter.h | 10 +++-- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf index 02495bac6..c47aefd5a 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf @@ -6,8 +6,9 @@ # Options quality_level=128 -# sRGB images should have this enabled, turn this flag off for linear images -perceptual=true +# Treat images as sRGB color data, rather than linear intensity. Leave blank to +# determine from the image format. +perceptual= debug=false debug_images=false compute_stats=false @@ -31,8 +32,9 @@ disable_hierarchical_endpoint_codebooks=false # Mipmap generation options mip_gen=false -# Generate mipmaps assuming sRGB input, turn this flag off for linear images -mip_srgb=true +# Generate mipmaps assuming sRGB color data, rather than linear intensity. +# Leave blank to determine from the image format. +mip_srgb= mip_scale=1.0 mip_filter=kaiser mip_renormalize=false diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index 9bdd90cf4..cbf00b7fe 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -53,20 +53,38 @@ ImageConverterFeatures BasisImageConverter::doFeatures() const { return ImageCon Containers::Array BasisImageConverter::doConvertToData(const ImageView2D& image) { /* Check input */ - if(image.format() != PixelFormat::RGB8Unorm && - image.format() != PixelFormat::RGBA8Unorm && - image.format() != PixelFormat::RG8Unorm && - image.format() != PixelFormat::R8Unorm) - { - Error{} << "Trade::BasisImageConverter::convertToData(): unsupported format" << image.format(); - return {}; + bool isSrgb; + switch(image.format()) { + case PixelFormat::RGBA8Unorm: + case PixelFormat::RGB8Unorm: + case PixelFormat::RG8Unorm: + case PixelFormat::R8Unorm: + isSrgb = false; + break; + case PixelFormat::RGBA8Srgb: + case PixelFormat::RGB8Srgb: + case PixelFormat::RG8Srgb: + case PixelFormat::R8Srgb: + isSrgb = true; + break; + default: + Error{} << "Trade::BasisImageConverter::convertToData(): unsupported format" << image.format(); + return {}; } + basisu::basis_compressor_params params; + + /* Options deduced from input data. Can be overridden by config values, if + present. */ + params.m_perceptual = isSrgb; + params.m_mip_srgb = isSrgb; + /* To retain sanity, keep this in the same order and grouping as in the conf file */ - basisu::basis_compressor_params params; - #define PARAM_CONFIG(name, type) params.m_##name = configuration().value(#name) - #define PARAM_CONFIG_FIX_NAME(name, type, fixed) params.m_##name = configuration().value(fixed) + #define PARAM_CONFIG(name, type) \ + if(configuration().hasValue(#name)) params.m_##name = configuration().value(#name) + #define PARAM_CONFIG_FIX_NAME(name, type, fixed) \ + if(configuration().hasValue(fixed)) params.m_##name = configuration().value(fixed) /* Options */ PARAM_CONFIG(quality_level, int); PARAM_CONFIG(perceptual, bool); diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h index d4afc46a9..078d968d5 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h @@ -58,10 +58,12 @@ namespace Magnum { namespace Trade { @m_since_{plugins,2019,10} Creates [Basis Universal](https://github.com/binomialLLC/basis_universal) -(`*.basis`) files from images with format @ref PixelFormat::R8Unorm, -@ref PixelFormat::RG8Unorm, @ref PixelFormat::RGB8Unorm or -@ref PixelFormat::RGBA8Unorm. Use @ref BasisImporter to import images in this -format. +(`*.basis`) files from images with format +@ref PixelFormat::R8Unorm, @ref PixelFormat::R8Srgb, +@ref PixelFormat::RG8Unorm, @ref PixelFormat::RG8Srgb, +@ref PixelFormat::RGB8Unorm, @ref PixelFormat::RGB8Srgb, +@ref PixelFormat::RGBA8Unorm or @ref PixelFormat::RGBA8Srgb. +Use @ref BasisImporter to import images in this format. @m_class{m-block m-success} From 564f380b0d1756016b1f489d1ef748d09dcc61b2 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 17 Oct 2021 21:40:38 +0200 Subject: [PATCH 07/76] BasisImageConverter: support writing KTX2 files TODO tests --- .../BasisImageConverter/BasisImageConverter.conf | 5 +++++ .../BasisImageConverter/BasisImageConverter.cpp | 12 +++++++++++- .../BasisImageConverter/BasisImageConverter.h | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf index c47aefd5a..722da65aa 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf @@ -55,6 +55,11 @@ global_palette_bits=8 global_modifier_bits=8 hybrid_selector_codebook_quality_threshold=2.0 +# KTX2 options +create_ktx2_file=false +ktx2_uastc_supercompression=false +ktx2_zstd_supercompression_level=6 + # Set various fields in the Basis file header userdata0=0 userdata1=0 diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index cbf00b7fe..181a2a185 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -159,6 +159,13 @@ Containers::Array BasisImageConverter::doConvertToData(const ImageView2D& PARAM_CONFIG_FIX_NAME(global_mod_bits, int, "global_modifier_bits"); PARAM_CONFIG_FIX_NAME(hybrid_sel_cb_quality_thresh, float, "hybrid_sel_codebook_quality_threshold"); + /* KTX2 options */ + PARAM_CONFIG(create_ktx2_file, bool); + params.m_ktx2_uastc_supercompression = + configuration().value("ktx2_uastc_supercompression") ? basist::KTX2_SS_ZSTANDARD : basist::KTX2_SS_NONE; + PARAM_CONFIG(ktx2_zstd_supercompression_level, int); + params.m_ktx2_srgb_transfer_func = params.m_perceptual; + /* Set various fields in the Basis file header */ PARAM_CONFIG(userdata0, int); PARAM_CONFIG(userdata1, int); @@ -231,6 +238,9 @@ Containers::Array BasisImageConverter::doConvertToData(const ImageView2D& /* process() will have printed additional error information to stderr */ Error{} << "Trade::BasisImageConverter::convertToData(): assembling basis file data or transcoding failed"; return {}; + case basisu::basis_compressor::error_code::cECFailedCreateKTX2File: + Error{} << "Trade::BasisImageConverter::convertToData(): assembling KTX2 file failed"; + return {}; /* LCOV_EXCL_START */ case basisu::basis_compressor::error_code::cECFailedFontendExtract: @@ -243,7 +253,7 @@ Containers::Array BasisImageConverter::doConvertToData(const ImageView2D& /* LCOV_EXCL_STOP */ } - const basisu::uint8_vec& out = basis.get_output_basis_file(); + const basisu::uint8_vec& out = params.m_create_ktx2_file ? basis.get_output_ktx2_file() : basis.get_output_basis_file(); Containers::Array fileData{NoInit, out.size()}; Utility::copy(Containers::arrayView(out.data(), out.size()), fileData); diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h index 078d968d5..542eac4e2 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h @@ -58,7 +58,7 @@ namespace Magnum { namespace Trade { @m_since_{plugins,2019,10} Creates [Basis Universal](https://github.com/binomialLLC/basis_universal) -(`*.basis`) files from images with format +compressed image files (`*.basis` or `*.ktx2`) from images with format @ref PixelFormat::R8Unorm, @ref PixelFormat::R8Srgb, @ref PixelFormat::RG8Unorm, @ref PixelFormat::RG8Srgb, @ref PixelFormat::RGB8Unorm, @ref PixelFormat::RGB8Srgb, From 256efbc94e71787df11d1f0c21bc25f85fdef8e0 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 17 Oct 2021 23:14:00 +0200 Subject: [PATCH 08/76] BasisImageConverter: support user-supplied mip levels TODO tests --- .../BasisImageConverter.cpp | 105 +++++++++++------- .../BasisImageConverter/BasisImageConverter.h | 15 ++- 2 files changed, 74 insertions(+), 46 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index 181a2a185..a592228cf 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -49,12 +49,13 @@ BasisImageConverter::BasisImageConverter() = default; BasisImageConverter::BasisImageConverter(PluginManager::AbstractManager& manager, const std::string& plugin): AbstractImageConverter{manager, plugin} {} -ImageConverterFeatures BasisImageConverter::doFeatures() const { return ImageConverterFeature::Convert2DToData; } +ImageConverterFeatures BasisImageConverter::doFeatures() const { return ImageConverterFeature::ConvertLevels2DToData; } -Containers::Array BasisImageConverter::doConvertToData(const ImageView2D& image) { +Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayView imageLevels) { /* Check input */ + const PixelFormat format = imageLevels.front().format(); bool isSrgb; - switch(image.format()) { + switch(format) { case PixelFormat::RGBA8Unorm: case PixelFormat::RGB8Unorm: case PixelFormat::RG8Unorm: @@ -68,10 +69,19 @@ Containers::Array BasisImageConverter::doConvertToData(const ImageView2D& isSrgb = true; break; default: - Error{} << "Trade::BasisImageConverter::convertToData(): unsupported format" << image.format(); + Error{} << "Trade::BasisImageConverter::convertToData(): unsupported format" << format; return {}; } + const Vector2i size = imageLevels.front().size(); + + const UnsignedInt numMipmaps = Math::min(imageLevels.size(), Math::log2(size.max()) + 1); + if(imageLevels.size() > numMipmaps) { + Error{} << "Trade::BasisImageConverter::convertToData(): there can be only" << numMipmaps << + "levels with base image size" << imageLevels.front().size() << "but got" << imageLevels.size(); + return {}; + } + basisu::basis_compressor_params params; /* Options deduced from input data. Can be overridden by config values, if @@ -180,39 +190,58 @@ Containers::Array BasisImageConverter::doConvertToData(const ImageView2D& basist::etc1_global_selector_codebook sel_codebook(basist::g_global_selector_cb_size, basist::g_global_selector_cb); params.m_pSel_codebook = &sel_codebook; - /* Copy image data into the basis image. There is no way to construct a - basis image from existing data as it is based on a std::vector, moreover - we need to tightly pack it and flip Y. The `dst` is an Y-flipped view - already to make the following loops simpler. */ - params.m_source_images.push_back({uint32_t(image.size().x()), uint32_t(image.size().y())}); - auto dst = Containers::arrayCast(Containers::StridedArrayView2D({params.m_source_images.back().get_ptr(), params.m_source_images.back().get_total_pixels()}, {std::size_t(image.size().y()), std::size_t(image.size().x())})).flipped<0>(); - - /* basis image is always RGBA, fill in alpha if necessary */ - if(image.format() == PixelFormat::RGBA8Unorm) { - auto src = image.pixels>(); - for(std::size_t y = 0; y != src.size()[0]; ++y) - for(std::size_t x = 0; x != src.size()[1]; ++x) - dst[y][x] = src[y][x]; - - } else if(image.format() == PixelFormat::RGB8Unorm) { - auto src = image.pixels>(); - for(std::size_t y = 0; y != src.size()[0]; ++y) - for(std::size_t x = 0; x != src.size()[1]; ++x) - dst[y][x] = src[y][x]; /* Alpha implicitly 255 */ - - } else if(image.format() == PixelFormat::RG8Unorm) { - auto src = image.pixels>(); - for(std::size_t y = 0; y != src.size()[0]; ++y) - for(std::size_t x = 0; x != src.size()[1]; ++x) - dst[y][x] = Math::gather<'r', 'r', 'r', 'g'>(src[y][x]); - - } else if(image.format() == PixelFormat::R8Unorm) { - auto src = image.pixels>(); - for(std::size_t y = 0; y != src.size()[0]; ++y) - for(std::size_t x = 0; x != src.size()[1]; ++x) - dst[y][x] = Math::gather<'r', 'r', 'r'>(src[y][x]); - - } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + /* The base mip is in m_source_images, mip 1 and higher go into + m_source_mipmap_images. If m_source_mipmap_images is not empty, mip + generation is disabled. */ + params.m_source_images.resize(1); + params.m_source_mipmap_images.resize(1); + params.m_source_mipmap_images[0].resize(imageLevels.size() - 1); + + for(UnsignedInt i = 0; i != imageLevels.size(); ++i) { + const Vector2i mipSize = Math::max(size >> i, 1); + const auto& image = imageLevels[i]; + + if(image.size() != mipSize) { + Error() << "Trade::BasisImageConverter::convertToData(): expected " + "size" << mipSize << "for level" << i << "but got" << image.size(); + return {}; + } + + /* Copy image data into the basis image. There is no way to construct a + basis image from existing data as it is based on basisu::vector, + moreover we need to tightly pack it and flip Y. The `dst` is a Y-flipped + view already to make the following loops simpler. */ + basisu::image& basisImage = i == 0 ? params.m_source_images[0] : params.m_source_mipmap_images[0][i - 1]; + basisImage = {uint32_t(image.size().x()), uint32_t(image.size().y())}; + auto dst = Containers::arrayCast(Containers::StridedArrayView2D({basisImage.get_ptr(), basisImage.get_total_pixels()}, {std::size_t(image.size().y()), std::size_t(image.size().x())})).flipped<0>(); + + /* basis image is always RGBA, fill in alpha if necessary */ + if(format == PixelFormat::RGBA8Unorm) { + auto src = image.pixels>(); + for(std::size_t y = 0; y != src.size()[0]; ++y) + for(std::size_t x = 0; x != src.size()[1]; ++x) + dst[y][x] = src[y][x]; + + } else if(format == PixelFormat::RGB8Unorm) { + auto src = image.pixels>(); + for(std::size_t y = 0; y != src.size()[0]; ++y) + for(std::size_t x = 0; x != src.size()[1]; ++x) + dst[y][x] = src[y][x]; /* Alpha implicitly 255 */ + + } else if(format == PixelFormat::RG8Unorm) { + auto src = image.pixels>(); + for(std::size_t y = 0; y != src.size()[0]; ++y) + for(std::size_t x = 0; x != src.size()[1]; ++x) + dst[y][x] = Math::gather<'r', 'r', 'r', 'g'>(src[y][x]); + + } else if(format == PixelFormat::R8Unorm) { + auto src = image.pixels>(); + for(std::size_t y = 0; y != src.size()[0]; ++y) + for(std::size_t x = 0; x != src.size()[1]; ++x) + dst[y][x] = Math::gather<'r', 'r', 'r'>(src[y][x]); + + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } basisu::basis_compressor basis; basis.init(params); @@ -256,7 +285,7 @@ Containers::Array BasisImageConverter::doConvertToData(const ImageView2D& const basisu::uint8_vec& out = params.m_create_ktx2_file ? basis.get_output_ktx2_file() : basis.get_output_basis_file(); Containers::Array fileData{NoInit, out.size()}; - Utility::copy(Containers::arrayView(out.data(), out.size()), fileData); + Utility::copy(Containers::arrayCast(Containers::arrayView(out.data(), out.size())), fileData); return fileData; } diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h index 542eac4e2..82d658dec 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h @@ -114,15 +114,14 @@ See @ref building-plugins, @ref cmake-plugins, @ref plugins and @section Trade-BasisImageConverter-behavior Behavior and limitations -@subsection Trade-BasisImageConverter-behavior-multiple-images Multiple images in one file +@subsection Trade-BasisImageConverter-behavior-multilevel Multilevel images -Due to limitations in the @ref AbstractImageConverter API, it's currently not -possible to create a Basis file containing multiple images --- you'd need to -use the upstream `basisu` tool for that instead. +Images can be saved with multiple levels by using the list variants of +@ref convertToFile() / @ref convertToData(). Largest level is expected to be +first, with each following level having width and height divided by two, +rounded down. Incomplete mip chains are supported. -Supplying custom mip levels will be possible when the converter gets updated to -[the upcoming version 1.16](https://github.com/BinomialLLC/basis_universal/commit/ee626cec19e8e2d206bfc127296dfd9519352dc6). Right now, there's only a -possibility to generate the mip levels from the top-level image using the +To generate mip levels from a single top-level image instead, you can use the @cb{.ini} mip_gen @ce @ref Trade-BasisImageConverter-configuration "configuration option". @subsection Trade-BasisImageConverter-behavior-loading Loading the plugin fails with undefined symbol: pthread_create @@ -164,7 +163,7 @@ class MAGNUM_BASISIMAGECONVERTER_EXPORT BasisImageConverter: public AbstractImag private: MAGNUM_BASISIMAGECONVERTER_LOCAL ImageConverterFeatures doFeatures() const override; - MAGNUM_BASISIMAGECONVERTER_LOCAL Containers::Array doConvertToData(const ImageView2D& image) override; + MAGNUM_BASISIMAGECONVERTER_LOCAL Containers::Array doConvertToData(Containers::ArrayView imageLevels) override; }; }} From c603aa50f18a6f6337ac3fe0469e8877db270227 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 17 Oct 2021 23:23:07 +0200 Subject: [PATCH 09/76] BasisImporter: import array/cube/3D images Array layers, cube faces and z slices are imported in an extra image dimension, ie. Image3D. Image type is exposed via texture(). TODO tests, docs, some extra validation on .basis files --- .../BasisImporter/BasisImporter.cpp | 209 ++++++++++++++---- .../BasisImporter/BasisImporter.h | 13 +- 2 files changed, 183 insertions(+), 39 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 0c42fd0af..05bf24f3b 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include @@ -143,8 +144,15 @@ struct BasisImporter::State { Containers::Array in; + TextureType textureType; + /* Only > 1 for plain 2D .basis files with multiple images. Anything else + (cube/array/volume) contains a single logical image. KTX2 only supports + one image to begin with. */ UnsignedInt numImages; + /* Number of faces/layers/z-slices in the third image dimension, 1 if 2D */ + UnsignedInt numSlices; Containers::Array numLevels; + basist::basis_tex_format compressionType; bool isYFlipped; bool isSrgb; @@ -260,14 +268,25 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { /* Save some global file info we need later */ + /* Remember the type for doTexture(). ktx2_transcoder::init() already + checked we're dealing with a valid 2D texture. */ + if(_state->ktx2Transcoder->get_faces() != 1) + _state->textureType = _state->ktx2Transcoder->get_layers() > 0 ? TextureType::CubeMapArray : TextureType::CubeMap; + else + _state->textureType = _state->ktx2Transcoder->get_layers() > 0 ? TextureType::Texture2DArray : TextureType::Texture2D; + /* KTX2 files only ever contain one image */ _state->numImages = 1; + _state->numSlices = _state->ktx2Transcoder->get_faces()*Math::max(_state->ktx2Transcoder->get_layers(), 1u); _state->numLevels = Containers::Array{DirectInit, _state->ktx2Transcoder->get_levels()}; _state->compressionType = _state->ktx2Transcoder->get_format(); + + /* Get y-flip flag from KTXorientation key/value entry. If it's + missing, the default is Y-down. Y-up = flipped. */ const basisu::uint8_vec* orientation = _state->ktx2Transcoder->find_key("KTXorientation"); - /* The default without orientation key is Y-down. Y-up = flipped. */ _state->isYFlipped = orientation && orientation->size() >= 2 && (*orientation)[1] == 'u'; + _state->isSrgb = _state->ktx2Transcoder->get_dfd_transfer_func() == basist::KTX2_KHR_DF_TRANSFER_SRGB; #endif } else { @@ -289,9 +308,70 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { /* Save some global file info we need later. We can't save fileInfo directly because that's specific to .basis files, and there's no equivalent in ktx2_transcoder. */ - _state->numImages = fileInfo.m_total_images; + + /* Remember the type for doTexture(). Depending on the type, we're + either dealing with multiple independent 2D images or each image is + an array layer, cubemap face or depth slice with the same + resolution. */ + + /** @todo Validate this, the transcoder doesn't seem to + check anything like: + - 2D array and 3D images should have the same resolution + (can be checked in doImage2D) + - image count for cube maps should be a multiple of 6 + */ + + /* This is checked by basis_transcoder::start_transcoding() */ + CORRADE_INTERNAL_ASSERT(fileInfo.m_total_images > 0); + + /* Default, swapped for cBASISTexType2D */ + _state->numImages = 1; + _state->numSlices = fileInfo.m_total_images; + + switch(fileInfo.m_tex_type) { + case basist::basis_texture_type::cBASISTexType2D: + std::swap(_state->numImages, _state->numSlices); + _state->textureType = TextureType::Texture2D; + break; + case basist::basis_texture_type::cBASISTexTypeVideoFrames: + /* We don't do anything special with video frames, treat them + like array layers */ + case basist::basis_texture_type::cBASISTexType2DArray: + _state->textureType = TextureType::Texture2DArray; + break; + case basist::basis_texture_type::cBASISTexTypeCubemapArray: + _state->textureType = _state->numImages > 6 ? TextureType::CubeMapArray : TextureType::CubeMap; + break; + case basist::basis_texture_type::cBASISTexTypeVolume: + _state->textureType = TextureType::Texture3D; + break; + default: + /* This is caught by basis_transcoder::get_file_info() */ + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + CORRADE_INTERNAL_ASSERT(fileInfo.m_image_mipmap_levels.size() == fileInfo.m_total_images); + _state->numLevels = Containers::Array{NoInit, _state->numImages}; - Utility::copy(Containers::arrayView(fileInfo.m_image_mipmap_levels.begin(), fileInfo.m_image_mipmap_levels.size()), _state->numLevels); + UnsignedInt firstLevels = 0; + for(UnsignedInt i = 0; i != fileInfo.m_total_images; ++i) { + const UnsignedInt levels = fileInfo.m_image_mipmap_levels[i]; + CORRADE_INTERNAL_ASSERT(levels > 0); + + /** @todo Error if levels > 1 for Texture3D, the mips don't halve + in the z dimension */ + + if(i == 0) + firstLevels = levels; + + if(levels != firstLevels) { + Error() << "Trade::BasisImporter::openData(): mismatching level count for successive image slices"; + return; + } + + if(i < _state->numImages) + _state->numLevels[i] = levels; + } _state->compressionType = fileInfo.m_tex_format; _state->isYFlipped = fileInfo.m_y_flipped; @@ -306,32 +386,31 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { CORRADE_INTERNAL_ASSERT(!_state->ktx2Transcoder != !_state->basisTranscoder); /* Can't have a KTX2 transcoder without KTX2 support compiled into basisu */ CORRADE_INTERNAL_ASSERT(BASISD_SUPPORT_KTX2 || !_state->ktx2Transcoder); + /* 1D images should be treated like 2D images with width or height 1 */ + CORRADE_INTERNAL_ASSERT(_state->textureType != TextureType::Texture1D && _state->textureType != TextureType::Texture1DArray); /* All good, release the resource guard */ resourceGuard.release(); } -UnsignedInt BasisImporter::doImage2DCount() const { - return _state->numImages; -} - -UnsignedInt BasisImporter::doImage2DLevelCount(const UnsignedInt id) { - return _state->numLevels[id]; -} +template +Containers::Optional> BasisImporter::doImage(const UnsignedInt id, const UnsignedInt level) { + static_assert(dimensions >= 2 && dimensions <= 3, "Only 2D and 3D images are supported"); + constexpr const char* prefixes[2]{"Trade::BasisImporter::image2D():", "Trade::BasisImporter::image3D():"}; + constexpr const char* prefix = prefixes[dimensions - 2]; -Containers::Optional BasisImporter::doImage2D(const UnsignedInt id, const UnsignedInt level) { - std::string targetFormatStr = configuration().value("format"); + const std::string targetFormatStr = configuration().value("format"); TargetFormat targetFormat; if(targetFormatStr.empty()) { if(!_state->noTranscodeFormatWarningPrinted) - Warning{} << "Trade::BasisImporter::image2D(): no format to transcode to was specified, falling back to uncompressed RGBA8. To get rid of this warning either load the plugin via one of its BasisImporterEtc1RGB, ... aliases, or explicitly set the format option in plugin configuration."; + Warning{} << prefix << "no format to transcode to was specified, falling back to uncompressed RGBA8. To get rid of this warning either load the plugin via one of its BasisImporterEtc1RGB, ... aliases, or explicitly set the format option in plugin configuration."; targetFormat = TargetFormat::RGBA8; _state->noTranscodeFormatWarningPrinted = true; } else { targetFormat = configuration().value("format"); if(UnsignedInt(targetFormat) == ~UnsignedInt{}) { - Error() << "Trade::BasisImporter::image2D(): invalid transcoding target format" - << targetFormatStr << Debug::nospace << ", expected to be one of EacR, EacRG, Etc1RGB, Etc2RGBA, Bc1RGB, Bc3RGBA, Bc4R, Bc5RG, Bc7RGB, Bc7RGBA, Pvrtc1RGB4bpp, Pvrtc1RGBA4bpp, Astc4x4RGBA, RGBA8"; + Error() << prefix << "invalid transcoding target format" << targetFormatStr << Debug::nospace + << ", expected to be one of EacR, EacRG, Etc1RGB, Etc2RGBA, Bc1RGB, Bc3RGBA, Bc4R, Bc5RG, Bc7RGB, Bc7RGBA, Pvrtc1RGB4bpp, Pvrtc1RGBA4bpp, Astc4x4RGBA, RGBA8"; return Containers::NullOpt; } } @@ -343,10 +422,10 @@ Containers::Optional BasisImporter::doImage2D(const UnsignedInt id, compiled in or UASTC doesn't support a certain format. None of the formats in TargetFormat are currently affected by UASTC. */ if(!basist::basis_is_format_supported(format, _state->compressionType)) { - /** @todo Test */ - /** @todo Mention that some formats may not be compiled in? */ - Error{} << "Trade::BasisImporter::image2D(): unsupported transcoding target format" - << targetFormatStr << "for a" << (_state->compressionType == basist::basis_tex_format::cUASTC4x4 ? "UASTC" : "ETC1S") << "image"; + /** @todo Mention that some formats may not be compiled in? Since it's + really the only way to trigger this error. */ + Error{} << prefix << "unsupported transcoding target format" << targetFormatStr << "for a" + << (_state->compressionType == basist::basis_tex_format::cUASTC4x4 ? "UASTC" : "ETC1S") << "image"; return Containers::NullOpt; } @@ -354,7 +433,7 @@ Containers::Optional BasisImporter::doImage2D(const UnsignedInt id, is not a power-of-two: https://github.com/BinomialLLC/basis_universal/blob/77b7df8e5df3532a42ef3c76de0c14cc005d0f65/basisu_tool.cpp#L1458 */ - UnsignedInt origWidth, origHeight, totalBlocks; + UnsignedInt origWidth, origHeight, totalBlocks, numFaces, numLayers; if(_state->ktx2Transcoder) { #if BASISD_SUPPORT_KTX2 basist::ktx2_image_level_info levelInfo; @@ -366,19 +445,27 @@ Containers::Optional BasisImporter::doImage2D(const UnsignedInt id, up for someome, we'd most probably need to harden doOpenData() to catch that, not turning this into a graceful error. Layer/face index doesn't affect the output we're interested in, so - 0, 0 is enough here. */ + 0, 0 is enough here to apply to all slices. */ CORRADE_INTERNAL_ASSERT_OUTPUT(_state->ktx2Transcoder->get_image_level_info(levelInfo, level, 0, 0)); origWidth = levelInfo.m_orig_width; origHeight = levelInfo.m_orig_height; totalBlocks = levelInfo.m_total_blocks; + numFaces = _state->ktx2Transcoder->get_faces(); + numLayers = Math::max(_state->ktx2Transcoder->get_layers(), 1u); #endif } else { /* Same as above, it checks for state we already verified before. If this blows up for someone, we can reconsider. */ CORRADE_INTERNAL_ASSERT_OUTPUT(_state->basisTranscoder->get_image_level_desc(_state->in.data(), _state->in.size(), id, level, origWidth, origHeight, totalBlocks)); + numFaces = (_state->textureType == TextureType::CubeMap || _state->textureType == TextureType::CubeMapArray) ? 6 : 1; + numLayers = _state->numSlices / numFaces; } + CORRADE_INTERNAL_ASSERT(numFaces == 1 || numFaces == 6); + CORRADE_INTERNAL_ASSERT(numLayers > 0); + CORRADE_INTERNAL_ASSERT(numFaces*numLayers == _state->numSlices); + /* No flags used by transcode_image_level() by default */ const std::uint32_t flags = 0; if(!_state->isYFlipped) { @@ -387,38 +474,86 @@ Containers::Optional BasisImporter::doImage2D(const UnsignedInt id, //flags |= basist::basisu_transcoder::cDecodeFlagsFlipY; } - const Vector2i size{Int(origWidth), Int(origHeight)}; + const Vector3ui size{origWidth, origHeight, _state->numSlices}; UnsignedInt rowStride, outputRowsInPixels, outputSizeInBlocksOrPixels; if(isUncompressed) { rowStride = size.x(); outputRowsInPixels = size.y(); - outputSizeInBlocksOrPixels = size.product(); + outputSizeInBlocksOrPixels = size.x()*size.y(); } else { rowStride = 0; /* left up to Basis to calculate */ outputRowsInPixels = 0; /* not used for compressed data */ + /** @todo Make sure this is correct for layer/cube/depth textures */ outputSizeInBlocksOrPixels = totalBlocks; } - const UnsignedInt dataSize = basis_get_bytes_per_block_or_pixel(format)*outputSizeInBlocksOrPixels; + + const UnsignedInt sliceSize = basis_get_bytes_per_block_or_pixel(format)*outputSizeInBlocksOrPixels; + const UnsignedInt dataSize = sliceSize*size.z(); Containers::Array dest{DefaultInit, dataSize}; - if(_state->ktx2Transcoder) { - #if BASISD_SUPPORT_KTX2 - if(!_state->ktx2Transcoder->transcode_image_level(level, 0, 0, dest.data(), outputSizeInBlocksOrPixels, format, flags, rowStride, outputRowsInPixels)) { - Error{} << "Trade::BasisImporter::image2D(): transcoding failed"; - return Containers::NullOpt; - } - #endif - } else { - if(!_state->basisTranscoder->transcode_image_level(_state->in.data(), _state->in.size(), id, level, dest.data(), outputSizeInBlocksOrPixels, format, flags, rowStride, nullptr, outputRowsInPixels)) { - Error{} << "Trade::BasisImporter::image2D(): transcoding failed"; - return Containers::NullOpt; + /* There's no function for transcoding the entire level, so loop over all + layers and faces and transcode each one. This matches the image layout + imported by KtxImporter, ie. all faces +X through -Z for the first + layer, then all faces of the second layer, etc. */ + /** @todo Check if the Basis layout actually matches this one */ + UnsignedInt currentId = id; + for(UnsignedInt l = 0; l != numLayers; ++l) { + for(UnsignedInt f = 0; f != numFaces; ++f) { + const UnsignedInt offset = (l*numFaces + f)*sliceSize; + if(_state->ktx2Transcoder) { + #if BASISD_SUPPORT_KTX2 + if(!_state->ktx2Transcoder->transcode_image_level(level, l, f, dest.data() + offset, outputSizeInBlocksOrPixels, format, flags, rowStride, outputRowsInPixels)) { + Error{} << "Trade::BasisImporter::image2D(): transcoding failed"; + return Containers::NullOpt; + } + #endif + } else { + if(!_state->basisTranscoder->transcode_image_level(_state->in.data(), _state->in.size(), currentId, level, dest.data() + offset, outputSizeInBlocksOrPixels, format, flags, rowStride, nullptr, outputRowsInPixels)) { + Error{} << "Trade::BasisImporter::image2D(): transcoding failed"; + return Containers::NullOpt; + } + ++currentId; + } } } if(isUncompressed) - return Trade::ImageData2D{pixelFormat(targetFormat, _state->isSrgb), size, std::move(dest)}; + return Trade::ImageData{pixelFormat(targetFormat, _state->isSrgb), Math::Vector::pad(Vector3i{size}), std::move(dest)}; else - return Trade::ImageData2D{compressedPixelFormat(targetFormat, _state->isSrgb), size, std::move(dest)}; + return Trade::ImageData{compressedPixelFormat(targetFormat, _state->isSrgb), Math::Vector::pad(Vector3i{size}), std::move(dest)}; +} + +UnsignedInt BasisImporter::doImage2DCount() const { + return _state->textureType == TextureType::Texture2D ? _state->numImages : 0; +} + +UnsignedInt BasisImporter::doImage2DLevelCount(const UnsignedInt id) { + return _state->numLevels[id]; +} + +Containers::Optional BasisImporter::doImage2D(const UnsignedInt id, const UnsignedInt level) { + return doImage<2>(id, level); +} + +UnsignedInt BasisImporter::doImage3DCount() const { + return _state->textureType != TextureType::Texture2D ? _state->numImages : 0; +} + +UnsignedInt BasisImporter::doImage3DLevelCount(const UnsignedInt id) { + return _state->numLevels[id]; +} + +Containers::Optional BasisImporter::doImage3D(const UnsignedInt id, const UnsignedInt level) { + return doImage<3>(id, level); +} + +UnsignedInt BasisImporter::doTextureCount() const { + return _state->numImages; +} + +Containers::Optional BasisImporter::doTexture(UnsignedInt id) { + return TextureData{_state->textureType, SamplerFilter::Linear, SamplerFilter::Linear, + SamplerMipmap::Linear, SamplerWrapping::Repeat, id}; } void BasisImporter::setTargetFormat(TargetFormat format) { diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index 547ef1a7e..4ab760516 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -301,8 +301,6 @@ class MAGNUM_BASISIMPORTER_EXPORT BasisImporter: public AbstractImporter { void setTargetFormat(TargetFormat format); private: - struct State; - MAGNUM_BASISIMPORTER_LOCAL ImporterFeatures doFeatures() const override; MAGNUM_BASISIMPORTER_LOCAL bool doIsOpened() const override; MAGNUM_BASISIMPORTER_LOCAL void doClose() override; @@ -312,7 +310,18 @@ class MAGNUM_BASISIMPORTER_EXPORT BasisImporter: public AbstractImporter { MAGNUM_BASISIMPORTER_LOCAL UnsignedInt doImage2DLevelCount(UnsignedInt id) override; MAGNUM_BASISIMPORTER_LOCAL Containers::Optional doImage2D(UnsignedInt id, UnsignedInt level) override; + MAGNUM_BASISIMPORTER_LOCAL UnsignedInt doImage3DCount() const override; + MAGNUM_BASISIMPORTER_LOCAL UnsignedInt doImage3DLevelCount(UnsignedInt id) override; + MAGNUM_BASISIMPORTER_LOCAL Containers::Optional doImage3D(UnsignedInt id, UnsignedInt level) override; + + MAGNUM_BASISIMPORTER_LOCAL UnsignedInt doTextureCount() const override; + MAGNUM_BASISIMPORTER_LOCAL Containers::Optional doTexture(UnsignedInt id) override; + + struct State; Containers::Pointer _state; + + template + Containers::Optional> doImage(UnsignedInt id, UnsignedInt level); }; }} From e7c5843afb72738af71dad2626431d070bc5b676 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 18 Oct 2021 21:26:29 +0200 Subject: [PATCH 10/76] BasisImporter: test KTX2 and sRGB support --- .../BasisImporter/Test/BasisImporterTest.cpp | 411 +++++++++++++----- .../BasisImporter/Test/CMakeLists.txt | 27 +- .../BasisImporter/Test/README.md | 23 +- .../BasisImporter/Test/configure.h.cmake | 1 + .../BasisImporter/Test/convert.sh | 32 ++ .../BasisImporter/Test/rgb-31x13.png | Bin 625 -> 0 bytes .../BasisImporter/Test/rgb-linear-pow2.basis | Bin 0 -> 366 bytes .../BasisImporter/Test/rgb-linear-pow2.ktx2 | Bin 0 -> 490 bytes .../BasisImporter/Test/rgb-linear.basis | Bin 0 -> 358 bytes .../BasisImporter/Test/rgb-linear.ktx2 | Bin 0 -> 482 bytes .../BasisImporter/Test/rgb-noflip.basis | Bin 394 -> 401 bytes .../BasisImporter/Test/rgb-noflip.ktx2 | Bin 0 -> 525 bytes .../BasisImporter/Test/rgb-pow2.basis | Bin 376 -> 367 bytes .../BasisImporter/Test/rgb-pow2.ktx2 | Bin 0 -> 491 bytes .../BasisImporter/Test/rgb.basis | Bin 355 -> 368 bytes src/MagnumPlugins/BasisImporter/Test/rgb.ktx2 | Bin 0 -> 492 bytes .../Test/rgba-2images-mips.basis | Bin 1712 -> 1687 bytes .../BasisImporter/Test/rgba-pow2.basis | Bin 518 -> 531 bytes .../BasisImporter/Test/rgba-pow2.ktx2 | Bin 0 -> 648 bytes .../BasisImporter/Test/rgba.basis | Bin 587 -> 600 bytes .../BasisImporter/Test/rgba.ktx2 | Bin 0 -> 717 bytes 21 files changed, 368 insertions(+), 126 deletions(-) create mode 100644 src/MagnumPlugins/BasisImporter/Test/convert.sh delete mode 100644 src/MagnumPlugins/BasisImporter/Test/rgb-31x13.png create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgb-linear-pow2.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgb-linear-pow2.ktx2 create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgb-linear.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgb-linear.ktx2 create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgb-noflip.ktx2 create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgb-pow2.ktx2 create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgb.ktx2 create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-pow2.ktx2 create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba.ktx2 diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index f9fd05f82..43d908a79 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -46,20 +47,29 @@ struct BasisImporterTest: TestSuite::Tester { explicit BasisImporterTest(); void empty(); - void invalid(); + + void invalidHeader(); + void invalidFile(); + void fileTooShort(); + void unconfigured(); void invalidConfiguredFormat(); - void fileTooShort(); void transcodingFailure(); + void nonBasisKtx(); void rgbUncompressed(); void rgbUncompressedNoFlip(); + void rgbUncompressedLinear(); void rgbaUncompressed(); void rgbaUncompressedMultipleImages(); void rgb(); void rgba(); + void linear(); + + void ktxImporterAlias(); + void openSameTwice(); void openDifferent(); void importMultipleFormats(); @@ -69,56 +79,101 @@ struct BasisImporterTest: TestSuite::Tester { }; constexpr struct { + const char* name; + const char* extension; +} FileTypeData[] { + {"Basis", ".basis"}, + {"KTX2", ".ktx2"} +}; + +constexpr struct { + const char* name; const char* file; - const char* fileAlpha; + const Containers::ArrayView data; + const char* message; +} InvalidHeaderData[] { + {"Invalid", "rgb.basis", "NotAValidFile", "invalid basis header"}, + {"Invalid basis header", "rgb.basis", "sB\xff\xff", "invalid basis header"}, + {"Invalid KTX2 identifier", "rgb.ktx2", "\xabKTX 30\xbb\r\n\x1a\n", "invalid basis header"}, + {"Invalid KTX2 header", "rgb.ktx2", "\xabKTX 20\xbb\r\n\x1a\n\xff\xff\xff\xff", "invalid KTX2 header"} +}; + +constexpr struct { + const char* name; + const char* file; + const std::size_t size; + const char* message; +} FileTooShortData[] { + {"Basis", "rgb.basis", 64, "invalid basis header"}, + {"KTX2", "rgb.ktx2", 64, "invalid KTX2 header"} +}; + +constexpr struct { + const char* fileBase; + const char* fileBaseAlpha; + const char* fileBaseLinear; + const Vector2i expectedSize; const char* suffix; const CompressedPixelFormat expectedFormat; - const Vector2i expectedSize; + const CompressedPixelFormat expectedLinearFormat; } FormatData[] { - {"rgb.basis", "rgba.basis", - "Etc1RGB", CompressedPixelFormat::Etc2RGB8Unorm, {63, 27}}, - {"rgb.basis", "rgba.basis", - "Etc2RGBA", CompressedPixelFormat::Etc2RGBA8Unorm, {63, 27}}, - {"rgb.basis", "rgba.basis", - "Bc1RGB", CompressedPixelFormat::Bc1RGBUnorm, {63, 27}}, - {"rgb.basis", "rgba.basis", - "Bc3RGBA", CompressedPixelFormat::Bc3RGBAUnorm, {63, 27}}, - {"rgb.basis", "rgba.basis", - "Bc4R", CompressedPixelFormat::Bc4RUnorm, {63, 27}}, - {"rgb.basis", "rgba.basis", - "Bc5RG", CompressedPixelFormat::Bc5RGUnorm, {63, 27}}, - {"rgb.basis", "rgba.basis", - "Bc7RGB", CompressedPixelFormat::Bc7RGBAUnorm, {63, 27}}, - {"rgb-pow2.basis", "rgba-pow2.basis", - "PvrtcRGB4bpp", CompressedPixelFormat::PvrtcRGB4bppUnorm, {64, 32}}, - {"rgb-pow2.basis", "rgba-pow2.basis", - "PvrtcRGBA4bpp", CompressedPixelFormat::PvrtcRGBA4bppUnorm, {64, 32}}, - {"rgb.basis", "rgba.basis", - "Astc4x4RGBA", CompressedPixelFormat::Astc4x4RGBAUnorm, {63, 27}}, - {"rgb.basis", "rgba.basis", - "EacR", CompressedPixelFormat::EacR11Unorm, {63, 27}}, - {"rgb.basis", "rgba.basis", - "EacRG", CompressedPixelFormat::EacRG11Unorm, {63, 27}} + {"rgb", "rgba", "rgb-linear", {63, 27}, + "Etc1RGB", CompressedPixelFormat::Etc2RGB8Srgb, CompressedPixelFormat::Etc2RGB8Unorm}, + {"rgb", "rgba", "rgb-linear", {63, 27}, + "Etc2RGBA", CompressedPixelFormat::Etc2RGBA8Srgb, CompressedPixelFormat::Etc2RGBA8Unorm}, + {"rgb", "rgba", "rgb-linear", {63, 27}, + "Bc1RGB", CompressedPixelFormat::Bc1RGBSrgb, CompressedPixelFormat::Bc1RGBUnorm}, + {"rgb", "rgba", "rgb-linear", {63, 27}, + "Bc3RGBA", CompressedPixelFormat::Bc3RGBASrgb, CompressedPixelFormat::Bc3RGBAUnorm}, + {"rgb", "rgba", "rgb-linear", {63, 27}, + "Bc4R", CompressedPixelFormat::Bc4RUnorm, CompressedPixelFormat::Bc4RUnorm}, + {"rgb", "rgba", "rgb-linear", {63, 27}, + "Bc5RG", CompressedPixelFormat::Bc5RGUnorm, CompressedPixelFormat::Bc5RGUnorm}, + {"rgb", "rgba", "rgb-linear", {63, 27}, + "Bc7RGB", CompressedPixelFormat::Bc7RGBASrgb, CompressedPixelFormat::Bc7RGBAUnorm}, + {"rgb-pow2", "rgba-pow2", "rgb-linear-pow2", {64, 32}, + "PvrtcRGB4bpp", CompressedPixelFormat::PvrtcRGB4bppSrgb, CompressedPixelFormat::PvrtcRGB4bppUnorm}, + {"rgb-pow2", "rgba-pow2", "rgb-linear-pow2", {64, 32}, + "PvrtcRGBA4bpp", CompressedPixelFormat::PvrtcRGBA4bppSrgb, CompressedPixelFormat::PvrtcRGBA4bppUnorm}, + {"rgb", "rgba", "rgb-linear", {63, 27}, + "Astc4x4RGBA", CompressedPixelFormat::Astc4x4RGBASrgb, CompressedPixelFormat::Astc4x4RGBAUnorm}, + {"rgb", "rgba", "rgb-linear", {63, 27}, + "EacR", CompressedPixelFormat::EacR11Unorm, CompressedPixelFormat::EacR11Unorm}, + {"rgb", "rgba", "rgb-linear", {63, 27}, + "EacRG", CompressedPixelFormat::EacRG11Unorm, CompressedPixelFormat::EacRG11Unorm} }; BasisImporterTest::BasisImporterTest() { - addTests({&BasisImporterTest::empty, - &BasisImporterTest::invalid, - &BasisImporterTest::unconfigured, + addTests({&BasisImporterTest::empty}); + + addInstancedTests({&BasisImporterTest::invalidHeader}, + Containers::arraySize(InvalidHeaderData)); + + addTests({&BasisImporterTest::invalidFile}); + + addInstancedTests({&BasisImporterTest::fileTooShort}, + Containers::arraySize(FileTooShortData)); + + addTests({&BasisImporterTest::unconfigured, &BasisImporterTest::invalidConfiguredFormat, - &BasisImporterTest::fileTooShort, &BasisImporterTest::transcodingFailure, + &BasisImporterTest::nonBasisKtx}); - &BasisImporterTest::rgbUncompressed, - &BasisImporterTest::rgbUncompressedNoFlip, - &BasisImporterTest::rgbaUncompressed, - &BasisImporterTest::rgbaUncompressedMultipleImages}); + addInstancedTests({&BasisImporterTest::rgbUncompressed, + &BasisImporterTest::rgbUncompressedNoFlip, + &BasisImporterTest::rgbUncompressedLinear, + &BasisImporterTest::rgbaUncompressed}, + Containers::arraySize(FileTypeData)); + + addTests({&BasisImporterTest::rgbaUncompressedMultipleImages}); addInstancedTests({&BasisImporterTest::rgb, - &BasisImporterTest::rgba}, - Containers::arraySize(FormatData)); + &BasisImporterTest::rgba, + &BasisImporterTest::linear}, + Containers::arraySize(FormatData)); - addTests({&BasisImporterTest::openSameTwice, + addTests({&BasisImporterTest::ktxImporterAlias, + &BasisImporterTest::openSameTwice, &BasisImporterTest::openDifferent, &BasisImporterTest::importMultipleFormats}); @@ -148,13 +203,53 @@ void BasisImporterTest::empty() { CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): the file is empty\n"); } -void BasisImporterTest::invalid() { +void BasisImporterTest::invalidHeader() { + auto&& data = InvalidHeaderData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + Containers::Pointer importer = _manager.instantiate("BasisImporter"); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->openData("NotABasisFile")); + CORRADE_VERIFY(!importer->openData(data.data)); - CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): invalid basis header\n"); + CORRADE_COMPARE(out.str(), Utility::formatString("Trade::BasisImporter::openData(): {}\n", data.message)); +} + +void BasisImporterTest::invalidFile() { + Containers::Pointer importer = _manager.instantiate("BasisImporter"); + + /* There's currently no way to make start_transcoding() fail in the KTX2 + transcoder */ + auto basisData = Utility::Directory::read( + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb.basis")); + + std::ostringstream out; + Error redirectError{&out}; + + /* This corrupts the texture type */ + constexpr std::size_t Offset = 23; + CORRADE_INTERNAL_ASSERT(Offset < basisData.size()); + basisData[Offset] = 0x7f; + CORRADE_VERIFY(!importer->openData(basisData)); + + CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): bad basis file\n"); +} + +void BasisImporterTest::fileTooShort() { + auto&& data = FileTooShortData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer importer = _manager.instantiate("BasisImporter"); + auto basisData = Utility::Directory::read( + Utility::Directory::join(BASISIMPORTER_TEST_DIR, data.file)); + + std::ostringstream out; + Error redirectError{&out}; + + /* Shorten the data */ + CORRADE_INTERNAL_ASSERT(data.size < basisData.size()); + CORRADE_VERIFY(!importer->openData(basisData.prefix(data.size))); + CORRADE_COMPARE(out.str(), Utility::formatString("Trade::BasisImporter::openData(): {}\n", data.message)); } void BasisImporterTest::unconfigured() { @@ -173,7 +268,7 @@ void BasisImporterTest::unconfigured() { } CORRADE_VERIFY(image); CORRADE_VERIFY(!image->isCompressed()); - CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); CORRADE_COMPARE(image->size(), (Vector2i{63, 27})); CORRADE_COMPARE(out.str(), "Trade::BasisImporter::image2D(): no format to transcode to was specified, falling back to uncompressed RGBA8. To get rid of this warning either load the plugin via one of its BasisImporterEtc1RGB, ... aliases, or explicitly set the format option in plugin configuration.\n"); @@ -186,7 +281,7 @@ void BasisImporterTest::unconfigured() { CORRADE_COMPARE_WITH(Containers::arrayCast(image->pixels()), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"), /* There are moderately significant compression artifacts */ - (DebugTools::CompareImageToFile{_manager, 55.67f, 6.589f})); + (DebugTools::CompareImageToFile{_manager, 58.334f, 6.622f})); } void BasisImporterTest::invalidConfiguredFormat() { @@ -202,25 +297,6 @@ void BasisImporterTest::invalidConfiguredFormat() { CORRADE_COMPARE(out.str(), "Trade::BasisImporter::image2D(): invalid transcoding target format Banana, expected to be one of EacR, EacRG, Etc1RGB, Etc2RGBA, Bc1RGB, Bc3RGBA, Bc4R, Bc5RG, Bc7RGB, Bc7RGBA, Pvrtc1RGB4bpp, Pvrtc1RGBA4bpp, Astc4x4RGBA, RGBA8\n"); } -void BasisImporterTest::fileTooShort() { - Containers::Pointer importer = _manager.instantiate("BasisImporter"); - auto basisData = Utility::Directory::read( - Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb.basis")); - - std::ostringstream out; - Error redirectError{&out}; - - CORRADE_VERIFY(!importer->openData(basisData.prefix(64))); - - /* Corrupt the header */ - basisData[100] = 100; - CORRADE_VERIFY(!importer->openData(basisData)); - - CORRADE_COMPARE(out.str(), - "Trade::BasisImporter::openData(): invalid basis header\n" - "Trade::BasisImporter::openData(): bad basis file\n"); -} - void BasisImporterTest::transcodingFailure() { Containers::Pointer importer = _manager.instantiate("BasisImporterPvrtcRGB4bpp"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb.basis"))); @@ -235,13 +311,24 @@ void BasisImporterTest::transcodingFailure() { CORRADE_COMPARE(out.str(), "Trade::BasisImporter::image2D(): transcoding failed\n"); } +void BasisImporterTest::nonBasisKtx() { + Containers::Pointer importer = _manager.instantiate("BasisImporter"); + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->openFile(Utility::Directory::join(KTXIMPORTER_TEST_DIR, "2d-rgba.ktx2"))); + CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): invalid KTX2 header\n"); +} + void BasisImporterTest::rgbUncompressed() { + auto&& data = FileTypeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, - "rgb.basis"))); + std::string{"rgb"} + data.extension))); CORRADE_COMPARE(importer->image2DCount(), 1); Containers::Optional image; @@ -254,7 +341,7 @@ void BasisImporterTest::rgbUncompressed() { /* There should be no Y-flip warning as the image is pre-flipped */ CORRADE_COMPARE(out.str(), ""); CORRADE_VERIFY(!image->isCompressed()); - CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); CORRADE_COMPARE(image->size(), (Vector2i{63, 27})); if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) @@ -265,16 +352,19 @@ void BasisImporterTest::rgbUncompressed() { CORRADE_COMPARE_WITH(Containers::arrayCast(image->pixels()), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"), /* There are moderately significant compression artifacts */ - (DebugTools::CompareImageToFile{_manager, 55.67f, 6.589f})); + (DebugTools::CompareImageToFile{_manager, 58.334f, 6.622f})); } void BasisImporterTest::rgbUncompressedNoFlip() { + auto&& data = FileTypeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, - "rgb-noflip.basis"))); + std::string{"rgb-noflip"} + data.extension))); CORRADE_COMPARE(importer->image2DCount(), 1); Containers::Optional image; @@ -286,7 +376,7 @@ void BasisImporterTest::rgbUncompressedNoFlip() { CORRADE_VERIFY(image); CORRADE_COMPARE(out.str(), "Trade::BasisImporter::image2D(): the image was not encoded Y-flipped, imported data will have wrong orientation\n"); CORRADE_VERIFY(!image->isCompressed()); - CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); CORRADE_COMPARE(image->size(), (Vector2i{63, 27})); if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) @@ -297,16 +387,19 @@ void BasisImporterTest::rgbUncompressedNoFlip() { CORRADE_COMPARE_WITH(Containers::arrayCast(image->pixels().flipped<0>()), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"), /* There are moderately significant compression artifacts */ - (DebugTools::CompareImageToFile{_manager, 49.67f, 8.326f})); + (DebugTools::CompareImageToFile{_manager, 51.334f, 8.643f})); } -void BasisImporterTest::rgbaUncompressed() { +void BasisImporterTest::rgbUncompressedLinear() { + auto&& data = FileTypeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, - "rgba.basis"))); + std::string{"rgb-linear"} + data.extension))); CORRADE_COMPARE(importer->image2DCount(), 1); Containers::Optional image = importer->image2D(0); @@ -315,6 +408,35 @@ void BasisImporterTest::rgbaUncompressed() { CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Unorm); CORRADE_COMPARE(image->size(), (Vector2i{63, 27})); + if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("AnyImageImporter plugin not found, cannot test contents"); + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + CORRADE_COMPARE_WITH(Containers::arrayCast(image->pixels()), + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"), + /* There are moderately significant compression artifacts */ + (DebugTools::CompareImageToFile{_manager, 61.0f, 6.321f})); +} + +void BasisImporterTest::rgbaUncompressed() { + auto&& data = FileTypeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + CORRADE_VERIFY(importer); + CORRADE_COMPARE(importer->configuration().value("format"), + "RGBA8"); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + std::string{"rgba"} + data.extension))); + CORRADE_COMPARE(importer->image2DCount(), 1); + + Containers::Optional image = importer->image2D(0); + CORRADE_VERIFY(image); + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); + CORRADE_COMPARE(image->size(), (Vector2i{63, 27})); + if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("AnyImageImporter plugin not found, cannot test contents"); if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) @@ -323,7 +445,7 @@ void BasisImporterTest::rgbaUncompressed() { CORRADE_COMPARE_WITH(image->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), /* There are moderately significant compression artifacts */ - (DebugTools::CompareImageToFile{_manager, 78.3f, 8.31f})); + (DebugTools::CompareImageToFile{_manager, 94.0f, 8.039f})); } void BasisImporterTest::rgbaUncompressedMultipleImages() { @@ -355,7 +477,7 @@ void BasisImporterTest::rgbaUncompressedMultipleImages() { &*image1, &*image1l1, &*image1l2}) { CORRADE_ITERATION(image->size()); CORRADE_VERIFY(!image->isCompressed()); - CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); } CORRADE_COMPARE(image0->size(), (Vector2i{63, 27})); @@ -374,16 +496,16 @@ void BasisImporterTest::rgbaUncompressedMultipleImages() { one image alone as there's more to compress */ CORRADE_COMPARE_WITH(image0->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), - (DebugTools::CompareImageToFile{_manager, 88.25f, 8.357f})); + (DebugTools::CompareImageToFile{_manager, 92.25f, 8.043f})); CORRADE_COMPARE_WITH(image0l1->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-31x13.png"), - (DebugTools::CompareImageToFile{_manager, 75.25f, 14.85f})); + (DebugTools::CompareImageToFile{_manager, 75.75f, 14.077f})); CORRADE_COMPARE_WITH(image0l2->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-15x6.png"), - (DebugTools::CompareImageToFile{_manager, 64.5f, 23.85f})); + (DebugTools::CompareImageToFile{_manager, 65.0f, 23.487f})); CORRADE_COMPARE_WITH(image1->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x63.png"), - (DebugTools::CompareImageToFile{_manager, 87.8f, 9.984f})); + (DebugTools::CompareImageToFile{_manager, 85.5f, 10.23f})); /* Rotating the pixels so we don't need to store the ground truth twice. Somehow it compresses differently for those, tho (I would expect the compression to be invariant of the orientation). */ @@ -392,7 +514,7 @@ void BasisImporterTest::rgbaUncompressedMultipleImages() { (DebugTools::CompareImageToFile{_manager, 82.5f, 33.27f})); CORRADE_COMPARE_WITH((image1l2->pixels().transposed<0, 1>()), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-15x6.png"), - (DebugTools::CompareImageToFile{_manager, 85.25f, 40.15f})); + (DebugTools::CompareImageToFile{_manager, 82.75f, 40.406f})); } void BasisImporterTest::rgb() { @@ -400,8 +522,8 @@ void BasisImporterTest::rgb() { #if defined(BASISD_SUPPORT_BC7) && !BASISD_SUPPORT_BC7 /* BC7 is YUUGE and thus defined out on Emscripten. Skip the test if that's - the case. This assumes -DBASISD_SUPPORT_*=0 issupplied globally. */ - if(formatData.expectedFormat == CompressedPixelFormat::Bc7RGBAUnorm) + the case. This assumes -DBASISD_SUPPORT_*=0 is supplied globally. */ + if(formatData.expectedFormat == CompressedPixelFormat::Bc7RGBASrgb) CORRADE_SKIP("This format is not compiled into Basis."); #endif @@ -412,18 +534,23 @@ void BasisImporterTest::rgb() { CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), formatData.suffix); - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, - formatData.file))); - CORRADE_COMPARE(importer->image2DCount(), 1); - Containers::Optional image = importer->image2D(0); - CORRADE_VERIFY(image); - CORRADE_VERIFY(image->isCompressed()); - CORRADE_COMPARE(image->compressedFormat(), formatData.expectedFormat); - CORRADE_COMPARE(image->size(), formatData.expectedSize); - /** @todo remove this once CompressedImage etc. tests for data size on its - own / we're able to decode the data ourselves */ - CORRADE_COMPARE(image->data().size(), compressedBlockDataSize(formatData.expectedFormat)*((image->size() + compressedBlockSize(formatData.expectedFormat).xy() - Vector2i{1})/compressedBlockSize(formatData.expectedFormat).xy()).product()); + for(const auto& fileType: FileTypeData) { + CORRADE_ITERATION(fileType.name); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + std::string{formatData.fileBase} + fileType.extension))); + CORRADE_COMPARE(importer->image2DCount(), 1); + + Containers::Optional image = importer->image2D(0); + CORRADE_VERIFY(image); + CORRADE_VERIFY(image->isCompressed()); + CORRADE_COMPARE(image->compressedFormat(), formatData.expectedFormat); + CORRADE_COMPARE(image->size(), formatData.expectedSize); + /** @todo remove this once CompressedImage etc. tests for data size on its + own / we're able to decode the data ourselves */ + CORRADE_COMPARE(image->data().size(), compressedBlockDataSize(formatData.expectedFormat)*((image->size() + compressedBlockSize(formatData.expectedFormat).xy() - Vector2i{1})/compressedBlockSize(formatData.expectedFormat).xy()).product()); + } } void BasisImporterTest::rgba() { @@ -431,8 +558,8 @@ void BasisImporterTest::rgba() { #if defined(BASISD_SUPPORT_BC7) && !BASISD_SUPPORT_BC7 /* BC7 is YUUGE and thus defined out on Emscripten. Skip the test if that's - the case. This assumes -DBASISD_SUPPORT_*=0 issupplied globally. */ - if(formatData.expectedFormat == CompressedPixelFormat::Bc7RGBAUnorm) + the case. This assumes -DBASISD_SUPPORT_*=0 is supplied globally. */ + if(formatData.expectedFormat == CompressedPixelFormat::Bc7RGBASrgb) CORRADE_SKIP("This format is not compiled into Basis."); #endif @@ -443,18 +570,88 @@ void BasisImporterTest::rgba() { CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), formatData.suffix); - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, - formatData.fileAlpha))); - CORRADE_COMPARE(importer->image2DCount(), 1); - Containers::Optional image = importer->image2D(0); + for(const auto& fileType: FileTypeData) { + CORRADE_ITERATION(fileType.name); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + std::string{formatData.fileBaseAlpha} + fileType.extension))); + CORRADE_COMPARE(importer->image2DCount(), 1); + + Containers::Optional image = importer->image2D(0); + CORRADE_VERIFY(image); + CORRADE_VERIFY(image->isCompressed()); + CORRADE_COMPARE(image->compressedFormat(), formatData.expectedFormat); + CORRADE_COMPARE(image->size(), formatData.expectedSize); + /** @todo remove this once CompressedImage etc. tests for data size on its + own / we're able to decode the data ourselves */ + CORRADE_COMPARE(image->data().size(), compressedBlockDataSize(formatData.expectedFormat)*((image->size() + compressedBlockSize(formatData.expectedFormat).xy() - Vector2i{1})/compressedBlockSize(formatData.expectedFormat).xy()).product()); + } +} + +void BasisImporterTest::linear() { + auto& formatData = FormatData[testCaseInstanceId()]; + + /* Test linear formats, sRGB was tested in rgb() */ + + #if defined(BASISD_SUPPORT_BC7) && !BASISD_SUPPORT_BC7 + /* BC7 is YUUGE and thus defined out on Emscripten. Skip the test if that's + the case. This assumes -DBASISD_SUPPORT_*=0 is supplied globally. */ + if(formatData.expectedLinearFormat == CompressedPixelFormat::Bc7RGBAUnorm) + CORRADE_SKIP("This format is not compiled into Basis."); + #endif + + const std::string pluginName = "BasisImporter" + std::string(formatData.suffix); + setTestCaseDescription(formatData.suffix); + + Containers::Pointer importer = _manager.instantiate(pluginName); + CORRADE_VERIFY(importer); + CORRADE_COMPARE(importer->configuration().value("format"), + formatData.suffix); + + for(const auto& fileType: FileTypeData) { + CORRADE_ITERATION(fileType.name); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + std::string{formatData.fileBaseLinear} + fileType.extension))); + CORRADE_COMPARE(importer->image2DCount(), 1); + + Containers::Optional image = importer->image2D(0); + CORRADE_VERIFY(image); + CORRADE_VERIFY(image->isCompressed()); + CORRADE_COMPARE(image->compressedFormat(), formatData.expectedLinearFormat); + CORRADE_COMPARE(image->size(), formatData.expectedSize); + /** @todo remove this once CompressedImage etc. tests for data size on its + own / we're able to decode the data ourselves */ + CORRADE_COMPARE(image->data().size(), compressedBlockDataSize(formatData.expectedLinearFormat)*((image->size() + compressedBlockSize(formatData.expectedLinearFormat).xy() - Vector2i{1})/compressedBlockSize(formatData.expectedLinearFormat).xy()).product()); + } +} + +void BasisImporterTest::ktxImporterAlias() { + if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("AnyImageImporter plugin not found, cannot test forwarding"); + + Containers::Pointer anyImageImporter = _manager.instantiate("AnyImageImporter"); + CORRADE_VERIFY(anyImageImporter); + CORRADE_VERIFY(anyImageImporter->configuration().setValue("format", "RGBA8")); + + CORRADE_VERIFY(anyImageImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + "rgba.ktx2"))); + CORRADE_COMPARE(anyImageImporter->image2DCount(), 1); + + Containers::Optional image = anyImageImporter->image2D(0); CORRADE_VERIFY(image); - CORRADE_VERIFY(image->isCompressed()); - CORRADE_COMPARE(image->compressedFormat(), formatData.expectedFormat); - CORRADE_COMPARE(image->size(), formatData.expectedSize); - /** @todo remove this once CompressedImage etc. tests for data size on its - own / we're able to decode the data ourselves */ - CORRADE_COMPARE(image->data().size(), compressedBlockDataSize(formatData.expectedFormat)*((image->size() + compressedBlockSize(formatData.expectedFormat).xy() - Vector2i{1})/compressedBlockSize(formatData.expectedFormat).xy()).product()); + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); + CORRADE_COMPARE(image->size(), (Vector2i{63, 27})); + + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + CORRADE_COMPARE_WITH(image->pixels(), + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), + /* There are moderately significant compression artifacts */ + (DebugTools::CompareImageToFile{_manager, 94.0f, 8.039f})); } void BasisImporterTest::openSameTwice() { @@ -467,7 +664,7 @@ void BasisImporterTest::openSameTwice() { /* Shouldn't crash, leak or anything */ Containers::Optional image = importer->image2D(0); CORRADE_VERIFY(image); - CORRADE_COMPARE(image->compressedFormat(), CompressedPixelFormat::Etc2RGBA8Unorm); + CORRADE_COMPARE(image->compressedFormat(), CompressedPixelFormat::Etc2RGBA8Srgb); CORRADE_COMPARE(image->size(), (Vector2i{63, 27})); } @@ -482,7 +679,7 @@ void BasisImporterTest::openDifferent() { /* Shouldn't crash, leak or anything */ Containers::Optional image = importer->image2D(1); CORRADE_VERIFY(image); - CORRADE_COMPARE(image->compressedFormat(), CompressedPixelFormat::Etc2RGBA8Unorm); + CORRADE_COMPARE(image->compressedFormat(), CompressedPixelFormat::Etc2RGBA8Srgb); CORRADE_COMPARE(image->size(), (Vector2i{27, 63})); } @@ -496,14 +693,14 @@ void BasisImporterTest::importMultipleFormats() { Containers::Optional image = importer->image2D(0); CORRADE_VERIFY(image); - CORRADE_COMPARE(image->compressedFormat(), CompressedPixelFormat::Etc2RGBA8Unorm); + CORRADE_COMPARE(image->compressedFormat(), CompressedPixelFormat::Etc2RGBA8Srgb); CORRADE_COMPARE(image->size(), (Vector2i{63, 27})); } { importer->configuration().setValue("format", "Bc1RGB"); Containers::Optional image = importer->image2D(0); CORRADE_VERIFY(image); - CORRADE_COMPARE(image->compressedFormat(), CompressedPixelFormat::Bc1RGBUnorm); + CORRADE_COMPARE(image->compressedFormat(), CompressedPixelFormat::Bc1RGBSrgb); CORRADE_COMPARE(image->size(), (Vector2i{63, 27})); } } diff --git a/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt b/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt index 3de72708c..88a6c66ba 100644 --- a/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt @@ -30,8 +30,10 @@ find_package(Magnum REQUIRED DebugTools) find_package(Magnum COMPONENTS AnyImageImporter) if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) + set(KTXIMPORTER_TEST_DIR ".") set(BASISIMPORTER_TEST_DIR ".") else() + set(KTXIMPORTER_TEST_DIR ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/KtxImporter/Test) set(BASISIMPORTER_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) endif() @@ -55,10 +57,27 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h corrade_add_test(BasisImporterTest BasisImporterTest.cpp LIBRARIES Magnum::Trade Magnum::DebugTools FILES - rgb.basis rgb-pow2.basis rgb-noflip.basis - rgba.basis rgba-pow2.basis rgba-2images-mips.basis - rgb-63x27.png rgba-63x27.png rgba-31x13.png rgba-15x6.png - rgba-27x63.png) + rgb.basis + rgb.ktx2 + rgb-pow2.basis + rgb-pow2.ktx2 + rgb-linear.basis + rgb-linear.ktx2 + rgb-linear-pow2.basis + rgb-linear-pow2.ktx2 + rgb-noflip.basis + rgb-noflip.ktx2 + rgba.basis + rgba.ktx2 + rgba-pow2.basis + rgba-pow2.ktx2 + rgba-2images-mips.basis + rgb-63x27.png + rgba-63x27.png + rgba-31x13.png + rgba-15x6.png + rgba-27x63.png + ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/KtxImporter/Test/2d-rgba.ktx2) target_include_directories(BasisImporterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_BASISIMPORTER_BUILD_STATIC) target_link_libraries(BasisImporterTest PRIVATE BasisImporter) diff --git a/src/MagnumPlugins/BasisImporter/Test/README.md b/src/MagnumPlugins/BasisImporter/Test/README.md index 8e14e08d5..cba3e78bd 100644 --- a/src/MagnumPlugins/BasisImporter/Test/README.md +++ b/src/MagnumPlugins/BasisImporter/Test/README.md @@ -1,8 +1,8 @@ -Creating input Basis files -========================== +Creating input files +==================== -The images were converted from central cutouts from `ambient-texture.tga` and `diffuse-alpha-texture.tga` -from the [Magnum Shaders test files](https://github.com/mosra/magnum/tree/master/src/Magnum/Shaders/Test/TestFiles). +The images were converted from central cutouts from `ambient-texture.tga` +and `diffuse-alpha-texture.tga` from the [Magnum Shaders test files](https://github.com/mosra/magnum/tree/master/src/Magnum/Shaders/Test/TestFiles). - `rgb-63x27.png` - `rgb-64x32.png` @@ -12,19 +12,12 @@ from the [Magnum Shaders test files](https://github.com/mosra/magnum/tree/master using the official basis universal [conversion tool](https://github.com/BinomialLLC/basis_universal/#command-line-compression-tool). +For conversion to `*.ktx2` at least version 1.15 is required. -To convert, run the following commands: +To convert to all the required `*.basis`/`*.ktx2` files, run the `convert.sh` script. -```sh -basisu rgb-63x27.png -output_file rgb.basis -y_flip -basisu rgb-63x27.png -output_file rgb-noflip.basis -basisu rgba-63x27.png -output_file rgba.basis -force_alpha -y_flip -basisu rgba-63x27.png rgba-27x63.png -output_file rgba-2images-mips.basis -y_flip -mipmap -mip_smallest 16 -mip_filter box - -# Required for PVRTC1 target, which requires pow2 dimensions -basisu rgb-64x32.png -output_file rgb-pow2.basis -y_flip -basisu rgba-64x32.png -output_file rgba-pow2.basis -force_alpha -y_flip -``` +Creating mipmaps +================ For mipmap testing, the PNG image is resized to two more levels. Using the box filter, the same as Basis itself, to have the least difference. diff --git a/src/MagnumPlugins/BasisImporter/Test/configure.h.cmake b/src/MagnumPlugins/BasisImporter/Test/configure.h.cmake index cd22a3cc4..caa4fe4e0 100644 --- a/src/MagnumPlugins/BasisImporter/Test/configure.h.cmake +++ b/src/MagnumPlugins/BasisImporter/Test/configure.h.cmake @@ -26,4 +26,5 @@ #cmakedefine BASISIMPORTER_PLUGIN_FILENAME "${BASISIMPORTER_PLUGIN_FILENAME}" #cmakedefine STBIMAGEIMPORTER_PLUGIN_FILENAME "${STBIMAGEIMPORTER_PLUGIN_FILENAME}" +#define KTXIMPORTER_TEST_DIR "${KTXIMPORTER_TEST_DIR}" #define BASISIMPORTER_TEST_DIR "${BASISIMPORTER_TEST_DIR}" diff --git a/src/MagnumPlugins/BasisImporter/Test/convert.sh b/src/MagnumPlugins/BasisImporter/Test/convert.sh new file mode 100644 index 000000000..250212dcb --- /dev/null +++ b/src/MagnumPlugins/BasisImporter/Test/convert.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -e + +# RGB +basisu rgb-63x27.png -output_file rgb.basis -y_flip +basisu rgb-63x27.png -output_file rgb.ktx2 -y_flip -ktx2 +# basisu doesn't write KTXorientation metadata in KTX2 but that's the only way +# for the plugin to detect y-flip on import. Patch it in manually for testing. +sed -b 's/\x1f\x00\x00\x00KTXwriter\x00Basis Universal /\x12\x00\x00\x00KTXorientation\x00ru\x00\x00\x00\x07\x00\x00\x00_\x00/' rgb.ktx2 > rgb.ktx2.tmp +mv rgb.ktx2.tmp rgb.ktx2 +# Without y-flip +basisu rgb-63x27.png -output_file rgb-noflip.basis +basisu rgb-63x27.png -output_file rgb-noflip.ktx2 -ktx2 +# Linear image data +basisu rgb-63x27.png -output_file rgb-linear.basis -y_flip -linear +basisu rgb-63x27.png -output_file rgb-linear.ktx2 -y_flip -linear -ktx2 + +# RGBA +basisu rgba-63x27.png -output_file rgba.basis -force_alpha -y_flip +basisu rgba-63x27.png -output_file rgba.ktx2 -force_alpha -y_flip -ktx2 + +# Multiple images, not possible with KTX2 +basisu rgba-63x27.png rgba-27x63.png -output_file rgba-2images-mips.basis -y_flip -mipmap -mip_smallest 16 -mip_filter box + +# Required for PVRTC1 target, which requires pow2 dimensions +basisu rgb-64x32.png -output_file rgb-pow2.basis -y_flip +basisu rgb-64x32.png -output_file rgb-pow2.ktx2 -y_flip -ktx2 +basisu rgb-64x32.png -output_file rgb-linear-pow2.basis -y_flip -linear +basisu rgb-64x32.png -output_file rgb-linear-pow2.ktx2 -y_flip -linear -ktx2 +basisu rgba-64x32.png -output_file rgba-pow2.basis -force_alpha -y_flip +basisu rgba-64x32.png -output_file rgba-pow2.ktx2 -force_alpha -y_flip -ktx2 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgb-31x13.png b/src/MagnumPlugins/BasisImporter/Test/rgb-31x13.png deleted file mode 100644 index 91a76922ff2fc2a832a459c9064134d36df4567f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 625 zcmeAS@N?(olHy`uVBq!ia0vp^@*p+`GmuQnbDa&OSkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP~t{_Pl)UB^ZNbMOtDlb7^PE!ID4Y0Z?Ha{Xm~ooz~)k#)ur_0i{Z!485}>SZ+$7<@=`_;kbB<1`f|GU#dNFV zDOM*^51lfwSQTqNH`07oq~)gg#jB0$+s$_!GO#+CVm3X(>U65r(G>HU5!UC@b{;h7 zoM^grui^jy|JUv7dIj_&W0JSKi+jS}lhr^Ddx@v7EBg~B2_9BGw(Iv#0fnY{x;TbN zT&_Lm&UeUx$06`A17GQjxf2)`Ugz2S{kJ@4fKt-t2l+WKPflW-Q&aa*vN3@}aBG2z zRJZy)>0LULH5KNaODV2g5~m?_JZNXo+CX;3D}k3wQ~2)ecbuC#qgRbPN@w-mmzx$G zf5qRXEY=e_`Q@AE%HnVSuY1Pr8mp&Tx-}|)+mWYv^#b#5AHV#wXz#?|zaPK+!5w>2 zxpLF|WDlSVRZCnWN>UO_QmvAUQh^kMk%5tcu7RPhp-G6LrInG9m5Hggfq|8Qfy9hM yfhZbs^HVa@DuEgdEp-hnb&U-|3=ORe%&iPeAR6lCq!a@+FnGH9xvXqGb1#T9|P>7X#X8Tk-S%PayaY2H(Gbe+~oxx6A+k z!CzljU0KcYzuscn`sJ%utqKYZ>j3c-AXWk59Y72M%pe6oup3J2 zL%47fqz43;7&I6bGcqzTGeh_g(jLzF4+Qc+9?)6kMVTe3MGQ`f#hJwlp?R5QsYS(! zISPh)hNcV*d<-HC?m%^cKy5p?l;Udsk Y9-#R7cV{|Nrp6|Nnn~|NQy$ z??C+g`TPI>_5ZKgE+79RJQM<4K~!LHb!9Zm|Nr+l{r_uQ!T8r<3D7NVi4QnWBnzmP z8oZpk<-mgZMnHc7-NfQBYv*JpIZuuQ3Ifg3F7?|@E)gqRUcu-nvd2}S!2;xo0}5f! zIXD>ZZPwWKTJ3>JapRf>sL?Z)ayX6nu2D#ml^P)hD?1DOmeg!55hL*E0cB&d$ ziEtRGg7vZZ7BC!SR$v0TU13G%6rGQA#V&UhTl_Sip`P^AY&sLepa1`V{`~nDh=2b6 z4Rm7o-~a!=zkmMx`F9|G{`~#_|N8&eY?qJ!5grNwt{^HfxVkc$<^TWtoBsc`tzi7? zumtFqw!{aVCz1tJOATI5-Ev^Td?TPwfNo-On6+~?F+K%dm{kg^_{bN&%1nA_hSqMg>9)DL{cB1_n`vZ4d^>L@R~*V4!L{ zAiwLo!T}keRwgEfiXaJ&&420{{2AC77@BIA-(X>>e#l&L$bf-C!Q<^EOJ>Fn1_dCS z}FQI{1VQ~!NJhM{)kP$!9b*K|L*;h?_AVe zyWnan3#+xnkz#N_fsuCu6SI>O z)1K+C^q&{a`F++Jh6 zLLHrR?sJu2h^jnpw2zUk;_p)tC52s&epdTgvnaOAIoIe=(k!@PpS$;>!r$w4CS|Nx IF;|BH0I__8w*UYD delta 373 zcmbQp+{J8F>?F+K%P^bImXU$sssoS!A}|J0NI-}o1;`6wU=U&00c7|yFtAUwQm79C zsS-_m7pzO6vu0bm&iD;8?^a&|tvBV83z^ z7qf(l^n(CNhQ^iYQ_dV<*?V~5stxs1PI57P_-|jEwtJ@TJN2}kG8^LRT}?VXLar9R zd-UO+<@RYpHi9o2A2oz85HjQV>oBK*k)fsWfvQJ^YG?!Fk&q--g=MlFr@a{j8CVzu zS`z<=Dk^;rbx`4C6kuyo=;GJ1%lJCMby^kkh5i(a@PxO{r@m975pyZYIF+I4eu1vn$1ymK8ZzU=UI-!zuYb>_w0_}oCyHB C9(!N_ diff --git a/src/MagnumPlugins/BasisImporter/Test/rgb-noflip.ktx2 b/src/MagnumPlugins/BasisImporter/Test/rgb-noflip.ktx2 new file mode 100644 index 0000000000000000000000000000000000000000..7709f66189daed0cb88900584c2c28647b0c2def GIT binary patch literal 525 zcmZ4O9TK5nWU!l;ONxsD2pECb9*Ctu94G*Z!EgqUtpmhUfLH~HcK|U6h=UXW!Fecc z2jRj=kRA|VV$fh%%*e#R%nac}NRW*X@E-`|flQ#Y%8N2fQi~Xz5{om76+-he%TkMq z6LS;{^$blJ7=#!^8G?Z7K-e3i6;2}Ct#CjF=yoP1hKe8wj?I7S8T=X87#Nyrm)~Gv zseZ^@amavyLBZqgB}-<;4h97vo8y(U@vR=0`tQG1e+fKuduqFr-;!m9-|S{qz5Ei+ z%fZ3W!TyL%z`;PIZU655lkZ&AT)W_EDhsQ%#N(@4trwo1dzq^sed8btBg2pX`S%6H zjH}&VY?pY>kz#N_fsuCu6SI>O)1K+C^q&{a`F+jLayQwyUIa!qp3%A4g{4?(mFfz0xGE|7o>Uw0%sWQXinL>jF12Y3d(*m1f;Z+SDlb9ShSR8+`e_1-UmAQQv z^TA#gE&;;?psNLvK6*Pa{JpU(rDEraNz-@dpDili{6x=M)p)w4tbm|WlgFO+rZ@K< QcNua2cb;qh?N1K_0Hp+f!vFvP literal 376 zcmXSR5@zsa@VueT$iUEeA4mWZ12Yh#0zQTmpnw+xgDAr~AR`p02B^>#$n-^3$iU#h zpuix&z`@`FRA2y>&msx}iYe3nGF+I5?QQ zzOMS`e=vCe|Nf@;-`_unf&cH{pWpvq|No!O^8bJQ|M~mx-`~GY{_m~$U0==efBh;D zSh0fP?17dBMus*=hA?iH#OVf-=AFy}>=hL_9>&npsL}O+eZiyTcN3JB zzH{&q=1AdXXYCLGdW3;tVhtk;!`|1om94E7J^1_V{_1ZtI-7Q{*)AM2ExEBnK+xrp Y*sc@L{z(PQt3I|iw&|vA;we)G0CpgX0{{R3 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgb-pow2.ktx2 b/src/MagnumPlugins/BasisImporter/Test/rgb-pow2.ktx2 new file mode 100644 index 0000000000000000000000000000000000000000..9dd72038cab56ae8be4cc118659882d299bb080b GIT binary patch literal 491 zcmZ4O9TK5nWU!l;ONxsD2pECb0f-fV7zRLMFq{Eo>j3c-AXWk59Y72MY#;?dun$V> zLbz}eqz43;7&I6bGcqwSGeh_g(jLzF4+Qc+9?)6kMVTe3MGQ`f#hJwlp?R5QsYS(! zISPh)hNcV*0t_MyUO;t$K7R7bbCX?1Hs8g5l8D8@)_8Gj##?z_O#;% znLC8Q8Y&$b89NvhfS%^il6Stbz~AcZM1Iq6*R)(;-&q%(=eGVIGlv30O*G5@|2Ots z|Npl(^#9(9-~WI9{QdVg5d8oD{`>d$&!OP|cKQE5`0ML_dxkr?xV;?_xgK%fcmKm;iLOK+;EV2Zp~lmZeneJTYnd?)wf24NsZ1^f&tKmjiX20?~(K!z&=!)FK|s2L5|Ge|QC zFt9V&0!=ao${9Pz9gt>ZVqj1_6wv&+$*w`2fti8f;JNg!N5U+h8EqH^fQlyhOLnm` zc7Ti$;Bcy(@g;Myg;(Bw!)v#vq}uXxvI7G{b*~16Zq-`5UA~@y!Ep`Hmo1Gi zxD_;FCYc&EEEGRrVErNZ2D1R80|SGC#I<*>kAfUjJQ)QzniT99KAzIaO5ndL%kj4Zr_uN{ac`dbcg*Rgcdo Ux-9+g#?1X{>(|^l_rXdF09C>R6=h6abS0y)0EuCAY` z4B^5_kRA|VV$fh%%*e#R%nac}NRW*X@E-_-fJ~sX@{2N4^GXs+GV}8oib{cE>_8mP zV5nzk%D}+SAjseaalKcKLb+ z2FEqPploS;!L6VXGs)DTVWIc|1M3gLH<$$&9T*rCB(A-4eH7%N;>jq$(WGF<@bQ#R zRs#Q3S&na3j~Hh#DFEHbahOk!n}tDZm*sgL#cBC>veX~PrcBWBc9U?BX!!kKQ&P;g c*1K&fuX=n&(Pim>H)ifvTfgScxer!a08e0o)&Kwi literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-2images-mips.basis b/src/MagnumPlugins/BasisImporter/Test/rgba-2images-mips.basis index f3d7e398eda08fc286dcf6bfe6ab6a5f0f9dbbd7..cc43a04a8e0167119ef60fac6d4780aac347a6fc 100644 GIT binary patch literal 1687 zcmY+>2{hDO90&0G|C^>smcJ#ah+zy$$78v$Gp1rurr$dUr!3JkG+4gz2fQ~?H-f{i!;oZ;fuoP`9k$X6lT zLZ~^opg;r20s_E+F~}RB)E-`7WRZzW0MLXI6aWVl$OAmgqF^&*SttVS1x6Ox;s>LX zuvh@=Fo$(uz!vf`IM+~GU}TYNAafw#N+2WfJPmj@0qDY)(1ZND>H;H+yypkIIY35X z9rAF*fgs3oFfHS{z{n!UK$eHRem9AV!@FXU$Y8XK%>iz-#ZpCV5UHgS!uOFo-a&#^-W$7Vvt$qpBs=sg_ zojfKBR#Q%=iu)@L>}AF@bRXgu0P229;*eWH_n`-rQLp07i9`FCd-n^v?w06}hwxM2 zJ18IkAu9Orbo-oIf_u-B^S(d3ElHXTX^P{f-A_qNP1DUjxF25d|3=6qcq`iw!Jz1q zFr}waH^JU2UELH_RAkKli`Sp+_GPiGComR6)}xcCXjHJ2DpVLV{IH+3$JADRZa#D> z(SL{6XZJ_GmTUPgM0`R*-Q`t6XF)TCQ|{B-L>ZNYi>$42hzjm%$}16NC8~lW@G|&w z8~7;}I2GQNx&m6eos$wqLL-$O#dBi{Soj?hp-ntp|9FSDYNbQEA*H_Ambzw4{Mk9PdWj{71oeN_F4EBnQ;Rk4;U@%-x1 z>wYa1LC17fXVCm{;Z~}VT+ga+6Fyas3iBS=VT9ZCbx8bI zGm8Onb90S(Dxmd+(XhDe53F(%+N9U%OV^P*Gdf!gqDl?@Or@K9DL_9MT_!uyid-a_ zxRByTzBTT8l50}VcQPpqS61*B)g2Q(jbHa%idDxon*SU9i!Jc{EiPV%q$3Ppjgd@A z>eQnJuFDLy)iV<&(Iw1tid`bLH~LOn*bfz(Hr2;|dd~7}W*;Ypt%biuhydQCTq&gd z)65ak)~Y$V^b6YwdcOCPMZA$JErk5#@Ov-aEwmFwIoz6LvZwu8UWxncOnOwJEBY?l zGiiI1t#a2~rr+{Q$t4V|OOp0J?-uw+9IK>d z*gWh@tQ7JJSpu*A274jR$FyLC3C`wKhxRsDI(~H6NH%GdJ1IyGoI&hA++-HY zF*-kmb=crGS6}F1=sx6ojBGlMnl%Y$V~$g{d-z328(E~4s*}|S&UBQN^;id!!7?3i zq*b8?&IV&(-v(@f8*ffm{#3!wsF^P39qfuJ!!42nuO3#h-IkZ n!Z<4|mUBb1D>IsI_gHT$_BBK9&$jIQx~80aV0P7TRe;KW9V3-W literal 1712 zcmY+?eKgcr7zgnCGc%05#9)vXYbPq1vD?;A7Q>X)vSJpS)@u|stt6wE)vPcaBYCMX zF=LFE$Xg<8GRI0Srjn(`OXWQ!GK8jR@0_##=$>=$IrnqF=Q+>0_eMHs0ax(8`iLR` zYiv~j6aWB9aDF*{2Z0CxMhJ)kESRuCz#S9-S7FHY%OC)DzyN5#5;QabKJbt|Cr=>j zTnX6=f^maffe}yxSbzp73;;wZ^NVsu*7;k=Uqfk)go^^$2ozyf0eC=GgJMCEGqTS9 z|MRb6*erm3*ug$D03C8T+$W;sjI8q@jj{=AW`ubs~U4!1toOsAd6pf@>L75G& z9n-_BG#1GN34u|{drU{Gcl46b#_~5{X>6qn%R%F}9VZ8OYlcyU{j!$jTm61iVL8pS zvi@Knd1uxq>toJI$uXOqkTaaXX#dK-qo#Z34v`vTcSkPk$&RPaP0$Xw;WR1KQ_%=( z+$7%PfSBGrv|!`jXI?B^et1nQ)8yQ&p=a^3A84B6_78}yr)E>JZ{i!a#-uRCILT@3 zVzp?J_J(Qt<2ZqzT(vgSSDm-+ zR%^awVqkiB9`|hV6sb@FD=4`rtR7W8E_Mt!@Uy^D?_Woj=qbM=TSOqNDpypMx$2j~-r3rwRNodvkSKiRw73DA~&5(rni@-c;izG2fk3arJb{+%N(t+J92? zu!2`^HH2b)@mI#AS0-LdyRpI5NoDp-w*AtELAsX9KaEHR(KOH)E9$080K3Dj5`)Z5h~|o$!g?R`UIv0m_+tTDp=KY^9?aDBP{{N>eo^7y@7CWs zjo&!o;obUKz`8V@ScPv0H4<7k4rFE1oL?OudA*?Y)@il5RV5?8*t?SX#3Y{emEDFL zy2^B?lLO+mR@uU%^Np)5tL0VwOQ-9v4?Wx`?5(=51M%TzKLdp^AGg(@%lbYc=&UK!KDSW>KHx$ zq4??vyV$m~YqiqbA;_&GJv}PltQ0)EJC(OaW)=l8s(w(qO>!ujHejI5sVUpS&u-!P zIzNj#tTHL9Du`XYQasIN7~u19S^0GAH#p-2pn)N#SQ_)~lmpt6i$wW=QCQ*@_1KF8{D?C2rE+e(s$4-u#X= X*ULb{d@bEPe#BP0W&V$a8r}Z@?B2ER diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-pow2.basis b/src/MagnumPlugins/BasisImporter/Test/rgba-pow2.basis index 74ef6403589f5824003ed90a69586f2c005be2aa..7d85115be2a728cecb4aee6bfd0479ccc3ceb241 100644 GIT binary patch literal 531 zcmXSR5@zsan38jhk%3`W9}@!ukY*4EVpO2WPz@BwXJC+Jcm-snF)#!G6$SvAzNiWr z7#tWB7z7wN7#08(H~}R+GC(RAVZx7q!iGSB&0iD_NH;JtusC*oeJWMFhl6PkqX2`0 zz_y82WvsD^5eg163_w|@2U3ddxrctYF!%s9IS8;g%y}wiGu6v8`TBuqE8}~sW~rv% z+bHN0Q4^XJcSHE1%{#@$tWdd!D3mpEm9NK9dmB z9n-8mhn6r1GqEu+v?SiB&|*_yxR~$Pr=9&zAk)9VpJf_Pz@AFVqlPFxC>``fdM^|lWPrA@G$yJL z1_d65S{uZsWnt=)B^A?bU7xa5UjH>dD~MC8QJQex)fG5U}&k+DDjU)kOh N4P4EvZ3h^d83Fw%%KQKT diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-pow2.ktx2 b/src/MagnumPlugins/BasisImporter/Test/rgba-pow2.ktx2 new file mode 100644 index 0000000000000000000000000000000000000000..2eca388f28b06ed21bfd243dd21cd7ede5e1d9d2 GIT binary patch literal 648 zcmZ4O9TK5nWU!l;ONxsD2pECb0f-fV7zRLMFq{Eo+W_$rAXWk56F>|Cz90oaAj$-x ziy;)81nB_*CI$i*sDQ7#HAcY981$=x=QWrpM=k5iR`4M5Et&%$_` z93&VXyl1*1w7A-!fz^)7o2#C?`umQSjHgm&K84Z;G}QPG{I@?)zx_jun$V=U8^RZ5 zUZ?HY!n3=rXW|sCuixHmeWLU{F7x>J=Te6sm#kkEAK|ObsPafG>DNDVnO#P+xxXA( z!@wxP(!lUb$w%+x4yk5Q@jr$g2^=3P7V(!po0fj#SMaI))t~Qb z<_rFkeg93GcW060t#{93CzKl;3Vl_+cB)F6Te-i`q_ni_ch7$_5}xF_X%&M*dHG(D zd%%EW5u3mv2LadnPt{i3C~2@dJZ*!j!VO8TjzwL$N^L^xy6hi3GMv(oq{gaJUnl*; zWG-ur(dSq1{!N&&*0lV){DetNFBk4>vKexM5<}AaRHm>#N4$G#$%#rHZeR3+w+0Yjkx&<70(NqP

~X1&q1v#$gnlzqCfdc&$MuMhluWtL#x9W22it!rH+%H(LFb3VG? z@<-q2cYG{O4tnAT+8uh&)SvlRSMdLn(RWWl?%aqp=BU3@PAr@hGWlC?voovH>$(E1 zE3VGZB;EVHE^YOne?(5SBjee|@P%@sQ7OR%T%1o9dt@+VlsYcn6P0oK%hpE>vO=1nLuDaoloh&i@;mqo3O5SGm5w|J3HL z`?LS&rK7J#Ykz$>T|c1Z>yKqjZ8U8ukVPKSCS@fcL3nR06=O0$12IuGqt25JgUlUB=YhYk#Vqi#~ z!CJfbs$v)8`aD)g1&&~mKu`Y!@zp=uWFxK%o16Npa=Ko+Z@}Xq0t^HO#{d<*MusTI zuU|Uu%`J)8oUr5R&iJ*?z_@!#ilL-E8Pv52oEt)e+=BQPF*OjD-!=ZlhdyX0?e7_ZaxPT$l%>JHXn|aRf wRf^q)JQ|D4$`2JN^VCL`iSRva?Pk(qkg5&{zVLf;#EuW=IoPLnw(>Fo0D2$OtN;K2 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba.ktx2 b/src/MagnumPlugins/BasisImporter/Test/rgba.ktx2 new file mode 100644 index 0000000000000000000000000000000000000000..a452269619e8ad2a90393bb4900641e986d80d75 GIT binary patch literal 717 zcmZ4O9TK5nWU!l;ONxsD2pECb9*Ctu94G*Z!EgqUZ3DzhfLH~HPXI9p6o3=}fdvzU zo(iGhBuEbkFfmv#EM{b4U}lE!AtcB~2>1^K4h;7E$b5Mq2k6Z5qRf)iA_k|#;>==& z(7epD)S}|V90fx?LsJF@O$J4VMxeTUAdY}(hSGsR8iZYdSmA&)&@U{B$vb3Ko!b2! zs~8FySP~>yn#BKd@D*lpMR76hVH99+5O9mX5b;7WSWvKmgMpcWRY`Lz_ZOO!mUjK_`EN$TlRP)A zVsI!g-wSmM0|Un*Hi1J90IHe&; zja8+-PWp$*T-F$)&#&J7n=oaqY58~g36q#!FbXJo8c%qwr7-V)Zg-{3S%x!hTLb7r&L&H z=qNBa7%(_gpMCV;5kH5i;e)Mj1$bC5Rvb}p>5z}eHuC+Z$G%jH?{#3PO3WbxhC%_L zFB%e(^c)yoJ^r$y=w*|}T(5x4dZTM+UkN@a`*dUVhE-c$ANcvoEWx}xSb{@Z*SboS z$ Date: Mon, 18 Oct 2021 22:13:57 +0200 Subject: [PATCH 11/76] BasisImporter: test unsupported target format error --- .../BasisImporter/Test/BasisImporterTest.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index 43d908a79..753de2355 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -54,6 +54,7 @@ struct BasisImporterTest: TestSuite::Tester { void unconfigured(); void invalidConfiguredFormat(); + void unsupportedFormat(); void transcodingFailure(); void nonBasisKtx(); @@ -156,6 +157,7 @@ BasisImporterTest::BasisImporterTest() { addTests({&BasisImporterTest::unconfigured, &BasisImporterTest::invalidConfiguredFormat, + &BasisImporterTest::unsupportedFormat, &BasisImporterTest::transcodingFailure, &BasisImporterTest::nonBasisKtx}); @@ -297,6 +299,25 @@ void BasisImporterTest::invalidConfiguredFormat() { CORRADE_COMPARE(out.str(), "Trade::BasisImporter::image2D(): invalid transcoding target format Banana, expected to be one of EacR, EacRG, Etc1RGB, Etc2RGBA, Bc1RGB, Bc3RGBA, Bc4R, Bc5RG, Bc7RGB, Bc7RGBA, Pvrtc1RGB4bpp, Pvrtc1RGBA4bpp, Astc4x4RGBA, RGBA8\n"); } +void BasisImporterTest::unsupportedFormat() { + #if !defined(BASISD_SUPPORT_BC7) || BASISD_SUPPORT_BC7 + /* BC7 is YUUGE and thus defined out on Emscripten. Skip the test if that's + NOT the case. This assumes -DBASISD_SUPPORT_*=0 is supplied globally. */ + CORRADE_SKIP("BC7 is compiled into Basis, can't test unsupported target format error."); + #endif + + Containers::Pointer importer = _manager.instantiate("BasisImporter"); + CORRADE_VERIFY(importer->openFile( + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb.basis"))); + + std::ostringstream out; + Error redirectError{&out}; + importer->configuration().setValue("format", "Bc7RGBA"); + CORRADE_VERIFY(!importer->image2D(0)); + + CORRADE_COMPARE(out.str(), "Trade::BasisImporter::image2D(): unsupported transcoding target format Bc7RGBA for a ETC1S image\n"); +} + void BasisImporterTest::transcodingFailure() { Containers::Pointer importer = _manager.instantiate("BasisImporterPvrtcRGB4bpp"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb.basis"))); From f315885ac4aeacccc94f7cc03909eadc7ac43bd2 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 18 Oct 2021 22:56:18 +0200 Subject: [PATCH 12/76] BasisImageConverter: write KTX2 orientation metadata basisu doesn't do it for us, only in .basis files --- .../BasisImageConverter/BasisImageConverter.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index a592228cf..d4d23a4f3 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -176,6 +176,19 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi PARAM_CONFIG(ktx2_zstd_supercompression_level, int); params.m_ktx2_srgb_transfer_func = params.m_perceptual; + /* y_flip sets a flag in Basis files, but not in KTX2 files. Specify the + orientation in the key/value data: + https://www.khronos.org/registry/KTX/specs/2.0/ktxspec_v2.html#_ktxorientation */ + constexpr char OrientationKey[] = "KTXorientation"; + char orientationValue[] = "rd"; + if(params.m_y_flip) + orientationValue[1] = 'u'; + basist::ktx2_transcoder::key_value& keyValue = *params.m_ktx2_key_values.enlarge(1); + keyValue.m_key.resize(sizeof(OrientationKey)); + std::memcpy(keyValue.m_key.data(), OrientationKey, sizeof(OrientationKey)); + keyValue.m_value.resize(sizeof(orientationValue)); + std::memcpy(keyValue.m_value.data(), orientationValue, sizeof(orientationValue)); + /* Set various fields in the Basis file header */ PARAM_CONFIG(userdata0, int); PARAM_CONFIG(userdata1, int); From 38abfea263579c7a9a25ecf10f0a9c1f92672e5e Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 18 Oct 2021 22:57:17 +0200 Subject: [PATCH 13/76] BasisImageConverter: test KTX2 export --- .../Test/BasisImageConverterTest.cpp | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp index 49e4b1177..9871934c6 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp +++ b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,8 @@ struct BasisImageConverterTest: TestSuite::Tester { void rgb(); void rgba(); + void ktx(); + /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _converterManager{"nonexistent"}; @@ -74,6 +77,14 @@ constexpr struct { {"all threads", "0"} }; +constexpr struct { + const char* name; + const bool yFlip; +} FlippedData[] { + {"y-flip", true}, + {"no y-flip", false} +}; + BasisImageConverterTest::BasisImageConverterTest() { addTests({&BasisImageConverterTest::wrongFormat, &BasisImageConverterTest::processError, @@ -86,6 +97,9 @@ BasisImageConverterTest::BasisImageConverterTest() { addInstancedTests({&BasisImageConverterTest::rgba}, Containers::arraySize(ThreadsData)); + addInstancedTests({&BasisImageConverterTest::ktx}, + Containers::arraySize(FlippedData)); + /* Pull in the AnyImageImporter dependency for image comparison, load StbImageImporter from the build tree, if defined. Otherwise it's static and already loaded. */ @@ -299,6 +313,57 @@ void BasisImageConverterTest::rgba() { (DebugTools::CompareImageToFile{_manager, 78.3f, 8.302f})); } +void BasisImageConverterTest::ktx() { + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + auto&& data = FlippedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer pngImporter = + _manager.instantiate("PngImporter"); + pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png")); + const auto originalImage = pngImporter->image2D(0); + CORRADE_VERIFY(originalImage); + + /* Use the original image and add a skip of {7, 8} to ensure the converter + reads the image data properly. */ + const UnsignedInt dataSize = ((63 + 7)*4)*(27 + 7); + Image2D imageWithSkip{PixelStorage{}.setSkip({7, 8, 0}), + PixelFormat::RGBA8Unorm, originalImage->size(), + Containers::Array{ValueInit, dataSize}}; + Utility::copy(originalImage->pixels(), + imageWithSkip.pixels()); + + Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); + converter->configuration().setValue("create_ktx2_file", true); + converter->configuration().setValue("y_flip", data.yFlip); + const auto compressedData = converter->convertToData(imageWithSkip); + CORRADE_VERIFY(compressedData); + + char KTXorientation[] = "KTXorientation\0r?"; + KTXorientation[sizeof(KTXorientation) - 1] = data.yFlip ? 'u' : 'd'; + CORRADE_VERIFY(Containers::StringView{compressedData, compressedData.size()}.contains(KTXorientation)); + + if(_manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("BasisImporter plugin not found, cannot test"); + + Containers::Pointer importer = + _manager.instantiate("BasisImporterRGBA8"); + CORRADE_VERIFY(importer->openData(compressedData)); + Containers::Optional image = importer->image2D(0); + CORRADE_VERIFY(image); + + /* Basis can only load RGBA8 uncompressed data, which corresponds to RGB1 + from our RGB8 image data. */ + auto pixels = image->pixels(); + if(!data.yFlip) pixels = pixels.flipped<0>(); + CORRADE_COMPARE_WITH(pixels, + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), + /* There are moderately significant compression artifacts */ + (DebugTools::CompareImageToFile{_manager, 78.3f, 8.302f})); +} + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::BasisImageConverterTest) From 7cc5707e768cd5838b9bb6fd70a25d0ccb8bddab Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 18 Oct 2021 22:58:27 +0200 Subject: [PATCH 14/76] BasisImageConverter: fix sRGB format whoops --- .../BasisImageConverter/BasisImageConverter.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index d4d23a4f3..a81c6bb55 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -229,25 +229,26 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi auto dst = Containers::arrayCast(Containers::StridedArrayView2D({basisImage.get_ptr(), basisImage.get_total_pixels()}, {std::size_t(image.size().y()), std::size_t(image.size().x())})).flipped<0>(); /* basis image is always RGBA, fill in alpha if necessary */ - if(format == PixelFormat::RGBA8Unorm) { + const UnsignedInt channels = pixelSize(format); + if(channels == 4) { auto src = image.pixels>(); for(std::size_t y = 0; y != src.size()[0]; ++y) for(std::size_t x = 0; x != src.size()[1]; ++x) dst[y][x] = src[y][x]; - } else if(format == PixelFormat::RGB8Unorm) { + } else if(channels == 3) { auto src = image.pixels>(); for(std::size_t y = 0; y != src.size()[0]; ++y) for(std::size_t x = 0; x != src.size()[1]; ++x) dst[y][x] = src[y][x]; /* Alpha implicitly 255 */ - } else if(format == PixelFormat::RG8Unorm) { + } else if(channels == 2) { auto src = image.pixels>(); for(std::size_t y = 0; y != src.size()[0]; ++y) for(std::size_t x = 0; x != src.size()[1]; ++x) dst[y][x] = Math::gather<'r', 'r', 'r', 'g'>(src[y][x]); - } else if(format == PixelFormat::R8Unorm) { + } else if(channels == 1) { auto src = image.pixels>(); for(std::size_t y = 0; y != src.size()[0]; ++y) for(std::size_t x = 0; x != src.size()[1]; ++x) From f1c5d0538cbe54d537ae1273c5ce3e4735d23cf8 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 18 Oct 2021 23:06:37 +0200 Subject: [PATCH 15/76] BasisImageConverter: remove separate_rg_to_color_alpha Removed from the basisu C++ API, can be replaced by swizzle=rrrg --- .../BasisImageConverter/BasisImageConverter.conf | 5 ++++- .../BasisImageConverter/BasisImageConverter.cpp | 8 +++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf index 722da65aa..22cf2b9ce 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf @@ -22,7 +22,10 @@ y_flip=true # `mip_srgb` and enabling `no_selector_rdo` & `no_endpoint_rdo` check_for_alpha=true force_alpha=false -separate_rg_to_color_alpha=false +# Remap color channels before compression. Must be empty or 4 characters long, +# valid characters are r,g,b,a. This replaced separate_rg_to_color_alpha, +# for the same effect use 'rrrg'. +swizzle= # Number of threads Basis should use during compression, 0 sets it to the # value returned by std::thread::hardware_concurrency(), 1 disables # multithreading. This value is clamped to std::thread::hardware_concurrency() diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index a81c6bb55..f56feb025 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -110,11 +110,7 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi PARAM_CONFIG(check_for_alpha, bool); PARAM_CONFIG(force_alpha, bool); - std::string swizzle = configuration().value("swizzle"); - /* swizzle has precedence in the basisu commandline tool, do the same */ - if(swizzle.empty() && configuration().value("separate_rg_to_color_alpha")) - swizzle = "rrrg"; - + const std::string swizzle = configuration().value("swizzle"); if(!swizzle.empty()) { if(swizzle.size() != 4) { Error{} << "Trade::BasisImageConverter::convertToData(): invalid swizzle length, expected 4 but got" << swizzle.size(); @@ -246,6 +242,8 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi auto src = image.pixels>(); for(std::size_t y = 0; y != src.size()[0]; ++y) for(std::size_t x = 0; x != src.size()[1]; ++x) + /** @todo Doesn't this break if swizzle is rrrg? -> output + would be rrrr */ dst[y][x] = Math::gather<'r', 'r', 'r', 'g'>(src[y][x]); } else if(channels == 1) { From 015f24e0521149ad4ba833da1552c6c1e793749f Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 18 Oct 2021 23:07:11 +0200 Subject: [PATCH 16/76] BasisImageConverter: test invalid swizzle values --- .../Test/BasisImageConverterTest.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp index 9871934c6..1d9a0acac 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp +++ b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp @@ -52,6 +52,7 @@ struct BasisImageConverterTest: TestSuite::Tester { explicit BasisImageConverterTest(); void wrongFormat(); + void invalidSwizzle(); void processError(); void r(); @@ -87,6 +88,7 @@ constexpr struct { BasisImageConverterTest::BasisImageConverterTest() { addTests({&BasisImageConverterTest::wrongFormat, + &BasisImageConverterTest::invalidSwizzle, &BasisImageConverterTest::processError, &BasisImageConverterTest::r, @@ -129,6 +131,25 @@ void BasisImageConverterTest::wrongFormat() { CORRADE_COMPARE(out.str(), "Trade::BasisImageConverter::convertToData(): unsupported format PixelFormat::RG32F\n"); } +void BasisImageConverterTest::invalidSwizzle() { + Containers::Pointer converter = + _converterManager.instantiate("BasisImageConverter"); + + const char data[8]{}; + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_VERIFY(converter->configuration().setValue("swizzle", "gbgbg")); + CORRADE_VERIFY(!converter->convertToData(ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, data})); + + CORRADE_VERIFY(converter->configuration().setValue("swizzle", "xaaa")); + CORRADE_VERIFY(!converter->convertToData(ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, data})); + + CORRADE_COMPARE(out.str(), + "Trade::BasisImageConverter::convertToData(): invalid swizzle length, expected 4 but got 5\n" + "Trade::BasisImageConverter::convertToData(): invalid characters in swizzle xaaa\n"); +} + void BasisImageConverterTest::processError() { Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); From 3c4852d150ae7920033781ead193ad903e93d2ac Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 18 Oct 2021 23:26:33 +0200 Subject: [PATCH 17/76] BasisImageConverter: expose UASTC options --- .../BasisImageConverter.conf | 13 ++++++++++++ .../BasisImageConverter.cpp | 20 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf index 22cf2b9ce..0af7f9613 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf @@ -58,6 +58,19 @@ global_palette_bits=8 global_modifier_bits=8 hybrid_selector_codebook_quality_threshold=2.0 +# UASTC options +uastc=false +pack_uastc_level=2 +pack_uastc_flags= +rdo_uastc=false +rdo_uastc_quality_scalar=1.0 +rdo_uastc_dict_size=4096 +rdo_uastc_max_smooth_block_error_scale=10.0 +rdo_uastc_smooth_block_max_std_dev=18.0 +rdo_uastc_max_allowed_rms_increase_ratio=10.0 +rdo_uastc_skip_block_rms_threshold=8.0 +rdo_uastc_favor_simpler_modes_in_rdo_mode=true + # KTX2 options create_ktx2_file=false ktx2_uastc_supercompression=false diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index f56feb025..5eaff1484 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -165,6 +165,20 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi PARAM_CONFIG_FIX_NAME(global_mod_bits, int, "global_modifier_bits"); PARAM_CONFIG_FIX_NAME(hybrid_sel_cb_quality_thresh, float, "hybrid_sel_codebook_quality_threshold"); + /* UASTC options */ + PARAM_CONFIG(uastc, bool); + params.m_pack_uastc_flags = configuration().value("pack_uastc_level"); + PARAM_CONFIG(pack_uastc_flags, int); + PARAM_CONFIG(rdo_uastc, bool); + PARAM_CONFIG(rdo_uastc_quality_scalar, float); + PARAM_CONFIG(rdo_uastc_dict_size, int); + PARAM_CONFIG(rdo_uastc_max_smooth_block_error_scale, float); + PARAM_CONFIG(rdo_uastc_smooth_block_max_std_dev, float); + PARAM_CONFIG(rdo_uastc_max_allowed_rms_increase_ratio, float); + PARAM_CONFIG_FIX_NAME(rdo_uastc_skip_block_rms_thresh, float, "rdo_uastc_skip_block_rms_threshold"); + PARAM_CONFIG(rdo_uastc_favor_simpler_modes_in_rdo_mode, bool); + params.m_rdo_uastc_multithreading = multithreading; + /* KTX2 options */ PARAM_CONFIG(create_ktx2_file, bool); params.m_ktx2_uastc_supercompression = @@ -268,6 +282,9 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi /* process() will have printed additional error information to stderr */ Error{} << "Trade::BasisImageConverter::convertToData(): type constraint validation failed"; return {}; + case basisu::basis_compressor::error_code::cECFailedEncodeUASTC: + Error{} << "Trade::BasisImageConverter::convertToData(): UASTC encoding failed"; + return {}; case basisu::basis_compressor::error_code::cECFailedFrontEnd: /* process() will have printed additional error information to stderr */ Error{} << "Trade::BasisImageConverter::convertToData(): frontend processing failed"; @@ -279,6 +296,9 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi /* process() will have printed additional error information to stderr */ Error{} << "Trade::BasisImageConverter::convertToData(): assembling basis file data or transcoding failed"; return {}; + case basisu::basis_compressor::error_code::cECFailedUASTCRDOPostProcess: + Error{} << "Trade::BasisImageConverter::convertToData(): UASTC RDO postprocessing failed"; + return {}; case basisu::basis_compressor::error_code::cECFailedCreateKTX2File: Error{} << "Trade::BasisImageConverter::convertToData(): assembling KTX2 file failed"; return {}; From 426da4431b224e5af0fed323d549ee6f894a8679 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 19 Oct 2021 01:10:07 +0200 Subject: [PATCH 18/76] BasisImageConverter: fix user-supplied mipmaps basisu doesn't flip level 1 or higher --- .../BasisImageConverter/BasisImageConverter.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index 5eaff1484..0a56f09cc 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -235,8 +235,16 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi moreover we need to tightly pack it and flip Y. The `dst` is a Y-flipped view already to make the following loops simpler. */ basisu::image& basisImage = i == 0 ? params.m_source_images[0] : params.m_source_mipmap_images[0][i - 1]; - basisImage = {uint32_t(image.size().x()), uint32_t(image.size().y())}; - auto dst = Containers::arrayCast(Containers::StridedArrayView2D({basisImage.get_ptr(), basisImage.get_total_pixels()}, {std::size_t(image.size().y()), std::size_t(image.size().x())})).flipped<0>(); + basisImage.resize(image.size().x(), image.size().y()); + auto dst = Containers::arrayCast(Containers::StridedArrayView2D({basisImage.get_ptr(), basisImage.get_total_pixels()}, {std::size_t(image.size().y()), std::size_t(image.size().x())})); + /* Y-flip the view to make the following loops simpler. basisu doesn't + apply m_y_flip (or m_renormalize) to user-supplied mipmaps, so only + do this for the base image. */ + /** @todo Probably a bug, file an issue/send a PR. There's also another + bug where it determines if alpha in any pixel > 0 *before* it + swizzles the mipmaps. */ + if(!params.m_y_flip || i == 0) + dst = dst.flipped<0>(); /* basis image is always RGBA, fill in alpha if necessary */ const UnsignedInt channels = pixelSize(format); From 2bb860f0ec382d1a49507dfeeaf3e340f2588779 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 19 Oct 2021 01:58:18 +0200 Subject: [PATCH 19/76] BasisImageConverter: test user-supplied mipmaps --- .../Test/BasisImageConverterTest.cpp | 214 +++++++++++++----- 1 file changed, 155 insertions(+), 59 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp index 1d9a0acac..400a93698 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp +++ b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp @@ -53,6 +53,10 @@ struct BasisImageConverterTest: TestSuite::Tester { void wrongFormat(); void invalidSwizzle(); + + void tooManyLevels(); + void levelWrongSize(); + void processError(); void r(); @@ -62,6 +66,8 @@ struct BasisImageConverterTest: TestSuite::Tester { void ktx(); + void customLevels(); + /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _converterManager{"nonexistent"}; @@ -89,6 +95,11 @@ constexpr struct { BasisImageConverterTest::BasisImageConverterTest() { addTests({&BasisImageConverterTest::wrongFormat, &BasisImageConverterTest::invalidSwizzle, + + + &BasisImageConverterTest::tooManyLevels, + &BasisImageConverterTest::levelWrongSize, + &BasisImageConverterTest::processError, &BasisImageConverterTest::r, @@ -102,6 +113,8 @@ BasisImageConverterTest::BasisImageConverterTest() { addInstancedTests({&BasisImageConverterTest::ktx}, Containers::arraySize(FlippedData)); + addTests({&BasisImageConverterTest::customLevels}); + /* Pull in the AnyImageImporter dependency for image comparison, load StbImageImporter from the build tree, if defined. Otherwise it's static and already loaded. */ @@ -150,6 +163,38 @@ void BasisImageConverterTest::invalidSwizzle() { "Trade::BasisImageConverter::convertToData(): invalid characters in swizzle xaaa\n"); } +void BasisImageConverterTest::tooManyLevels() { + Containers::Pointer converter = + _converterManager.instantiate("BasisImageConverter"); + + const UnsignedByte bytes[4]{}; + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->convertToData({ + ImageView2D{PixelFormat::RGB8Unorm, {1, 1}, bytes}, + ImageView2D{PixelFormat::RGB8Unorm, {1, 1}, bytes} + })); + CORRADE_COMPARE(out.str(), + "Trade::BasisImageConverter::convertToData(): there can be only 1 levels with base image size Vector(1, 1) but got 2\n"); +} + +void BasisImageConverterTest::levelWrongSize() { + Containers::Pointer converter = + _converterManager.instantiate("BasisImageConverter"); + + const UnsignedByte bytes[16]{}; + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->convertToData({ + ImageView2D{PixelFormat::RGB8Unorm, {2, 2}, bytes}, + ImageView2D{PixelFormat::RGB8Unorm, {2, 1}, bytes} + })); + CORRADE_COMPARE(out.str(), + "Trade::BasisImageConverter::convertToData(): expected size Vector(1, 1) for level 1 but got Vector(2, 1)\n"); +} + void BasisImageConverterTest::processError() { Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); @@ -166,25 +211,34 @@ void BasisImageConverterTest::processError() { "Trade::BasisImageConverter::convertToData(): frontend processing failed\n"); } +template +Image2D copyImageWithSkip(const ImageView2D& originalImage, Vector3i skip, PixelFormat format) { + const Vector2i size = originalImage.size(); + /* Width includes row alignment to 4 bytes */ + const UnsignedInt widthWithSkip = ((size.x() + skip.x())*DestinationType::Size + 3)/4*4; + const UnsignedInt dataSize = widthWithSkip*(size.y() + skip.y()); + Image2D imageWithSkip{PixelStorage{}.setSkip(skip), format, + size, Containers::Array{ValueInit, dataSize}}; + Utility::copy(Containers::arrayCast( + originalImage.pixels()), + imageWithSkip.pixels()); + return imageWithSkip; +} + void BasisImageConverterTest::r() { if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); - Containers::Pointer pngImporter = - _manager.instantiate("PngImporter"); - pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png")); + Containers::Pointer pngImporter = _manager.instantiate("PngImporter"); + CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"))); const auto originalImage = pngImporter->image2D(0); CORRADE_VERIFY(originalImage); - /* Use the original image and add a skip of {7, 8} to ensure the converter - reads the image data properly. Data size is computed with row alignment - to 4 bytes. During copy, we only use R channel to retrieve a R8 image */ - const UnsignedInt dataSize = (63 + 7 + 2)*(27 + 8); - Image2D imageWithSkip{PixelStorage{}.setSkip({7, 8, 0}), - PixelFormat::R8Unorm, originalImage->size(), Containers::Array{ValueInit, dataSize}}; - Utility::copy(Containers::arrayCast( - originalImage->pixels()), - imageWithSkip.pixels()); + /* Use the original image and add a skip to ensure the converter reads the + image data properly. During copy, we only use R channel to retrieve an + R8 image. */ + const Image2D imageWithSkip = copyImageWithSkip>( + *originalImage, {7, 8, 0}, PixelFormat::R8Unorm); const auto compressedData = _converterManager.instantiate("BasisImageConverter")->convertToData(imageWithSkip); CORRADE_VERIFY(compressedData); @@ -213,22 +267,16 @@ void BasisImageConverterTest::rg() { if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); - Containers::Pointer pngImporter = - _manager.instantiate("PngImporter"); - pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png")); + Containers::Pointer pngImporter = _manager.instantiate("PngImporter"); + CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"))); const auto originalImage = pngImporter->image2D(0); CORRADE_VERIFY(originalImage); - /* Use the original image and add a skip of {7, 8} to ensure the converter - reads the image data properly. Data size is computed with row alignment - to 4 bytes. During copy, we only use R and G channels to retrieve a RG8 - image. */ - const UnsignedInt dataSize = ((63 + 8)*2 + 2)*(27 + 7); - Image2D imageWithSkip{PixelStorage{}.setSkip({7, 8, 0}), - PixelFormat::RG8Unorm, originalImage->size(), Containers::Array{ValueInit, dataSize}}; - Utility::copy(Containers::arrayCast( - originalImage->pixels()), - imageWithSkip.pixels()); + /* Use the original image and add a skip to ensure the converter reads the + image data properly. During copy, we only use R and G channels to + retrieve an RG8 image. */ + const Image2D imageWithSkip = copyImageWithSkip( + *originalImage, {7, 8, 0}, PixelFormat::RG8Unorm); const auto compressedData = _converterManager.instantiate("BasisImageConverter")->convertToData(imageWithSkip); CORRADE_VERIFY(compressedData); @@ -256,21 +304,15 @@ void BasisImageConverterTest::rgb() { if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); - Containers::Pointer pngImporter = - _manager.instantiate("PngImporter"); - pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png")); + Containers::Pointer pngImporter = _manager.instantiate("PngImporter"); + CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"))); const auto originalImage = pngImporter->image2D(0); CORRADE_VERIFY(originalImage); - /* Use the original image and add a skip of {7, 8} to ensure the converter - reads the image data properly. Data size is computed with row alignment - to 4 bytes. */ - const UnsignedInt dataSize = ((63 + 7)*3 + 3)*(27 + 8); - Image2D imageWithSkip{PixelStorage{}.setSkip({7, 8, 0}), - PixelFormat::RGB8Unorm, originalImage->size(), - Containers::Array{ValueInit, dataSize}}; - Utility::copy(originalImage->pixels(), - imageWithSkip.pixels()); + /* Use the original image and add a skip to ensure the converter reads the + image data properly */ + const Image2D imageWithSkip = copyImageWithSkip( + *originalImage, {7, 8, 0}, PixelFormat::RGB8Unorm); const auto compressedData = _converterManager.instantiate("BasisImageConverter")->convertToData(imageWithSkip); CORRADE_VERIFY(compressedData); @@ -297,20 +339,15 @@ void BasisImageConverterTest::rgba() { auto&& data = ThreadsData[testCaseInstanceId()]; setTestCaseDescription(data.name); - Containers::Pointer pngImporter = - _manager.instantiate("PngImporter"); - pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png")); + Containers::Pointer pngImporter = _manager.instantiate("PngImporter"); + CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"))); const auto originalImage = pngImporter->image2D(0); CORRADE_VERIFY(originalImage); - /* Use the original image and add a skip of {7, 8} to ensure the converter - reads the image data properly. */ - const UnsignedInt dataSize = ((63 + 7)*4)*(27 + 7); - Image2D imageWithSkip{PixelStorage{}.setSkip({7, 8, 0}), - PixelFormat::RGBA8Unorm, originalImage->size(), - Containers::Array{ValueInit, dataSize}}; - Utility::copy(originalImage->pixels(), - imageWithSkip.pixels()); + /* Use the original image and add a skip to ensure the converter reads the + image data properly */ + const Image2D imageWithSkip = copyImageWithSkip( + *originalImage, {7, 8, 0}, PixelFormat::RGBA8Unorm); Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); if(data.threads) converter->configuration().setValue("threads", data.threads); @@ -341,20 +378,15 @@ void BasisImageConverterTest::ktx() { auto&& data = FlippedData[testCaseInstanceId()]; setTestCaseDescription(data.name); - Containers::Pointer pngImporter = - _manager.instantiate("PngImporter"); - pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png")); + Containers::Pointer pngImporter = _manager.instantiate("PngImporter"); + CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"))); const auto originalImage = pngImporter->image2D(0); CORRADE_VERIFY(originalImage); - /* Use the original image and add a skip of {7, 8} to ensure the converter - reads the image data properly. */ - const UnsignedInt dataSize = ((63 + 7)*4)*(27 + 7); - Image2D imageWithSkip{PixelStorage{}.setSkip({7, 8, 0}), - PixelFormat::RGBA8Unorm, originalImage->size(), - Containers::Array{ValueInit, dataSize}}; - Utility::copy(originalImage->pixels(), - imageWithSkip.pixels()); + /* Use the original image and add a skip to ensure the converter reads the + image data properly */ + const Image2D imageWithSkip = copyImageWithSkip( + *originalImage, {7, 8, 0}, PixelFormat::RGBA8Unorm); Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); converter->configuration().setValue("create_ktx2_file", true); @@ -385,6 +417,70 @@ void BasisImageConverterTest::ktx() { (DebugTools::CompareImageToFile{_manager, 78.3f, 8.302f})); } +void BasisImageConverterTest::customLevels() { + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + Containers::Pointer pngImporter = _manager.instantiate("PngImporter"); + CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"))); + const auto originalLevel0 = pngImporter->image2D(0); + CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-31x13.png"))); + const auto originalLevel1 = pngImporter->image2D(0); + CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-15x6.png"))); + const auto originalLevel2 = pngImporter->image2D(0); + CORRADE_VERIFY(originalLevel0); + CORRADE_VERIFY(originalLevel1); + CORRADE_VERIFY(originalLevel2); + + /* Use the original images and add a skip to ensure the converter reads the + image data properly */ + const Image2D level0WithSkip = copyImageWithSkip( + *originalLevel0, {7, 8, 0}, PixelFormat::RGBA8Unorm); + const Image2D level1WithSkip = copyImageWithSkip( + *originalLevel1, {7, 8, 0}, PixelFormat::RGBA8Unorm); + const Image2D level2WithSkip = copyImageWithSkip( + *originalLevel2, {7, 8, 0}, PixelFormat::RGBA8Unorm); + + Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); + const auto compressedData = converter->convertToData({level0WithSkip, level1WithSkip, level2WithSkip}); + CORRADE_VERIFY(compressedData); + + if(_manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("BasisImporter plugin not found, cannot test"); + + Containers::Pointer importer = + _manager.instantiate("BasisImporterRGBA8"); + CORRADE_VERIFY(importer->openData(compressedData)); + CORRADE_COMPARE(importer->image2DCount(), 1); + CORRADE_COMPARE(importer->image2DLevelCount(0), 3); + + Containers::Optional level0 = importer->image2D(0, 0); + Containers::Optional level1 = importer->image2D(0, 1); + Containers::Optional level2 = importer->image2D(0, 2); + CORRADE_VERIFY(level0); + CORRADE_VERIFY(level1); + CORRADE_VERIFY(level2); + + CORRADE_COMPARE(level0->size(), (Vector2i{63, 27})); + CORRADE_COMPARE(level1->size(), (Vector2i{31, 13})); + CORRADE_COMPARE(level2->size(), (Vector2i{15, 6})); + + /* Basis can only load RGBA8 uncompressed data, which corresponds to RGB1 + from our RGB8 image data. */ + CORRADE_COMPARE_WITH(level0->pixels(), + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), + /* There are moderately significant compression artifacts */ + (DebugTools::CompareImageToFile{_manager, 97.25f, 7.927f})); + CORRADE_COMPARE_WITH(level1->pixels(), + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-31x13.png"), + /* There are moderately significant compression artifacts */ + (DebugTools::CompareImageToFile{_manager, 81.0f, 14.322f})); + CORRADE_COMPARE_WITH(level2->pixels(), + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-15x6.png"), + /* There are moderately significant compression artifacts */ + (DebugTools::CompareImageToFile{_manager, 76.25f, 24.5f})); +} + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::BasisImageConverterTest) From 1bd045bfc9e60fad2c2cbac418452fc3fd626d3d Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 19 Oct 2021 01:58:36 +0200 Subject: [PATCH 20/76] BasisImageConverter: update image error thresholds --- .../BasisImageConverter/Test/BasisImageConverterTest.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp index 400a93698..1d8a80c74 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp +++ b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp @@ -329,7 +329,7 @@ void BasisImageConverterTest::rgb() { CORRADE_COMPARE_WITH(Containers::arrayCast(image->pixels()), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"), /* There are moderately significant compression artifacts */ - (DebugTools::CompareImageToFile{_manager, 55.7f, 6.589f})); + (DebugTools::CompareImageToFile{_manager, 61.0f, 6.347f})); } void BasisImageConverterTest::rgba() { @@ -368,7 +368,7 @@ void BasisImageConverterTest::rgba() { CORRADE_COMPARE_WITH(image->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), /* There are moderately significant compression artifacts */ - (DebugTools::CompareImageToFile{_manager, 78.3f, 8.302f})); + (DebugTools::CompareImageToFile{_manager, 97.25f, 7.914f})); } void BasisImageConverterTest::ktx() { @@ -414,7 +414,7 @@ void BasisImageConverterTest::ktx() { CORRADE_COMPARE_WITH(pixels, Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), /* There are moderately significant compression artifacts */ - (DebugTools::CompareImageToFile{_manager, 78.3f, 8.302f})); + (DebugTools::CompareImageToFile{_manager, 97.25f, 9.398f})); } void BasisImageConverterTest::customLevels() { From a4348cc8ded2ba681e07e4c1e1ae946bd8f8d354 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 19 Oct 2021 22:00:12 +0200 Subject: [PATCH 21/76] BasisImporter: import 3D images with mipmaps as 2D array textures All mipmaps in basis are 2D, but they'd need to halve in Z for correct 3D mipmaps. Print a warning and import them as 2D array textures with mipmaps instead. --- src/MagnumPlugins/BasisImporter/BasisImporter.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 05bf24f3b..39156e4a4 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -358,9 +358,6 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { const UnsignedInt levels = fileInfo.m_image_mipmap_levels[i]; CORRADE_INTERNAL_ASSERT(levels > 0); - /** @todo Error if levels > 1 for Texture3D, the mips don't halve - in the z dimension */ - if(i == 0) firstLevels = levels; @@ -373,6 +370,17 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { _state->numLevels[i] = levels; } + /* Mip levels in basis are per 2D image, for 3D images they + consequently don't halve in the z-dimension. Turn it into a 2D array + texture so users don't get surprised by the mip z-size not changing, + and print a warning. + For non-Texture2D images, at this point firstLevels is the number + of levels for all slices, checked in the loop above. */ + if(_state->textureType == TextureType::Texture3D && firstLevels > 1) { + Warning{} << "Trade::BasisImporter::openData(): found a 3D image with 2D mipmaps, importing as a 2D array texture"; + _state->textureType = TextureType::Texture2DArray; + } + _state->compressionType = fileInfo.m_tex_format; _state->isYFlipped = fileInfo.m_y_flipped; From 6f479a7de81717c5f1f6d86152c64e88085d2692 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 19 Oct 2021 22:00:46 +0200 Subject: [PATCH 22/76] BasisImporter: fix mip level count --- src/MagnumPlugins/BasisImporter/BasisImporter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 39156e4a4..cd177447c 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -278,7 +278,7 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { /* KTX2 files only ever contain one image */ _state->numImages = 1; _state->numSlices = _state->ktx2Transcoder->get_faces()*Math::max(_state->ktx2Transcoder->get_layers(), 1u); - _state->numLevels = Containers::Array{DirectInit, _state->ktx2Transcoder->get_levels()}; + _state->numLevels = Containers::Array{DirectInit, 1, _state->ktx2Transcoder->get_levels()}; _state->compressionType = _state->ktx2Transcoder->get_format(); From 1b78acfa4a0e3c4f547070a1ed1cd371d147563d Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 19 Oct 2021 22:01:15 +0200 Subject: [PATCH 23/76] BasisImporter: fix cubemap array textures --- src/MagnumPlugins/BasisImporter/BasisImporter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index cd177447c..6584e073f 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -340,7 +340,7 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { _state->textureType = TextureType::Texture2DArray; break; case basist::basis_texture_type::cBASISTexTypeCubemapArray: - _state->textureType = _state->numImages > 6 ? TextureType::CubeMapArray : TextureType::CubeMap; + _state->textureType = _state->numSlices > 6 ? TextureType::CubeMapArray : TextureType::CubeMap; break; case basist::basis_texture_type::cBASISTexTypeVolume: _state->textureType = TextureType::Texture3D; From 85ffabb79e9f2dc8f168c3e641fedeaad6a8463d Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 19 Oct 2021 22:05:32 +0200 Subject: [PATCH 24/76] BasisImporter: test all image types --- .../BasisImporter/Test/BasisImporterTest.cpp | 436 +++++++++++++++++- .../BasisImporter/Test/CMakeLists.txt | 19 + .../BasisImporter/Test/README.md | 20 +- .../BasisImporter/Test/rgba-27x27-slice1.png | Bin 0 -> 755 bytes .../BasisImporter/Test/rgba-27x27-slice2.png | Bin 0 -> 745 bytes .../BasisImporter/Test/rgba-27x27.png | Bin 0 -> 524 bytes .../BasisImporter/Test/rgba-3d-mips.basis | Bin 0 -> 2095 bytes .../BasisImporter/Test/rgba-3d-mips.ktx2 | Bin 0 -> 2052 bytes .../BasisImporter/Test/rgba-3d.basis | Bin 0 -> 1185 bytes .../BasisImporter/Test/rgba-3d.ktx2 | Bin 0 -> 1250 bytes .../BasisImporter/Test/rgba-63x27-slice1.png | Bin 0 -> 942 bytes .../BasisImporter/Test/rgba-63x27-slice2.png | Bin 0 -> 946 bytes .../BasisImporter/Test/rgba-array-mips.basis | Bin 0 -> 2095 bytes .../BasisImporter/Test/rgba-array-mips.ktx2 | Bin 0 -> 2052 bytes .../BasisImporter/Test/rgba-array.basis | Bin 0 -> 1185 bytes .../BasisImporter/Test/rgba-array.ktx2 | Bin 0 -> 1250 bytes .../Test/rgba-cubemap-array.basis | Bin 0 -> 2004 bytes .../Test/rgba-cubemap-array.ktx2 | Bin 0 -> 1835 bytes .../BasisImporter/Test/rgba-cubemap.basis | Bin 0 -> 1234 bytes .../BasisImporter/Test/rgba-cubemap.ktx2 | Bin 0 -> 1221 bytes .../BasisImporter/Test/rgba-video.basis | Bin 0 -> 1184 bytes .../BasisImporter/Test/rgba-video.ktx2 | Bin 0 -> 1281 bytes 22 files changed, 471 insertions(+), 4 deletions(-) create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-27x27-slice1.png create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-27x27-slice2.png create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-27x27.png create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-3d-mips.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-3d-mips.ktx2 create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-3d.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-3d.ktx2 create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-63x27-slice1.png create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-63x27-slice2.png create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-array-mips.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-array-mips.ktx2 create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-array.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-array.ktx2 create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-cubemap-array.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-cubemap-array.ktx2 create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-cubemap.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-cubemap.ktx2 create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-video.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-video.ktx2 diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index 753de2355..4f0c2ecc2 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include "configure.h" @@ -58,6 +59,8 @@ struct BasisImporterTest: TestSuite::Tester { void transcodingFailure(); void nonBasisKtx(); + void texture(); + void rgbUncompressed(); void rgbUncompressedNoFlip(); void rgbUncompressedLinear(); @@ -69,6 +72,14 @@ struct BasisImporterTest: TestSuite::Tester { void linear(); + void array2D(); + void array2DMipmaps(); + void video(); + void image3D(); + void image3DMipmaps(); + void cubeMap(); + void cubeMapArray(); + void ktxImporterAlias(); void openSameTwice(); @@ -109,6 +120,19 @@ constexpr struct { {"KTX2", "rgb.ktx2", 64, "invalid KTX2 header"} }; +const struct { + const char* name; + const char* fileBase; + const TextureType type; +} TextureData[]{ + {"2D", "rgb", TextureType::Texture2D}, + {"2D array", "rgba-array", TextureType::Texture2DArray}, + {"Cube map", "rgba-cubemap", TextureType::CubeMap}, + {"Cube map array", "rgba-cubemap-array", TextureType::CubeMapArray}, + {"3D", "rgba-3d", TextureType::Texture3D}, + {"3D mipmaps", "rgba-3d-mips", TextureType::Texture2DArray} +}; + constexpr struct { const char* fileBase; const char* fileBaseAlpha; @@ -161,6 +185,9 @@ BasisImporterTest::BasisImporterTest() { &BasisImporterTest::transcodingFailure, &BasisImporterTest::nonBasisKtx}); + addInstancedTests({&BasisImporterTest::texture}, + Containers::arraySize(TextureData)); + addInstancedTests({&BasisImporterTest::rgbUncompressed, &BasisImporterTest::rgbUncompressedNoFlip, &BasisImporterTest::rgbUncompressedLinear, @@ -174,6 +201,15 @@ BasisImporterTest::BasisImporterTest() { &BasisImporterTest::linear}, Containers::arraySize(FormatData)); + addInstancedTests({&BasisImporterTest::array2D, + &BasisImporterTest::array2DMipmaps, + &BasisImporterTest::video, + &BasisImporterTest::image3D, + &BasisImporterTest::image3DMipmaps, + &BasisImporterTest::cubeMap, + &BasisImporterTest::cubeMapArray}, + Containers::arraySize(FileTypeData)); + addTests({&BasisImporterTest::ktxImporterAlias, &BasisImporterTest::openSameTwice, &BasisImporterTest::openDifferent, @@ -340,12 +376,75 @@ void BasisImporterTest::nonBasisKtx() { CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): invalid KTX2 header\n"); } +void BasisImporterTest::texture() { + auto&& data = TextureData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer importer = _manager.instantiate("BasisImporter"); + + for(const auto& fileType: FileTypeData) { + CORRADE_ITERATION(fileType.name); + + CORRADE_VERIFY(importer->openFile( + Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{data.fileBase} + fileType.extension))); + + const Vector3ui counts{ + importer->image1DCount(), + importer->image2DCount(), + importer->image3DCount() + }; + const UnsignedInt total = counts.sum(); + + CORRADE_VERIFY(total > 0); + CORRADE_COMPARE(counts.max(), total); + CORRADE_COMPARE(importer->textureCount(), total); + + const bool isKtx2 = std::string{fileType.name} == "KTX2"; + const bool is3D = data.type == TextureType::Texture3D; + const TextureType realType = (isKtx2 && is3D) ? TextureType::Texture2DArray : data.type; + + for(UnsignedInt i = 0; i != total; ++i) { + CORRADE_ITERATION(i); + + const auto texture = importer->texture(i); + CORRADE_VERIFY(texture); + CORRADE_COMPARE(texture->minificationFilter(), SamplerFilter::Linear); + CORRADE_COMPARE(texture->magnificationFilter(), SamplerFilter::Linear); + CORRADE_COMPARE(texture->mipmapFilter(), SamplerMipmap::Linear); + CORRADE_COMPARE(texture->wrapping(), Math::Vector3{SamplerWrapping::Repeat}); + CORRADE_COMPARE(texture->image(), i); + CORRADE_COMPARE(texture->importerState(), nullptr); + { + CORRADE_EXPECT_FAIL_IF(isKtx2 && is3D, + "basisu saves volume textures as KTX2 2D arrays and the transcoder can't read 3D textures."); + CORRADE_COMPARE(texture->type(), data.type); + } + CORRADE_COMPARE(texture->type(), realType); + } + + UnsignedInt dimensions; + switch(realType) { + case TextureType::Texture2D: + dimensions = 2; + break; + case TextureType::Texture2DArray: + case TextureType::Texture3D: + case TextureType::CubeMap: + case TextureType::CubeMapArray: + dimensions = 3; + break; + /* No 1D (array) allowed */ + default: CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + CORRADE_COMPARE(counts[dimensions - 1], total); + } +} + void BasisImporterTest::rgbUncompressed() { auto&& data = FileTypeData[testCaseInstanceId()]; setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, @@ -648,6 +747,341 @@ void BasisImporterTest::linear() { } } +void BasisImporterTest::array2D() { + auto& data = FileTypeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + CORRADE_COMPARE(importer->configuration().value("format"), + "RGBA8"); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + std::string{"rgba-array"} + data.extension))); + + CORRADE_COMPARE(importer->image3DCount(), 1); + Containers::Optional image = importer->image3D(0); + CORRADE_VERIFY(image); + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); + + CORRADE_COMPARE(image->size(), (Vector3i{63, 27, 3})); + + if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("AnyImageImporter plugin not found, cannot test contents"); + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + /* There are moderately significant compression artifacts */ + CORRADE_COMPARE_WITH(image->pixels()[0], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), + (DebugTools::CompareImageToFile{_manager, 94.0f, 8.064f})); + CORRADE_COMPARE_WITH(image->pixels()[1], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27-slice1.png"), + (DebugTools::CompareImageToFile{_manager, 74.0f, 6.481f})); + CORRADE_COMPARE_WITH(image->pixels()[2], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27-slice2.png"), + (DebugTools::CompareImageToFile{_manager, 88.0f, 8.426f})); +} + +void BasisImporterTest::array2DMipmaps() { + auto& data = FileTypeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + CORRADE_COMPARE(importer->configuration().value("format"), + "RGBA8"); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + std::string{"rgba-array-mips"} + data.extension))); + + Containers::Optional levels[3]; + + CORRADE_COMPARE(importer->image3DCount(), 1); + CORRADE_COMPARE(importer->image3DLevelCount(0), Containers::arraySize(levels)); + + for(std::size_t i = 0; i != Containers::arraySize(levels); ++i) { + CORRADE_ITERATION(i); + levels[i] = importer->image3D(0, i); + CORRADE_VERIFY(levels[i]); + + CORRADE_VERIFY(!levels[i]->isCompressed()); + CORRADE_COMPARE(levels[i]->format(), PixelFormat::RGBA8Srgb); + CORRADE_COMPARE(levels[i]->size(), (Vector3i{Vector2i{63, 27} >> i, 3})); + } + + if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("AnyImageImporter plugin not found, cannot test contents"); + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + /* There are moderately significant compression artifacts */ + CORRADE_COMPARE_WITH(levels[0]->pixels()[0], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), + (DebugTools::CompareImageToFile{_manager, 96.0f, 8.027f})); + CORRADE_COMPARE_WITH(levels[0]->pixels()[1], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27-slice1.png"), + (DebugTools::CompareImageToFile{_manager, 74.0f, 6.482f})); + CORRADE_COMPARE_WITH(levels[0]->pixels()[2], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27-slice2.png"), + (DebugTools::CompareImageToFile{_manager, 90.0f, 8.399f})); + + /* Only testing the first layer's mips so we don't need all the slices' + mips as ground truth data, too */ + CORRADE_COMPARE_WITH(levels[1]->pixels()[0], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-31x13.png"), + (DebugTools::CompareImageToFile{_manager, 75.75f, 14.132f})); + CORRADE_COMPARE_WITH(levels[2]->pixels()[0], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-15x6.png"), + (DebugTools::CompareImageToFile{_manager, 65.0f, 23.47f})); +} + +void BasisImporterTest::video() { + auto& data = FileTypeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + CORRADE_COMPARE(importer->configuration().value("format"), + "RGBA8"); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + std::string{"rgba-video"} + data.extension))); + + CORRADE_COMPARE(importer->image3DCount(), 1); + Containers::Optional image = importer->image3D(0); + CORRADE_VERIFY(image); + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); + + CORRADE_COMPARE(image->size(), (Vector3i{63, 27, 3})); + + if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("AnyImageImporter plugin not found, cannot test contents"); + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + /* There are moderately significant compression artifacts */ + CORRADE_COMPARE_WITH(image->pixels()[0], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), + (DebugTools::CompareImageToFile{_manager, 96.25f, 8.198f})); + CORRADE_COMPARE_WITH(image->pixels()[1], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27-slice1.png"), + (DebugTools::CompareImageToFile{_manager, 74.0f, 6.507f})); + CORRADE_COMPARE_WITH(image->pixels()[2], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27-slice2.png"), + (DebugTools::CompareImageToFile{_manager, 76.0f, 8.311f})); +} + +void BasisImporterTest::image3D() { + auto& data = FileTypeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + CORRADE_COMPARE(importer->configuration().value("format"), + "RGBA8"); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + std::string{"rgba-3d"} + data.extension))); + + CORRADE_COMPARE(importer->image3DCount(), 1); + Containers::Optional image = importer->image3D(0); + CORRADE_VERIFY(image); + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); + + CORRADE_COMPARE(image->size(), (Vector3i{63, 27, 3})); + + if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("AnyImageImporter plugin not found, cannot test contents"); + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + /* There are moderately significant compression artifacts */ + CORRADE_COMPARE_WITH(image->pixels()[0], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), + (DebugTools::CompareImageToFile{_manager, 94.0f, 8.064f})); + CORRADE_COMPARE_WITH(image->pixels()[1], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27-slice1.png"), + (DebugTools::CompareImageToFile{_manager, 74.0f, 6.481f})); + CORRADE_COMPARE_WITH(image->pixels()[2], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27-slice2.png"), + (DebugTools::CompareImageToFile{_manager, 88.0f, 8.426f})); +} + +void BasisImporterTest::image3DMipmaps() { + auto& data = FileTypeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Data is identical to array2DMipmaps. Mip levels in basis are per 2D + image, for 3D images they consequently don't halve in the z-dimension. + The importer prints a warning (unless it's a KTX2 file, those don't + indicate 3D images at all) and imports as Texture2DArray. The texture + type is tested in texture(). */ + + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + CORRADE_COMPARE(importer->configuration().value("format"), + "RGBA8"); + + std::ostringstream out; + Warning redirectWarning{&out}; + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + std::string{"rgba-3d-mips"} + data.extension))); + + const bool isKtx2 = std::string{data.name} == "KTX2"; + + { + CORRADE_EXPECT_FAIL_IF(isKtx2, "basisu saves volume textures as KTX2 2D arrays and the transcoder can't read 3D textures."); + CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): found a 3D image with 2D mipmaps, importing as a 2D array texture\n"); + } + + if(isKtx2) + CORRADE_COMPARE(out.str(), ""); + + Containers::Optional levels[3]; + + CORRADE_COMPARE(importer->image3DCount(), 1); + CORRADE_COMPARE(importer->image3DLevelCount(0), Containers::arraySize(levels)); + + for(std::size_t i = 0; i != Containers::arraySize(levels); ++i) { + CORRADE_ITERATION(i); + levels[i] = importer->image3D(0, i); + CORRADE_VERIFY(levels[i]); + + CORRADE_VERIFY(!levels[i]->isCompressed()); + CORRADE_COMPARE(levels[i]->format(), PixelFormat::RGBA8Srgb); + CORRADE_COMPARE(levels[i]->size(), (Vector3i{Vector2i{63, 27} >> i, 3})); + } + + if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("AnyImageImporter plugin not found, cannot test contents"); + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + /* There are moderately significant compression artifacts */ + CORRADE_COMPARE_WITH(levels[0]->pixels()[0], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), + (DebugTools::CompareImageToFile{_manager, 96.0f, 8.027f})); + CORRADE_COMPARE_WITH(levels[0]->pixels()[1], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27-slice1.png"), + (DebugTools::CompareImageToFile{_manager, 74.0f, 6.482f})); + CORRADE_COMPARE_WITH(levels[0]->pixels()[2], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27-slice2.png"), + (DebugTools::CompareImageToFile{_manager, 90.0f, 8.399f})); + + /* Only testing the first layer's mips so we don't need all the slices' + mips as ground truth data, too */ + CORRADE_COMPARE_WITH(levels[1]->pixels()[0], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-31x13.png"), + (DebugTools::CompareImageToFile{_manager, 75.75f, 14.132f})); + CORRADE_COMPARE_WITH(levels[2]->pixels()[0], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-15x6.png"), + (DebugTools::CompareImageToFile{_manager, 65.0f, 23.47f})); +} + +void BasisImporterTest::cubeMap() { + auto& data = FileTypeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + CORRADE_COMPARE(importer->configuration().value("format"), + "RGBA8"); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + std::string{"rgba-cubemap"} + data.extension))); + + CORRADE_COMPARE(importer->image3DCount(), 1); + Containers::Optional image = importer->image3D(0); + CORRADE_VERIFY(image); + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); + + CORRADE_COMPARE(image->size(), (Vector3i{27, 27, 6})); + + if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("AnyImageImporter plugin not found, cannot test contents"); + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + /* There are moderately significant compression artifacts */ + CORRADE_COMPARE_WITH(image->pixels()[0], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27.png"), + (DebugTools::CompareImageToFile{_manager, 94.0f, 10.83f})); + CORRADE_COMPARE_WITH(image->pixels()[1], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27-slice1.png"), + (DebugTools::CompareImageToFile{_manager, 74.0f, 6.972f})); + CORRADE_COMPARE_WITH(image->pixels()[2], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27-slice2.png"), + (DebugTools::CompareImageToFile{_manager, 88.0f, 10.591f})); + CORRADE_COMPARE_WITH(image->pixels()[3], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27.png"), + (DebugTools::CompareImageToFile{_manager, 94.0f, 10.83f})); + CORRADE_COMPARE_WITH(image->pixels()[4], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27-slice1.png"), + (DebugTools::CompareImageToFile{_manager, 74.0f, 6.972f})); + CORRADE_COMPARE_WITH(image->pixels()[5], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27-slice2.png"), + (DebugTools::CompareImageToFile{_manager, 88.0f, 10.591f})); +} + +void BasisImporterTest::cubeMapArray() { + auto& data = FileTypeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + CORRADE_COMPARE(importer->configuration().value("format"), + "RGBA8"); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + std::string{"rgba-cubemap-array"} + data.extension))); + + CORRADE_COMPARE(importer->image3DCount(), 1); + Containers::Optional image = importer->image3D(0); + CORRADE_VERIFY(image); + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); + + CORRADE_COMPARE(image->size(), (Vector3i{27, 27, 12})); + + if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("AnyImageImporter plugin not found, cannot test contents"); + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + /* There are moderately significant compression artifacts */ + CORRADE_COMPARE_WITH(image->pixels()[0], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27.png"), + (DebugTools::CompareImageToFile{_manager, 94.0f, 10.83f})); + CORRADE_COMPARE_WITH(image->pixels()[1], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27-slice1.png"), + (DebugTools::CompareImageToFile{_manager, 74.0f, 6.972f})); + CORRADE_COMPARE_WITH(image->pixels()[2], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27-slice2.png"), + (DebugTools::CompareImageToFile{_manager, 88.0f, 10.591f})); + CORRADE_COMPARE_WITH(image->pixels()[3], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27.png"), + (DebugTools::CompareImageToFile{_manager, 94.0f, 10.83f})); + CORRADE_COMPARE_WITH(image->pixels()[4], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27-slice1.png"), + (DebugTools::CompareImageToFile{_manager, 74.0f, 6.972f})); + CORRADE_COMPARE_WITH(image->pixels()[5], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27-slice2.png"), + (DebugTools::CompareImageToFile{_manager, 88.0f, 10.591f})); + + /* Second layer, 2nd and 3rd face are switched */ + CORRADE_COMPARE_WITH(image->pixels()[6], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27.png"), + (DebugTools::CompareImageToFile{_manager, 94.0f, 10.83f})); + CORRADE_COMPARE_WITH(image->pixels()[8], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27-slice1.png"), + (DebugTools::CompareImageToFile{_manager, 74.0f, 6.972f})); + CORRADE_COMPARE_WITH(image->pixels()[7], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27-slice2.png"), + (DebugTools::CompareImageToFile{_manager, 88.0f, 10.591f})); + CORRADE_COMPARE_WITH(image->pixels()[9], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27.png"), + (DebugTools::CompareImageToFile{_manager, 94.0f, 10.83f})); + CORRADE_COMPARE_WITH(image->pixels()[10], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27-slice1.png"), + (DebugTools::CompareImageToFile{_manager, 74.0f, 6.972f})); + CORRADE_COMPARE_WITH(image->pixels()[11], + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x27-slice2.png"), + (DebugTools::CompareImageToFile{_manager, 88.0f, 10.591f})); +} + void BasisImporterTest::ktxImporterAlias() { if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("AnyImageImporter plugin not found, cannot test forwarding"); diff --git a/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt b/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt index 88a6c66ba..08b530324 100644 --- a/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt @@ -72,11 +72,30 @@ corrade_add_test(BasisImporterTest BasisImporterTest.cpp rgba-pow2.basis rgba-pow2.ktx2 rgba-2images-mips.basis + rgba-3d.basis + rgba-3d.ktx2 + rgba-3d-mips.basis + rgba-3d-mips.ktx2 + rgba-array.basis + rgba-array.ktx2 + rgba-array-mips.basis + rgba-array-mips.ktx2 + rgba-cubemap.basis + rgba-cubemap.ktx2 + rgba-cubemap-array.basis + rgba-cubemap-array.ktx2 + rgba-video.basis + rgba-video.ktx2 rgb-63x27.png rgba-63x27.png + rgba-63x27-slice1.png + rgba-63x27-slice1.png rgba-31x13.png rgba-15x6.png rgba-27x63.png + rgba-27x27.png + rgba-27x27-slice1.png + rgba-27x27-slice1.png ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/KtxImporter/Test/2d-rgba.ktx2) target_include_directories(BasisImporterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_BASISIMPORTER_BUILD_STATIC) diff --git a/src/MagnumPlugins/BasisImporter/Test/README.md b/src/MagnumPlugins/BasisImporter/Test/README.md index cba3e78bd..81f4887dc 100644 --- a/src/MagnumPlugins/BasisImporter/Test/README.md +++ b/src/MagnumPlugins/BasisImporter/Test/README.md @@ -2,17 +2,31 @@ Creating input files ==================== The images were converted from central cutouts from `ambient-texture.tga` -and `diffuse-alpha-texture.tga` from the [Magnum Shaders test files](https://github.com/mosra/magnum/tree/master/src/Magnum/Shaders/Test/TestFiles). +and `diffuse-alpha-texture.tga` from the [Magnum Shaders test files](https://github.com/mosra/magnum/tree/master/src/Magnum/Shaders/Test/TestFiles): - `rgb-63x27.png` - `rgb-64x32.png` - `rgba-27x63.png` - `rgba-63x27.png` - `rgba-64x32.png` +- `rgba-27x27.png` -using the official basis universal +`*-slice*.png` files were generated by h-flipping and inverting color channels: + +```sh +convert -flop rgba-63x27.png rgba-63x27-slice1.png +convert -flop rgba-27x27.png rgba-27x27-slice1.png +convert -negate -channel RGB rgba-63x27.png rgba-63x27-slice2.png +convert -negate -channel RGB rgba-27x27.png rgba-27x27-slice2.png +pngcrush -ow rgba-63x27-slice1.png +pngcrush -ow rgba-27x27-slice1.png +pngcrush -ow rgba-63x27-slice2.png +pngcrush -ow rgba-27x27-slice2.png +``` + +They are converted using the official basis universal [conversion tool](https://github.com/BinomialLLC/basis_universal/#command-line-compression-tool). -For conversion to `*.ktx2` at least version 1.15 is required. +For `*.ktx2` files at least version 1.15 is required. To convert to all the required `*.basis`/`*.ktx2` files, run the `convert.sh` script. diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-27x27-slice1.png b/src/MagnumPlugins/BasisImporter/Test/rgba-27x27-slice1.png new file mode 100644 index 0000000000000000000000000000000000000000..233fd992c5003a6aa2704ead97cd38072edf4621 GIT binary patch literal 755 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i3?z4Pv7`ejmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fHRz`)29;1lAi-+V?tYo2~U z{&{l)5W8%vzH{Vh6Ft4=GkP@#^lZG&8X1Aan@;PSJL)%`x?rjg;(8|O*Bv@zYz$NTG|XKHxX#OU}1{j(;9K=Pc4;feEl zXH5-{oi~7p0A){{1IwP*I|Eb$6gM_FX>4%D*Z?BF)YLS{)H>MII@Z)`o2k*UbNU-i zjXlilwwoHCGc{UnYU*KXchuAfsIkD*YMQAz(AZp4s~IMyCobqmn^|o#F##IeU}oNF zW`6vf{tH89XJ9ael?3?(-@0{6^~Q}G>esGa)4T!%+Lta}(gnKF@Qm>((-Y>$ERR?p zvOQqGuW(P%u96+4+se08Y^vH&y>8vQ+BINMw+aFpR)9d`G7xB%I=1u=&<4%|kH}&M z2EHR8%s5q>4-}j1C7!;n>`%FbMKw7t8EykbZHuRiV~EE2+DneyjEN$x4}Tk-Jao)y zl8Wag70*o@HgEa#|Np6!h#&imj_oNg7W4mp@2sNIg1%#4>>U-&M5J9A&bNu4JXSCF zY_I2*c9GzXGjGJ?C~gyp?p%A+EGc^9+z8EW;?^CqNBxr0H+Fw{%C)B|%M(2qoO{)^P6WZPC)mlmbgZgq$HN4S|t~y0x1R~10zFSLjzqy%Me3L zD-#ndBV%m?BP#=gxFo?RC>nC}Q!>*k(KQ%a8CXCxbeb5>25MmNboFyt=akR{03GNf Al>h($ literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-27x27-slice2.png b/src/MagnumPlugins/BasisImporter/Test/rgba-27x27-slice2.png new file mode 100644 index 0000000000000000000000000000000000000000..785e8af17c9c6ec3f7953b86074517e955db9bee GIT binary patch literal 745 zcmZ8eOH30{6n%WOPz2HtVw4RFKuK*nZ)UI`C8R=Q$Iu3mD7rw(S7JoO@)24-qKo5QeU62~0bQj)L_D3hCUNNzS+O#n;{r~R^T!{^+3OQjWno&o>{t^n{8Qv<62 zbPxc1tp&h%7l55@qk*Fb0Z2%!D6cXTVL^0K;^ZySYo}RB&S%F(_YpE0m7J9Hep(tT z(IQdiT}5JePIS=}$H~XRV$uHqMtGdltlWSlf|DW;9!PHW;Hu=LX;xl=tJp(?8B(tm zNjAFac^x4^mNyUriTJ+^+^iB3$?|$apjcjq8BU-$VLvArv*NQdE4wfy z2L1Ba_Aq6ps=b0M2GdM-aP{L_yJ9Gm%XnHnc?=o9QLwQwx!Sj*%OmtItS#Ql_)z?)b8Y{aF@8o90s-l`%(gP$q1wxT0KD!)qW}N^ literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-27x27.png b/src/MagnumPlugins/BasisImporter/Test/rgba-27x27.png new file mode 100644 index 0000000000000000000000000000000000000000..b9f70b15c107b469516f2c4eb08640484a63c110 GIT binary patch literal 524 zcmV+n0`vWeP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf0ij7mK~zXft=2J1 z!%z?h@VkpuY&$6^ii<-Xt()LS5cM-S=;G$As}4@$?AFiV?C2y4E`9(Pbt(>RX>!kh zB$cFnS4jFE{2*M8hP!vkE5GdSw8&V;2lo%zW%>2oAzE(}t!-xkB8d|<;l5V(^QF{W zEyP50&g76#*`TX#zEOxt6eT7`LM2X>hdpIHEFYH`67vdt3EuUCQ zt**3pHW8OZZR5$a!XQMZe7{5Rwr|7{V~WB-yCM^jL;2x&D;jY~2niK96QlHyFbHDu zP1G!eBUE7UZSpMm>9u{1r+U}?NpzT923cvV@CsP`Q&p9)L(44ab4a2j_*(io#?cUv zFrxwkIZRPNX$k2;;Dv2Nufq|C3M{@&o&|m|oddu8ksd;E`X=FsuZAEQi(wi)&CU3C za=K4^=x(L1*V51%xPo`7{oTXV@ibaSAMU8B`!v)`>gffXQeQi~s2_~4Ek+!9eh#IV z@#Z3JQLI}o{*SHTpVpBZ=p!v)8u9j;JexdC7cE?798P2L4VUOQll}svuT7niPy$H+ O0000T={PDY<`*Z!S>-k>a>wY5lZ3d1& z2fV-lP-7tv015!01SH`6{D}wC2mmY);0(}#8;%GN0avcU701ul#P3$%UqBXMfQ%#n z-tb)W_X6S*hd{Q6bl__^5I`MB11$X9&yWqEG&+eH@rhqTR)can1`Z^^12o)40t6ZW z9F*8mF(W>)^nVyR1H*14*#g-ep4&pjyq+W+vNeP^7|0t*_>hetyRgN)o@5NNHe~xI z$Qwzpus1wpQ*SY^CsBed13A113FmqzI*`5KxeFua^&|<9|LFwsMv_j*;!a|##k`(m z0rEecK;B3~zyjDM0JKywuP4!hEUpSUL!?N6eX_}j5hFs59(i|`bqetwij^QMk?=r% zywjgi;yYrP477X)OHttd;h>qCZ0|M28b6>o|({3*V_)er>B zh>;jlE2`&xZTQnoqAOY86BS8A}yo=@C^ z-F_W*?x4RhKPGh7Yp;495jS6X`;}_`>58XU<%l9;tB7-9=oFLcdYKXP`~Amh)Aa{* zWkN^Cw=rN{|NUTJNChfTl^?_pcawIst8B~W)hH*P4dMIJBsA7ocw<$wM7Mm+E?RiW z_aAbay^%jvRm){z6)sZlu~r({p+nBwNl)M~ zs7Cd>m$QYCSK9cH8`KnVyTrv8qj1kA48wIEnWv4tB_nYxZIGtycUi&Dz6!HDSMTvY za`I+Yzyh`x#mDPKIc8oDAZ}HyZLZ{)cMksB?%BEiss$sLubjUCY@GFtrZ#dDFvcZm z|5Bf%jqtrEKXb{1!=B*(!QO{@1|$a*S^4?o#vOJoWZuMLRea>)s9?KS7(P&T;1Bto zC_ALE3q84?`@Zg0eZ$_p-a8Bp>uyb#zOGJLWnk9|3n%wu8wKIQ!wDVjTkpo;du;RP zf?d<&qlNWPf>+$=oCN0Z@Hh%KH(nJZ-a>U+c?=v64pv4@Jj zVRR+(kUTDU6dyY>)+<7Uqe(O(uDM~jz$PfMRUX~U{Zhs$89|bdXOX_^);4S{yFtJj zL<|2!_FVre*f9O&?82rnBc8?6T8%_Y{Xx0VAI{Yo^Be?W2*v4i_0biXZDEY53~jR` z%-qt8Kkjs!;)JQNu<#ecHdU=UX-Z+i>DifYJR2rOg4X7N6n5Ca*Sl;iLRh*^55IM3 zOTANcXj{zmuHz%UQ~V#YKiXZo-9+rj3l9|b%z9wr&%eH5)5fA4-P?1}w1*o}>F<=a zI*YnXcq3eltfMHomOJ=)X1!`bk0KVOUP`7>m1X^Ir^y|#!X16I!_J@y6<#XsM$Rzi z_T4N{L~D_7yi(@z>eOsA-OF60ngxksu-)I?JakStl%-ltU5!dS|FOyqzs!k_BF?x@ zw;bVH@;W?n_4vc`tJlA9&za)eT;nWV6qLCunt0;JwfwE0>e^;1Sf&`;epkKo@ELRI z8U287mHN7o<$IdfE?$vTYwog_q(3_tpr~?|{~?qsvG{IWNM6H6t0q;+>k>K_2%L_n<+WUG}%XaoyFNy56toEK>QYG{V~+wR^4$eN$a^ydyYt*l#3RX zJEl*J4KziW3cc6P`4mba1~VsG&@{@FO+c!26U(IUM1^gpahY`Y1?6N!ntw{4CJsvr z47zkf5gmk9bbePJMxar&$|yY1V7KQ0_htd_Dot~>*&*fKV)(J>%~z5}`-A5F>EnfA z!G;+ok^=QzwIOoL4)HFgqi0WO`D7HF3Be7T&KQW8mCuS2TWVX{1i1$iz6@NW&bD9Z zD@^L(-%)z*ML^8H9xtC;@bU7(RN$|4p{7e74gG%CBRN6PyzI8=7tcJKdcQS`+BsFm zs|SY?O$ORCKG;O0D{jY?XyJZU88Hj3qgeHJ6=yf9=m(sovigNK2^VN_yUwB|8k(0}ms7BbrWaTzAgKWAC<5F@tqNZ|* znY2kUnXP7J)3(c{Tym+c*fL>9L{~QBI&a;A0ZuAuh+#`yo+)uxj&nNxF~~_)kxPE2Hr5fsEc4hv<{VWA@o41UK(l40_(o6i z>LFB+EuaI6_MEBRH#G4})yJFqA>^RnAK=7Gh~Wf63)uaoHu}@g++7!Fhx;$p>-z0i zp(^Q71D6Wss;w^V#46{ta^2?=-0PWCS*+o4BP^5m#Rl-E4YFgqxQ77F#eOj(gizXW za3HSW*%lzbKsERjLqHk_gJ`?stMArv4QOSr7a4-sct1wx-oWseNE;8t(N01Pp6&bkfpLp zAjk#fvE`T>D3sUcBUN7-W)P|EssSFRV(Ne{CHQgPCR}0pljr>K#?bp^UO@`#k6l;_ ze9z%I`sgPj62mb9GPHuPsRz3Y6?PSvzH}m{ZRCW`qkBLR)->)w_U%yoW}VumY96U$ z;3u~?7y5+rmR>tNe*m}`=R1w9#74jhlWO#9eX0>6=&{=LRU006T=XZ`348;{_iJ#9 ziil6Pxiu37;|s#1*o6`CR(}>YOr`%%we6rAQqqZ<*vtP?cdx!-&z^v778Z5)rpiCm zq^~g1vXYXCz33<6Xvv}E_O{KB60qH_MY9pK%%pfp{i}#&Zw4=!Jv8)Lnn)Ud^uF3r z3ILQrAPSEzod8Md*+FK$i3o-eR5j}L3>CYC zr?jY{n)uer9%%@IS`vqFvdhS#rQ!|_Z5}WA8`*t(M}$S@yP5e7EK7m?>stL32eSdy zs9P86tOQgs3!yQUsW-B$Y|LU#W*ONXW*3xS`FXqdB#))dK|}uybfa*^!$1)YRPJt) zUAZo=+tleU&v>&xR73l`=xY>TZsx;SOFZ?+xr_ z0+Xj1QByy>Sgf#qY9%h^(l?o497HJEU8=o~+q^=p0FD+AQ>hW^WgArHPvUc_I2}{Ko64BYoj> zA&jvSR)j^Cjl5WIXKkeF5;e)ocI5nV!@#WKbCH+<+i7ztyZX(|l;+yzR&l|;Wa}^) zWv1Iwc`J2u!q0UEXq}V&->2%#W_wU#niS^}a<(Of3W5rbG@7 z=Zbkb4a_vm|K9G*%=C|b4HHsvOH+S3m(~Bzoi6r~921Y{C%jrOKD>F*IALn%vEiOc z(XHHXZddO&;@b<&cl;>E3VK&5x-L@yJ=30UzN$N)}@4$eOfD zYOr6<`)1S#VnOkpdOM!zkr&(C%p1YzawTtKmKDGEZm|Z+kbn`CvyatW z%(XMQ#z*Sf6UgRULwrb4vy#Caof^u*=P?OUhK|=s6{?xwI_7b_tOuhZXZ~{jRAp5` zapZ3#=V{SZUKDne=2CnWj@5)=H+hGe*OHr#e#gg!LNRWF7?4O``TF@=KE&{Q*8R%vc9^+WNK{1lo?`v z(;l{qsv|r0be85m(KZV`PvHhXy~&o<@!l3WmfzOZ2a#Sg_Xvw%X*Hv8(;_Ycm9<;i PM|x$^<7W5&yup71wKEWG literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-3d.basis b/src/MagnumPlugins/BasisImporter/Test/rgba-3d.basis new file mode 100644 index 0000000000000000000000000000000000000000..175658e467ec6d18c6e8a1242aad8d8d933cfc39 GIT binary patch literal 1185 zcmXSR5@zsakUbf~!ocu(HyZ;3GXn#II0KM}0z4S51Nny-7#tX8GJ+Y=ObiTVKrvqw zbwG|igEWHx13SY-pn`B9KR^nkf)OUH0kpskD8Q8gGzXzN4=5ZAAFiQ(6=f9s1|&uFl4eBel6U~zo# zFz}Js%QHVaf&?B2HZX87ZETb8Ji5{S#U0#QnMZ zv-Z>k&#Y;6pK_lwMzgGc=_0d5cEXbHd?&j(?$)ZP8VgQbx7ENn$NQ6qkYLw|ZL^Bf z=AXT@b4$W~2iJr4pWjMOciuLNxO9CAL102h^JeU(@7(UGZzVWa^UXylIUt%B&KU);f|E$Xe`wk{ZJHU=z1;i$_o`Kwz8*eXQ}_SV&R-R~ zqxI_j_5&mC4nv{@+oBiD%T4AyR1>Uc6xR%@kmz5bw6yJKJ8R|~7S0ZBEgmLEk*8~B zXmCE^R1z!uCRTUAs5oEYLsrt{jzbv=4Y^zMSQZBgO>gox$-2_<+w%B(weNg(XAGQQ zKW1pv+`{>nGoD$1(E(_g#J2mME{u#(Y+nz(ef)LVXTj6I@AzD_x%B;lG{*(`n2<<; zpC0wE9&F6rcVPGC+3AjdUi!}TVY!s_{oVQfGvBVRv%1@)z;WgNd4|p#@m1@-KDm`$ z_x<|+pZ==%EbdJ^E~RE#dBnuS`<1Syk{U;8xU0!T=ZjO1XQy#qULc;kWueaug+-UA zoOJ0+Srq;%f2C0q-z4o9Oc&I<@>Dlxq%V_tXLDXAg~^itmFf5A8uMigUpl)~AK$Lj ze|oR#-!JSym$)phcaU9>D(K7d0_4_-H}3_drVF?TU3qD{aI<|W$DQw$??eh8|M~Ed z|M>N)P5a|F{EoFxpU3v=d)B88PA4C+CxXH*7nbhUS=5=aSk(0hi(>!znW{bliHoM6 ztD5H1q801lTkky6z;VX9m)ABF&df0i^nIBk#>?b3p)!}hPy zAAXrCM;cwUG{3l><$dI5)=ww5ooaefeh=tDh3Jjd->*NeVGI9TvWwMGz+#`t0aeDk bA7X;*I4}8QL5pO literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-3d.ktx2 b/src/MagnumPlugins/BasisImporter/Test/rgba-3d.ktx2 new file mode 100644 index 0000000000000000000000000000000000000000..5fbed6d1d094f1476d824b4617f8c92aaca70b86 GIT binary patch literal 1250 zcmZ4O9TK5nWU!l;ONxsD2pECb9*Ctu93Wr@VvraNX8_qYK)eKqRe<;e5QBgh6NDB5 zX#j!?APq80s0CGB9{BI4~RnssrINm}V#)4x~ZY4a%Pjq=SL@0}wkx#SDS& zjRdm0fHX**!T}bBMvgWnhF{12tuJmpqrt-Qfg^!|#qq(zz(-;)&;0BN5_llkz`((@ zu}!}7=tlP&)8=qEumE*7I0U4Bc;r1-wEpFxi5>U0#QnMZv-Z>k&#Y;6pK_lwMzgGc z=_0d5cEXbHd?&j(?$)ZP8VgQbx7ENn$NQ6qkYLw|ZL^Bf=AXT@b4$W~2iJr4pWjMOciuLNxOG3Q^qBxdu zc`zr+FnpN*edA$;ye93azQjNlezqu{|5=v{_8m-+%H76)#EzxeTg|flsQHhso9~im zsD}Z8o6D0q#ZzAh%ZACGN!yaExwNOy=B~M+CVRB$oP?I9uG6y)PVM9C=x}g3H^EqN z`_d_V^_{$$OIg}-w!V=Sa%9)O_5R|D%o}$zXPxc(oM|q{)0TVL#{Ed(xt$#+4kig6 ze6(?$e%$rfigo92)E%n#db#)g?^UZVeLZ}(rtbfzoxdt}N9)!5?FUBA9fm{+wnZ

F(HuxKRxPSJ=mDL@4)WOv(p{_ zy!4&v!*VI<`@8e|XTDusXLYwpf#b^k^9-Fg;;YtueR3)OE7R}KHRj70zI1k}KE7S4|MXtfzhBsaE^%30?;yJ% zRnV8^1<0)vZ{7<^O&4$xy7JO?;b!|%jyvBg--#4H{`27@|MBZpoA$?V_#JDXK9B9! z_pDDHoK8MsPXvWsE-c-xv#2v;v8d}27RCPaGgW;A5*JNBS2fM2MJv|9x88ZCf#ZyI zFRyJVoS9=3==(B7jF;i(?&x;;Iq`@0@B1lu)oc6phwWdZKm0OPjx@SxX?}4%%lpXB zte;M9JJs}}{2tJQ3eg*@zh8e`!xsLxWEZQWfWGXSwe# literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-63x27-slice1.png b/src/MagnumPlugins/BasisImporter/Test/rgba-63x27-slice1.png new file mode 100644 index 0000000000000000000000000000000000000000..3604d0c584839fa4a66cfc01635a76578c05b774 GIT binary patch literal 942 zcmYL|Z%i9y9LFCr2oq%UjhSv5_a_@s!adL3J-^=d4z6LeS&J4}$BgI}H_LRI5EvBK zvDsA4#4!`mE@Ldg`3D%&EX{@!7&Zu`b{%c6glrA)rY34)HSyJ6Y|Z@h9DT7*@;tx0 z@Avop-CZtEe{)9zu_#@4ot8`%CRGM-|k_?*qJc5uigwU4G;hFt42o)V~ex=2M^(21pGH&ijOJrUm znxsjRW{U6sfa_Zml#5c1QgwvVLsaGTBzIu#D$vL2?3 z&^A}Q!&03J{efO;5|Nf2TNo|xgA4yxBDMFWHzAUZAD6o}&hPSAb^flWBnrd<4j zviY5$41&}S$`FP45}uspT}|p-9J2?#VNoV14O*mri*(z-=R(kZO74!F-@$%{-{)yO zu(r0go3XO8QfZROWL`A6%cv@_yu4gpU`Wfn(>=`!9t~p4rjn|E#m9=b7ouldWSDHLGgPLCb^ePal8%!s`PsZHzqI`&A}z|JMz} zx1OJ?uk`lgA+OgsPqSN_T(({R4Bs!wZ5e$t6maArCyi!9fqnVf+~!eR=wExjd+&ce z4?oF`@2UTGWcG5vl06r_H#okzoExZZES~b)UdFE)JEtojd|DbozZLH~JKuFF#y(-U z&+(eC?WpgV;_W)d3KEFGSM#Krmsc`Ta)d`Oi7-W z3;;;6Wm$4@H{msX%m`eEf;UCnM(!}%%>dl19XC`k3hz_5W#!rdsF(wQ=NJHStUR9p zC?f#)oDYEU5&%<6uRO_F1VGZx?3H;|f|EIpSGt?w&;#3N(DIo@Y1m7+G+;Y>K(5@wnOrbS@#1wk1&l7RwO$*8m-D&eh$N4Vl!WEV|98M!DN#0C$=+wxhLxE=2W>&I0 zc<6d>^$#_hRRX#H2g6RI&&SX(=a_fJ#!VhySwKr zbai#95_DqH6LfTRsMUy+Xh92goFTlB&3IvYzPD{((D|h7QF-9t!;1Tr%~gKC-;HS+ z#^docCOCq*rBGd6-JmeJy=w-3hOwnt>-PgNfmK%01=WP$Scj)5)dI-lW=jd&Xz(9?FiOMc+OK^9y@7x2vKP z!^eZ~=lbfr?qzX9|5V==uiF~e_Un9az3$9-M*l>g$@|+9|FLE9m-ZFst4uvJf2Dx7 zipE#k$G04vfed?l-^J23_Z;7Uov9oQKCX-FMh0e|%29p%gI^=fXf%K*^rM;_Zk22k1P~VAN7Ztu7Jhzj*E^ O4gj0gVev1?Kl&HVNWSO* literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-array-mips.basis b/src/MagnumPlugins/BasisImporter/Test/rgba-array-mips.basis new file mode 100644 index 0000000000000000000000000000000000000000..11317c9f3b409a9d01ac6c34dce583edc19b5d88 GIT binary patch literal 2095 zcmb``dpOkj9tZI6%rGTL zh276F0MuF#06+l%6afOxFP{W31IYpb&H!z=;e-H@5&#s!6{j!P#P3$%A3zphfQ%#n zKJZ-oaRKp(Lm@jrI`AzV2%rX}0TxJre?vBa(&Q{=#3z0MSry9ZSU8XX572NI2@q%i za8TmL#EkgF(*I%P?8p3{KDh<52V86q6Z1xr2*@@NUSlACPQr(51lg4(=8YudkhLH? zG(-NJ1Pgn^LpJph^F|Uy$TE;4N|11FbfOK}8=ku{V%|uS2>I_$Ab(EM1zFrlT#cAF zk}N|0yA#NtlMt~0b_oDAP0SlfG$D(tLe7#X5@4Tf3S!iVn5#$Loo$m!x{G2a%1R{O zCroq&(94`RQJBZ%Q6wyKzpwr(T~OGU_^}`$i)=$#qTL?Ql?O(qey)05q8>o;`>mEJ zSV4@&>a?MH-_=FD`H8jroZFGX^9`zA2W1_l)f|0S@)oL%&+o#^#*K>cqIK{04UX=)t)Ud!`7Y2mzPYN){AywB#W~Y^PH}#OBwgx2KfhX?MfQ5^ z5#s*qh)XB!wfQljhh7KoS!Db|^{tmG1*fZ?T$Lk-z5p`&=qW-dgE?!bp$Kjt=E*Iow*Mq_d%XKdOZKIumcKVwU7ypwUf@ zDErs@+!i0?&%7GB?Cp%CQ|W)6N{M4qMC+Bn&lc^hecT?!lT_v)UBc`o6EM4*MRA3eqkehbcx01P&OT2`rr7^*F?{IvO?BE}S zohW;xup2$KpYyK%W<%rNy*@h(4eM{tl)tJ;U87^yi;JiBW19pK!o!K39b50j;(P51 z=0n^v5@Li6k3&}7Y3xMC$jDn!y!qsvm(^Ax0IY{W7ztZCg)*1V^3(T>N6>gE%&`Xw ze&IAl@-P7xGKP;E9q$t%BG5Wi60W6jq{ucnsf~bc;e0LQoPy9HBrtV;=+QE4tGG_Y z8pH_yMD||$Cd4q~#oXeia3ik8lREVzOZ_3aupiIW8*?26;RuD947IUUnQh_p=}axN zBaFQAi$CpjpJs7r}u)-aExWnF{85L13?M}`# z=JekvQb23!;JD?C<27kHX1bR-NL33Riox~(5A(2j;c&J}jrCe|()kZOcl-)FCYm(s zHq&~9Z^`ZS%+uqK5Z11J<&ithw=HBZUlde$D42NRCo}_Wp6J?T$y=rx+x<|p^Y9sS z=~?~2?-l!dkd=E{)-PU>RBh>Ykfc357^t9pmj6DCBeC>$LP%c6#;7Fo2)e|sMIt-1 zc0AzpbDK9+#ue69oXwP6C93Sh{I1d*YfsGanjriZ7yWV6&{o|EO-Y;iO?!?=os^3a zRXSx%jt@3Rn+ko_&-oThA%?OhThUa?v~6ITbTiYW|3sBtmT`r2&jqCvM0!AKzXlFV z4GO+=T>%}8R&aS+8BU~9G%F}v(NK@qAm>IA_bOFmt;I3*?NY?Cn9Wy`#|DBI0%#M( z;UR{ZCXxcRU3H;yD~<`SrekMMX!>Rroe9Mana&!B7}ZZpl3MFp+XZ8<(=T56whjL47IpI~ z^q2P!C7BF%WWKkJ%uv{lE7QdNsyu2IR!_0&>n_b{Qq~VVYt8b%SHf7YXFn^jD!-cu>D!LEH literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-array-mips.ktx2 b/src/MagnumPlugins/BasisImporter/Test/rgba-array-mips.ktx2 new file mode 100644 index 0000000000000000000000000000000000000000..b6c39c854a5bb9d093431d8d208258f05c198cb0 GIT binary patch literal 2052 zcmb`Hdo+~$8pq$6moeGHAch<8k(0}ms7BbrWaTzAgKWAC<5F@tqNZ|* znY2kUnXP7J)3(c{Tym+c*fL>9L{~QBI&a;A0ZuAuh+#`yo+)uxj&nNxF~~_)kxPE2Hr5fsEc4hv<{VWA@o41UK(l40_(o6i z>LFB+EuaI6_MEBRH#G4})yJFqA>^RnAK=7Gh~Wf63)uaoHu}@g++7!Fhx;$p>-z0i zp(^Q71D6Wss;w^V#46{ta^2?=-0PWCS*+o4BP^5m#Rl-E4YFgqxQ77F#eOj(gizXW za3HSW*%lzbKsERjLqHk_gJ`?stMArv4QOSr7a4-sct1wx-oWseNE;8t(N01Pp6&bkfpLp zAjk#fvE`T>D3sUcBUN7-W)P|EssSFRV(Ne{CHQgPCR}0pljr>K#?bp^UO@`#k6l;_ ze9z%I`sgPj62mb9GPHuPsRz3Y6?PSvzH}m{ZRCW`qkBLR)->)w_U%yoW}VumY96U$ z;3u~?7y5+rmR>tNe*m}`=R1w9#74jhlWO#9eX0>6=&{=LRU006T=XZ`348;{_iJ#9 ziil6Pxiu37;|s#1*o6`CR(}>YOr`%%we6rAQqqZ<*vtP?cdx!-&z^v778Z5)rpiCm zq^~g1vXYXCz33<6Xvv}E_O{KB60qH_MY9pK%%pfp{i}#&Zw4=!Jv8)Lnn)Ud^uF3r z3ILQrAPSEzod8Md*+FK$i3o-eR5j}L3>CYC zr?jY{n)uer9%%@IS`vqFvdhS#rQ!|_Z5}WA8`*t(M}$S@yP5e7EK7m?>stL32eSdy zs9P86tOQgs3!yQUsW-B$Y|LU#W*ONXW*3xS`FXqdB#))dK|}uybfa*^!$1)YRPJt) zUAZo=+tleU&v>&xR73l`=xY>TZsx;SOFZ?+xr_ z0+Xj1QByy>Sgf#qY9%h^(l?o497HJEU8=o~+q^=p0FD+AQ>hW^WgArHPvUc_I2}{Ko64BYoj> zA&jvSR)j^Cjl5WIXKkeF5;e)ocI5nV!@#WKbCH+<+i7ztyZX(|l;+yzR&l|;Wa}^) zWv1Iwc`J2u!q0UEXq}V&->2%#W_wU#niS^}a<(Of3W5rbG@7 z=Zbkb4a_vm|K9G*%=C|b4HHsvOH+S3m(~Bzoi6r~921Y{C%jrOKD>F*IALn%vEiOc z(XHHXZddO&;@b<&cl;>E3VK&5x-L@yJ=30UzN$N)}@4$eOfD zYOr6<`)1S#VnOkpdOM!zkr&(C%p1YzawTtKmKDGEZm|Z+kbn`CvyatW z%(XMQ#z*Sf6UgRULwrb4vy#Caof^u*=P?OUhK|=s6{?xwI_7b_tOuhZXZ~{jRAp5` zapZ3#=V{SZUKDne=2CnWj@5)=H+hGe*OHr#e#gg!LNRWF7?4O``TF@=KE&{Q*8R%vc9^+WNK{1lo?`v z(;l{qsv|r0be85m(KZV`PvHhXy~&o<@!l3WmfzOZ2a#Sg_Xvw%X*Hv8(;_Ycm9<;i PM|x$^<7W5&yup71wKEWG literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-array.basis b/src/MagnumPlugins/BasisImporter/Test/rgba-array.basis new file mode 100644 index 0000000000000000000000000000000000000000..192f37f9a8b8f9b27f00430c5cb92e6c92c66402 GIT binary patch literal 1185 zcmXSR5@zsaSi%y*!ocu(HyZ;3GXn#II0GY)L_F2HW-|d@8wunytpN$cRYwDbA!aKaU}0$FXk%jdb?o2z;?^@7EF2#=5*Sz< zA3O|vB=+*m&yFC02Z9X@984SACMA-g8CkUmlv+ac@i9 zpSwS6PfhU5npXEI_c>!U%leltGFxOPEcwoNvYX>>t%|C#;KX%X4UBWVKY0iVcAeNZ zt0-;$**iP8B;0p!J!t>=z15ZLQ8!O~-@D;$q|K+Fv!rtE!v5U;Zt?rN;jwq6@uwD_ zl>HgvJ=djJul~Fw&;c;Ov5d=uIZ=k;!~E|X4=dy~X-D-X2D0$8Me+R4x?HgDV1iWc zHvS`aEY03(mhDH)e{|h^mpnr~3<%s@p3Etp`a)PXO!iFLmR!xHJ%u)R%?&l#qfO@| zv@~^{o^^0)A74j@gUh)I#)8|IPT{NX^N~SN$}vKjqCK|uD@2SJAb3@P`%g7z3+doT6O8`;j=Y$|3B^g zRk1r-uikGzFyihoBucO?dcnNhWX?l1!Fon<&7ca2{uN3~+kUpQX3k;Z?9kTYVR96C zx@Lw3=MzpPv9fPsbq9=!^A$d1B~9))l%dd&yETtxaiGxjCU29hD;>WrkH1&@&S!VV z!1?uKhE~ljoPRmvnFSaffR;&YyYK13$QZ@;_0ZeLUzdFrJpKEQ&qbR{-!DjWT#%0m zi4^$hQUB_}#@u}ec5j}Y?)c}W?@S+-OG)3~o!>w6?dm$KyG;rlSMHx@=)4hMweIVa zTiJErumAt)uX@ko-n8RVYNnM(Ogy|_>1ryeag>I;noM-QIQ4jT8t3H&;<;NE`pi&R zba~22m%fxm;ji*n8a44v(tg2oLA@(ab#q4gGO2eq=VeluEcsuVet)hpU&ipIvrF~y z?MnTp_p1K=!VYwa%i?+m*#)VBzAP_5Zk>4ZUQlYffQ!(Tm$nNx+m~|O`Cj=>r10^d z4294xof(ToU5~IR_Me}r>LZZ2 zX!^OTX+AAlu@1iV&NB@hXRLd9ZA0PA9HT(rmnmYr3_o{Ax699oKfHh6Pr<8R+pj-t z{~G<_m#K23(M3!1i|bk5M}B7gbaLCNrWfV+fF4wc-dO$p`r{h5@V_OySRDl{_L&?| eWxV?#Cb&MH``_!;A9MVkyZZdm>}RA-kO2TEIN?qJ literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-array.ktx2 b/src/MagnumPlugins/BasisImporter/Test/rgba-array.ktx2 new file mode 100644 index 0000000000000000000000000000000000000000..5fbed6d1d094f1476d824b4617f8c92aaca70b86 GIT binary patch literal 1250 zcmZ4O9TK5nWU!l;ONxsD2pECb9*Ctu93Wr@VvraNX8_qYK)eKqRe<;e5QBgh6NDB5 zX#j!?APq80s0CGB9{BI4~RnssrINm}V#)4x~ZY4a%Pjq=SL@0}wkx#SDS& zjRdm0fHX**!T}bBMvgWnhF{12tuJmpqrt-Qfg^!|#qq(zz(-;)&;0BN5_llkz`((@ zu}!}7=tlP&)8=qEumE*7I0U4Bc;r1-wEpFxi5>U0#QnMZv-Z>k&#Y;6pK_lwMzgGc z=_0d5cEXbHd?&j(?$)ZP8VgQbx7ENn$NQ6qkYLw|ZL^Bf=AXT@b4$W~2iJr4pWjMOciuLNxOG3Q^qBxdu zc`zr+FnpN*edA$;ye93azQjNlezqu{|5=v{_8m-+%H76)#EzxeTg|flsQHhso9~im zsD}Z8o6D0q#ZzAh%ZACGN!yaExwNOy=B~M+CVRB$oP?I9uG6y)PVM9C=x}g3H^EqN z`_d_V^_{$$OIg}-w!V=Sa%9)O_5R|D%o}$zXPxc(oM|q{)0TVL#{Ed(xt$#+4kig6 ze6(?$e%$rfigo92)E%n#db#)g?^UZVeLZ}(rtbfzoxdt}N9)!5?FUBA9fm{+wnZ

F(HuxKRxPSJ=mDL@4)WOv(p{_ zy!4&v!*VI<`@8e|XTDusXLYwpf#b^k^9-Fg;;YtueR3)OE7R}KHRj70zI1k}KE7S4|MXtfzhBsaE^%30?;yJ% zRnV8^1<0)vZ{7<^O&4$xy7JO?;b!|%jyvBg--#4H{`27@|MBZpoA$?V_#JDXK9B9! z_pDDHoK8MsPXvWsE-c-xv#2v;v8d}27RCPaGgW;A5*JNBS2fM2MJv|9x88ZCf#ZyI zFRyJVoS9=3==(B7jF;i(?&x;;Iq`@0@B1lu)oc6phwWdZKm0OPjx@SxX?}4%%lpXB zte;M9JJs}}{2tJQ3eg*@zh8e`!xsLxWEZQWfWGXSwe# literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-cubemap-array.basis b/src/MagnumPlugins/BasisImporter/Test/rgba-cubemap-array.basis new file mode 100644 index 0000000000000000000000000000000000000000..006f2445d7a1e4c6b5b227b99194476f76753a6c GIT binary patch literal 2004 zcmXSR5@zsa(Bp4sXJF|4B*DPI!@$5G&cFmDk%1XQDNtxG1A{t)5HkZq0|Ub(Ae9DG z;)|>d#Fhp^b|Cx#RA2z)7yJf^GQxy4SQr@8fC8s77(wDNVLza-Hjw|@03-}oT?rIc z1`4E0f?WhtJqIYP3lxrC0TPC*J_$D)Xg9*__dsEYOF+VK)$*(`mjLAvs$GG?5SM_2 z;i?OO!Vs4L83@&rfjo#yK*Dg<2Y@_?FWHb)KLZLwTmlk?s}=+DAgY0}fH2zzC=4+h zBn(%b30KX5thyg43~@I|7_NF3P#EHFPGr@0fx-}%fP~?yd4V>m0R>KFa3QNU1`0#W z1_{Ge#{q>QW^*H}ZUhQL+zk?jt6m2;Tj78*1ET;-((V*zC9hrA+`l+9xHB**a2zpv z_PB{JNas--7ty}JpIr(O9Cr6I<%1Ik^PnMkCUp0LmiyyyfL1Tr&{@lH-=S1g6 zrl+2pwrBHe)%5jW>|Xsh{1tJ1#|2)H`5>@>eGv!GAp?=N{mX71*|bE0Crj!ov%#2Y9$9G%!s#u=A+p^Jz>msxxPvd=R-* zVEM}(Jcs!Wj3+N)QdywVFh$|mj~UO)p46~yd4IL~zua-RY{rP|>#R#_>(Af2uXQA! zqkezxdVU7SI}C{eT!%jJOCC5d`^gTaR56eFhYkhCoMp7zu%Wc0e8HY)PjVV|h&c<2 zFdXdpJ+XpGYp#ppjm^z$d$!GBYgCvO!726s&njIlt*SI574GoePVW=9U%0e<22%o4 z1IULgml&B!bM|C^&REX5^XA_d)2fwye%Q?L?X<4GX07~3KQ8K0sr0VR2WN8rJXo<> zaf^&UKf~6kD)$bZmRw@F+bA>jZOYX}`&=h&TpuN!Z0ogQ)+%ccm+5scHZk7+JbV51 zEgyHBRxrxkpKB0X^D?E^;O0jUtNs6P&u2LBQ>7u`@xlEHD)+R)_lYfD6>hP$b@Z;=EQ7y-g6x1 p>kFi3e>N*gXHIHbFH!B|xZ}e&A{{vDZ7Rk#4dREo3221IHUKWc+syy~ literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-cubemap-array.ktx2 b/src/MagnumPlugins/BasisImporter/Test/rgba-cubemap-array.ktx2 new file mode 100644 index 0000000000000000000000000000000000000000..808928bb800ffd6c39c5ff1b1544e9ef96ebb062 GIT binary patch literal 1835 zcmZ4O9TK5nWU!l;ONxsD2pEA_8j3-DCLm@5VvslpX8^Gc5HA5@6(BwV#30ZPQV0b8 z%n80s0CGBB7is58t3s%rq^G$2L>20#u7t3d_BfV4IcHv+LTR1D-F zT_C;)#4vT=fozaIb)d&#V!l8&NN)v@hUuLRWP|jcKvMS($Oh?^1BMGsuM3b3Qdfv1 zHXXcQV*BZzMsY?UWFm;_kHpskfKpLb@;eav&qX0|N?i6Pw zuU*&Nzc@6wGcYM|95H+LxQQ=F=TRFM(;h|%1_uF=*WOkU?ic4MF$e+{XDPa?WK7mbdGe`nqyur1~ZyGN4rB-PBcbxS6)oP4vllOsoa<)jU|CreK6ubMuO z#gE^#ps_+>f9~GabE5Mj(^Jn)+q3z#YWn&wcCY>${))K1;{q?pd=OZ`zKDb8kby|s z{$)3hY+540lO=VPSz+E$-!0Q`81{&)tNI-K8Rc@u>9o?p2PeG#*>#YsVq=wn4)m($BgG?Piok;-8HP=P)#^z?WJ=mVIeW4{XDsL3dGqg!Y1PU;KWt|Bc3M|ovsV72 z9~X70RC?FugEKjQ9;{fcxJAaFpJD4%m3xOyOD?h8ZIqe%Hs$J~eXf%>u8)#Vw)NUD zYn8Q!%k;Vzn;7qZp1uD1mXAA5D;Q<&&ozjxd708{aPy;w)&Bpt=QAAmsnQVe_~3p8 zm3vy@`@|NnanzW3XvxIt)lG98*I2quJo``MV`pT@w_KOy_qI=ODLGw~eaYwVld13j z^i4KCWWu&ab7HnU?>Ub1^##(iKbw`LGbc5zm#Fq}-0|TXkq#X7HWg!=2Ju7P1T;cp F8vvNS(9-|_ literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-cubemap.basis b/src/MagnumPlugins/BasisImporter/Test/rgba-cubemap.basis new file mode 100644 index 0000000000000000000000000000000000000000..1f33d6d65ef58cac11930e7ccac239be23903ff1 GIT binary patch literal 1234 zcmXSR5@zsasLE_*VPG)7&cndK#=yWJ&cFmDk%1XQA|nICTm}YphJQdt0|P@HP)Qn4 zi7&D;5L+4u*@5s0P=NuEU+^0w$_Nt{VP;@Z0}7nV0GfkPZ3Pt82J(L!fP~?yQ-Q+D zK!J2gu!~@ZbO42Qfx^)%K*DgD3C76imbXGC=784NEohq4Nw?jw!#5r21Wsvq}?gbN?yCJxqoqJaA#mr z;5cIT>~Rxckj|qvE~Y(<5)2LkBCoxzBHS;|QDP7T*}&v@@>sO%t>f$dlTa^8RY|f4Sps*^Cj_*IAd=)}Oz3 zU+YLdNB#cX_52KucNh`{xDI{bmppJ__LCh-sbU`U4;>1OIm>9bVMA$0`GP&qp5!#_ z5OWq3VK~_HdtwEX)?63G8=ITi_H3KM)~GNmf>Y}MpH;eAT2*OAD%|0_o!%#Izi?^! z45kF829OU~E-^Bd=IqJ-oUxp9=gq$_rd2EZ{IHqf+i6{W&06`7eq7Y0Qt4fr56A(>K}pkO|uw&57Ca iyyrO1*B407{%lr~&YaY=UZUE^amR;mL^^PUdK&;!M#QuL literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-cubemap.ktx2 b/src/MagnumPlugins/BasisImporter/Test/rgba-cubemap.ktx2 new file mode 100644 index 0000000000000000000000000000000000000000..a84339ae418aca2c7310f8ff956926befea39f08 GIT binary patch literal 1221 zcmZ4O9TK5nWU!l;ONxsD2pEA_8j3-D5MTphkT?iu0I>}aF9Bi|AU*-aAi&QAp|3;f zcOb<`0Hg;5m>4V=7Bez2Ff&8=5Yisb`40pR4EFpm0T5pv$N@UDyeP9IwTQtfu{g6> zAv7Bb{Q?v71+qbUD}Xdi?`$9&r1u0=ox%ZS21Wsvq}?gbN?yCJxqoqJ zaA#mr;5cIT>~Rxckj|qvE~Y(<5)2LkBCoxzBHS;|QDP7T>SthbJb5hI_15Q`X`e)N zdoCIs5C6`*LttCX6Lyaz=}D@YY3r6uWI6d}Zzo5N_R2{cbWfI?-d{C+9*ZBpX+dL! z!v5U7t>;ANN2aHqo3>~3Yt{7iU+iA}H~bZGea8h}koh36fPE1M&mjYmw*AX)9@(@+ zf+tJrDzn18qrPjr%eeCn)OY^m;yL<;V=v=(2UAC1Utiv=sRwwtCNwZjII#1m<@0Gw zF{(3Xo_r9wRABkb9XyBm4U8u*VNzM3(lABg*pC^{%bwJ*ZFzsS`oG+9w`|6U>+7sb zYwOS7yRUU5pQC<%?s|R($2$y(0$hha@Jk*zF#E|4rBpGG`G*b##++re+pwXuqkO@h zXHRk(c8EC(iZC4P`8~0MNo%f);*HJCY|IaF2Ev>3FBNguO-A?Zl zw_mulddXckZ-vz%kOQU-coY9DEpGn-zQVw|LL1-e8_}t njpoE`dERpz=j#ikXMZ*;NoP)KS}#%Uk literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-video.basis b/src/MagnumPlugins/BasisImporter/Test/rgba-video.basis new file mode 100644 index 0000000000000000000000000000000000000000..bb75249d254e7bbd5142bc36b796892f9166c3a6 GIT binary patch literal 1184 zcmXSR5@zsaIA0UY!oaZCl#PLbnSp^p983ZkaNxmk9mqbyz~I0zn~{OxAOk}T69YpD zP|O#u2Et&nXOLzPU|?ss08|hT6#M=VB*6?5RtH+(1{C1R0Gh+V029sy3WossuHGPF zMwswHps*uQ;0-&_G=ypP#a8 zKLd+{K-=4M7o7EN#g4pgWXNY=YvAapeat>l?aCojb`}MYSq%aj$8vi9Ei?c8C1>W_ zK-U^w)1U7yZ(XXoePwOp!g^c&0QHKy(>#`zI!rQTQ&Cqk+dNq%)YWCu%rEykIVQ_^ zDjia~R2UTeeAYCJKcCku_z^37p!BQ%X4&|i%kt(KJ0D+HchxlLe(}ZFAhw!~yQc_E z%3ixAquM*p`)b9urKbPaiUHjK1uxhiad;d~5IJ6d(s*I>jE0m;4;Q}(Vqwnn`+rsU zsLKW0fN=j04l=Deg=K3jk20$t-2H+vDl#%s)AQZC1Dg&QF`Q;F=)15@P>{EvwD{Pp z(ioZP4A&iaStdA#J8evDkUPQPFp1%$>;c&&9WxdRdR)j|7{fSKGG)eFUbYA8?M?+n zJ=L5$|5~Y6F1K^ANa*ZKI+>5(H9l#|oRml;NhO?%u!CI%h;bGXTb&yDGk z7f-W+fQzXcOF+;Q?ni36w$pVsls#u=F9aD z-hv4?t=Df>c*XoTv+eq}pG?8&=BJx}%q^Yj!y;5zaeu!3%(vZzMz&1~9AEVP89OcF zs@MH}aZ3B2{_U4mVV&mZ%%2*rn%riSG%LjWovwzG8b|qbSChcs%e0SapK8gRR4Kh~ zrI1?3i=0&{i}-RDg}=*RY20KrRr>|gB7c*+UTGK4d70j-oMw4~WhdV(+vmYk`dxgg zgbnXKOM8;IbNABusrkXG!WUo2U*M?-bP;AOJ@;Ct<3vvW%FV_cN}?t&Y!?>W6m#79 z{^N~E;p3|Rk7qy5w%@f!-ha3I|M&anvHkig^{Io?Y1WyO%4!`8R=-=+p|*&3x`?1~ zi^TjuH6MY*Md5QpXZo~Q#X30G`%X1*bP(TVdTWQ5*~}HbPgBHr8Gi1)%<}#A)(6kk z-(Ot2WG|Q87x{(a>nEk0ba{TpWxr9)>1W(eKIhJ5ow$9?>J+63i!JuJ=l4%Kc+_I1 moTC8CymqKD=*3{NiAY8gy3X2kOjhS zKn(K6Tp$hs;*UV=2*e;UBcOjGfOHp-2B}kUVBunvU}AdldFN{1g^8!mG&1lrus8^` zy*+oqS>IOd$m>Rid=V_l95Q8RQ2-g#AfRz9r{~`?^Uq&$X1)z{twO|$s(dCh_!vBC#Rzxr>Mjo-N}Z=SL9@pW}qO@r3|W#X$FJ73)=(*c?(L5kIgELk(thL z-GP^7f^)dj#?%J66ATWM7*5I_kX_O-W1*nOh1`WPj8i33X1wKPd$8W_R8Z7Y&AIch zm3rlJI|qw|&c39R`S@Mqlcvl`302yscl#UvZha-q_rG?({mJ6$irRZ`%>*~ktS$A| zm;c`X{&B_6Vr~YQqnFE^RzlYG#d!G zn7Xk91U=z?q^4^-U1vktb5^GHs%9%Sy4YB9R+lOr%s8^D)52x3+fl~$ts!Lm~hj2 z{bq$%%zrc6u5bIv6r65;y6MN<(y2ZyLWLFg=iASG+g)g6+oZtpMc<#X(;}{V-QO3d zwEyYferXlfX@1W9so|>0Z8k}>LcHJUYAC63luvgx3H-fG`8;9XmM2(t^3Ae+9z3Pr#ivTx@ZPhu zCy6_EFP)#7AFL{T@rC>ao{B&hVaC#PuXQ?3=ibXK-*0by@Lc`<#kEWJ za>;#>UnstQQp!n}=Vx5@8`YeC#{J}T?p)T1+t;j4QJS#WVvl=%|D=OQEoRC&3b4$J h_iw0YskvMH_jkDI@yqd7FPGYAr~lRKXQWP$0RXdM-mm}w literal 0 HcmV?d00001 From 85e46e764a392148631268f101d6121da10cb758 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 19 Oct 2021 22:24:54 +0200 Subject: [PATCH 25/76] BasisImporter: test start_transcoding failure for KTX2 files --- .../BasisImporter/Test/BasisImporterTest.cpp | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index 4f0c2ecc2..585804b1a 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -100,14 +100,24 @@ constexpr struct { constexpr struct { const char* name; - const char* file; const Containers::ArrayView data; const char* message; } InvalidHeaderData[] { - {"Invalid", "rgb.basis", "NotAValidFile", "invalid basis header"}, - {"Invalid basis header", "rgb.basis", "sB\xff\xff", "invalid basis header"}, - {"Invalid KTX2 identifier", "rgb.ktx2", "\xabKTX 30\xbb\r\n\x1a\n", "invalid basis header"}, - {"Invalid KTX2 header", "rgb.ktx2", "\xabKTX 20\xbb\r\n\x1a\n\xff\xff\xff\xff", "invalid KTX2 header"} + {"Invalid", "NotAValidFile", "invalid basis header"}, + {"Invalid basis header", "sB\xff\xff", "invalid basis header"}, + {"Invalid KTX2 identifier", "\xabKTX 30\xbb\r\n\x1a\n", "invalid basis header"}, + {"Invalid KTX2 header", "\xabKTX 20\xbb\r\n\x1a\n\xff\xff\xff\xff", "invalid KTX2 header"} +}; + +constexpr struct { + const char* name; + const char* file; + const std::size_t offset; + const char value; + const char* message; +} InvalidFileData[] { + {"Corrupt KTX2 supercompression data", "rgb.ktx2", 184, 0x00, "bad KTX2 file"}, + {"Corrupt basis texture type", "rgb.basis", 23, 0x7f, "bad basis file"} }; constexpr struct { @@ -174,7 +184,8 @@ BasisImporterTest::BasisImporterTest() { addInstancedTests({&BasisImporterTest::invalidHeader}, Containers::arraySize(InvalidHeaderData)); - addTests({&BasisImporterTest::invalidFile}); + addInstancedTests({&BasisImporterTest::invalidFile}, + Containers::arraySize(InvalidFileData)); addInstancedTests({&BasisImporterTest::fileTooShort}, Containers::arraySize(FileTooShortData)); @@ -254,23 +265,22 @@ void BasisImporterTest::invalidHeader() { } void BasisImporterTest::invalidFile() { + auto&& data = InvalidFileData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + Containers::Pointer importer = _manager.instantiate("BasisImporter"); - /* There's currently no way to make start_transcoding() fail in the KTX2 - transcoder */ auto basisData = Utility::Directory::read( - Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb.basis")); + Utility::Directory::join(BASISIMPORTER_TEST_DIR, data.file)); + + CORRADE_INTERNAL_ASSERT(data.offset < basisData.size()); + basisData[data.offset] = data.value; std::ostringstream out; Error redirectError{&out}; - /* This corrupts the texture type */ - constexpr std::size_t Offset = 23; - CORRADE_INTERNAL_ASSERT(Offset < basisData.size()); - basisData[Offset] = 0x7f; CORRADE_VERIFY(!importer->openData(basisData)); - - CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): bad basis file\n"); + CORRADE_COMPARE(out.str(), Utility::formatString("Trade::BasisImporter::openData(): {}\n", data.message)); } void BasisImporterTest::fileTooShort() { From d29a013e8332d4481c03e3793b05c34c7ead020c Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 19 Oct 2021 23:06:50 +0200 Subject: [PATCH 26/76] BasisImporter: document image types and mipmaps --- .../BasisImporter/BasisImporter.h | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index 4ab760516..bc1dbaf55 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -62,7 +62,7 @@ namespace Magnum { namespace Trade { @m_keywords{BasisImporterRGBA8 KtxImporter} Supports [Basis Universal](https://github.com/binomialLLC/basis_universal) -compressed images (`*.basis` or `.ktx2`) by parsing and transcoding files into +compressed images (`*.basis` or `*.ktx2`) by parsing and transcoding files into an explicitly specified GPU format (see @ref Trade-BasisImporter-target-format). You can use @ref BasisImageConverter to transcode images into this format. @@ -119,6 +119,38 @@ target_link_libraries(your-app PRIVATE MagnumPlugins::BasisImporter) See @ref building-plugins, @ref cmake-plugins, @ref plugins and @ref file-formats for more information. +@section Trade-BasisImporter-behavior Behavior and limitations + +@subsection Trade-BasisImporter-behavior-types Image types + +You can import all image types supported by `basisu`: (layered) 2D images, +(layered) cube maps, 3D images and videos. With the exception of 3D images, +they can in turn all have multiple mip levels. The image type can be determined +from @ref texture() and @ref TextureData::type(). + +For layered 2D images and (layered) cube maps, the array layers and faces are +exposed as an additional image dimension. @ref image3D will return an +@ref ImageData3D with n z-slices, or 6*n z-slices for cube maps. + +@subsection Trade-BasisImporter-behavior-multilevel Multilevel images + +Files with multiple mip levels are imported with the largest level first, with +the size of each following level divided by 2, rounded down. Mip chains can be +incomplete, ie. they don't have to extend all the way down to a level of size +1x1. + +Because mip levels in `.basis` files are always 2-dimensional, they wouldn't +halve correctly in the z-dimension for 3D images. If a 3D image with mip levels +is detected, it gets imported as a layered 2D image instead, along with a +warning being printed. + +@subsection Trade-BasisImporter-behavior-cube Cube maps + +Cube map faces are imported in the order +X, -X, +Y, -Y, +Z, -Z as seen from a +left-handed coordinate system (+X is right, +Y is up, +Z is forward). Layered +cube maps are stored as multiple sets of faces, ie. all faces +X through -Z for +the first layer, then all faces of the second layer, etc. + @section Trade-BasisImporter-configuration Plugin-specific configuration Basis allows configuration of the format of loaded compressed data. From d85bfb19e2397cc68fba80ba87dd33fa5a42f63f Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Tue, 19 Oct 2021 23:29:50 +0200 Subject: [PATCH 27/76] BasisImporter: mention sRGB versions of target formats --- .../BasisImporter/BasisImporter.h | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index bc1dbaf55..b9381a2b1 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -216,27 +216,29 @@ class MAGNUM_BASISIMPORTER_EXPORT BasisImporter: public AbstractImporter { /* ID kept the same as in Basis itself to make the mapping easy */ /** - * ETC1 RGB. Loaded as @ref CompressedPixelFormat::Etc2RGB8Unorm - * (which ETC1 is a subset of). If the image contains an alpha - * channel, it will be dropped since ETC1 alone doesn't support - * alpha. + * ETC1 RGB. Loaded as @ref CompressedPixelFormat::Etc2RGB8Unorm/ + * @ref CompressedPixelFormat::Etc2RGB8Srgb (which ETC1 is a + * subset of). If the image contains an alpha channel, it will be + * dropped since ETC1 alone doesn't support alpha. */ Etc1RGB = 0, /** - * ETC2 RGBA. Loaded as @ref CompressedPixelFormat::Etc2RGBA8Unorm. + * ETC2 RGBA. Loaded as @ref CompressedPixelFormat::Etc2RGBA8Unorm/ + * @ref CompressedPixelFormat::Etc2RGBA8Srgb. */ Etc2RGBA = 1, /** - * BC1 RGB. Loaded as @ref CompressedPixelFormat::Bc1RGBUnorm. - * Punchthrough alpha mode of @ref CompressedPixelFormat::Bc1RGBAUnorm - * is not supported. + * BC1 RGB. Loaded as @ref CompressedPixelFormat::Bc1RGBUnorm/ + * @ref CompressedPixelFormat::Bc1RGBSrgb. Punchthrough alpha mode + * of BC1 RGBA is not supported. */ Bc1RGB = 2, /** - * BC2 RGBA. Loaded as @ref CompressedPixelFormat::Bc3RGBAUnorm. + * BC3 RGBA. Loaded as @ref CompressedPixelFormat::Bc3RGBAUnorm/ + * @ref CompressedPixelFormat::Bc3RGBASrgb. */ Bc3RGBA = 3, @@ -248,32 +250,37 @@ class MAGNUM_BASISIMPORTER_EXPORT BasisImporter: public AbstractImporter { /** * BC7 RGB (mode 6). Loaded as - * @ref CompressedPixelFormat::Bc7RGBAUnorm, but with alpha values + * @ref CompressedPixelFormat::Bc7RGBAUnorm/ + * @ref CompressedPixelFormat::Bc7RGBASrgb, but with alpha values * set to opaque. */ Bc7RGB = 6, /** * BC7 RGBA (mode 5). Loaded as - * @ref CompressedPixelFormat::Bc7RGBAUnorm. + * @ref CompressedPixelFormat::Bc7RGBAUnorm/ + * @ref CompressedPixelFormat::Bc7RGBASrgb. */ Bc7RGBA = 7, /** * PVRTC1 RGB 4 bpp. Loaded as - * @ref CompressedPixelFormat::PvrtcRGB4bppUnorm. + * @ref CompressedPixelFormat::PvrtcRGB4bppUnorm/ + * @ref CompressedPixelFormat::PvrtcRGB4bppSrgb. */ PvrtcRGB4bpp = 8, /** * PVRTC1 RGBA 4 bpp. Loaded as - * @ref CompressedPixelFormat::PvrtcRGBA4bppUnorm. + * @ref CompressedPixelFormat::PvrtcRGBA4bppUnorm/ + * @ref CompressedPixelFormat::PvrtcRGBA4bppSrgb. */ PvrtcRGBA4bpp = 9, /** * ASTC 4x4 RGBA. Loaded as - * @ref CompressedPixelFormat::Astc4x4RGBAUnorm. + * @ref CompressedPixelFormat::Astc4x4RGBAUnorm/ + * @ref CompressedPixelFormat::Astc4x4RGBASrgb. */ Astc4x4RGBA = 10, @@ -281,8 +288,9 @@ class MAGNUM_BASISIMPORTER_EXPORT BasisImporter: public AbstractImporter { /** * Uncompressed 32-bit RGBA. Loaded as - * @ref PixelFormat::RGBA8Unorm. If no concrete format is - * specified, the importer will fall back to this. + * @ref PixelFormat::RGBA8Unorm/@ref PixelFormat::RGBA8Srgb. If no + * concrete format is specified, the importer will fall back to + * this. */ RGBA8 = 13, From 2d3a38e11ca73f808c367e22d2b7035f2b49b627 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 21 Oct 2021 17:22:52 +0200 Subject: [PATCH 28/76] BasisImporter: import video as separate images because transcoding all frames at once is probably a stupid idea. We also catch any attempt to seek ahead as an error, since basis doesn't allow seeking (all but the first image are P-frames). --- .../BasisImporter/BasisImporter.cpp | 153 +++++++++++------- .../BasisImporter/BasisImporter.h | 6 + .../BasisImporter/Test/BasisImporterTest.cpp | 74 +++++++-- 3 files changed, 168 insertions(+), 65 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 6584e073f..01f77bc19 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -154,10 +154,12 @@ struct BasisImporter::State { Containers::Array numLevels; basist::basis_tex_format compressionType; + bool isVideo; bool isYFlipped; bool isSrgb; bool noTranscodeFormatWarningPrinted = false; + UnsignedInt lastTranscodedImageId = ~0u; explicit State(): codebook(basist::g_global_selector_cb_size, basist::g_global_selector_cb) {} @@ -270,15 +272,25 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { /* Remember the type for doTexture(). ktx2_transcoder::init() already checked we're dealing with a valid 2D texture. */ + _state->isVideo = false; if(_state->ktx2Transcoder->get_faces() != 1) _state->textureType = _state->ktx2Transcoder->get_layers() > 0 ? TextureType::CubeMapArray : TextureType::CubeMap; - else + else if(_state->ktx2Transcoder->is_video()) { + _state->textureType = TextureType::Texture2D; + _state->isVideo = true; + } else _state->textureType = _state->ktx2Transcoder->get_layers() > 0 ? TextureType::Texture2DArray : TextureType::Texture2D; - /* KTX2 files only ever contain one image */ - _state->numImages = 1; - _state->numSlices = _state->ktx2Transcoder->get_faces()*Math::max(_state->ktx2Transcoder->get_layers(), 1u); - _state->numLevels = Containers::Array{DirectInit, 1, _state->ktx2Transcoder->get_levels()}; + /* KTX2 files only ever contain one image, but for videos we choose to + expose layers as multiple images, one for each frame */ + if(_state->isVideo) { + _state->numImages = Math::max(_state->ktx2Transcoder->get_layers(), 1u); + _state->numSlices = 1; + } else { + _state->numImages = 1; + _state->numSlices = _state->ktx2Transcoder->get_faces()*Math::max(_state->ktx2Transcoder->get_layers(), 1u); + } + _state->numLevels = Containers::Array{DirectInit, _state->numImages, _state->ktx2Transcoder->get_levels()}; _state->compressionType = _state->ktx2Transcoder->get_format(); @@ -309,38 +321,31 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { directly because that's specific to .basis files, and there's no equivalent in ktx2_transcoder. */ + /* This is checked by basis_transcoder::start_transcoding() */ + CORRADE_INTERNAL_ASSERT(fileInfo.m_total_images > 0); + /* Remember the type for doTexture(). Depending on the type, we're either dealing with multiple independent 2D images or each image is an array layer, cubemap face or depth slice with the same resolution. */ - - /** @todo Validate this, the transcoder doesn't seem to - check anything like: - - 2D array and 3D images should have the same resolution - (can be checked in doImage2D) - - image count for cube maps should be a multiple of 6 - */ - - /* This is checked by basis_transcoder::start_transcoding() */ - CORRADE_INTERNAL_ASSERT(fileInfo.m_total_images > 0); - - /* Default, swapped for cBASISTexType2D */ - _state->numImages = 1; - _state->numSlices = fileInfo.m_total_images; - + _state->isVideo = false; switch(fileInfo.m_tex_type) { + case basist::basis_texture_type::cBASISTexTypeVideoFrames: + /* Decoding all video frames at once is usually not what you + want, so we treat videos as independent 2D images. We still + have to check that the sizes match, and need to remember + this is a video to disallow seeking (not supported by + basisu). */ + _state->isVideo = true; + CORRADE_FALLTHROUGH case basist::basis_texture_type::cBASISTexType2D: - std::swap(_state->numImages, _state->numSlices); _state->textureType = TextureType::Texture2D; break; - case basist::basis_texture_type::cBASISTexTypeVideoFrames: - /* We don't do anything special with video frames, treat them - like array layers */ case basist::basis_texture_type::cBASISTexType2DArray: _state->textureType = TextureType::Texture2DArray; break; case basist::basis_texture_type::cBASISTexTypeCubemapArray: - _state->textureType = _state->numSlices > 6 ? TextureType::CubeMapArray : TextureType::CubeMap; + _state->textureType = fileInfo.m_total_images > 6 ? TextureType::CubeMapArray : TextureType::CubeMap; break; case basist::basis_texture_type::cBASISTexTypeVolume: _state->textureType = TextureType::Texture3D; @@ -350,33 +355,45 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } + if(_state->textureType == TextureType::Texture2D) { + _state->numImages = fileInfo.m_total_images; + _state->numSlices = 1; + } else { + _state->numImages = 1; + _state->numSlices = fileInfo.m_total_images; + } + + /** @todo Validate things the transcoder doesn't check: + - non-plain-2D images/video should have the same resolution + (can be checked in doImage2D) + - image count for cube maps should be a multiple of 6 + */ + CORRADE_INTERNAL_ASSERT(fileInfo.m_image_mipmap_levels.size() == fileInfo.m_total_images); + /* Get mip level count for each image, check that they're the same for + videos, cube maps and arrays */ + const bool levelsMustMatch = _state->textureType != TextureType::Texture2D || _state->isVideo; _state->numLevels = Containers::Array{NoInit, _state->numImages}; - UnsignedInt firstLevels = 0; for(UnsignedInt i = 0; i != fileInfo.m_total_images; ++i) { - const UnsignedInt levels = fileInfo.m_image_mipmap_levels[i]; - CORRADE_INTERNAL_ASSERT(levels > 0); - - if(i == 0) - firstLevels = levels; + const UnsignedInt numLevels = fileInfo.m_image_mipmap_levels[i]; + CORRADE_INTERNAL_ASSERT(numLevels > 0); - if(levels != firstLevels) { - Error() << "Trade::BasisImporter::openData(): mismatching level count for successive image slices"; + if(levelsMustMatch && i > 0 && numLevels != _state->numLevels[0]) { + /** @todo Test? */ + Error{} << "Trade::BasisImporter::openData(): mismatching level count for successive image slices"; return; } if(i < _state->numImages) - _state->numLevels[i] = levels; + _state->numLevels[i] = numLevels; } - /* Mip levels in basis are per 2D image, for 3D images they - consequently don't halve in the z-dimension. Turn it into a 2D array - texture so users don't get surprised by the mip z-size not changing, - and print a warning. - For non-Texture2D images, at this point firstLevels is the number - of levels for all slices, checked in the loop above. */ - if(_state->textureType == TextureType::Texture3D && firstLevels > 1) { + /* Mip levels in basis are per 2D image, so for 3D images they don't + halve in the z-dimension. Turn it into a 2D array texture so users + don't get surprised by the mip z-size not changing, and print a + warning. */ + if(_state->textureType == TextureType::Texture3D && _state->numLevels[0] > 1) { Warning{} << "Trade::BasisImporter::openData(): found a 3D image with 2D mipmaps, importing as a 2D array texture"; _state->textureType = TextureType::Texture2DArray; } @@ -394,11 +411,18 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { CORRADE_INTERNAL_ASSERT(!_state->ktx2Transcoder != !_state->basisTranscoder); /* Can't have a KTX2 transcoder without KTX2 support compiled into basisu */ CORRADE_INTERNAL_ASSERT(BASISD_SUPPORT_KTX2 || !_state->ktx2Transcoder); - /* 1D images should be treated like 2D images with width or height 1 */ + /* These file formats don't support 1D images */ CORRADE_INTERNAL_ASSERT(_state->textureType != TextureType::Texture1D && _state->textureType != TextureType::Texture1DArray); + /* There's one image with faces/layers, or multiple images without any */ + CORRADE_INTERNAL_ASSERT(_state->numImages == 1 || _state->numSlices == 1); /* All good, release the resource guard */ resourceGuard.release(); + + if(flags() & ImporterFlag::Verbose) { + if(_state->isVideo) + Debug{} << "Trade::BasisImporter::openData(): file contains video frames, images must be loaded sequentially"; + } } template @@ -441,7 +465,8 @@ Containers::Optional> BasisImporter::doImage(const Unsigne is not a power-of-two: https://github.com/BinomialLLC/basis_universal/blob/77b7df8e5df3532a42ef3c76de0c14cc005d0f65/basisu_tool.cpp#L1458 */ - UnsignedInt origWidth, origHeight, totalBlocks, numFaces, numLayers; + UnsignedInt origWidth, origHeight, totalBlocks, numFaces; + bool isIFrame; if(_state->ktx2Transcoder) { #if BASISD_SUPPORT_KTX2 basist::ktx2_image_level_info levelInfo; @@ -459,21 +484,38 @@ Containers::Optional> BasisImporter::doImage(const Unsigne origWidth = levelInfo.m_orig_width; origHeight = levelInfo.m_orig_height; totalBlocks = levelInfo.m_total_blocks; + isIFrame = levelInfo.m_iframe_flag; + numFaces = _state->ktx2Transcoder->get_faces(); - numLayers = Math::max(_state->ktx2Transcoder->get_layers(), 1u); #endif } else { /* Same as above, it checks for state we already verified before. If this blows up for someone, we can reconsider. */ - CORRADE_INTERNAL_ASSERT_OUTPUT(_state->basisTranscoder->get_image_level_desc(_state->in.data(), _state->in.size(), id, level, origWidth, origHeight, totalBlocks)); + basist::basisu_image_level_info levelInfo; + CORRADE_INTERNAL_ASSERT_OUTPUT(_state->basisTranscoder->get_image_level_info(_state->in.data(), _state->in.size(), levelInfo, id, level)); + + origWidth = levelInfo.m_orig_width; + origHeight = levelInfo.m_orig_height; + totalBlocks = levelInfo.m_total_blocks; + isIFrame = levelInfo.m_iframe_flag; + numFaces = (_state->textureType == TextureType::CubeMap || _state->textureType == TextureType::CubeMapArray) ? 6 : 1; - numLayers = _state->numSlices / numFaces; } - CORRADE_INTERNAL_ASSERT(numFaces == 1 || numFaces == 6); - CORRADE_INTERNAL_ASSERT(numLayers > 0); - CORRADE_INTERNAL_ASSERT(numFaces*numLayers == _state->numSlices); + const UnsignedInt numLayers = _state->numSlices/numFaces; + /* basisu doesn't allow seeking to arbitrary video frames for ETC1S. If + this isn't an I-frame, only allow transcoding the frame following the + last P-frame. Frame 0 is always an I-frame. */ + if(_state->isVideo) { + const UnsignedInt expectedImageId = _state->lastTranscodedImageId + 1; + if(!isIFrame && id != expectedImageId) { + Error{} << prefix << "video frames must be transcoded sequentially, expected frame" + << expectedImageId << (expectedImageId == 0 ? "but got" : "or 0 but got") << id; + return Containers::NullOpt; + } + _state->lastTranscodedImageId = expectedImageId; + } /* No flags used by transcode_image_level() by default */ const std::uint32_t flags = 0; if(!_state->isYFlipped) { @@ -502,25 +544,28 @@ Containers::Optional> BasisImporter::doImage(const Unsigne /* There's no function for transcoding the entire level, so loop over all layers and faces and transcode each one. This matches the image layout imported by KtxImporter, ie. all faces +X through -Z for the first - layer, then all faces of the second layer, etc. */ - /** @todo Check if the Basis layout actually matches this one */ - UnsignedInt currentId = id; + layer, then all faces of the second layer, etc. + + If the user is requesting id > 0, there can't be any layers or faces, + this is already asserted in doOpenData(). This allows us to calculate + the layer (KTX2) or image id to transcode with a simple addition. */ for(UnsignedInt l = 0; l != numLayers; ++l) { for(UnsignedInt f = 0; f != numFaces; ++f) { const UnsignedInt offset = (l*numFaces + f)*sliceSize; if(_state->ktx2Transcoder) { #if BASISD_SUPPORT_KTX2 - if(!_state->ktx2Transcoder->transcode_image_level(level, l, f, dest.data() + offset, outputSizeInBlocksOrPixels, format, flags, rowStride, outputRowsInPixels)) { + const UnsignedInt currentLayer = id + l; + if(!_state->ktx2Transcoder->transcode_image_level(level, currentLayer, f, dest.data() + offset, outputSizeInBlocksOrPixels, format, flags, rowStride, outputRowsInPixels)) { Error{} << "Trade::BasisImporter::image2D(): transcoding failed"; return Containers::NullOpt; } #endif } else { + const UnsignedInt currentId = id + (l*numFaces + f); if(!_state->basisTranscoder->transcode_image_level(_state->in.data(), _state->in.size(), currentId, level, dest.data() + offset, outputSizeInBlocksOrPixels, format, flags, rowStride, nullptr, outputRowsInPixels)) { Error{} << "Trade::BasisImporter::image2D(): transcoding failed"; return Containers::NullOpt; } - ++currentId; } } } diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index b9381a2b1..3c624ad75 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -132,6 +132,12 @@ For layered 2D images and (layered) cube maps, the array layers and faces are exposed as an additional image dimension. @ref image3D will return an @ref ImageData3D with n z-slices, or 6*n z-slices for cube maps. +Video files will be imported as multiple 2D images with the same size and level +count. Due to the way video is encoded by Basis Universal, seeking to arbitrary +frames is not allowed in ETC1S-compressed videos. If you call @ref image2D with +non-sequential frame indices and that frame is not an I-frame, it will print an +error and fail. Restarting from frame 0 is always allowed. + @subsection Trade-BasisImporter-behavior-multilevel Multilevel images Files with multiple mip levels are imported with the largest level first, with diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index 585804b1a..76f7d9a81 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -80,6 +80,9 @@ struct BasisImporterTest: TestSuite::Tester { void cubeMap(); void cubeMapArray(); + void videoVerbose(); + void videoSeeking(); + void ktxImporterAlias(); void openSameTwice(); @@ -140,7 +143,8 @@ const struct { {"Cube map", "rgba-cubemap", TextureType::CubeMap}, {"Cube map array", "rgba-cubemap-array", TextureType::CubeMapArray}, {"3D", "rgba-3d", TextureType::Texture3D}, - {"3D mipmaps", "rgba-3d-mips", TextureType::Texture2DArray} + {"3D mipmaps", "rgba-3d-mips", TextureType::Texture2DArray}, + {"Video", "rgba-video", TextureType::Texture2D} }; constexpr struct { @@ -221,7 +225,11 @@ BasisImporterTest::BasisImporterTest() { &BasisImporterTest::cubeMapArray}, Containers::arraySize(FileTypeData)); - addTests({&BasisImporterTest::ktxImporterAlias, + addTests({&BasisImporterTest::videoVerbose, + &BasisImporterTest::videoSeeking, + + &BasisImporterTest::ktxImporterAlias, + &BasisImporterTest::openSameTwice, &BasisImporterTest::openDifferent, &BasisImporterTest::importMultipleFormats}); @@ -853,13 +861,17 @@ void BasisImporterTest::video() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgba-video"} + data.extension))); - CORRADE_COMPARE(importer->image3DCount(), 1); - Containers::Optional image = importer->image3D(0); - CORRADE_VERIFY(image); - CORRADE_VERIFY(!image->isCompressed()); - CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); + Containers::Optional frames[3]; - CORRADE_COMPARE(image->size(), (Vector3i{63, 27, 3})); + CORRADE_COMPARE(importer->image2DCount(), Containers::arraySize(frames)); + + for(UnsignedInt i = 0; i != Containers::arraySize(frames); ++i) { + frames[i] = importer->image2D(i); + CORRADE_VERIFY(frames[i]); + CORRADE_VERIFY(!frames[i]->isCompressed()); + CORRADE_COMPARE(frames[i]->format(), PixelFormat::RGBA8Srgb); + CORRADE_COMPARE(frames[i]->size(), (Vector2i{63, 27})); + } if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("AnyImageImporter plugin not found, cannot test contents"); @@ -867,13 +879,13 @@ void BasisImporterTest::video() { CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); /* There are moderately significant compression artifacts */ - CORRADE_COMPARE_WITH(image->pixels()[0], + CORRADE_COMPARE_WITH(frames[0]->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), (DebugTools::CompareImageToFile{_manager, 96.25f, 8.198f})); - CORRADE_COMPARE_WITH(image->pixels()[1], + CORRADE_COMPARE_WITH(frames[1]->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27-slice1.png"), (DebugTools::CompareImageToFile{_manager, 74.0f, 6.507f})); - CORRADE_COMPARE_WITH(image->pixels()[2], + CORRADE_COMPARE_WITH(frames[2]->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27-slice2.png"), (DebugTools::CompareImageToFile{_manager, 76.0f, 8.311f})); } @@ -1092,6 +1104,46 @@ void BasisImporterTest::cubeMapArray() { (DebugTools::CompareImageToFile{_manager, 88.0f, 10.591f})); } +void BasisImporterTest::videoVerbose() { + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + + std::ostringstream out; + Debug redirectDebug{&out}; + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + "rgba-video.basis"))); + CORRADE_COMPARE(out.str(), ""); + + importer->close(); + importer->setFlags(ImporterFlag::Verbose); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + "rgba-video.basis"))); + CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): file contains video frames, images must be loaded sequentially\n"); +} + +void BasisImporterTest::videoSeeking() { + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + "rgba-video.basis"))); + + CORRADE_COMPARE(importer->image2DCount(), 3); + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_VERIFY(!importer->image2D(2)); + CORRADE_VERIFY(importer->image2D(0)); + CORRADE_VERIFY(!importer->image2D(2)); + CORRADE_VERIFY(importer->image2D(1)); + CORRADE_VERIFY(importer->image2D(2)); + CORRADE_VERIFY(importer->image2D(0)); + + CORRADE_COMPARE(out.str(), + "Trade::BasisImporter::image2D(): video frames must be transcoded sequentially, expected frame 0 but got 2\n" + "Trade::BasisImporter::image2D(): video frames must be transcoded sequentially, expected frame 1 or 0 but got 2\n"); +} + void BasisImporterTest::ktxImporterAlias() { if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("AnyImageImporter plugin not found, cannot test forwarding"); From 42ea61f02f8e66622aafb767a9f928872080a321 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 21 Oct 2021 21:02:22 +0200 Subject: [PATCH 29/76] BasisImporter: test opening KTX2 and basis files in the same instance --- .../BasisImporter/Test/BasisImporterTest.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index 76f7d9a81..d4b0afc29 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -1187,17 +1187,19 @@ void BasisImporterTest::openSameTwice() { void BasisImporterTest::openDifferent() { Containers::Pointer importer = _manager.instantiate("BasisImporterEtc2RGBA"); + CORRADE_VERIFY(importer->openFile( - Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb.basis"))); + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-video.basis"))); CORRADE_VERIFY(importer->openFile( - Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-2images-mips.basis"))); - CORRADE_COMPARE(importer->image2DCount(), 2); + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-cubemap-array.ktx2"))); + CORRADE_COMPARE(importer->image3DCount(), 1); - /* Shouldn't crash, leak or anything */ - Containers::Optional image = importer->image2D(1); + /* Verify that everything is working properly with different files + and transcoders. Shouldn't crash, leak or anything. */ + Containers::Optional image = importer->image3D(0); CORRADE_VERIFY(image); CORRADE_COMPARE(image->compressedFormat(), CompressedPixelFormat::Etc2RGBA8Srgb); - CORRADE_COMPARE(image->size(), (Vector2i{27, 63})); + CORRADE_COMPARE(image->size(), (Vector3i{27, 27, 12})); } void BasisImporterTest::importMultipleFormats() { From 57865fe9e4a1edf1aeb5f93eaeb889a600ab5d39 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 21 Oct 2021 21:14:23 +0200 Subject: [PATCH 30/76] BasisImageConverter: don't spam stdout with basisu debug output unless Verbose is set No test because I don't know how to reliably redirect stdout. There's still no way to redirect basis output to custom sinks. --- .../BasisImageConverter/BasisImageConverter.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index 0a56f09cc..e69678d0f 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -205,8 +205,13 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi #undef PARAM_CONFIG #undef PARAM_CONFIG_FIX_NAME - /* If these are enabled, the library reads PNGs from a filesystem and then - writes basis files there also. DO NOT WANT. */ + /* Don't spam stdout with debug info by default. Basis error output is + unaffected by this. Unfortunately, there's no way to redirect the output + to Debug. */ + params.m_status_output = flags() >= ImageConverterFlag::Verbose; + + /* If these are enabled, the library reads BMPs/JPGs/PNGs/TGAs from the + filesystem and then writes basis files there also. DO NOT WANT. */ params.m_read_source_images = false; params.m_write_output_basis_files = false; From e65f438c1430ad05780d06675e7c1e2dba8330b3 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 21 Oct 2021 21:15:50 +0200 Subject: [PATCH 31/76] BasisImageConverter: determine mip_gen option from the number of user-supplied levels Still defaults to false, but you can now leave it empty to always generate mipmaps if none are provided to the converter --- src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf | 3 +++ src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp | 1 + 2 files changed, 4 insertions(+) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf index 0af7f9613..5052b7fe2 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf @@ -34,6 +34,9 @@ threads=1 disable_hierarchical_endpoint_codebooks=false # Mipmap generation options +# Generate mipmaps from the base image. If you pass custom mip levels into +# openData, this option will be ignored. Leave blank to determine from the +# number of levels passed to convertToData. mip_gen=false # Generate mipmaps assuming sRGB color data, rather than linear intensity. # Leave blank to determine from the image format. diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index e69678d0f..a011470a5 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -87,6 +87,7 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi /* Options deduced from input data. Can be overridden by config values, if present. */ params.m_perceptual = isSrgb; + params.m_mip_gen = numMipmaps > 1; params.m_mip_srgb = isSrgb; /* To retain sanity, keep this in the same order and grouping as in the From a88304b5d1be74f686e396689a2154778e98b7c0 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 21 Oct 2021 21:17:04 +0200 Subject: [PATCH 32/76] Basis{ImageConverter,Importer}: set big endian flag for basisu --- modules/FindBasisUniversal.cmake | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/modules/FindBasisUniversal.cmake b/modules/FindBasisUniversal.cmake index 5577aef45..501fa97b5 100644 --- a/modules/FindBasisUniversal.cmake +++ b/modules/FindBasisUniversal.cmake @@ -31,6 +31,7 @@ # Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, # 2020, 2021 Vladimír Vondruš # Copyright © 2019 Jonathan Hale +# Copyright © 2021 Pablo Escobar # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -57,12 +58,22 @@ if(${_index} GREATER -1) list(REMOVE_DUPLICATES BasisUniversal_FIND_COMPONENTS) endif() +include(TestBigEndian) +test_big_endian(BIG_ENDIAN) + macro(_basis_setup_source_file source) # Compile any .c files as C++ since we can't guarantee that C is enabled # in the calling scope, either inside project() or with enable_language(). # Otherwise, they won't get compiled at all, leading to undefined symbols. set_property(SOURCE ${source} PROPERTY LANGUAGE CXX) + + # Tell Basis if we're on a big endian system. It currently doesn't figure + # this out by itself. + if(BIG_ENDIAN) + set_property(SOURCE ${source} APPEND PROPERTY COMPILE_DEFINITIONS + BASISD_IS_BIG_ENDIAN=1) + endif() # Basis shouldn't override the MSVC iterator debug level as it would make # it inconsistent with the rest of the code From 9d7a1e1aa347e975e0cdc61e0b4d934c5bcbd863 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 21 Oct 2021 23:49:11 +0200 Subject: [PATCH 33/76] BasisImageConverter: fix mip generation --- .../BasisImageConverter/BasisImageConverter.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index a011470a5..c0e5ed1f0 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -87,15 +87,15 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi /* Options deduced from input data. Can be overridden by config values, if present. */ params.m_perceptual = isSrgb; - params.m_mip_gen = numMipmaps > 1; + params.m_mip_gen = imageLevels.size() == 1; params.m_mip_srgb = isSrgb; /* To retain sanity, keep this in the same order and grouping as in the conf file */ #define PARAM_CONFIG(name, type) \ - if(configuration().hasValue(#name)) params.m_##name = configuration().value(#name) + if(!configuration().value(#name).empty()) params.m_##name = configuration().value(#name) #define PARAM_CONFIG_FIX_NAME(name, type, fixed) \ - if(configuration().hasValue(fixed)) params.m_##name = configuration().value(fixed) + if(!configuration().value(fixed).empty()) params.m_##name = configuration().value(fixed) /* Options */ PARAM_CONFIG(quality_level, int); PARAM_CONFIG(perceptual, bool); @@ -223,8 +223,10 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi m_source_mipmap_images. If m_source_mipmap_images is not empty, mip generation is disabled. */ params.m_source_images.resize(1); - params.m_source_mipmap_images.resize(1); - params.m_source_mipmap_images[0].resize(imageLevels.size() - 1); + if(imageLevels.size() > 1) { + params.m_source_mipmap_images.resize(1); + params.m_source_mipmap_images[0].resize(imageLevels.size() - 1); + } for(UnsignedInt i = 0; i != imageLevels.size(); ++i) { const Vector2i mipSize = Math::max(size >> i, 1); From fc648382aac5d68816696633e817735bb0b5eabe Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Thu, 21 Oct 2021 23:49:56 +0200 Subject: [PATCH 34/76] BasisImageConverter: test automatic mipgen and perceptual options --- .../Test/BasisImageConverterTest.cpp | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp index 1d8a80c74..467d0d678 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp +++ b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp @@ -59,6 +59,9 @@ struct BasisImageConverterTest: TestSuite::Tester { void processError(); + void configPerceptual(); + void configMipGen(); + void r(); void rg(); void rgb(); @@ -102,6 +105,9 @@ BasisImageConverterTest::BasisImageConverterTest() { &BasisImageConverterTest::processError, + &BasisImageConverterTest::configPerceptual, + &BasisImageConverterTest::configMipGen, + &BasisImageConverterTest::r, &BasisImageConverterTest::rg, @@ -211,6 +217,75 @@ void BasisImageConverterTest::processError() { "Trade::BasisImageConverter::convertToData(): frontend processing failed\n"); } +void BasisImageConverterTest::configPerceptual() { + const char bytes[4]{}; + ImageView2D originalImage{PixelFormat::RGBA8Unorm, Vector2i{1}, bytes}; + + Containers::Pointer converter = + _converterManager.instantiate("BasisImageConverter"); + /* Empty by default */ + CORRADE_COMPARE(converter->configuration().value("perceptual"), ""); + + const auto compressedDataAutomatic = converter->convertToData(originalImage); + CORRADE_VERIFY(compressedDataAutomatic); + + CORRADE_VERIFY(converter->configuration().setValue("perceptual", true)); + + const auto compressedDataOverridden = converter->convertToData(originalImage); + CORRADE_VERIFY(compressedDataOverridden); + + if(_manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("BasisImporter plugin not found, cannot test"); + + Containers::Pointer importer = + _manager.instantiate("BasisImporterRGBA8"); + + /* Empty perceptual config means to use the image format to determine if + the output data should be sRGB */ + CORRADE_VERIFY(importer->openData(compressedDataAutomatic)); + auto image = importer->image2D(0); + CORRADE_VERIFY(image); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Unorm); + + /* Perceptual true/false overrides the input format and forces sRGB on/off */ + CORRADE_VERIFY(importer->openData(compressedDataOverridden)); + image = importer->image2D(0); + CORRADE_VERIFY(image); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); +} + +void BasisImageConverterTest::configMipGen() { + const char bytes[16*16*4]{}; + ImageView2D originalLevel0{PixelFormat::RGBA8Unorm, Vector2i{16}, bytes}; + ImageView2D originalLevel1{PixelFormat::RGBA8Unorm, Vector2i{8}, bytes}; + + Containers::Pointer converter = + _converterManager.instantiate("BasisImageConverter"); + /* Empty by default */ + CORRADE_COMPARE(converter->configuration().value("mip_gen"), false); + CORRADE_VERIFY(converter->configuration().setValue("mip_gen", "")); + + const auto compressedDataGenerated = converter->convertToData({originalLevel0}); + CORRADE_VERIFY(compressedDataGenerated); + + const auto compressedDataProvided = converter->convertToData({originalLevel0, originalLevel1}); + CORRADE_VERIFY(compressedDataProvided); + + if(_manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("BasisImporter plugin not found, cannot test"); + + Containers::Pointer importer = + _manager.instantiate("BasisImporterRGBA8"); + + /* Empty mip_gen config means to use the level count to determine if mip + levels should be generated */ + CORRADE_VERIFY(importer->openData(compressedDataGenerated)); + CORRADE_COMPARE(importer->image2DLevelCount(0), 5); + + CORRADE_VERIFY(importer->openData(compressedDataProvided)); + CORRADE_COMPARE(importer->image2DLevelCount(0), 2); +} + template Image2D copyImageWithSkip(const ImageView2D& originalImage, Vector3i skip, PixelFormat format) { const Vector2i size = originalImage.size(); @@ -442,6 +517,7 @@ void BasisImageConverterTest::customLevels() { *originalLevel2, {7, 8, 0}, PixelFormat::RGBA8Unorm); Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); + const auto compressedData = converter->convertToData({level0WithSkip, level1WithSkip, level2WithSkip}); CORRADE_VERIFY(compressedData); @@ -450,6 +526,13 @@ void BasisImageConverterTest::customLevels() { Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + /* Off by default */ + CORRADE_COMPARE(converter->configuration().value("mip_gen"), false); + /* Making sure that providing custom levels turns off automatic mip level + generation. We only provide an incomplete mip chain so we can tell if + basis generated any extra levels beyond that. */ + CORRADE_VERIFY(converter->configuration().setValue("mip_gen", true)); + CORRADE_VERIFY(importer->openData(compressedData)); CORRADE_COMPARE(importer->image2DCount(), 1); CORRADE_COMPARE(importer->image2DLevelCount(0), 3); From cc933cf0f0769710308bccfbdcb40f8f9c88219f Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 22 Oct 2021 00:44:52 +0200 Subject: [PATCH 35/76] BasisImageConverter: test sRGB detection --- .../Test/BasisImageConverterTest.cpp | 131 ++++++++++++++---- 1 file changed, 106 insertions(+), 25 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp index 467d0d678..fb845a734 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp +++ b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp @@ -53,10 +53,8 @@ struct BasisImageConverterTest: TestSuite::Tester { void wrongFormat(); void invalidSwizzle(); - void tooManyLevels(); void levelWrongSize(); - void processError(); void configPerceptual(); @@ -67,8 +65,8 @@ struct BasisImageConverterTest: TestSuite::Tester { void rgb(); void rgba(); + void threads(); void ktx(); - void customLevels(); /* Explicitly forbid system-wide plugin dependencies */ @@ -78,11 +76,28 @@ struct BasisImageConverterTest: TestSuite::Tester { PluginManager::Manager _manager; }; +enum TransferFunction: std::size_t { + Linear, + Srgb +}; + +constexpr PixelFormat TransferFunctionFormats[2][4]{ + {PixelFormat::R8Unorm, PixelFormat::RG8Unorm, PixelFormat::RGB8Unorm, PixelFormat::RGBA8Unorm}, + {PixelFormat::R8Srgb, PixelFormat::RG8Srgb, PixelFormat::RGB8Srgb, PixelFormat::RGBA8Srgb} +}; + +constexpr struct { + const char* name; + const TransferFunction transferFunction; +} FormatTransferFunctionData[] { + {"Unorm", TransferFunction::Linear}, + {"Srgb", TransferFunction::Srgb} +}; + constexpr struct { const char* name; const char* threads; } ThreadsData[] { - {"", nullptr}, {"2 threads", "2"}, {"all threads", "0"} }; @@ -98,22 +113,20 @@ constexpr struct { BasisImageConverterTest::BasisImageConverterTest() { addTests({&BasisImageConverterTest::wrongFormat, &BasisImageConverterTest::invalidSwizzle, - - &BasisImageConverterTest::tooManyLevels, &BasisImageConverterTest::levelWrongSize, - &BasisImageConverterTest::processError, &BasisImageConverterTest::configPerceptual, - &BasisImageConverterTest::configMipGen, + &BasisImageConverterTest::configMipGen}); - &BasisImageConverterTest::r, - &BasisImageConverterTest::rg, + addInstancedTests({&BasisImageConverterTest::r, + &BasisImageConverterTest::rg, + &BasisImageConverterTest::rgb, + &BasisImageConverterTest::rgba}, + Containers::arraySize(FormatTransferFunctionData)); - &BasisImageConverterTest::rgb}); - - addInstancedTests({&BasisImageConverterTest::rgba}, + addInstancedTests({&BasisImageConverterTest::threads}, Containers::arraySize(ThreadsData)); addInstancedTests({&BasisImageConverterTest::ktx}, @@ -207,12 +220,12 @@ void BasisImageConverterTest::processError() { converter->configuration().setValue("max_endpoint_clusters", 16128 /* basisu_frontend::cMaxEndpointClusters */ + 1); - Image2D imageWithSkip{PixelFormat::RGBA8Unorm, Vector2i{16}, - Containers::Array{ValueInit, 16*16*4}}; + const char bytes[4]{}; + ImageView2D image{PixelFormat::RGBA8Unorm, Vector2i{1}, bytes}; std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!converter->convertToData(imageWithSkip)); + CORRADE_VERIFY(!converter->convertToData(image)); CORRADE_COMPARE(out.str(), "Trade::BasisImageConverter::convertToData(): frontend processing failed\n"); } @@ -290,7 +303,8 @@ template Image2D copyImageWithSkip(const ImageView2D& originalImage, Vector3i skip, PixelFormat format) { const Vector2i size = originalImage.size(); /* Width includes row alignment to 4 bytes */ - const UnsignedInt widthWithSkip = ((size.x() + skip.x())*DestinationType::Size + 3)/4*4; + const UnsignedInt formatSize = pixelSize(format); + const UnsignedInt widthWithSkip = ((size.x() + skip.x())*DestinationType::Size + 3)/formatSize*formatSize; const UnsignedInt dataSize = widthWithSkip*(size.y() + skip.y()); Image2D imageWithSkip{PixelStorage{}.setSkip(skip), format, size, Containers::Array{ValueInit, dataSize}}; @@ -304,6 +318,9 @@ void BasisImageConverterTest::r() { if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + auto&& data = FormatTransferFunctionData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + Containers::Pointer pngImporter = _manager.instantiate("PngImporter"); CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"))); const auto originalImage = pngImporter->image2D(0); @@ -313,7 +330,7 @@ void BasisImageConverterTest::r() { image data properly. During copy, we only use R channel to retrieve an R8 image. */ const Image2D imageWithSkip = copyImageWithSkip>( - *originalImage, {7, 8, 0}, PixelFormat::R8Unorm); + *originalImage, {7, 8, 0}, TransferFunctionFormats[data.transferFunction][0]); const auto compressedData = _converterManager.instantiate("BasisImageConverter")->convertToData(imageWithSkip); CORRADE_VERIFY(compressedData); @@ -326,14 +343,20 @@ void BasisImageConverterTest::r() { CORRADE_VERIFY(importer->openData(compressedData)); Containers::Optional image = importer->image2D(0); CORRADE_VERIFY(image); + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), TransferFunctionFormats[data.transferFunction][3]); + /* CompareImage doesn't support Srgb formats, so we need to create a view + on the original image, but with a Unorm format */ + const ImageView2D imageViewUnorm{imageWithSkip.storage(), + TransferFunctionFormats[TransferFunction::Linear][0], imageWithSkip.size(), imageWithSkip.data()}; /* Basis can only load RGBA8 uncompressed data, which corresponds to RRR1 from our R8 image data. We chose the red channel from the imported image - to compare to our original data */ + to compare to our original data. */ CORRADE_COMPARE_WITH( (Containers::arrayCast<2, const UnsignedByte>(image->pixels().prefix( {std::size_t(image->size()[1]), std::size_t(image->size()[0]), 1}))), - imageWithSkip, + imageViewUnorm, /* There are moderately significant compression artifacts */ (DebugTools::CompareImage{21.0f, 0.822f})); } @@ -342,6 +365,9 @@ void BasisImageConverterTest::rg() { if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + auto&& data = FormatTransferFunctionData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + Containers::Pointer pngImporter = _manager.instantiate("PngImporter"); CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"))); const auto originalImage = pngImporter->image2D(0); @@ -351,7 +377,7 @@ void BasisImageConverterTest::rg() { image data properly. During copy, we only use R and G channels to retrieve an RG8 image. */ const Image2D imageWithSkip = copyImageWithSkip( - *originalImage, {7, 8, 0}, PixelFormat::RG8Unorm); + *originalImage, {7, 8, 0}, TransferFunctionFormats[data.transferFunction][1]); const auto compressedData = _converterManager.instantiate("BasisImageConverter")->convertToData(imageWithSkip); CORRADE_VERIFY(compressedData); @@ -364,13 +390,19 @@ void BasisImageConverterTest::rg() { CORRADE_VERIFY(importer->openData(compressedData)); Containers::Optional image = importer->image2D(0); CORRADE_VERIFY(image); + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), TransferFunctionFormats[data.transferFunction][3]); + /* CompareImage doesn't support Srgb formats, so we need to create a view + on the original image, but with a Unorm format */ + const ImageView2D imageViewUnorm{imageWithSkip.storage(), + TransferFunctionFormats[TransferFunction::Linear][1], imageWithSkip.size(), imageWithSkip.data()}; /* Basis can only load RGBA8 uncompressed data, which corresponds to RRRG from our RG8 image data. We chose the B and A channels from the imported image to compare to our original data */ CORRADE_COMPARE_WITH( (Containers::arrayCast<2, const Math::Vector2>(image->pixels().suffix({0, 0, 2}))), - imageWithSkip, + imageViewUnorm, /* There are moderately significant compression artifacts */ (DebugTools::CompareImage{22.0f, 0.775f})); } @@ -379,6 +411,9 @@ void BasisImageConverterTest::rgb() { if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + auto&& data = FormatTransferFunctionData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + Containers::Pointer pngImporter = _manager.instantiate("PngImporter"); CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"))); const auto originalImage = pngImporter->image2D(0); @@ -387,7 +422,7 @@ void BasisImageConverterTest::rgb() { /* Use the original image and add a skip to ensure the converter reads the image data properly */ const Image2D imageWithSkip = copyImageWithSkip( - *originalImage, {7, 8, 0}, PixelFormat::RGB8Unorm); + *originalImage, {7, 8, 0}, TransferFunctionFormats[data.transferFunction][2]); const auto compressedData = _converterManager.instantiate("BasisImageConverter")->convertToData(imageWithSkip); CORRADE_VERIFY(compressedData); @@ -400,17 +435,63 @@ void BasisImageConverterTest::rgb() { CORRADE_VERIFY(importer->openData(compressedData)); Containers::Optional image = importer->image2D(0); CORRADE_VERIFY(image); + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), TransferFunctionFormats[data.transferFunction][3]); + /* CompareImage doesn't support Srgb formats, so we need to create a view + on the original image, but with a Unorm format */ + const ImageView2D imageViewUnorm{imageWithSkip.storage(), + TransferFunctionFormats[TransferFunction::Linear][2], imageWithSkip.size(), imageWithSkip.data()}; CORRADE_COMPARE_WITH(Containers::arrayCast(image->pixels()), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"), /* There are moderately significant compression artifacts */ - (DebugTools::CompareImageToFile{_manager, 61.0f, 6.347f})); + (DebugTools::CompareImageToFile{_manager, 61.0f, 6.588f})); } void BasisImageConverterTest::rgba() { if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + auto&& data = FormatTransferFunctionData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer pngImporter = _manager.instantiate("PngImporter"); + CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"))); + const auto originalImage = pngImporter->image2D(0); + CORRADE_VERIFY(originalImage); + + /* Use the original image and add a skip to ensure the converter reads the + image data properly */ + const Image2D imageWithSkip = copyImageWithSkip( + *originalImage, {7, 8, 0}, TransferFunctionFormats[data.transferFunction][3]); + + Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); + const auto compressedData = converter->convertToData(imageWithSkip); + CORRADE_VERIFY(compressedData); + + if(_manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("BasisImporter plugin not found, cannot test"); + + Containers::Pointer importer = + _manager.instantiate("BasisImporterRGBA8"); + CORRADE_VERIFY(importer->openData(compressedData)); + Containers::Optional image = importer->image2D(0); + CORRADE_VERIFY(image); + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), TransferFunctionFormats[data.transferFunction][3]); + + /* Basis can only load RGBA8 uncompressed data, which corresponds to RGB1 + from our RGB8 image data. */ + CORRADE_COMPARE_WITH(image->pixels(), + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), + /* There are moderately significant compression artifacts */ + (DebugTools::CompareImageToFile{_manager, 97.25f, 8.547f})); +} + +void BasisImageConverterTest::threads() { + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + auto&& data = ThreadsData[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -425,7 +506,7 @@ void BasisImageConverterTest::rgba() { *originalImage, {7, 8, 0}, PixelFormat::RGBA8Unorm); Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); - if(data.threads) converter->configuration().setValue("threads", data.threads); + converter->configuration().setValue("threads", data.threads); const auto compressedData = converter->convertToData(imageWithSkip); CORRADE_VERIFY(compressedData); From d6c9dc77efae846153aa6eccae7f5a4d13540aa0 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 22 Oct 2021 15:35:13 +0200 Subject: [PATCH 36/76] BasisImporter: fix I-frame detection for KTX2 + UASTC and test it --- .../BasisImporter/BasisImporter.cpp | 26 ++++---- .../BasisImporter/BasisImporter.h | 6 +- .../BasisImporter/Test/BasisImporterTest.cpp | 57 +++++++++++------- .../BasisImporter/Test/CMakeLists.txt | 2 + .../BasisImporter/Test/rgba-video-uastc.basis | Bin 0 -> 5522 bytes .../BasisImporter/Test/rgba-video-uastc.ktx2 | Bin 0 -> 1516 bytes 6 files changed, 56 insertions(+), 35 deletions(-) create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-video-uastc.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-video-uastc.ktx2 diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 01f77bc19..134a6b469 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -421,7 +421,7 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { if(flags() & ImporterFlag::Verbose) { if(_state->isVideo) - Debug{} << "Trade::BasisImporter::openData(): file contains video frames, images must be loaded sequentially"; + Debug{} << "Trade::BasisImporter::openData(): file contains video frames, images must be transcoded sequentially"; } } @@ -477,20 +477,24 @@ Containers::Optional> BasisImporter::doImage(const Unsigne cover an error path, so turning this into an assert. When this blows up for someome, we'd most probably need to harden doOpenData() to catch that, not turning this into a graceful error. - Layer/face index doesn't affect the output we're interested in, so - 0, 0 is enough here to apply to all slices. */ - CORRADE_INTERNAL_ASSERT_OUTPUT(_state->ktx2Transcoder->get_image_level_info(levelInfo, level, 0, 0)); + + For independent images and videos we use the correct layer. For + images as slices, they're all the same size, (checked in + doOpenData()) and isIFrame is not used, so any layer or face works. */ + CORRADE_INTERNAL_ASSERT_OUTPUT(_state->ktx2Transcoder->get_image_level_info(levelInfo, level, id, 0)); origWidth = levelInfo.m_orig_width; origHeight = levelInfo.m_orig_height; totalBlocks = levelInfo.m_total_blocks; - isIFrame = levelInfo.m_iframe_flag; + /* m_iframe_flag is always false for UASTC video: + https://github.com/BinomialLLC/basis_universal/issues/259 + However, it's safe to assume the first frame is always an I-frame. */ + isIFrame = levelInfo.m_iframe_flag || id == 0; numFaces = _state->ktx2Transcoder->get_faces(); #endif } else { - /* Same as above, it checks for state we already verified before. If this - blows up for someone, we can reconsider. */ + /* See comment right above */ basist::basisu_image_level_info levelInfo; CORRADE_INTERNAL_ASSERT_OUTPUT(_state->basisTranscoder->get_image_level_info(_state->in.data(), _state->in.size(), levelInfo, id, level)); @@ -504,9 +508,8 @@ Containers::Optional> BasisImporter::doImage(const Unsigne const UnsignedInt numLayers = _state->numSlices/numFaces; - /* basisu doesn't allow seeking to arbitrary video frames for ETC1S. If - this isn't an I-frame, only allow transcoding the frame following the - last P-frame. Frame 0 is always an I-frame. */ + /* basisu doesn't allow seeking to arbitrary video frames. If this isn't an + I-frame, only allow transcoding the frame following the last P-frame. */ if(_state->isVideo) { const UnsignedInt expectedImageId = _state->lastTranscodedImageId + 1; if(!isIFrame && id != expectedImageId) { @@ -514,8 +517,9 @@ Containers::Optional> BasisImporter::doImage(const Unsigne << expectedImageId << (expectedImageId == 0 ? "but got" : "or 0 but got") << id; return Containers::NullOpt; } - _state->lastTranscodedImageId = expectedImageId; + _state->lastTranscodedImageId = id; } + /* No flags used by transcode_image_level() by default */ const std::uint32_t flags = 0; if(!_state->isYFlipped) { diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index 3c624ad75..be3fb6508 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -134,9 +134,9 @@ exposed as an additional image dimension. @ref image3D will return an Video files will be imported as multiple 2D images with the same size and level count. Due to the way video is encoded by Basis Universal, seeking to arbitrary -frames is not allowed in ETC1S-compressed videos. If you call @ref image2D with -non-sequential frame indices and that frame is not an I-frame, it will print an -error and fail. Restarting from frame 0 is always allowed. +frames is not allowed. If you call @ref image2D with non-sequential frame +indices and that frame is not an I-frame, it will print an error and fail. +Restarting from frame 0 is always allowed. @subsection Trade-BasisImporter-behavior-multilevel Multilevel images diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index d4b0afc29..02ad5fab8 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -80,8 +80,8 @@ struct BasisImporterTest: TestSuite::Tester { void cubeMap(); void cubeMapArray(); - void videoVerbose(); void videoSeeking(); + void videoVerbose(); void ktxImporterAlias(); @@ -182,6 +182,16 @@ constexpr struct { "EacRG", CompressedPixelFormat::EacRG11Unorm, CompressedPixelFormat::EacRG11Unorm} }; +const struct { + const char* name; + const char* file; +} VideoSeekingData[]{ + {"Basis ETC1S", "rgba-video.basis"}, + {"KTX2 ETC1S", "rgba-video.ktx2"}, + {"Basis UASTC", "rgba-video-uastc.basis"}, + {"KTX2 UASTC", "rgba-video-uastc.ktx2"} +}; + BasisImporterTest::BasisImporterTest() { addTests({&BasisImporterTest::empty}); @@ -225,8 +235,10 @@ BasisImporterTest::BasisImporterTest() { &BasisImporterTest::cubeMapArray}, Containers::arraySize(FileTypeData)); +addInstancedTests({&BasisImporterTest::videoSeeking}, + Containers::arraySize(VideoSeekingData)); + addTests({&BasisImporterTest::videoVerbose, - &BasisImporterTest::videoSeeking, &BasisImporterTest::ktxImporterAlias, @@ -1104,28 +1116,13 @@ void BasisImporterTest::cubeMapArray() { (DebugTools::CompareImageToFile{_manager, 88.0f, 10.591f})); } -void BasisImporterTest::videoVerbose() { - Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - - std::ostringstream out; - Debug redirectDebug{&out}; - - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, - "rgba-video.basis"))); - CORRADE_COMPARE(out.str(), ""); - - importer->close(); - importer->setFlags(ImporterFlag::Verbose); - - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, - "rgba-video.basis"))); - CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): file contains video frames, images must be loaded sequentially\n"); -} - void BasisImporterTest::videoSeeking() { + auto& data = VideoSeekingData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, - "rgba-video.basis"))); + data.file))); CORRADE_COMPARE(importer->image2DCount(), 3); @@ -1144,6 +1141,24 @@ void BasisImporterTest::videoSeeking() { "Trade::BasisImporter::image2D(): video frames must be transcoded sequentially, expected frame 1 or 0 but got 2\n"); } +void BasisImporterTest::videoVerbose() { + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + + std::ostringstream out; + Debug redirectDebug{&out}; + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + "rgba-video.basis"))); + CORRADE_COMPARE(out.str(), ""); + + importer->close(); + importer->setFlags(ImporterFlag::Verbose); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + "rgba-video.basis"))); + CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): file contains video frames, images must be transcoded sequentially\n"); +} + void BasisImporterTest::ktxImporterAlias() { if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("AnyImageImporter plugin not found, cannot test forwarding"); diff --git a/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt b/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt index 08b530324..97d32d753 100644 --- a/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt @@ -86,6 +86,8 @@ corrade_add_test(BasisImporterTest BasisImporterTest.cpp rgba-cubemap-array.ktx2 rgba-video.basis rgba-video.ktx2 + rgba-video-uastc.basis + rgba-video-uastc.ktx2 rgb-63x27.png rgba-63x27.png rgba-63x27-slice1.png diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-video-uastc.basis b/src/MagnumPlugins/BasisImporter/Test/rgba-video-uastc.basis new file mode 100644 index 0000000000000000000000000000000000000000..063ae2f35f8b58e1353eb44f094dfea7b58ed9ee GIT binary patch literal 5522 zcmc(jUrbY19LG;#=ti$?PZ89pl>c$8^6z#$EHeWZjoM;ZvMy$ke~#1#sHAptF3VQB zsNndcGLu;zq;A<)uO z>j?~*Zw~ITs{ojk$DFk`2PVu>b2My+-2` zvj5R#+!Q-5e4b|AF8L&`jEgkO|A<|A@*_jrfn|*!S~0fZ6Sqyg)MaOQe_q)`ERQx@ z8ioUGUn_$c*^07nrsWamG-G41-@eiUVgIwZ)H3x1{;_KW1ouDMtl*A~kJ)T%5@A}J z!N9UKW9!YpeYUlfd{im9KT@Upa*5Q1T9gn%CvJuFxgPR~bUn=$``)d*)>XyyX*ELs z3hzn1a(B(HM6zD!?|*SF7_Qd2#(wV>uAjOVCNH&~o{tZ0Oyzmhilp0LvQLxWKZ;}A zj)Bh+b3|DU9nb?k+d0#t2M9Gy;=8^XJcHxiraM;&7coEul0hr zq8r9M%b$cJ@Z+~~uO`s*#hA`VnsH`Iz%kM1)3*Gn0`~7^9cNE}Etk8-O!4<6b-?GN z^VpJ*8OWdLyEhn|e=(k3Zz2D``4j296(s?Vkezfyu!S$k?zjVGwJ8$Ug zMLcgs@}16urSkkddIcuKrPkB=D8|a+aQ;@~hY3n9%)cnD>&5C9jEm$AogZ25>kf6- znVt(B+fN+acIvM-_v!47gHL|@p<}8OPxp}L@x}9t9;fq>=GES+@$wEEOoWDSS)S#$ zaK)`Z5&zM(|9uos9i7WxPz(A&00iiIF;;kY!t~-jKZpJAc`m$lIq7uUQi_HC1^*ZPFS2YY>dy@Af5`u2#P@{$ zXZk*KMkBC!IF%TC=L~whljz9X%e6xGbz2CWK_k6cO zuze?!%lXxTxS9w7-~hh>bXy4k2!JcO72*L?0NH>i0mA|RkD+%7SDSY)&TpS9nib$7 z7F3VJLxh#W@1cAjVF0cm589Gsq{^Zs*^>XxroP+Dc)OOHm72XL3yPE|QWebjjMSVx zSqe!yGbAWv9R#u3gO|mS(*YKAY&~t-m71AM>skfJ186jU_i*;!O;+~oOF^Yibmc9~ z9Bj4@Du(;EYC-8aD!xHoY@I`viZRM^4GMQFdn8kGlxGkK#R23-3fa~eap0?}L$FJ)aBzLP3~R=wXMG}#6%U98Bxi%W+zoX~gc-O% z+;c0+Lr5YI?V`vA)bB+S*<=9P^nzF#UYzLykB#{m)#|I{K@K}i&|>UJOHZbCDtlUo z#Q5<58|M@a@P z8J*?CzQCwT)rgg+FC`;nibL%AMK4)_ehb&n=o!nN!LBuqLNsIf)VN0Tku&V&C6)4c zn0nayoL9O9bVmaFNpw?pB*}o3&|w(XVWn^;j5;f|8JkgoK}^;Q_*L=rq~u-BV3j)} z5gNvCH+j0j9#~$WCyFAa2r1@6?IK3Sn_7F0Bjh5w1a5`RusOyke>smJg>%?r%QCi% z5l!+3{U8n}Lk!3R@=$7n3cv|WG8!HF0!~Wao)9?%k0ay626`|Y2B*Pkc+&vVf*?8V zmSYyuOcRWEEP@Yj3+{Kse3{nP8NEeNGRc6nmnqc^wRKdgVO&mpj!TCu7Nw)FHDpHr ztgn-}7WRPYBNFU|Bi(qDu6Fk`4YaZwFEn>l$DBMCedb}AsWu?6@AY3-suyao#b(TP zT0E6dt%03kX+00+p&N}6rN4m53oDM}N_k9M(UEx;Vl zJd5^rtOEZQ=zPkAK!+x>KLDc%GOA-a>7zP!7|zcqQSk*d<4!p|Msd0`kTocn&rIuA;YcQ!MLwx`wjKk(mx$X&h0;N zs~5?0yZ-bjQJ;2nbWwZZ(7nkx#{9k?Lpv^07pDe9T!-$5a~j2?o}=~rx_uf;d?E>GNY0|Wbf69B{L!hc| lvObBq7V`K0(BD7rPs(qx;@H^^xtuG Date: Fri, 22 Oct 2021 15:39:00 +0200 Subject: [PATCH 37/76] BasisImporter: test UASTC import Just making sure nothing explodes. You can turn those error threshholds way down :o --- .../BasisImporter/Test/BasisImporterTest.cpp | 32 +++++++++++++++++- .../BasisImporter/Test/CMakeLists.txt | 2 ++ .../BasisImporter/Test/rgba-uastc.basis | Bin 0 -> 1892 bytes .../BasisImporter/Test/rgba-uastc.ktx2 | Bin 0 -> 789 bytes 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-uastc.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-uastc.ktx2 diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index 02ad5fab8..0604fa35f 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -65,6 +65,7 @@ struct BasisImporterTest: TestSuite::Tester { void rgbUncompressedNoFlip(); void rgbUncompressedLinear(); void rgbaUncompressed(); + void rgbaUncompressedUastc(); void rgbaUncompressedMultipleImages(); void rgb(); @@ -216,7 +217,8 @@ BasisImporterTest::BasisImporterTest() { addInstancedTests({&BasisImporterTest::rgbUncompressed, &BasisImporterTest::rgbUncompressedNoFlip, &BasisImporterTest::rgbUncompressedLinear, - &BasisImporterTest::rgbaUncompressed}, + &BasisImporterTest::rgbaUncompressed, + &BasisImporterTest::rgbaUncompressedUastc}, Containers::arraySize(FileTypeData)); addTests({&BasisImporterTest::rgbaUncompressedMultipleImages}); @@ -598,6 +600,34 @@ void BasisImporterTest::rgbaUncompressed() { (DebugTools::CompareImageToFile{_manager, 94.0f, 8.039f})); } +void BasisImporterTest::rgbaUncompressedUastc() { + auto&& data = FileTypeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + CORRADE_COMPARE(importer->configuration().value("format"), + "RGBA8"); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, + std::string{"rgba-uastc"} + data.extension))); + CORRADE_COMPARE(importer->image2DCount(), 1); + + Containers::Optional image = importer->image2D(0); + CORRADE_VERIFY(image); + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); + CORRADE_COMPARE(image->size(), (Vector2i{63, 27})); + + if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("AnyImageImporter plugin not found, cannot test contents"); + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + CORRADE_COMPARE_WITH(image->pixels(), + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), + /* There are insignificant compression artifacts */ + (DebugTools::CompareImageToFile{_manager, 4.75f, 0.576f})); +} + void BasisImporterTest::rgbaUncompressedMultipleImages() { Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); CORRADE_VERIFY(importer); diff --git a/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt b/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt index 97d32d753..2947a874d 100644 --- a/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt @@ -71,6 +71,8 @@ corrade_add_test(BasisImporterTest BasisImporterTest.cpp rgba.ktx2 rgba-pow2.basis rgba-pow2.ktx2 + rgba-uastc.basis + rgba-uastc.ktx2 rgba-2images-mips.basis rgba-3d.basis rgba-3d.ktx2 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-uastc.basis b/src/MagnumPlugins/BasisImporter/Test/rgba-uastc.basis new file mode 100644 index 0000000000000000000000000000000000000000..f1d4d71f696313b91a0494dbab79326bcd5e91b9 GIT binary patch literal 1892 zcmXSR5@zsa_;yj8oq<8wjFEu>gv1z#2fk=Z8SNRQ83Y*E8B#zJKvO5z3cQfu&rcE9 zCoUKY1T$x%^VM!KawxC+sPq22_aqh1Nhkh4)PMN#a`DyEehcIo7(Vbbt#0i8_~Fc- z?*Dv$=YJMFh^`-i4#QEPlzwi zsLS~2<7eYD-0^Y?e#NuWnh*5<5wQQy#Od$=fxwF&326@9`C`EEpE>jHT|#`d3yqE2 zcrF|0*m`dOg%=_91m!R6$j(U-*aeAyp#3!F*B{z=wb0lE82_<)Apf&nnCQ9*6#p`e z8*+1h1LL2*{Qn)yk3sI~_KD&D@IRiF)_ndu%px-?)A}md+RGdN|F8c)<$mF3?z0Pc zKEF2z`1+rp-u(K8Yafj__wtU%w#F_CLsR{yqFOxl#~)93N#Or|MByUt|j`My?1II z$bLm>K{-qqC;FVJfT`d@+Pv!@x<|6c`@QbJuwAjKfS z4xvDLK!AxsgJBsX69Y3dgbyL>;k^GqAP*D-I;*@Wvm~{M!6~sgvsfWCFS9JQs5mi4 z!BEf8lz~BGtNz~v2DZ6E3@LH~FC_T$Qv~*j3x)#0%$aJp7&(;JebjmX-FuRX=cE(= zAL>8+c)9rMX}<+Pqd)L7t#0i8_~Fc-?*Dv$=YJMFSbr$-ap7m1v|uWz{a(Rgz&&lw&*-hidQ zlyciQ`x>cTPz=y{^MCuA+YAC1COp13bEc%IppsHrnz8Z!#;YGc8Ta}EUB?Yn&n}mE@FexP@*6E7iy)*Z?RtxD1+6rb1W@{-- zI3#lC45NYr^Tl)GAr2}#QyX*+vheYq;A7-vpSiI@%+XM4fuiq2mg4>s+bkJX>kb@~ zsNnq{a?#I|-*-;NQ!`KFn^&3F{PZ<=mC!6HrFXzUn^%yZF`Ajf#ax%8fm0^QP0r`h I1%+Bp0MJ4s(EtDd literal 0 HcmV?d00001 From b63e2e5b7040ba88038243867dae42647ae032c1 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 22 Oct 2021 18:45:03 +0200 Subject: [PATCH 38/76] BasisImporter: validate texture type constraints A few necessary assumptions are not checked by basis_transcoder, e.g. cube maps must have n*6 square faces, layer sizes must match, etc. --- .../BasisImporter/BasisImporter.cpp | 60 ++++++++++++------ .../BasisImporter/Test/BasisImporterTest.cpp | 60 ++++++++++-------- .../BasisImporter/Test/CMakeLists.txt | 5 +- .../BasisImporter/Test/README.md | 8 ++- .../Test/invalid-cube-face-count.basis | Bin 0 -> 1185 bytes .../Test/invalid-cube-face-size.basis | Bin 0 -> 599 bytes .../BasisImporter/Test/rgba-13x31.png | Bin 0 -> 639 bytes .../BasisImporter/Test/rgba-27x63.png | Bin 818 -> 0 bytes .../Test/rgba-2images-mips.basis | Bin 1687 -> 1279 bytes .../BasisImporter/Test/rgba-6x15.png | Bin 0 -> 478 bytes 10 files changed, 86 insertions(+), 47 deletions(-) create mode 100644 src/MagnumPlugins/BasisImporter/Test/invalid-cube-face-count.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/invalid-cube-face-size.basis create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-13x31.png delete mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-27x63.png create mode 100644 src/MagnumPlugins/BasisImporter/Test/rgba-6x15.png diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 134a6b469..8ba82d60b 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -249,7 +249,7 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { /* Init handles all the validation checks, there's no extra function for that */ if(!_state->ktx2Transcoder->init(_state->in.data(), _state->in.size())) { - Error() << "Trade::BasisImporter::openData(): invalid KTX2 header"; + Error{} << "Trade::BasisImporter::openData(): invalid KTX2 header, or not Basis compressed"; return; } @@ -326,8 +326,7 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { /* Remember the type for doTexture(). Depending on the type, we're either dealing with multiple independent 2D images or each image is - an array layer, cubemap face or depth slice with the same - resolution. */ + an array layer, cubemap face or depth slice. */ _state->isVideo = false; switch(fileInfo.m_tex_type) { case basist::basis_texture_type::cBASISTexTypeVideoFrames: @@ -363,30 +362,42 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { _state->numSlices = fileInfo.m_total_images; } - /** @todo Validate things the transcoder doesn't check: - - non-plain-2D images/video should have the same resolution - (can be checked in doImage2D) - - image count for cube maps should be a multiple of 6 - */ - CORRADE_INTERNAL_ASSERT(fileInfo.m_image_mipmap_levels.size() == fileInfo.m_total_images); - /* Get mip level count for each image, check that they're the same for - videos, cube maps and arrays */ - const bool levelsMustMatch = _state->textureType != TextureType::Texture2D || _state->isVideo; + /* Get mip level counts and check that they, as well as resolution, are + equal for all non-independent images (layers, faces, video frames). + These checks, including the cube map checks below, are either not + necessary for the KTX2 file format or are already handled by + ktx2_transcoder. */ + const bool imageSizeMustMatch = _state->textureType != TextureType::Texture2D || _state->isVideo; + basist::basisu_image_info imageInfo; + UnsignedInt firstWidth = 0, firstHeight = 0; _state->numLevels = Containers::Array{NoInit, _state->numImages}; for(UnsignedInt i = 0; i != fileInfo.m_total_images; ++i) { - const UnsignedInt numLevels = fileInfo.m_image_mipmap_levels[i]; - CORRADE_INTERNAL_ASSERT(numLevels > 0); + /* Header validation etc. is already done in get_file_info and + start_transcoding, so by looking at the code there's nothing else + that could fail and wasn't already caught before */ + CORRADE_INTERNAL_ASSERT_OUTPUT(_state->basisTranscoder->get_image_info(_state->in.data(), _state->in.size(), imageInfo, i)); + if(i == 0) { + firstWidth = imageInfo.m_orig_width; + firstHeight = imageInfo.m_orig_height; + } + + if(imageSizeMustMatch && (imageInfo.m_orig_width != firstWidth || imageInfo.m_orig_height != firstHeight)) { + Error{} << "Trade::BasisImporter::openData(): image slices have mismatching size, expected" + << firstWidth << "by" << firstHeight << "but got" + << imageInfo.m_orig_width << "by" << imageInfo.m_orig_height; + return; + } - if(levelsMustMatch && i > 0 && numLevels != _state->numLevels[0]) { - /** @todo Test? */ - Error{} << "Trade::BasisImporter::openData(): mismatching level count for successive image slices"; + if(imageSizeMustMatch && i > 0 && imageInfo.m_total_levels != _state->numLevels[0]) { + Error{} << "Trade::BasisImporter::openData(): image slices have mismatching level counts, expected" + << _state->numLevels[0] << "but got" << imageInfo.m_total_levels; return; } if(i < _state->numImages) - _state->numLevels[i] = numLevels; + _state->numLevels[i] = imageInfo.m_total_levels; } /* Mip levels in basis are per 2D image, so for 3D images they don't @@ -398,6 +409,19 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { _state->textureType = TextureType::Texture2DArray; } + if(_state->textureType == TextureType::CubeMap || _state->textureType == TextureType::CubeMapArray) { + if(_state->numSlices % 6 != 0) { + Error{} << "Trade::BasisImporter::openData(): cube map face count must be a multiple of 6 but got" << _state->numSlices; + return; + } + + if(firstWidth != firstHeight) { + Error{} << "Trade::BasisImporter::openData(): cube map must be square but got" + << firstWidth << "by" << firstHeight; + return; + } + } + _state->compressionType = fileInfo.m_tex_format; _state->isYFlipped = fileInfo.m_y_flipped; diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index 0604fa35f..652848b1e 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -110,18 +110,30 @@ constexpr struct { {"Invalid", "NotAValidFile", "invalid basis header"}, {"Invalid basis header", "sB\xff\xff", "invalid basis header"}, {"Invalid KTX2 identifier", "\xabKTX 30\xbb\r\n\x1a\n", "invalid basis header"}, - {"Invalid KTX2 header", "\xabKTX 20\xbb\r\n\x1a\n\xff\xff\xff\xff", "invalid KTX2 header"} + {"Invalid KTX2 header", "\xabKTX 20\xbb\r\n\x1a\n\xff\xff\xff\xff", "invalid KTX2 header, or not Basis compressed"} }; constexpr struct { const char* name; const char* file; - const std::size_t offset; + std::size_t offset; const char value; const char* message; } InvalidFileData[] { + /* Change ktx2_etc1s_global_data_header::m_endpoint_count */ {"Corrupt KTX2 supercompression data", "rgb.ktx2", 184, 0x00, "bad KTX2 file"}, - {"Corrupt basis texture type", "rgb.basis", 23, 0x7f, "bad basis file"} + /* Changing anything in basis_file_header after m_header_crc16 makes the + CRC16 check fail. Only the header checksum is currently checked, not the + data checksum, so patching slice metadata still works. */ + {"Invalid header CRC16", "rgb.basis", 23, 0x7f, "bad basis file"}, + /* Change basis_slice_desc::m_orig_width of slice 0 */ + {"Mismatching array sizes", "rgba-array.basis", 82, 0x7f, "image slices have mismatching size, expected 127 by 27 but got 63 by 27"}, + /* Change basis_slice_desc::m_orig_width of slice 0 */ + {"Mismatching video sizes", "rgba-video.basis", 82, 0x7f, "image slices have mismatching size, expected 127 by 27 but got 63 by 27"}, + /* We can't patch m_tex_type in the header because of the CRC check, so we + need dedicated files for the cube map checks */ + {"Invalid face count", "invalid-cube-face-count.basis", ~std::size_t(0), 0, "cube map face count must be a multiple of 6 but got 3"}, + {"Face not square", "invalid-cube-face-size.basis", ~std::size_t(0), 0, "cube map must be square but got 15 by 6"} }; constexpr struct { @@ -131,7 +143,7 @@ constexpr struct { const char* message; } FileTooShortData[] { {"Basis", "rgb.basis", 64, "invalid basis header"}, - {"KTX2", "rgb.ktx2", 64, "invalid KTX2 header"} + {"KTX2", "rgb.ktx2", 64, "invalid KTX2 header, or not Basis compressed"} }; const struct { @@ -294,9 +306,12 @@ void BasisImporterTest::invalidFile() { auto basisData = Utility::Directory::read( Utility::Directory::join(BASISIMPORTER_TEST_DIR, data.file)); + CORRADE_VERIFY(basisData); - CORRADE_INTERNAL_ASSERT(data.offset < basisData.size()); - basisData[data.offset] = data.value; + if(data.offset != ~std::size_t(0)) { + CORRADE_VERIFY(data.offset < basisData.size()); + basisData[data.offset] = data.value; + } std::ostringstream out; Error redirectError{&out}; @@ -393,7 +408,7 @@ void BasisImporterTest::transcodingFailure() { std::ostringstream out; Error redirectError{&out}; - /* PVRTC1 requires power of 2 image dimensions, but rgb.basis is 27x63, + /* PVRTC1 requires power of 2 image dimensions, but rgb.basis is 63x27, hence basis will fail during transcoding */ Containers::Optional image = importer->image2D(0); CORRADE_VERIFY(!image); @@ -405,7 +420,7 @@ void BasisImporterTest::nonBasisKtx() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->openFile(Utility::Directory::join(KTXIMPORTER_TEST_DIR, "2d-rgba.ktx2"))); - CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): invalid KTX2 header\n"); + CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): invalid KTX2 header, or not Basis compressed\n"); } void BasisImporterTest::texture() { @@ -637,7 +652,7 @@ void BasisImporterTest::rgbaUncompressedMultipleImages() { "rgba-2images-mips.basis"))); CORRADE_COMPARE(importer->image2DCount(), 2); CORRADE_COMPARE(importer->image2DLevelCount(0), 3); - CORRADE_COMPARE(importer->image2DLevelCount(1), 3); + CORRADE_COMPARE(importer->image2DLevelCount(1), 2); Containers::Optional image0 = importer->image2D(0); Containers::Optional image0l1 = importer->image2D(0, 1); @@ -648,13 +663,11 @@ void BasisImporterTest::rgbaUncompressedMultipleImages() { Containers::Optional image1 = importer->image2D(1); Containers::Optional image1l1 = importer->image2D(1, 1); - Containers::Optional image1l2 = importer->image2D(1, 2); CORRADE_VERIFY(image1); CORRADE_VERIFY(image1l1); - CORRADE_VERIFY(image1l2); for(auto* image: {&*image0, &*image0l1, &*image0l2, - &*image1, &*image1l1, &*image1l2}) { + &*image1, &*image1l1}) { CORRADE_ITERATION(image->size()); CORRADE_VERIFY(!image->isCompressed()); CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); @@ -663,9 +676,8 @@ void BasisImporterTest::rgbaUncompressedMultipleImages() { CORRADE_COMPARE(image0->size(), (Vector2i{63, 27})); CORRADE_COMPARE(image0l1->size(), (Vector2i{31, 13})); CORRADE_COMPARE(image0l2->size(), (Vector2i{15, 6})); - CORRADE_COMPARE(image1->size(), (Vector2i{27, 63})); - CORRADE_COMPARE(image1l1->size(), (Vector2i{13, 31})); - CORRADE_COMPARE(image1l2->size(), (Vector2i{6, 15})); + CORRADE_COMPARE(image1->size(), (Vector2i{13, 31})); + CORRADE_COMPARE(image1l1->size(), (Vector2i{6, 15})); if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("AnyImageImporter plugin not found, cannot test contents"); @@ -676,7 +688,7 @@ void BasisImporterTest::rgbaUncompressedMultipleImages() { one image alone as there's more to compress */ CORRADE_COMPARE_WITH(image0->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), - (DebugTools::CompareImageToFile{_manager, 92.25f, 8.043f})); + (DebugTools::CompareImageToFile{_manager, 96.0f, 8.11f})); CORRADE_COMPARE_WITH(image0l1->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-31x13.png"), (DebugTools::CompareImageToFile{_manager, 75.75f, 14.077f})); @@ -684,17 +696,11 @@ void BasisImporterTest::rgbaUncompressedMultipleImages() { Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-15x6.png"), (DebugTools::CompareImageToFile{_manager, 65.0f, 23.487f})); CORRADE_COMPARE_WITH(image1->pixels(), - Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-27x63.png"), - (DebugTools::CompareImageToFile{_manager, 85.5f, 10.23f})); - /* Rotating the pixels so we don't need to store the ground truth twice. - Somehow it compresses differently for those, tho (I would expect the - compression to be invariant of the orientation). */ - CORRADE_COMPARE_WITH((image1l1->pixels().transposed<0, 1>()), - Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-31x13.png"), - (DebugTools::CompareImageToFile{_manager, 82.5f, 33.27f})); - CORRADE_COMPARE_WITH((image1l2->pixels().transposed<0, 1>()), - Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-15x6.png"), - (DebugTools::CompareImageToFile{_manager, 82.75f, 40.406f})); + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-13x31.png"), + (DebugTools::CompareImageToFile{_manager, 55.5f, 12.305f})); + CORRADE_COMPARE_WITH(image1l1->pixels(), + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-6x15.png"), + (DebugTools::CompareImageToFile{_manager, 68.25f, 21.792f})); } void BasisImporterTest::rgb() { diff --git a/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt b/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt index 2947a874d..82b4df3af 100644 --- a/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt @@ -57,6 +57,8 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h corrade_add_test(BasisImporterTest BasisImporterTest.cpp LIBRARIES Magnum::Trade Magnum::DebugTools FILES + invalid-cube-face-count.basis + invalid-cube-face-size.basis rgb.basis rgb.ktx2 rgb-pow2.basis @@ -95,8 +97,9 @@ corrade_add_test(BasisImporterTest BasisImporterTest.cpp rgba-63x27-slice1.png rgba-63x27-slice1.png rgba-31x13.png + rgba-13x31.png rgba-15x6.png - rgba-27x63.png + rgba-6x15.png rgba-27x27.png rgba-27x27-slice1.png rgba-27x27-slice1.png diff --git a/src/MagnumPlugins/BasisImporter/Test/README.md b/src/MagnumPlugins/BasisImporter/Test/README.md index 81f4887dc..cc6d95cf8 100644 --- a/src/MagnumPlugins/BasisImporter/Test/README.md +++ b/src/MagnumPlugins/BasisImporter/Test/README.md @@ -6,7 +6,6 @@ and `diffuse-alpha-texture.tga` from the [Magnum Shaders test files](https://git - `rgb-63x27.png` - `rgb-64x32.png` -- `rgba-27x63.png` - `rgba-63x27.png` - `rgba-64x32.png` - `rgba-27x27.png` @@ -39,6 +38,13 @@ box filter, the same as Basis itself, to have the least difference. ```sh convert rgba-63x27.png -filter box -resize 31x13\! rgba-31x13.png convert rgba-63x27.png -filter box -resize 15x6\! rgba-15x6.png +convert rgba-31x13.png -rotate 90 rgba-13x31.png +convert rgba-15x6.png -rotate 90 rgba-6x15.png + pngcrush -ow rgba-31x13.png pngcrush -ow rgba-15x6.png +pngcrush -ow rgba-13x31.png +# For whatever reason, this removes the alpha channel +# 'libpng warning: Invalid number of transparent colors specified' +#pngcrush -ow rgba-6x15.png ``` diff --git a/src/MagnumPlugins/BasisImporter/Test/invalid-cube-face-count.basis b/src/MagnumPlugins/BasisImporter/Test/invalid-cube-face-count.basis new file mode 100644 index 0000000000000000000000000000000000000000..82f97080ae25ab0f030b4f94b116bd726debaa6e GIT binary patch literal 1185 zcmXSR5@zsa(Af~e!ocu(HyZ;3GXn#II0F-qL3UFlr%|WQn0}2NN`S)*tgyE_W0)-uc z0&m!XrXf@_0bLsjU#Ga4)$A2<>iSR5Zb z416T^^32bUAb|&h4GbJi8{6bNk8X6oF>MZq15i6dgF`_2hezIXMeAQ4n%Hq~OWdEk zKWk4-@XVT4_bK-|V>HY9mo73}WG5{7&Udn#<8G~rsP}^y99-R;)XJqwY|>*UP={f3I3~>FeRMHFf_#?fg}- zJ6f;aZ$B{N?l2@uuq}GQyxe5YLp8yAMsdxc3W@#|N=w^*wzFo=Vd3o1*5YAu6nVO4 zh6d*oP9?FjZ(?-^jEeIWK4c|L?l_d8(2%<|k7aS7(DWv6ldLNpzb%iySNqOqcgDc^ z^<#!s%`KdNIpdiH7#)C?No>3C>B7hu#rE~k+s9v*eHJ|Z`;N~=n@is>NON3}j|qts z_~}vq>cPg`eFt`Lo}KRa=cVsVAC^l=-`}0zKlAPCI;*=)3LIDNpJ(X25nr|L>yumA zb>FZ5|LL!K&*I*+<5Fs-l}AiGykF^RDyealhP#?fbiO$Ccy=1+d!)@lDcx!E`~rD^GQEM*1?TcQ)r`QkX3HUzvV?t}$Q6@TId$_3`aW z{ipY;{{6xZbcxI2dI#ABse-;NFFv;B@j4dm8u}X8m+>+o`4(<@bOdREXYK{r&pm8n*DiCA(N11uXWN98hJv c`ynQ{KA!vE>(w7~{GPk|{L$=Zq)w0l02-m;g8%>k literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/invalid-cube-face-size.basis b/src/MagnumPlugins/BasisImporter/Test/invalid-cube-face-size.basis new file mode 100644 index 0000000000000000000000000000000000000000..2b10128ba3e4f91339815a37197a5a5c770c55d1 GIT binary patch literal 599 zcmXSR5@zsa@LI~n#K6#3$iu+E#=yWJ$^hh{0A7YfMg|5~1_nNcWk7}{1H*kFWdKy; zi=qz5;RhPQ0yKahXa>lz4h@hBMwqZDR5-B-XaGXBG+Z@E7_M3ot{TWds8)xo1_{Ge z>%vttBda!ss|E?fRa?SUvmmRshpPq&!&SS&RkI?i_J*ql3By$f!c{9AxYGdik|TrL z8y)=zj4q4<3=9Hoh81%>O*onu0zpC4kg!;5i#dk_3j-rV1B1ZX&3Sq?TR*!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBC~+phC&cymIsIej^nv8@ zb9%?l>mNU_cdE|djETWnW5Y8>24_tS&lww>G1XhV%6PxAVYjJi_axJ;X2wmXX8TPI zdd$I4v}RlDt~_t;eLv#!~H#>5b4%Nb*kT@aL%F;lPkw0`p$2+{*HnoZ4@n3!~! zn(G&@(~IlSPn*$YYIfS#K(FD1e&wzfQ}bCSrgIjwbp!q7QWE4B+*DsvSyt?CWoD$O zrB)9_KuS-m2}m^od9&xvTd-*H?CCRR&7L!F`RetXx9>i1^yJx#S8py~y&6caUJqim zpP9V~XdY*QM`SSr1K$x4W}K?cCk+&2FY)wsWq-;gB*>!5QCP146pHY4aSV~ToSc&2 z!j|&6V*jBTIz2r+O!F7W@VTYPnLjRfGdy-ZK+gVgx|`;)=LWS-A~*UDO?G~m8QhwF z@CR#T-+$vNN6(!&u|oIE54m@aZtBOvL$=GdvNKd&=TG|hG=c|chiZvyL`h0wNvc(H zQ7VvPFfuSQ)HO8FHM9sZG_^7?u`)K-HZZa>FnH9H*NCDaH$NpatrE9}$rrEL0yQvr My85}Sb4q9e04Sa5c>n+a literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-27x63.png b/src/MagnumPlugins/BasisImporter/Test/rgba-27x63.png deleted file mode 100644 index 6e0a1501fa536e0ffb2e1109147d26d573beef20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 818 zcmeAS@N?(olHy`uVBq!ia0vp^(m-s_!3HD+Uv4f1Qk(@Ik;M!Qd`Cc-ajG^SNQG*N zYeY#(Vo9o1a#1RfVlXl=wA3}Q)HO5;F*39=FtswU&^9ozGB9|Y`Ryl)hTQy=%(P0} z8YY?exiT;?HF>%?hD01*dfhNvBv6F)LG`&a?{*b7M=hBY!FpLWX_W$3R-zJ@(lXD5 zt!7D6PlXA1Dn~@zo0 z&ncWqIQ0I?;i;4MvaNsiOiT66#Up>`mfSdXCB&O^ckSDoX;yDu--vMM++FzkshicC zyI)GeId@n8e0jd)#`Dh3!(KPe?tFg#a$^4Z9)9h_?`hM*Hedg6q0B!iZQjwc{rVFR z$(8Spd0n=1sz}_qX5cczmbnvrF{>?zi!8wzF$q zGP~yMzNmGpbf?I*{98hrZyZ-2wf8-2<;i7z*QDmIRN7T7&7d1Ontvjs_`I}LL;u>! ztm1yX>seZCgz6HxSGGA_pW`*xoO<{F@1$)XXY&_sS$E^>ET0Ks+d@L;Xh*MEHA!ji z&C>hNJj>1EmV4Z`h_uw4aHuPLi9wa>#B)85%-Rlaj@UD4Mr*okLh8dv9XTNho9+^? zC1#7bI)eoE{5RRTV2|MT_aWhz^N(!%ZKxq|tAnHWQ^0P)u7*mn#t9KK(gpJZW*pos z!OAt3-^!1Qii*?Tvwygmo|)&Fd0x-^c|MjHkXn+Jxq5uyaDp}eD zT;@T<+B-9GS_Ni6A7Fq0>`;AMBugqS!(|RfY>wneGFouJ01#0Y2aX`tMuI8%J6V4)Dto=->CYF4sk!y0u10O zA-c=JS`%GrHpPCNla4S8&ZJY;Gy>{zPEF{E%yRkS)oSIfYicKQS1w<}vcRTT}+yEZVyJr*`6oxKU7kw|)c8Y%HhmdWje8a_QE`m`*H z#;5Klp3$_}rfIlbu5L1kPD?c{u7W{nl1Di(6bws}Y9SYDbm#%=;clUx3cpMCw={E) zZmg9G8f^^b>YOk8_N-j~o%o4g$iCi;7^wS=C3u)4e4Z^37UNHfDyUDM-U-Avj?B6| z_|$#4AOECca%<&eS6qE!R{?(WOWgTVpinQ`8GfDBe|d zk63f!EiqLH=Gwf#PIs5-^4ss{8Ea>zcP&n(NG(<$;N4`xBZZ%OasKC&&(0BDaQ=+Q z@-fW6ffXJ?-eG>&$p744stEfh!w4HEt*}WCJhtoKsY}!vPvU6o#;6H@$S)&`blT|KjUAA^+^qW ztxO(>Y|kH59%Jl}Y$usKTo?UjqH(`Cpw`BJ)U;jt=RsSmj?#s0s)ll$H`$fLB`$@@ OdZ%(dSV@U0D*O-Oi{3^6 literal 1687 zcmY+>2{hDO90&0G|C^>smcJ#ah+zy$$78v$Gp1rurr$dUr!3JkG+4gz2fQ~?H-f{i!;oZ;fuoP`9k$X6lT zLZ~^opg;r20s_E+F~}RB)E-`7WRZzW0MLXI6aWVl$OAmgqF^&*SttVS1x6Ox;s>LX zuvh@=Fo$(uz!vf`IM+~GU}TYNAafw#N+2WfJPmj@0qDY)(1ZND>H;H+yypkIIY35X z9rAF*fgs3oFfHS{z{n!UK$eHRem9AV!@FXU$Y8XK%>iz-#ZpCV5UHgS!uOFo-a&#^-W$7Vvt$qpBs=sg_ zojfKBR#Q%=iu)@L>}AF@bRXgu0P229;*eWH_n`-rQLp07i9`FCd-n^v?w06}hwxM2 zJ18IkAu9Orbo-oIf_u-B^S(d3ElHXTX^P{f-A_qNP1DUjxF25d|3=6qcq`iw!Jz1q zFr}waH^JU2UELH_RAkKli`Sp+_GPiGComR6)}xcCXjHJ2DpVLV{IH+3$JADRZa#D> z(SL{6XZJ_GmTUPgM0`R*-Q`t6XF)TCQ|{B-L>ZNYi>$42hzjm%$}16NC8~lW@G|&w z8~7;}I2GQNx&m6eos$wqLL-$O#dBi{Soj?hp-ntp|9FSDYNbQEA*H_Ambzw4{Mk9PdWj{71oeN_F4EBnQ;Rk4;U@%-x1 z>wYa1LC17fXVCm{;Z~}VT+ga+6Fyas3iBS=VT9ZCbx8bI zGm8Onb90S(Dxmd+(XhDe53F(%+N9U%OV^P*Gdf!gqDl?@Or@K9DL_9MT_!uyid-a_ zxRByTzBTT8l50}VcQPpqS61*B)g2Q(jbHa%idDxon*SU9i!Jc{EiPV%q$3Ppjgd@A z>eQnJuFDLy)iV<&(Iw1tid`bLH~LOn*bfz(Hr2;|dd~7}W*;Ypt%biuhydQCTq&gd z)65ak)~Y$V^b6YwdcOCPMZA$JErk5#@Ov-aEwmFwIoz6LvZwu8UWxncOnOwJEBY?l zGiiI1t#a2~rr+{Q$t4V|OOp0J?-uw+9IK>d z*gWh@tQ7JJSpu*A274jR$FyLC3C`wKhxRsDI(~H6NH%GdJ1IyGoI&hA++-HY zF*-kmb=crGS6}F1=sx6ojBGlMnl%Y$V~$g{d-z328(E~4s*}|S&UBQN^;id!!7?3i zq*b8?&IV&(-v(@f8*ffm{#3!wsF^P39qfuJ!!42nuO3#h-IkZ n!Z<4|mUBb1D>IsI_gHT$_BBK9&$jIQx~80aV0P7TRe;KW9V3-W diff --git a/src/MagnumPlugins/BasisImporter/Test/rgba-6x15.png b/src/MagnumPlugins/BasisImporter/Test/rgba-6x15.png new file mode 100644 index 0000000000000000000000000000000000000000..57e929dedcbc06eb37696e186bc03331bcb59863 GIT binary patch literal 478 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJDWfi2)GpI%u%f#AxwqqcbK3>r9LmEH_$hVtgvY;B<Ls{{R1v;va`xIv5d2%mX9rZtnB?v5B3Hzvpbg}37I;J!GcfQS0b$0e+I-SL zLG}_)Usv|0Ttb2@svL#&3P2%yPZ!4!j_b(@2`NcwiKz`iISO5K8<{-{iwp~zD{OdH zcvbxT!OGGq(c64!{5l*E!$ ztK_0oAjM#0U}UIkXrOCo5n^a+Wnf}uY_4r!WMyFRs3)%xMMG|WN@iLmZVi(!Ub6*i OVDNPHb6Mw<&;$UGoS6;) literal 0 HcmV?d00001 From 2ce88533f4216b70afd8a96ec83554a38c714fc4 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 22 Oct 2021 18:47:27 +0200 Subject: [PATCH 39/76] BasisImporter: print a more helpful error message if format support is not compiled into the transcoder --- src/MagnumPlugins/BasisImporter/BasisImporter.cpp | 10 ++++------ .../BasisImporter/Test/BasisImporterTest.cpp | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 8ba82d60b..2c42060af 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -475,13 +475,11 @@ Containers::Optional> BasisImporter::doImage(const Unsigne const bool isUncompressed = basist::basis_transcoder_format_is_uncompressed(format); /* Some target formats may be unsupported, either because support wasn't - compiled in or UASTC doesn't support a certain format. - None of the formats in TargetFormat are currently affected by UASTC. */ + compiled in or UASTC doesn't support a certain format. All of the + formats in TargetFormat are transcodable from UASTC so we can provide a + slightly more useful error message than "impossible!". */ if(!basist::basis_is_format_supported(format, _state->compressionType)) { - /** @todo Mention that some formats may not be compiled in? Since it's - really the only way to trigger this error. */ - Error{} << prefix << "unsupported transcoding target format" << targetFormatStr << "for a" - << (_state->compressionType == basist::basis_tex_format::cUASTC4x4 ? "UASTC" : "ETC1S") << "image"; + Error{} << prefix << "Basis Universal was compiled without support for" << targetFormatStr; return Containers::NullOpt; } diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index 652848b1e..c792c14b0 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -398,7 +398,7 @@ void BasisImporterTest::unsupportedFormat() { importer->configuration().setValue("format", "Bc7RGBA"); CORRADE_VERIFY(!importer->image2D(0)); - CORRADE_COMPARE(out.str(), "Trade::BasisImporter::image2D(): unsupported transcoding target format Bc7RGBA for a ETC1S image\n"); + CORRADE_COMPARE(out.str(), "Trade::BasisImporter::image2D(): Basis Universal was compiled without support for Bc7RGBA\n"); } void BasisImporterTest::transcodingFailure() { From 0542a35eee1669f7abda2bb72d6a81e406105d09 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 22 Oct 2021 18:49:38 +0200 Subject: [PATCH 40/76] BasisImporter: comments++ --- .../BasisImporter/BasisImporter.cpp | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 2c42060af..e547e845e 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -135,6 +135,10 @@ struct BasisImporter::State { /* There is only this type of codebook */ basist::etc1_global_selector_codebook codebook; + /* One transcoder for each supported file type, and of course they have + wildly different interfaces. ktx2_transcoder is only defined if + BASISD_SUPPORT_KTX2 != 0, so we need to ifdef around it everywhere + it's used. */ Containers::Optional basisTranscoder; #if BASISD_SUPPORT_KTX2 Containers::Optional ktx2Transcoder; @@ -225,9 +229,9 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { std::memcmp(data.begin(), KtxFileIdentifier, sizeof(KtxFileIdentifier)) == 0; /* Keep a copy of the data. We have to do this first because transcoders - may hold on to the pointer we pass into init(). While basis_transcoder - currently doesn't keep the pointer around, it might in the future and - ktx2_transcoder already does. */ + may hold on to the pointer we pass into init()/start_transcoding(). + While basis_transcoder currently doesn't keep the pointer around, it + might in the future and ktx2_transcoder already does. */ _state->in = Containers::Array{NoInit, data.size()}; Utility::copy(data, _state->in); @@ -239,14 +243,15 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { if(isKTX2) { if(!basist::basisu_transcoder_supports_ktx2()) { - Error() << "Trade::BasisImporter::openData(): opening a KTX2 file but Basis Universal was compiled without KTX2 support"; + /** @todo Test */ + Error{} << "Trade::BasisImporter::openData(): opening a KTX2 file but Basis Universal was compiled without KTX2 support"; return; } #if BASISD_SUPPORT_KTX2 _state->ktx2Transcoder.emplace(&_state->codebook); - /* Init handles all the validation checks, there's no extra function + /* init() handles all the validation checks, there's no extra function for that */ if(!_state->ktx2Transcoder->init(_state->in.data(), _state->in.size())) { Error{} << "Trade::BasisImporter::openData(): invalid KTX2 header, or not Basis compressed"; @@ -256,6 +261,8 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { /* Check for supercompression and print a useful error if basisu was compiled without Zstandard support. Not exposed in ktx2_transcoder, get it from the KTX2 header directly. */ + /** @todo Can we test this? Maybe disable this on some CI, BC7 is + already disabled on Emscripten. */ const basist::ktx2_header& header = *reinterpret_cast(_state->in.data()); if(header.m_supercompression_scheme == basist::KTX2_SS_ZSTANDARD && !basist::basisu_transcoder_supports_ktx2_zstd()) { Error() << "Trade::BasisImporter::openData(): file uses Zstandard supercompression but Basis Universal was compiled without Zstandard support"; @@ -271,7 +278,9 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { /* Save some global file info we need later */ /* Remember the type for doTexture(). ktx2_transcoder::init() already - checked we're dealing with a valid 2D texture. */ + checked we're dealing with a valid 2D texture. -tex_type 3d results + in 2D array textures, and there's no get_depth() to begin with. Not + ideal because this skips the z-flip on what's meant to be 3D data. */ _state->isVideo = false; if(_state->ktx2Transcoder->get_faces() != 1) _state->textureType = _state->ktx2Transcoder->get_layers() > 0 ? TextureType::CubeMapArray : TextureType::CubeMap; @@ -302,6 +311,7 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { _state->isSrgb = _state->ktx2Transcoder->get_dfd_transfer_func() == basist::KTX2_KHR_DF_TRANSFER_SRGB; #endif } else { + /* .basis file */ _state->basisTranscoder.emplace(&_state->codebook); if(!_state->basisTranscoder->validate_header(_state->in.data(), _state->in.size())) { @@ -483,10 +493,6 @@ Containers::Optional> BasisImporter::doImage(const Unsigne return Containers::NullOpt; } - /** @todo Don't decode to PVRTC if width/height (not origWidth/origHeight?) - is not a power-of-two: - https://github.com/BinomialLLC/basis_universal/blob/77b7df8e5df3532a42ef3c76de0c14cc005d0f65/basisu_tool.cpp#L1458 */ - UnsignedInt origWidth, origHeight, totalBlocks, numFaces; bool isIFrame; if(_state->ktx2Transcoder) { @@ -559,7 +565,6 @@ Containers::Optional> BasisImporter::doImage(const Unsigne } else { rowStride = 0; /* left up to Basis to calculate */ outputRowsInPixels = 0; /* not used for compressed data */ - /** @todo Make sure this is correct for layer/cube/depth textures */ outputSizeInBlocksOrPixels = totalBlocks; } @@ -567,6 +572,8 @@ Containers::Optional> BasisImporter::doImage(const Unsigne const UnsignedInt dataSize = sliceSize*size.z(); Containers::Array dest{DefaultInit, dataSize}; + /** @todo z-flip 3D textures? */ + /* There's no function for transcoding the entire level, so loop over all layers and faces and transcode each one. This matches the image layout imported by KtxImporter, ie. all faces +X through -Z for the first From cbb05c729ef181aec25dae6fbc03af73c662dffc Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 22 Oct 2021 18:50:01 +0200 Subject: [PATCH 41/76] BasisImporter: cleanup --- src/MagnumPlugins/BasisImporter/BasisImporter.cpp | 13 +++++++------ .../BasisImporter/Test/BasisImporterTest.cpp | 10 ++-------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index e547e845e..0c3308c5f 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -265,13 +265,13 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { already disabled on Emscripten. */ const basist::ktx2_header& header = *reinterpret_cast(_state->in.data()); if(header.m_supercompression_scheme == basist::KTX2_SS_ZSTANDARD && !basist::basisu_transcoder_supports_ktx2_zstd()) { - Error() << "Trade::BasisImporter::openData(): file uses Zstandard supercompression but Basis Universal was compiled without Zstandard support"; + Error{} << "Trade::BasisImporter::openData(): file uses Zstandard supercompression but Basis Universal was compiled without Zstandard support"; return; } /* Start transcoding */ if(!_state->ktx2Transcoder->start_transcoding()) { - Error() << "Trade::BasisImporter::openData(): bad KTX2 file"; + Error{} << "Trade::BasisImporter::openData(): bad KTX2 file"; return; } @@ -310,12 +310,13 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { _state->isSrgb = _state->ktx2Transcoder->get_dfd_transfer_func() == basist::KTX2_KHR_DF_TRANSFER_SRGB; #endif + } else { /* .basis file */ _state->basisTranscoder.emplace(&_state->codebook); if(!_state->basisTranscoder->validate_header(_state->in.data(), _state->in.size())) { - Error() << "Trade::BasisImporter::openData(): invalid basis header"; + Error{} << "Trade::BasisImporter::openData(): invalid basis header"; return; } @@ -323,7 +324,7 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { basist::basisu_file_info fileInfo; if(!_state->basisTranscoder->get_file_info(_state->in.data(), _state->in.size(), fileInfo) || !_state->basisTranscoder->start_transcoding(_state->in.data(), _state->in.size())) { - Error() << "Trade::BasisImporter::openData(): bad basis file"; + Error{} << "Trade::BasisImporter::openData(): bad basis file"; return; } @@ -475,7 +476,7 @@ Containers::Optional> BasisImporter::doImage(const Unsigne } else { targetFormat = configuration().value("format"); if(UnsignedInt(targetFormat) == ~UnsignedInt{}) { - Error() << prefix << "invalid transcoding target format" << targetFormatStr << Debug::nospace + Error{} << prefix << "invalid transcoding target format" << targetFormatStr << Debug::nospace << ", expected to be one of EacR, EacRG, Etc1RGB, Etc2RGBA, Bc1RGB, Bc3RGBA, Bc4R, Bc5RG, Bc7RGB, Bc7RGBA, Pvrtc1RGB4bpp, Pvrtc1RGBA4bpp, Astc4x4RGBA, RGBA8"; return Containers::NullOpt; } @@ -552,7 +553,7 @@ Containers::Optional> BasisImporter::doImage(const Unsigne const std::uint32_t flags = 0; if(!_state->isYFlipped) { /** @todo replace with the flag once the PR is submitted */ - Warning{} << "Trade::BasisImporter::image2D(): the image was not encoded Y-flipped, imported data will have wrong orientation"; + Warning{} << prefix << "the image was not encoded Y-flipped, imported data will have wrong orientation"; //flags |= basist::basisu_transcoder::cDecodeFlagsFlipY; } diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index c792c14b0..d894dcde6 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -291,10 +291,11 @@ void BasisImporterTest::invalidHeader() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporter"); + std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->openData(data.data)); + CORRADE_VERIFY(!importer->openData(data.data)); CORRADE_COMPARE(out.str(), Utility::formatString("Trade::BasisImporter::openData(): {}\n", data.message)); } @@ -527,7 +528,6 @@ void BasisImporterTest::rgbUncompressedNoFlip() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, @@ -562,7 +562,6 @@ void BasisImporterTest::rgbUncompressedLinear() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, @@ -591,7 +590,6 @@ void BasisImporterTest::rgbaUncompressed() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, @@ -645,7 +643,6 @@ void BasisImporterTest::rgbaUncompressedUastc() { void BasisImporterTest::rgbaUncompressedMultipleImages() { Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, @@ -717,7 +714,6 @@ void BasisImporterTest::rgb() { setTestCaseDescription(formatData.suffix); Containers::Pointer importer = _manager.instantiate(pluginName); - CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), formatData.suffix); @@ -753,7 +749,6 @@ void BasisImporterTest::rgba() { setTestCaseDescription(formatData.suffix); Containers::Pointer importer = _manager.instantiate(pluginName); - CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), formatData.suffix); @@ -791,7 +786,6 @@ void BasisImporterTest::linear() { setTestCaseDescription(formatData.suffix); Containers::Pointer importer = _manager.instantiate(pluginName); - CORRADE_VERIFY(importer); CORRADE_COMPARE(importer->configuration().value("format"), formatData.suffix); From 25bdbc3cc5207ea8766b053e7ac09404e8b60699 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 22 Oct 2021 18:50:58 +0200 Subject: [PATCH 42/76] BasisImporter: update test file conversion script --- .../BasisImporter/Test/convert.sh | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/MagnumPlugins/BasisImporter/Test/convert.sh b/src/MagnumPlugins/BasisImporter/Test/convert.sh index 250212dcb..c98aa8ea1 100644 --- a/src/MagnumPlugins/BasisImporter/Test/convert.sh +++ b/src/MagnumPlugins/BasisImporter/Test/convert.sh @@ -20,8 +20,57 @@ basisu rgb-63x27.png -output_file rgb-linear.ktx2 -y_flip -linear -ktx2 basisu rgba-63x27.png -output_file rgba.basis -force_alpha -y_flip basisu rgba-63x27.png -output_file rgba.ktx2 -force_alpha -y_flip -ktx2 +# UASTC +basisu rgba-63x27.png -output_file rgba-uastc.basis -uastc -force_alpha -y_flip +basisu rgba-63x27.png -output_file rgba-uastc.ktx2 -uastc -force_alpha -y_flip -ktx2 + # Multiple images, not possible with KTX2 -basisu rgba-63x27.png rgba-27x63.png -output_file rgba-2images-mips.basis -y_flip -mipmap -mip_smallest 16 -mip_filter box +basisu rgba-63x27.png rgba-13x31.png -output_file rgba-2images-mips.basis -y_flip -mipmap -mip_smallest 16 -mip_filter box + +# 2D array +basisu rgba-63x27.png rgba-63x27-slice1.png rgba-63x27-slice2.png -tex_type 2darray -output_file rgba-array.basis -force_alpha -y_flip +basisu rgba-63x27.png rgba-63x27-slice1.png rgba-63x27-slice2.png -tex_type 2darray -output_file rgba-array.ktx2 -force_alpha -y_flip -ktx2 + +# 2D array with mipmaps +basisu rgba-63x27.png rgba-63x27-slice1.png rgba-63x27-slice2.png -tex_type 2darray -output_file rgba-array-mips.basis -force_alpha -y_flip -mipmap -mip_smallest 16 -mip_filter box +basisu rgba-63x27.png rgba-63x27-slice1.png rgba-63x27-slice2.png -tex_type 2darray -output_file rgba-array-mips.ktx2 -force_alpha -y_flip -mipmap -mip_smallest 16 -mip_filter box -ktx2 + +# Video +basisu rgba-63x27.png rgba-63x27-slice1.png rgba-63x27-slice2.png -tex_type video -output_file rgba-video.basis -force_alpha -y_flip +basisu rgba-63x27.png rgba-63x27-slice1.png rgba-63x27-slice2.png -tex_type video -output_file rgba-video.ktx2 -force_alpha -y_flip -ktx2 + +# Video with UASTC to make sure the I-frame detection works +basisu rgba-63x27.png rgba-63x27-slice1.png rgba-63x27-slice2.png -tex_type video -output_file rgba-video-uastc.basis -uastc -force_alpha -y_flip +basisu rgba-63x27.png rgba-63x27-slice1.png rgba-63x27-slice2.png -tex_type video -output_file rgba-video-uastc.ktx2 -uastc -force_alpha -y_flip -ktx2 + +# 3D +basisu rgba-63x27.png rgba-63x27-slice1.png rgba-63x27-slice2.png -tex_type 3d -output_file rgba-3d.basis -force_alpha -y_flip +basisu rgba-63x27.png rgba-63x27-slice1.png rgba-63x27-slice2.png -tex_type 3d -output_file rgba-3d.ktx2 -force_alpha -y_flip -ktx2 + +# 3D with mipmaps +basisu rgba-63x27.png rgba-63x27-slice1.png rgba-63x27-slice2.png -tex_type 3d -output_file rgba-3d-mips.basis -force_alpha -y_flip -mipmap -mip_smallest 16 -mip_filter box +basisu rgba-63x27.png rgba-63x27-slice1.png rgba-63x27-slice2.png -tex_type 3d -output_file rgba-3d-mips.ktx2 -force_alpha -y_flip -mipmap -mip_smallest 16 -mip_filter box -ktx2 + +# Cube map +basisu rgba-27x27.png rgba-27x27-slice1.png rgba-27x27-slice2.png rgba-27x27.png rgba-27x27-slice1.png rgba-27x27-slice2.png -tex_type cubemap -output_file rgba-cubemap.basis -force_alpha -y_flip +basisu rgba-27x27.png rgba-27x27-slice1.png rgba-27x27-slice2.png rgba-27x27.png rgba-27x27-slice1.png rgba-27x27-slice2.png -tex_type cubemap -output_file rgba-cubemap.ktx2 -force_alpha -y_flip -ktx2 + +# Cube map array +# Second layer has the 2nd and 3rd face switched +basisu rgba-27x27.png rgba-27x27-slice1.png rgba-27x27-slice2.png rgba-27x27.png rgba-27x27-slice1.png rgba-27x27-slice2.png rgba-27x27.png rgba-27x27-slice2.png rgba-27x27-slice1.png rgba-27x27.png rgba-27x27-slice1.png rgba-27x27-slice2.png -tex_type cubemap -output_file rgba-cubemap-array.basis -force_alpha -y_flip +basisu rgba-27x27.png rgba-27x27-slice1.png rgba-27x27-slice2.png rgba-27x27.png rgba-27x27-slice1.png rgba-27x27-slice2.png rgba-27x27.png rgba-27x27-slice2.png rgba-27x27-slice1.png rgba-27x27.png rgba-27x27-slice1.png rgba-27x27-slice2.png -tex_type cubemap -output_file rgba-cubemap-array.ktx2 -force_alpha -y_flip -ktx2 + +# Invalid files. These can't be patched at runtime because of CRC checks on the +# header. +# Cube map faces are not square. basisu allows this, BasisImporter does not +# (to match KTX2 behaviour). +basisu rgba-15x6.png rgba-15x6.png rgba-15x6.png rgba-15x6.png rgba-15x6.png rgba-15x6.png -tex_type cubemap -output_file invalid-cube-face-size.basis +# We can't generate cube maps with non-multiples-of-6 face counts, so for +# invalid-cube-face-count.basis we have to copy rgba-array.basis, patch +# m_tex_type in the header to CubemapArray and update the CRC16 manually. +cp rgba-array.basis invalid-cube-face-count.basis +printf '\x02' | dd conv=notrunc of=invalid-cube-face-count.basis bs=1 seek=23 +printf '\x2c\xb0' | dd conv=notrunc of=invalid-cube-face-count.basis bs=1 seek=6 # Required for PVRTC1 target, which requires pow2 dimensions basisu rgb-64x32.png -output_file rgb-pow2.basis -y_flip From 5cc3bbf6cb4a3f462623a3274c6cb4dd48fe356a Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 22 Oct 2021 19:49:04 +0200 Subject: [PATCH 43/76] BasisImageConverter: add missing config options, update defaults --- .../BasisImageConverter/BasisImageConverter.conf | 14 ++++++++++---- .../BasisImageConverter/BasisImageConverter.cpp | 7 +++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf index 5052b7fe2..703f4da5d 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf @@ -10,9 +10,10 @@ quality_level=128 # determine from the image format. perceptual= debug=false +validate=false debug_images=false compute_stats=false -compression_level=1 +compression_level=2 # More options max_endpoint_clusters=512 @@ -26,6 +27,10 @@ force_alpha=false # valid characters are r,g,b,a. This replaced separate_rg_to_color_alpha, # for the same effect use 'rrrg'. swizzle= +renormalize=false +resample_width= +resample_height= +resample_factor= # Number of threads Basis should use during compression, 0 sets it to the # value returned by std::thread::hardware_concurrency(), 1 disables # multithreading. This value is clamped to std::thread::hardware_concurrency() @@ -38,13 +43,14 @@ disable_hierarchical_endpoint_codebooks=false # openData, this option will be ignored. Leave blank to determine from the # number of levels passed to convertToData. mip_gen=false -# Generate mipmaps assuming sRGB color data, rather than linear intensity. -# Leave blank to determine from the image format. +# Filter mipmaps assuming sRGB color data, rather than linear intensity. Leave +# blank to determine from the image format. mip_srgb= mip_scale=1.0 mip_filter=kaiser mip_renormalize=false mip_wrapping=true +mip_fast=true mip_smallest_dimension=1 # Backend endpoint/selector RDO codec options @@ -76,7 +82,7 @@ rdo_uastc_favor_simpler_modes_in_rdo_mode=true # KTX2 options create_ktx2_file=false -ktx2_uastc_supercompression=false +ktx2_uastc_supercompression=true ktx2_zstd_supercompression_level=6 # Set various fields in the Basis file header diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index c0e5ed1f0..725ce5c61 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -100,6 +100,7 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi PARAM_CONFIG(quality_level, int); PARAM_CONFIG(perceptual, bool); PARAM_CONFIG(debug, bool); + PARAM_CONFIG(validate, bool); PARAM_CONFIG(debug_images, bool); PARAM_CONFIG(compute_stats, bool); PARAM_CONFIG(compression_level, int); @@ -134,6 +135,11 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi } } + PARAM_CONFIG(renormalize, bool); + PARAM_CONFIG(resample_width, int); + PARAM_CONFIG(resample_height, int); + PARAM_CONFIG(resample_factor, float); + UnsignedInt threadCount = configuration().value("threads"); if(threadCount == 0) threadCount = std::thread::hardware_concurrency(); const bool multithreading = threadCount > 1; @@ -150,6 +156,7 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi PARAM_CONFIG(mip_filter, std::string); PARAM_CONFIG(mip_renormalize, bool); PARAM_CONFIG(mip_wrapping, bool); + PARAM_CONFIG(mip_fast, bool); PARAM_CONFIG(mip_smallest_dimension, int); /* Backend endpoint/selector RDO codec options */ From e8b48cee2b12eb7524cbec38d58c97916d6591d6 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 22 Oct 2021 19:49:23 +0200 Subject: [PATCH 44/76] BasisImageConverter: cleanup --- .../BasisImageConverter.cpp | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index 725ce5c61..fe3b8d1df 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -39,6 +39,7 @@ #include #include #include + #include #include #include @@ -194,18 +195,17 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi PARAM_CONFIG(ktx2_zstd_supercompression_level, int); params.m_ktx2_srgb_transfer_func = params.m_perceptual; - /* y_flip sets a flag in Basis files, but not in KTX2 files. Specify the - orientation in the key/value data: + /* y_flip sets a flag in Basis files, but not in KTX2 files: + https://github.com/BinomialLLC/basis_universal/issues/258 + Manually specify the orientation in the key/value data: https://www.khronos.org/registry/KTX/specs/2.0/ktxspec_v2.html#_ktxorientation */ constexpr char OrientationKey[] = "KTXorientation"; char orientationValue[] = "rd"; if(params.m_y_flip) orientationValue[1] = 'u'; basist::ktx2_transcoder::key_value& keyValue = *params.m_ktx2_key_values.enlarge(1); - keyValue.m_key.resize(sizeof(OrientationKey)); - std::memcpy(keyValue.m_key.data(), OrientationKey, sizeof(OrientationKey)); - keyValue.m_value.resize(sizeof(orientationValue)); - std::memcpy(keyValue.m_value.data(), orientationValue, sizeof(orientationValue)); + keyValue.m_key.append(reinterpret_cast(OrientationKey), sizeof(OrientationKey)); + keyValue.m_key.append(reinterpret_cast(orientationValue), sizeof(orientationValue)); /* Set various fields in the Basis file header */ PARAM_CONFIG(userdata0, int); @@ -223,7 +223,7 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi params.m_read_source_images = false; params.m_write_output_basis_files = false; - basist::etc1_global_selector_codebook sel_codebook(basist::g_global_selector_cb_size, basist::g_global_selector_cb); + const basist::etc1_global_selector_codebook sel_codebook(basist::g_global_selector_cb_size, basist::g_global_selector_cb); params.m_pSel_codebook = &sel_codebook; /* The base mip is in m_source_images, mip 1 and higher go into @@ -240,24 +240,21 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi const auto& image = imageLevels[i]; if(image.size() != mipSize) { - Error() << "Trade::BasisImageConverter::convertToData(): expected " + Error{} << "Trade::BasisImageConverter::convertToData(): expected " "size" << mipSize << "for level" << i << "but got" << image.size(); return {}; } /* Copy image data into the basis image. There is no way to construct a basis image from existing data as it is based on basisu::vector, - moreover we need to tightly pack it and flip Y. The `dst` is a Y-flipped - view already to make the following loops simpler. */ + moreover we need to tightly pack it and flip Y. */ basisu::image& basisImage = i == 0 ? params.m_source_images[0] : params.m_source_mipmap_images[0][i - 1]; basisImage.resize(image.size().x(), image.size().y()); auto dst = Containers::arrayCast(Containers::StridedArrayView2D({basisImage.get_ptr(), basisImage.get_total_pixels()}, {std::size_t(image.size().y()), std::size_t(image.size().x())})); /* Y-flip the view to make the following loops simpler. basisu doesn't - apply m_y_flip (or m_renormalize) to user-supplied mipmaps, so only - do this for the base image. */ - /** @todo Probably a bug, file an issue/send a PR. There's also another - bug where it determines if alpha in any pixel > 0 *before* it - swizzles the mipmaps. */ + apply m_y_flip to user-supplied mipmaps, so only do this for the + base image: + https://github.com/BinomialLLC/basis_universal/issues/257 */ if(!params.m_y_flip || i == 0) dst = dst.flipped<0>(); @@ -295,7 +292,7 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi basisu::basis_compressor basis; basis.init(params); - basisu::basis_compressor::error_code errorCode = basis.process(); + const basisu::basis_compressor::error_code errorCode = basis.process(); if(errorCode != basisu::basis_compressor::error_code::cECSuccess) switch(errorCode) { case basisu::basis_compressor::error_code::cECFailedReadingSourceImages: /* Emitted e.g. when source image is 0-size */ From 3cd27aa5836318797a80f13b2c72abfc17de6d7e Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 13:44:53 +0200 Subject: [PATCH 45/76] BasisImageConverter: warn if mip_gen config option is overridden When basisu gets user-provided mip levels, it ignores the m_mip_gen parameter. Warn the user about it instead of ignoring it silently. And test it correctly this time... --- .../BasisImageConverter.cpp | 5 +++++ .../Test/BasisImageConverterTest.cpp | 18 +++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index fe3b8d1df..ce3692720 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -231,6 +231,11 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi generation is disabled. */ params.m_source_images.resize(1); if(imageLevels.size() > 1) { + if(params.m_mip_gen) { + Warning{} << "Trade::BasisImageConverter::convertToData(): found user-supplied mip levels, ignoring mip_gen config value"; + params.m_mip_gen = false; + } + params.m_source_mipmap_images.resize(1); params.m_source_mipmap_images[0].resize(imageLevels.size() - 1); } diff --git a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp index fb845a734..3b727170e 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp +++ b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp @@ -599,21 +599,25 @@ void BasisImageConverterTest::customLevels() { Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); + /* Off by default */ + CORRADE_COMPARE(converter->configuration().value("mip_gen"), false); + /* Making sure that providing custom levels turns off automatic mip level + generation. We only provide an incomplete mip chain so we can tell if + basis generated any extra levels beyond that. */ + converter->configuration().setValue("mip_gen", true); + + std::ostringstream out; + Warning redirectWarning{&out}; + const auto compressedData = converter->convertToData({level0WithSkip, level1WithSkip, level2WithSkip}); CORRADE_VERIFY(compressedData); + CORRADE_COMPARE(out.str(), "Trade::BasisImageConverter::convertToData(): found user-supplied mip levels, ignoring mip_gen config value\n"); if(_manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("BasisImporter plugin not found, cannot test"); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - /* Off by default */ - CORRADE_COMPARE(converter->configuration().value("mip_gen"), false); - /* Making sure that providing custom levels turns off automatic mip level - generation. We only provide an incomplete mip chain so we can tell if - basis generated any extra levels beyond that. */ - CORRADE_VERIFY(converter->configuration().setValue("mip_gen", true)); - CORRADE_VERIFY(importer->openData(compressedData)); CORRADE_COMPARE(importer->image2DCount(), 1); CORRADE_COMPARE(importer->image2DLevelCount(0), 3); From 7935c91b7cf937290d9c7f6b16c015d3ac1abe6e Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 13:54:56 +0200 Subject: [PATCH 46/76] BasisImporter: import 3D textures as 2D array textures They're not supported by KTX2, mip levels are always 2D, and it saves us from having to determine if we should flip the z-dimension (which there is no flag floor, or any sane way to deduce it correctly) --- .../BasisImporter/BasisImporter.cpp | 26 ++++++------ .../BasisImporter/BasisImporter.h | 11 +++-- .../BasisImporter/Test/BasisImporterTest.cpp | 40 +++++++------------ 3 files changed, 36 insertions(+), 41 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 0c3308c5f..bfef0582e 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -358,7 +358,14 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { _state->textureType = fileInfo.m_total_images > 6 ? TextureType::CubeMapArray : TextureType::CubeMap; break; case basist::basis_texture_type::cBASISTexTypeVolume: - _state->textureType = TextureType::Texture3D; + /* Import 3D textures as 2D arrays because: + - 3D textures are not supported in KTX2 so this unifies the + formats + - mip levels are always 2D images for each slice, meaning + they wouldn't halve in the z-dimension as users would very + likely expect */ + Warning{} << "Trade::BasisImporter::openData(): importing 3D texture as a 2D array texture"; + _state->textureType = TextureType::Texture2DArray; break; default: /* This is caught by basis_transcoder::get_file_info() */ @@ -411,14 +418,6 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { _state->numLevels[i] = imageInfo.m_total_levels; } - /* Mip levels in basis are per 2D image, so for 3D images they don't - halve in the z-dimension. Turn it into a 2D array texture so users - don't get surprised by the mip z-size not changing, and print a - warning. */ - if(_state->textureType == TextureType::Texture3D && _state->numLevels[0] > 1) { - Warning{} << "Trade::BasisImporter::openData(): found a 3D image with 2D mipmaps, importing as a 2D array texture"; - _state->textureType = TextureType::Texture2DArray; - } if(_state->textureType == TextureType::CubeMap || _state->textureType == TextureType::CubeMapArray) { if(_state->numSlices % 6 != 0) { @@ -446,8 +445,11 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { CORRADE_INTERNAL_ASSERT(!_state->ktx2Transcoder != !_state->basisTranscoder); /* Can't have a KTX2 transcoder without KTX2 support compiled into basisu */ CORRADE_INTERNAL_ASSERT(BASISD_SUPPORT_KTX2 || !_state->ktx2Transcoder); - /* These file formats don't support 1D images */ - CORRADE_INTERNAL_ASSERT(_state->textureType != TextureType::Texture1D && _state->textureType != TextureType::Texture1DArray); + /* These file formats don't support 1D images and we import 3D images as + 2D array images */ + CORRADE_INTERNAL_ASSERT(_state->textureType != TextureType::Texture1D && + _state->textureType != TextureType::Texture1DArray && + _state->textureType != TextureType::Texture3D); /* There's one image with faces/layers, or multiple images without any */ CORRADE_INTERNAL_ASSERT(_state->numImages == 1 || _state->numSlices == 1); @@ -573,8 +575,6 @@ Containers::Optional> BasisImporter::doImage(const Unsigne const UnsignedInt dataSize = sliceSize*size.z(); Containers::Array dest{DefaultInit, dataSize}; - /** @todo z-flip 3D textures? */ - /* There's no function for transcoding the entire level, so loop over all layers and faces and transcode each one. This matches the image layout imported by KtxImporter, ie. all faces +X through -Z for the first diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index be3fb6508..235c4d7f8 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -124,14 +124,19 @@ See @ref building-plugins, @ref cmake-plugins, @ref plugins and @subsection Trade-BasisImporter-behavior-types Image types You can import all image types supported by `basisu`: (layered) 2D images, -(layered) cube maps, 3D images and videos. With the exception of 3D images, -they can in turn all have multiple mip levels. The image type can be determined -from @ref texture() and @ref TextureData::type(). +(layered) cube maps, 3D images and videos. They can in turn all have multiple +mip levels. The image type can be determined from @ref texture() and +@ref TextureData::type(). For layered 2D images and (layered) cube maps, the array layers and faces are exposed as an additional image dimension. @ref image3D will return an @ref ImageData3D with n z-slices, or 6*n z-slices for cube maps. +All 3D images will be imported as 2D array textures with as many layers as +depth slices. This unifies the behaviour with Basis compressed KTX2 files that +don't support 3D images in the first place, and avoids confusing behaviour with +mip levels which are always 2-dimensional in Basis compressed images. + Video files will be imported as multiple 2D images with the same size and level count. Due to the way video is encoded by Basis Universal, seeking to arbitrary frames is not allowed. If you call @ref image2D with non-sequential frame diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index d894dcde6..a569a9711 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -156,7 +156,7 @@ const struct { {"Cube map", "rgba-cubemap", TextureType::CubeMap}, {"Cube map array", "rgba-cubemap-array", TextureType::CubeMapArray}, {"3D", "rgba-3d", TextureType::Texture3D}, - {"3D mipmaps", "rgba-3d-mips", TextureType::Texture2DArray}, + {"3D mipmaps", "rgba-3d-mips", TextureType::Texture3D}, {"Video", "rgba-video", TextureType::Texture2D} }; @@ -433,8 +433,21 @@ void BasisImporterTest::texture() { for(const auto& fileType: FileTypeData) { CORRADE_ITERATION(fileType.name); + const bool isKtx2 = std::string{fileType.name} == "KTX2"; + const bool is3D = data.type == TextureType::Texture3D; + /* basisu saves volume textures as KTX2 2D arrays, and we import 3D + basis files as 2D arrays, too */ + const TextureType realType = is3D ? TextureType::Texture2DArray : data.type; + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(importer->openFile( Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{data.fileBase} + fileType.extension))); + if(!isKtx2 && is3D) + CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): importing 3D texture as a 2D array texture\n"); + else + CORRADE_COMPARE(out.str(), ""); const Vector3ui counts{ importer->image1DCount(), @@ -447,10 +460,6 @@ void BasisImporterTest::texture() { CORRADE_COMPARE(counts.max(), total); CORRADE_COMPARE(importer->textureCount(), total); - const bool isKtx2 = std::string{fileType.name} == "KTX2"; - const bool is3D = data.type == TextureType::Texture3D; - const TextureType realType = (isKtx2 && is3D) ? TextureType::Texture2DArray : data.type; - for(UnsignedInt i = 0; i != total; ++i) { CORRADE_ITERATION(i); @@ -462,11 +471,6 @@ void BasisImporterTest::texture() { CORRADE_COMPARE(texture->wrapping(), Math::Vector3{SamplerWrapping::Repeat}); CORRADE_COMPARE(texture->image(), i); CORRADE_COMPARE(texture->importerState(), nullptr); - { - CORRADE_EXPECT_FAIL_IF(isKtx2 && is3D, - "basisu saves volume textures as KTX2 2D arrays and the transcoder can't read 3D textures."); - CORRADE_COMPARE(texture->type(), data.type); - } CORRADE_COMPARE(texture->type(), realType); } @@ -476,12 +480,11 @@ void BasisImporterTest::texture() { dimensions = 2; break; case TextureType::Texture2DArray: - case TextureType::Texture3D: case TextureType::CubeMap: case TextureType::CubeMapArray: dimensions = 3; break; - /* No 1D (array) allowed */ + /* No 1D/3D (array) allowed */ default: CORRADE_INTERNAL_ASSERT_UNREACHABLE(); } CORRADE_COMPARE(counts[dimensions - 1], total); @@ -981,22 +984,9 @@ void BasisImporterTest::image3DMipmaps() { CORRADE_COMPARE(importer->configuration().value("format"), "RGBA8"); - std::ostringstream out; - Warning redirectWarning{&out}; - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgba-3d-mips"} + data.extension))); - const bool isKtx2 = std::string{data.name} == "KTX2"; - - { - CORRADE_EXPECT_FAIL_IF(isKtx2, "basisu saves volume textures as KTX2 2D arrays and the transcoder can't read 3D textures."); - CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): found a 3D image with 2D mipmaps, importing as a 2D array texture\n"); - } - - if(isKtx2) - CORRADE_COMPARE(out.str(), ""); - Containers::Optional levels[3]; CORRADE_COMPARE(importer->image3DCount(), 1); From 3626ca278cd27437381a51d36f2ce3975c940dd7 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 14:01:05 +0200 Subject: [PATCH 47/76] BasisImporter: get rid of ScopeGuard There is no code we need to run at cleanup that destructors wouldn't handle for us --- .../BasisImporter/BasisImporter.cpp | 135 +++++++++--------- 1 file changed, 64 insertions(+), 71 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index bfef0582e..59bfc0218 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include #include @@ -232,14 +231,9 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { may hold on to the pointer we pass into init()/start_transcoding(). While basis_transcoder currently doesn't keep the pointer around, it might in the future and ktx2_transcoder already does. */ - _state->in = Containers::Array{NoInit, data.size()}; - Utility::copy(data, _state->in); - - Containers::ScopeGuard resourceGuard{_state.get(), [](BasisImporter::State* state) { - state->basisTranscoder = Containers::NullOpt; - state->ktx2Transcoder = Containers::NullOpt; - state->in = nullptr; - }}; + Containers::Pointer state{InPlaceInit}; + state->in = Containers::Array{NoInit, data.size()}; + Utility::copy(data, state->in); if(isKTX2) { if(!basist::basisu_transcoder_supports_ktx2()) { @@ -249,11 +243,11 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { } #if BASISD_SUPPORT_KTX2 - _state->ktx2Transcoder.emplace(&_state->codebook); + state->ktx2Transcoder.emplace(&state->codebook); /* init() handles all the validation checks, there's no extra function for that */ - if(!_state->ktx2Transcoder->init(_state->in.data(), _state->in.size())) { + if(!state->ktx2Transcoder->init(state->in.data(), state->in.size())) { Error{} << "Trade::BasisImporter::openData(): invalid KTX2 header, or not Basis compressed"; return; } @@ -263,14 +257,14 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { get it from the KTX2 header directly. */ /** @todo Can we test this? Maybe disable this on some CI, BC7 is already disabled on Emscripten. */ - const basist::ktx2_header& header = *reinterpret_cast(_state->in.data()); + const basist::ktx2_header& header = *reinterpret_cast(state->in.data()); if(header.m_supercompression_scheme == basist::KTX2_SS_ZSTANDARD && !basist::basisu_transcoder_supports_ktx2_zstd()) { Error{} << "Trade::BasisImporter::openData(): file uses Zstandard supercompression but Basis Universal was compiled without Zstandard support"; return; } /* Start transcoding */ - if(!_state->ktx2Transcoder->start_transcoding()) { + if(!state->ktx2Transcoder->start_transcoding()) { Error{} << "Trade::BasisImporter::openData(): bad KTX2 file"; return; } @@ -281,49 +275,49 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { checked we're dealing with a valid 2D texture. -tex_type 3d results in 2D array textures, and there's no get_depth() to begin with. Not ideal because this skips the z-flip on what's meant to be 3D data. */ - _state->isVideo = false; - if(_state->ktx2Transcoder->get_faces() != 1) - _state->textureType = _state->ktx2Transcoder->get_layers() > 0 ? TextureType::CubeMapArray : TextureType::CubeMap; - else if(_state->ktx2Transcoder->is_video()) { - _state->textureType = TextureType::Texture2D; - _state->isVideo = true; + state->isVideo = false; + if(state->ktx2Transcoder->get_faces() != 1) + state->textureType = state->ktx2Transcoder->get_layers() > 0 ? TextureType::CubeMapArray : TextureType::CubeMap; + else if(state->ktx2Transcoder->is_video()) { + state->textureType = TextureType::Texture2D; + state->isVideo = true; } else - _state->textureType = _state->ktx2Transcoder->get_layers() > 0 ? TextureType::Texture2DArray : TextureType::Texture2D; + state->textureType = state->ktx2Transcoder->get_layers() > 0 ? TextureType::Texture2DArray : TextureType::Texture2D; /* KTX2 files only ever contain one image, but for videos we choose to expose layers as multiple images, one for each frame */ - if(_state->isVideo) { - _state->numImages = Math::max(_state->ktx2Transcoder->get_layers(), 1u); - _state->numSlices = 1; + if(state->isVideo) { + state->numImages = Math::max(state->ktx2Transcoder->get_layers(), 1u); + state->numSlices = 1; } else { - _state->numImages = 1; - _state->numSlices = _state->ktx2Transcoder->get_faces()*Math::max(_state->ktx2Transcoder->get_layers(), 1u); + state->numImages = 1; + state->numSlices = state->ktx2Transcoder->get_faces()*Math::max(state->ktx2Transcoder->get_layers(), 1u); } - _state->numLevels = Containers::Array{DirectInit, _state->numImages, _state->ktx2Transcoder->get_levels()}; + state->numLevels = Containers::Array{DirectInit, state->numImages, state->ktx2Transcoder->get_levels()}; - _state->compressionType = _state->ktx2Transcoder->get_format(); + state->compressionType = state->ktx2Transcoder->get_format(); /* Get y-flip flag from KTXorientation key/value entry. If it's missing, the default is Y-down. Y-up = flipped. */ - const basisu::uint8_vec* orientation = _state->ktx2Transcoder->find_key("KTXorientation"); - _state->isYFlipped = orientation && orientation->size() >= 2 && (*orientation)[1] == 'u'; + const basisu::uint8_vec* orientation = state->ktx2Transcoder->find_key("KTXorientation"); + state->isYFlipped = orientation && orientation->size() >= 2 && (*orientation)[1] == 'u'; - _state->isSrgb = _state->ktx2Transcoder->get_dfd_transfer_func() == basist::KTX2_KHR_DF_TRANSFER_SRGB; + state->isSrgb = state->ktx2Transcoder->get_dfd_transfer_func() == basist::KTX2_KHR_DF_TRANSFER_SRGB; #endif } else { /* .basis file */ - _state->basisTranscoder.emplace(&_state->codebook); + state->basisTranscoder.emplace(&state->codebook); - if(!_state->basisTranscoder->validate_header(_state->in.data(), _state->in.size())) { + if(!state->basisTranscoder->validate_header(state->in.data(), state->in.size())) { Error{} << "Trade::BasisImporter::openData(): invalid basis header"; return; } /* Start transcoding */ basist::basisu_file_info fileInfo; - if(!_state->basisTranscoder->get_file_info(_state->in.data(), _state->in.size(), fileInfo) || - !_state->basisTranscoder->start_transcoding(_state->in.data(), _state->in.size())) { + if(!state->basisTranscoder->get_file_info(state->in.data(), state->in.size(), fileInfo) || + !state->basisTranscoder->start_transcoding(state->in.data(), state->in.size())) { Error{} << "Trade::BasisImporter::openData(): bad basis file"; return; } @@ -338,7 +332,7 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { /* Remember the type for doTexture(). Depending on the type, we're either dealing with multiple independent 2D images or each image is an array layer, cubemap face or depth slice. */ - _state->isVideo = false; + state->isVideo = false; switch(fileInfo.m_tex_type) { case basist::basis_texture_type::cBASISTexTypeVideoFrames: /* Decoding all video frames at once is usually not what you @@ -346,16 +340,17 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { have to check that the sizes match, and need to remember this is a video to disallow seeking (not supported by basisu). */ - _state->isVideo = true; - CORRADE_FALLTHROUGH + state->isVideo = true; + state->textureType = TextureType::Texture2D; + break; case basist::basis_texture_type::cBASISTexType2D: - _state->textureType = TextureType::Texture2D; + state->textureType = TextureType::Texture2D; break; case basist::basis_texture_type::cBASISTexType2DArray: - _state->textureType = TextureType::Texture2DArray; + state->textureType = TextureType::Texture2DArray; break; case basist::basis_texture_type::cBASISTexTypeCubemapArray: - _state->textureType = fileInfo.m_total_images > 6 ? TextureType::CubeMapArray : TextureType::CubeMap; + state->textureType = fileInfo.m_total_images > 6 ? TextureType::CubeMapArray : TextureType::CubeMap; break; case basist::basis_texture_type::cBASISTexTypeVolume: /* Import 3D textures as 2D arrays because: @@ -365,19 +360,19 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { they wouldn't halve in the z-dimension as users would very likely expect */ Warning{} << "Trade::BasisImporter::openData(): importing 3D texture as a 2D array texture"; - _state->textureType = TextureType::Texture2DArray; + state->textureType = TextureType::Texture2DArray; break; default: /* This is caught by basis_transcoder::get_file_info() */ CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } - if(_state->textureType == TextureType::Texture2D) { - _state->numImages = fileInfo.m_total_images; - _state->numSlices = 1; + if(state->textureType == TextureType::Texture2D) { + state->numImages = fileInfo.m_total_images; + state->numSlices = 1; } else { - _state->numImages = 1; - _state->numSlices = fileInfo.m_total_images; + state->numImages = 1; + state->numSlices = fileInfo.m_total_images; } CORRADE_INTERNAL_ASSERT(fileInfo.m_image_mipmap_levels.size() == fileInfo.m_total_images); @@ -387,15 +382,15 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { These checks, including the cube map checks below, are either not necessary for the KTX2 file format or are already handled by ktx2_transcoder. */ - const bool imageSizeMustMatch = _state->textureType != TextureType::Texture2D || _state->isVideo; basist::basisu_image_info imageInfo; + const bool imageSizeMustMatch = state->textureType != TextureType::Texture2D || state->isVideo; UnsignedInt firstWidth = 0, firstHeight = 0; - _state->numLevels = Containers::Array{NoInit, _state->numImages}; + state->numLevels = Containers::Array{NoInit, state->numImages}; for(UnsignedInt i = 0; i != fileInfo.m_total_images; ++i) { /* Header validation etc. is already done in get_file_info and start_transcoding, so by looking at the code there's nothing else that could fail and wasn't already caught before */ - CORRADE_INTERNAL_ASSERT_OUTPUT(_state->basisTranscoder->get_image_info(_state->in.data(), _state->in.size(), imageInfo, i)); + CORRADE_INTERNAL_ASSERT_OUTPUT(state->basisTranscoder->get_image_info(state->in.data(), state->in.size(), imageInfo, i)); if(i == 0) { firstWidth = imageInfo.m_orig_width; firstHeight = imageInfo.m_orig_height; @@ -408,20 +403,19 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { return; } - if(imageSizeMustMatch && i > 0 && imageInfo.m_total_levels != _state->numLevels[0]) { + if(imageSizeMustMatch && i > 0 && imageInfo.m_total_levels != state->numLevels[0]) { Error{} << "Trade::BasisImporter::openData(): image slices have mismatching level counts, expected" - << _state->numLevels[0] << "but got" << imageInfo.m_total_levels; + << state->numLevels[0] << "but got" << imageInfo.m_total_levels; return; } - if(i < _state->numImages) - _state->numLevels[i] = imageInfo.m_total_levels; + if(i < state->numImages) + state->numLevels[i] = imageInfo.m_total_levels; } - - if(_state->textureType == TextureType::CubeMap || _state->textureType == TextureType::CubeMapArray) { - if(_state->numSlices % 6 != 0) { - Error{} << "Trade::BasisImporter::openData(): cube map face count must be a multiple of 6 but got" << _state->numSlices; + if(state->textureType == TextureType::CubeMap || state->textureType == TextureType::CubeMapArray) { + if(state->numSlices % 6 != 0) { + Error{} << "Trade::BasisImporter::openData(): cube map face count must be a multiple of 6 but got" << state->numSlices; return; } @@ -432,34 +426,33 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { } } - _state->compressionType = fileInfo.m_tex_format; - _state->isYFlipped = fileInfo.m_y_flipped; + state->compressionType = fileInfo.m_tex_format; + state->isYFlipped = fileInfo.m_y_flipped; /* For some reason cBASISHeaderFlagSRGB is not exposed in basisu_file_info, get it from the basis header directly */ - const basist::basis_file_header& header = *reinterpret_cast(_state->in.data()); - _state->isSrgb = header.m_flags & basist::basis_header_flags::cBASISHeaderFlagSRGB; + const basist::basis_file_header& header = *reinterpret_cast(state->in.data()); + state->isSrgb = header.m_flags & basist::basis_header_flags::cBASISHeaderFlagSRGB; } /* There has to be exactly one transcoder */ - CORRADE_INTERNAL_ASSERT(!_state->ktx2Transcoder != !_state->basisTranscoder); + CORRADE_INTERNAL_ASSERT(!state->ktx2Transcoder != !state->basisTranscoder); /* Can't have a KTX2 transcoder without KTX2 support compiled into basisu */ - CORRADE_INTERNAL_ASSERT(BASISD_SUPPORT_KTX2 || !_state->ktx2Transcoder); + CORRADE_INTERNAL_ASSERT(BASISD_SUPPORT_KTX2 || !state->ktx2Transcoder); /* These file formats don't support 1D images and we import 3D images as 2D array images */ - CORRADE_INTERNAL_ASSERT(_state->textureType != TextureType::Texture1D && - _state->textureType != TextureType::Texture1DArray && - _state->textureType != TextureType::Texture3D); + CORRADE_INTERNAL_ASSERT(state->textureType != TextureType::Texture1D && + state->textureType != TextureType::Texture1DArray && + state->textureType != TextureType::Texture3D); /* There's one image with faces/layers, or multiple images without any */ - CORRADE_INTERNAL_ASSERT(_state->numImages == 1 || _state->numSlices == 1); - - /* All good, release the resource guard */ - resourceGuard.release(); + CORRADE_INTERNAL_ASSERT(state->numImages == 1 || state->numSlices == 1); if(flags() & ImporterFlag::Verbose) { - if(_state->isVideo) + if(state->isVideo) Debug{} << "Trade::BasisImporter::openData(): file contains video frames, images must be transcoded sequentially"; } + + _state = std::move(state); } template From 36af4d8ba821c8c9d2d082a88403bce582c28b40 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:17:53 +0200 Subject: [PATCH 48/76] BasisImporter: remove Bc7RGB target format This means the exact same to the transcoder as Bc7RGBA. Back in the day this controlled the BC7 mode, but now it's always mode 5 for ETC1S. For UASTC the mode is only controlled by the file content. So this does nothing. --- .../BasisImporter/BasisImporter.cpp | 4 +-- .../BasisImporter/BasisImporter.h | 25 +++++++++---------- .../BasisImporter/Test/BasisImporterTest.cpp | 2 +- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 59bfc0218..d6e65f7b8 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -68,8 +68,6 @@ CompressedPixelFormat compressedPixelFormat(BasisImporter::TargetFormat type, bo return CompressedPixelFormat::Bc4RUnorm; case BasisImporter::TargetFormat::Bc5RG: return CompressedPixelFormat::Bc5RGUnorm; - case BasisImporter::TargetFormat::Bc7RGB: - return isSrgb ? CompressedPixelFormat::Bc7RGBASrgb : CompressedPixelFormat::Bc7RGBAUnorm; case BasisImporter::TargetFormat::Bc7RGBA: return isSrgb ? CompressedPixelFormat::Bc7RGBASrgb : CompressedPixelFormat::Bc7RGBAUnorm; case BasisImporter::TargetFormat::PvrtcRGB4bpp: @@ -90,7 +88,7 @@ CompressedPixelFormat compressedPixelFormat(BasisImporter::TargetFormat type, bo constexpr const char* FormatNames[]{ "Etc1RGB", "Etc2RGBA", - "Bc1RGB", "Bc3RGBA", "Bc4R", "Bc5RG", "Bc7RGB", "Bc7RGBA", + "Bc1RGB", "Bc3RGBA", "Bc4R", "Bc5RG", nullptr, "Bc7RGBA", "PvrtcRGB4bpp", "PvrtcRGBA4bpp", "Astc4x4RGBA", nullptr, nullptr, /* ATC formats */ diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index 235c4d7f8..e056cd265 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -57,9 +57,11 @@ namespace Magnum { namespace Trade { @brief Basis Universal importer plugin @m_since_{plugins,2019,10} -@m_keywords{BasisImporterEacR BasisImporterEacRG BasisImporterEtc1RGB} @m_keywords{BasisImporterEtc2RGBA BasisImporterBc1RGB BasisImporterBc3RGBA} @m_keywords{BasisImporterBc4R BasisImporterBc5RG BasisImporterBc7RGB} @m_keywords{BasisImporterBc7RGBA BasisImporterPvrtc1RGB4bpp} -@m_keywords{BasisImporterPvrtc1RGBA4bpp BasisImporterAstc4x4RGBA} -@m_keywords{BasisImporterRGBA8 KtxImporter} +@m_keywords{BasisImporterEacR BasisImporterEacRG BasisImporterEtc1RGB} +@m_keywords{BasisImporterEtc2RGBA BasisImporterBc1RGB BasisImporterBc3RGBA} +@m_keywords{BasisImporterBc4R BasisImporterBc5RG BasisImporterBc7RGBA} +@m_keywords{BasisImporterPvrtc1RGB4bpp BasisImporterPvrtc1RGBA4bpp} +@m_keywords{BasisImporterAstc4x4RGBA BasisImporterRGBA8 KtxImporter} Supports [Basis Universal](https://github.com/binomialLLC/basis_universal) compressed images (`*.basis` or `*.ktx2`) by parsing and transcoding files into @@ -69,7 +71,7 @@ You can use @ref BasisImageConverter to transcode images into this format. This plugin provides `BasisImporterEacR`, `BasisImporterEacRG`, `BasisImporterEtc1RGB`, `BasisImporterEtc2RGBA`, `BasisImporterBc1RGB`, `BasisImporterBc3RGBA`, `BasisImporterBc4R`, `BasisImporterBc5RG`, -`BasisImporterBc7RGB`, `BasisImporterBc7RGBA`, `BasisImporterPvrtc1RGB4bpp`, +`BasisImporterBc7RGBA`, `BasisImporterPvrtc1RGB4bpp`, `BasisImporterPvrtc1RGBA4bpp`, `BasisImporterAstc4x4RGBA`, `BasisImporterRGBA8`, `KtxImporter`. @@ -259,20 +261,17 @@ class MAGNUM_BASISIMPORTER_EXPORT BasisImporter: public AbstractImporter { /** BC5 RG. Loaded as @ref CompressedPixelFormat::Bc5RGUnorm. */ Bc5RG = 5, - /** - * BC7 RGB (mode 6). Loaded as - * @ref CompressedPixelFormat::Bc7RGBAUnorm/ - * @ref CompressedPixelFormat::Bc7RGBASrgb, but with alpha values - * set to opaque. - */ - Bc7RGB = 6, + /* Bc7RGB (=6) used to be the mode 6 transcoder which went away + when UASTC was added. The old mode 5 transcoder (=7) is called + BC7_ALT in the transcoder and is only kept around for backward + compatibility but treated exactly the same as BC7_RGBA (=6). */ /** - * BC7 RGBA (mode 5). Loaded as + * BC7 RGBA. If no alpha is present, it's set to opaque. Loaded as * @ref CompressedPixelFormat::Bc7RGBAUnorm/ * @ref CompressedPixelFormat::Bc7RGBASrgb. */ - Bc7RGBA = 7, + Bc7RGBA = 6, /** * PVRTC1 RGB 4 bpp. Loaded as diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index a569a9711..980d19db4 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -182,7 +182,7 @@ constexpr struct { {"rgb", "rgba", "rgb-linear", {63, 27}, "Bc5RG", CompressedPixelFormat::Bc5RGUnorm, CompressedPixelFormat::Bc5RGUnorm}, {"rgb", "rgba", "rgb-linear", {63, 27}, - "Bc7RGB", CompressedPixelFormat::Bc7RGBASrgb, CompressedPixelFormat::Bc7RGBAUnorm}, + "Bc7RGBA", CompressedPixelFormat::Bc7RGBASrgb, CompressedPixelFormat::Bc7RGBAUnorm}, {"rgb-pow2", "rgba-pow2", "rgb-linear-pow2", {64, 32}, "PvrtcRGB4bpp", CompressedPixelFormat::PvrtcRGB4bppSrgb, CompressedPixelFormat::PvrtcRGB4bppUnorm}, {"rgb-pow2", "rgba-pow2", "rgb-linear-pow2", {64, 32}, From ed50c2699abee364b90b7b70c1e8d97437e4b7cb Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:18:44 +0200 Subject: [PATCH 49/76] BasisImporter: mention supported basis_universal version in the docs --- src/MagnumPlugins/BasisImporter/BasisImporter.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index e056cd265..07642bef6 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -89,7 +89,9 @@ This plugin provides `BasisImporterEacR`, `BasisImporterEacRG`, This plugin depends on the @ref Trade and [Basis Universal](https://github.com/binomialLLC/basis_universal) libraries and is built if `WITH_BASISIMPORTER` is enabled when building Magnum Plugins. To use as a dynamic plugin, load @cpp "BasisImporter" @ce via -@ref Corrade::PluginManager::Manager. +@ref Corrade::PluginManager::Manager. Current version of the plugin is tested +against the [`v1_15_update2` tag](https://github.com/BinomialLLC/basis_universal/tree/v1_15_update2), +but could possibly compile against newer versions as well. Additionally, if you're using Magnum as a CMake subproject, bundle the [magnum-plugins](https://github.com/mosra/magnum-plugins) and From 3ec1fc62064eb0a43cc2824bd299ae16988d21ef Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:25:46 +0200 Subject: [PATCH 50/76] BasisImporter: cleanup --- .../BasisImporter/BasisImporter.cpp | 22 +++++------ .../BasisImporter/BasisImporter.h | 39 ++++++++++--------- .../BasisImporter/Test/BasisImporterTest.cpp | 27 +------------ 3 files changed, 31 insertions(+), 57 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index d6e65f7b8..114fcad80 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -234,13 +234,12 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { Utility::copy(data, state->in); if(isKTX2) { - if(!basist::basisu_transcoder_supports_ktx2()) { - /** @todo Test */ - Error{} << "Trade::BasisImporter::openData(): opening a KTX2 file but Basis Universal was compiled without KTX2 support"; - return; - } - - #if BASISD_SUPPORT_KTX2 + #if !BASISD_SUPPORT_KTX2 + /** @todo Can we test this? Maybe disable this on some CI, BC7 is + already disabled on Emscripten. */ + Error{} << "Trade::BasisImporter::openData(): opening a KTX2 file but Basis Universal was compiled without KTX2 support"; + return; + #else state->ktx2Transcoder.emplace(&state->codebook); /* init() handles all the validation checks, there's no extra function @@ -256,7 +255,7 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { /** @todo Can we test this? Maybe disable this on some CI, BC7 is already disabled on Emscripten. */ const basist::ktx2_header& header = *reinterpret_cast(state->in.data()); - if(header.m_supercompression_scheme == basist::KTX2_SS_ZSTANDARD && !basist::basisu_transcoder_supports_ktx2_zstd()) { + if(header.m_supercompression_scheme == basist::KTX2_SS_ZSTANDARD && !BASISD_SUPPORT_KTX2_ZSTD) { Error{} << "Trade::BasisImporter::openData(): file uses Zstandard supercompression but Basis Universal was compiled without Zstandard support"; return; } @@ -270,9 +269,8 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { /* Save some global file info we need later */ /* Remember the type for doTexture(). ktx2_transcoder::init() already - checked we're dealing with a valid 2D texture. -tex_type 3d results - in 2D array textures, and there's no get_depth() to begin with. Not - ideal because this skips the z-flip on what's meant to be 3D data. */ + checked we're dealing with a valid 2D texture. basisu -tex_type 3d + results in 2D array textures, and there's no get_depth() at all. */ state->isVideo = false; if(state->ktx2Transcoder->get_faces() != 1) state->textureType = state->ktx2Transcoder->get_layers() > 0 ? TextureType::CubeMapArray : TextureType::CubeMap; @@ -380,7 +378,6 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { These checks, including the cube map checks below, are either not necessary for the KTX2 file format or are already handled by ktx2_transcoder. */ - basist::basisu_image_info imageInfo; const bool imageSizeMustMatch = state->textureType != TextureType::Texture2D || state->isVideo; UnsignedInt firstWidth = 0, firstHeight = 0; state->numLevels = Containers::Array{NoInit, state->numImages}; @@ -388,6 +385,7 @@ void BasisImporter::doOpenData(const Containers::ArrayView data) { /* Header validation etc. is already done in get_file_info and start_transcoding, so by looking at the code there's nothing else that could fail and wasn't already caught before */ + basist::basisu_image_info imageInfo; CORRADE_INTERNAL_ASSERT_OUTPUT(state->basisTranscoder->get_image_info(state->in.data(), state->in.size(), imageInfo, i)); if(i == 0) { firstWidth = imageInfo.m_orig_width; diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index 07642bef6..2f470f9a6 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -133,7 +133,7 @@ mip levels. The image type can be determined from @ref texture() and @ref TextureData::type(). For layered 2D images and (layered) cube maps, the array layers and faces are -exposed as an additional image dimension. @ref image3D will return an +exposed as an additional image dimension. @ref image3D() will return an @ref ImageData3D with n z-slices, or 6*n z-slices for cube maps. All 3D images will be imported as 2D array textures with as many layers as @@ -143,7 +143,7 @@ mip levels which are always 2-dimensional in Basis compressed images. Video files will be imported as multiple 2D images with the same size and level count. Due to the way video is encoded by Basis Universal, seeking to arbitrary -frames is not allowed. If you call @ref image2D with non-sequential frame +frames is not allowed. If you call @ref image2D() with non-sequential frame indices and that frame is not an I-frame, it will print an error and fail. Restarting from frame 0 is always allowed. @@ -166,6 +166,21 @@ left-handed coordinate system (+X is right, +Y is up, +Z is forward). Layered cube maps are stored as multiple sets of faces, ie. all faces +X through -Z for the first layer, then all faces of the second layer, etc. +@m_class{m-block m-warning} + +@par Y-flipping + While all importers for uncompressed image data are performing a Y-flip on + import to have the origin at the bottom (as expected by OpenGL), it's a + non-trivial operation with compressed images. In case of Basis, you can + pass a `-y_flip` flag to the `basisu` tool to Y-flip the image + * *during encoding*, however right now there's no way do so on import. To + inform the user, the importer checks for the Y-flip flag in the file and if + it's not there, prints a warning about the data having wrong orientation. +@par + To account for this on the application side for files that you don't have + control over, flip texture coordinates of the mesh or patch texture data + loading in the shader. + @section Trade-BasisImporter-configuration Plugin-specific configuration Basis allows configuration of the format of loaded compressed data. @@ -191,31 +206,17 @@ you may also use @ref setTargetFormat(). @snippet BasisImporter.cpp target-format-config -There's many options and you should be generally striving for highest-quality -format available on given platform. Detailed description of the choices is -in [Basis Universal README](https://github.com/BinomialLLC/basis_universal#how-to-use-the-system). +There are many options and you should generally be striving for the +highest-quality format available on a given platform. A detailed description of +the choices can be found in the [Basis Universal Wiki](https://github.com/BinomialLLC/basis_universal/wiki/How-to-Deploy-ETC1S-Texture-Content-Using-Basis-Universal). As an example, the following code is a decision making used by @ref magnum-player "magnum-player" based on availability of corresponding OpenGL, OpenGL ES and WebGL extensions, in its full ugly glory: @snippet BasisImporter.cpp gl-extension-checks - -@m_class{m-block m-warning} -@par Y-flipping - While all importers for uncompressed image data are doing an Y-flip on - import to have origin at the bottom (as expected by OpenGL), it's a - non-trivial operation with compressed images. In case of Basis, you can - pass a `-y_flip` flag to the `basisu` tool to Y-flip the image - * *during encoding*, however right now there's no way do so on import. To - inform the user, the importer checks for the Y-flip flag in the file and if - it's not there, prints a warning about the data having wrong orientation. -@par - To account for this on the application side for files that you don't have a - control of, flip texture coordinates of the mesh or patch texture data - loading in the shader. */ class MAGNUM_BASISIMPORTER_EXPORT BasisImporter: public AbstractImporter { public: diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index 980d19db4..3a2c4a5a1 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -531,8 +531,6 @@ void BasisImporterTest::rgbUncompressedNoFlip() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_COMPARE(importer->configuration().value("format"), - "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgb-noflip"} + data.extension))); CORRADE_COMPARE(importer->image2DCount(), 1); @@ -565,8 +563,6 @@ void BasisImporterTest::rgbUncompressedLinear() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_COMPARE(importer->configuration().value("format"), - "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgb-linear"} + data.extension))); CORRADE_COMPARE(importer->image2DCount(), 1); @@ -593,8 +589,6 @@ void BasisImporterTest::rgbaUncompressed() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_COMPARE(importer->configuration().value("format"), - "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgba"} + data.extension))); CORRADE_COMPARE(importer->image2DCount(), 1); @@ -621,8 +615,6 @@ void BasisImporterTest::rgbaUncompressedUastc() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_COMPARE(importer->configuration().value("format"), - "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgba-uastc"} + data.extension))); CORRADE_COMPARE(importer->image2DCount(), 1); @@ -646,8 +638,6 @@ void BasisImporterTest::rgbaUncompressedUastc() { void BasisImporterTest::rgbaUncompressedMultipleImages() { Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_COMPARE(importer->configuration().value("format"), - "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-2images-mips.basis"))); CORRADE_COMPARE(importer->image2DCount(), 2); @@ -815,8 +805,6 @@ void BasisImporterTest::array2D() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_COMPARE(importer->configuration().value("format"), - "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgba-array"} + data.extension))); @@ -850,8 +838,6 @@ void BasisImporterTest::array2DMipmaps() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_COMPARE(importer->configuration().value("format"), - "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgba-array-mips"} + data.extension))); @@ -901,8 +887,6 @@ void BasisImporterTest::video() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_COMPARE(importer->configuration().value("format"), - "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgba-video"} + data.extension))); @@ -940,8 +924,6 @@ void BasisImporterTest::image3D() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_COMPARE(importer->configuration().value("format"), - "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgba-3d"} + data.extension))); @@ -981,9 +963,6 @@ void BasisImporterTest::image3DMipmaps() { type is tested in texture(). */ Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_COMPARE(importer->configuration().value("format"), - "RGBA8"); - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgba-3d-mips"} + data.extension))); @@ -1033,8 +1012,6 @@ void BasisImporterTest::cubeMap() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_COMPARE(importer->configuration().value("format"), - "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgba-cubemap"} + data.extension))); @@ -1077,8 +1054,6 @@ void BasisImporterTest::cubeMapArray() { setTestCaseDescription(data.name); Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); - CORRADE_COMPARE(importer->configuration().value("format"), - "RGBA8"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, std::string{"rgba-cubemap-array"} + data.extension))); @@ -1185,7 +1160,7 @@ void BasisImporterTest::ktxImporterAlias() { Containers::Pointer anyImageImporter = _manager.instantiate("AnyImageImporter"); CORRADE_VERIFY(anyImageImporter); - CORRADE_VERIFY(anyImageImporter->configuration().setValue("format", "RGBA8")); + anyImageImporter->configuration().setValue("format", "RGBA8"); CORRADE_VERIFY(anyImageImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba.ktx2"))); From 3d9914bc15da08c42c2e8edbed7672c8b687ed17 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:27:34 +0200 Subject: [PATCH 51/76] BasisImporter: mention how to turn off features/formats for smaller binary size --- src/MagnumPlugins/BasisImporter/BasisImporter.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index 2f470f9a6..097fc1ece 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -215,8 +215,23 @@ OpenGL, OpenGL ES and WebGL extensions, in its full ugly glory: @snippet BasisImporter.cpp gl-extension-checks +@subsection Trade-BasisImporter-compile-size Reducing compile size +To reduce the binary size of the transcoder, Basis Universal supports a set of +preprocessor defines to turn off unneeded features. The Basis Universal Wiki +lists macros to disable specific [target formats](https://github.com/BinomialLLC/basis_universal/wiki/How-to-Use-and-Configure-the-Transcoder#shrinking-the-transcoders-compiled-size) +as well as [KTX2 support](https://github.com/BinomialLLC/basis_universal/wiki/How-to-Use-and-Configure-the-Transcoder#disabling-ktx2-or-zstandard-usage). +If you're building it from source with `BASIS_UNIVERSAL_DIR` set, add the +desired defines before adding `magnum-plugins` as a subfolder: +@code{.cmake} +add_definitions( + -DBASISD_SUPPORT_BC7=0 + -DBASISD_SUPPORT_KTX2=0) + +# ... +add_subdirectory(magnum-plugins EXCLUDE_FROM_ALL) +@endcode */ class MAGNUM_BASISIMPORTER_EXPORT BasisImporter: public AbstractImporter { public: From d6eb04b8cd8006e4aa2d7c7995f6471a2159692b Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:31:27 +0200 Subject: [PATCH 52/76] BasisImporter: remove KtxImporter alias Only makes sense to set this if the plugin supports a considerable portion of the format, otherwise it leads to confusion, especially when paired with AnyImageImporter. --- .../BasisImporter/BasisImporter.conf | 1 - .../BasisImporter/BasisImporter.h | 5 ++- .../BasisImporter/Test/BasisImporterTest.cpp | 31 ------------------- 3 files changed, 2 insertions(+), 35 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.conf b/src/MagnumPlugins/BasisImporter/BasisImporter.conf index bce60f6f5..a7865a7e0 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.conf +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.conf @@ -12,7 +12,6 @@ provides=BasisImporterPvrtcRGB4bpp provides=BasisImporterPvrtcRGBA4bpp provides=BasisImporterAstc4x4RGBA provides=BasisImporterRGBA8 -provides=KtxImporter # [configuration_] [configuration] diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index 097fc1ece..61adf93ac 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -61,7 +61,7 @@ namespace Magnum { namespace Trade { @m_keywords{BasisImporterEtc2RGBA BasisImporterBc1RGB BasisImporterBc3RGBA} @m_keywords{BasisImporterBc4R BasisImporterBc5RG BasisImporterBc7RGBA} @m_keywords{BasisImporterPvrtc1RGB4bpp BasisImporterPvrtc1RGBA4bpp} -@m_keywords{BasisImporterAstc4x4RGBA BasisImporterRGBA8 KtxImporter} +@m_keywords{BasisImporterAstc4x4RGBA BasisImporterRGBA8} Supports [Basis Universal](https://github.com/binomialLLC/basis_universal) compressed images (`*.basis` or `*.ktx2`) by parsing and transcoding files into @@ -72,8 +72,7 @@ This plugin provides `BasisImporterEacR`, `BasisImporterEacRG`, `BasisImporterEtc1RGB`, `BasisImporterEtc2RGBA`, `BasisImporterBc1RGB`, `BasisImporterBc3RGBA`, `BasisImporterBc4R`, `BasisImporterBc5RG`, `BasisImporterBc7RGBA`, `BasisImporterPvrtc1RGB4bpp`, -`BasisImporterPvrtc1RGBA4bpp`, `BasisImporterAstc4x4RGBA`, `BasisImporterRGBA8`, -`KtxImporter`. +`BasisImporterPvrtc1RGBA4bpp`, `BasisImporterAstc4x4RGBA`, `BasisImporterRGBA8`. @m_class{m-block m-success} diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index 3a2c4a5a1..d3d92c6fb 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -84,8 +84,6 @@ struct BasisImporterTest: TestSuite::Tester { void videoSeeking(); void videoVerbose(); - void ktxImporterAlias(); - void openSameTwice(); void openDifferent(); void importMultipleFormats(); @@ -254,8 +252,6 @@ addInstancedTests({&BasisImporterTest::videoSeeking}, addTests({&BasisImporterTest::videoVerbose, - &BasisImporterTest::ktxImporterAlias, - &BasisImporterTest::openSameTwice, &BasisImporterTest::openDifferent, &BasisImporterTest::importMultipleFormats}); @@ -1154,33 +1150,6 @@ void BasisImporterTest::videoVerbose() { CORRADE_COMPARE(out.str(), "Trade::BasisImporter::openData(): file contains video frames, images must be transcoded sequentially\n"); } -void BasisImporterTest::ktxImporterAlias() { - if(_manager.loadState("AnyImageImporter") == PluginManager::LoadState::NotFound) - CORRADE_SKIP("AnyImageImporter plugin not found, cannot test forwarding"); - - Containers::Pointer anyImageImporter = _manager.instantiate("AnyImageImporter"); - CORRADE_VERIFY(anyImageImporter); - anyImageImporter->configuration().setValue("format", "RGBA8"); - - CORRADE_VERIFY(anyImageImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, - "rgba.ktx2"))); - CORRADE_COMPARE(anyImageImporter->image2DCount(), 1); - - Containers::Optional image = anyImageImporter->image2D(0); - CORRADE_VERIFY(image); - CORRADE_VERIFY(!image->isCompressed()); - CORRADE_COMPARE(image->format(), PixelFormat::RGBA8Srgb); - CORRADE_COMPARE(image->size(), (Vector2i{63, 27})); - - if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) - CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); - - CORRADE_COMPARE_WITH(image->pixels(), - Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), - /* There are moderately significant compression artifacts */ - (DebugTools::CompareImageToFile{_manager, 94.0f, 8.039f})); -} - void BasisImporterTest::openSameTwice() { Containers::Pointer importer = _manager.instantiate("BasisImporterEtc2RGBA"); CORRADE_VERIFY(importer->openFile( From 6f41d58e97c7d8c8b78f54fca764c846ab9523fd Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:40:49 +0200 Subject: [PATCH 53/76] BasisImageConverter: handle KTX2 conversion through plugin alias and file extension More discoverable than the config option. The default alias saves as a Basis file, unless overridden in openFile. If a format is explicitly selected through the non-manager constructor or KTX2 is selected through the BasisKtxImageConverter alias, openFile can not override the format selection. --- .../BasisImageConverter.conf | 3 +- .../BasisImageConverter.cpp | 55 +++++++-- .../BasisImageConverter/BasisImageConverter.h | 36 +++++- .../Test/BasisImageConverterTest.cpp | 114 +++++++++++++++++- .../BasisImageConverter/Test/CMakeLists.txt | 6 + .../Test/configure.h.cmake | 1 + 6 files changed, 199 insertions(+), 16 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf index 703f4da5d..b376dfd14 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf @@ -1,3 +1,5 @@ +provides=BasisKtxImageConverter + # [configuration_] [configuration] # All following options correspond to options of the basisu tool, grouped in @@ -81,7 +83,6 @@ rdo_uastc_skip_block_rms_threshold=8.0 rdo_uastc_favor_simpler_modes_in_rdo_mode=true # KTX2 options -create_ktx2_file=false ktx2_uastc_supercompression=true ktx2_zstd_supercompression_level=6 diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index ce3692720..03baf00ff 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -46,17 +47,27 @@ namespace Magnum { namespace Trade { -BasisImageConverter::BasisImageConverter() = default; +using namespace Containers::Literals; -BasisImageConverter::BasisImageConverter(PluginManager::AbstractManager& manager, const std::string& plugin): AbstractImageConverter{manager, plugin} {} +BasisImageConverter::BasisImageConverter(Format format): _format{format} { + /* Passing an invalid Format enum is user error, we'll assert on that in + the convertToData() function */ +} + +BasisImageConverter::BasisImageConverter(PluginManager::AbstractManager& manager, const std::string& plugin): AbstractImageConverter{manager, plugin} { + if(plugin == "BasisKtxImageConverter") + _format = Format::Ktx; + else + _format = {}; /* Overridable by openFile() */ +} ImageConverterFeatures BasisImageConverter::doFeatures() const { return ImageConverterFeature::ConvertLevels2DToData; } Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayView imageLevels) { /* Check input */ - const PixelFormat format = imageLevels.front().format(); + const PixelFormat imageFormat = imageLevels.front().format(); bool isSrgb; - switch(format) { + switch(imageFormat) { case PixelFormat::RGBA8Unorm: case PixelFormat::RGB8Unorm: case PixelFormat::RG8Unorm: @@ -70,7 +81,7 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi isSrgb = true; break; default: - Error{} << "Trade::BasisImageConverter::convertToData(): unsupported format" << format; + Error{} << "Trade::BasisImageConverter::convertToData(): unsupported format" << imageFormat; return {}; } @@ -85,8 +96,13 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi basisu::basis_compressor_params params; - /* Options deduced from input data. Can be overridden by config values, if - present. */ + if(_format == Format::Ktx) + params.m_create_ktx2_file = true; + else + CORRADE_INTERNAL_ASSERT(_format == Format{} || _format == Format::Basis); + + /* Options deduced from input data. Config values that are not emptied out + override these below. */ params.m_perceptual = isSrgb; params.m_mip_gen = imageLevels.size() == 1; params.m_mip_srgb = isSrgb; @@ -189,7 +205,6 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi params.m_rdo_uastc_multithreading = multithreading; /* KTX2 options */ - PARAM_CONFIG(create_ktx2_file, bool); params.m_ktx2_uastc_supercompression = configuration().value("ktx2_uastc_supercompression") ? basist::KTX2_SS_ZSTANDARD : basist::KTX2_SS_NONE; PARAM_CONFIG(ktx2_zstd_supercompression_level, int); @@ -264,7 +279,7 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi dst = dst.flipped<0>(); /* basis image is always RGBA, fill in alpha if necessary */ - const UnsignedInt channels = pixelSize(format); + const UnsignedInt channels = pixelSize(imageFormat); if(channels == 4) { auto src = image.pixels>(); for(std::size_t y = 0; y != src.size()[0]; ++y) @@ -347,6 +362,28 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi return fileData; } +bool BasisImageConverter::doConvertToFile(const ImageView2D& image, const Containers::StringView filename) { + /** @todo once Directory is std::string-free, use splitExtension() */ + const Containers::String normalized = Utility::String::lowercase(filename); + + /* Save the previous format to restore it back after, detect the format + from extension if it's not supplied explicitly */ + const Format previousFormat = _format; + if(_format == Format{}) { + if(normalized.hasSuffix(".ktx2"_s)) + _format = Format::Ktx; + else + _format = Format::Basis; + } + + /* Delegate to the base implementation which calls doConvertToData() */ + const bool out = AbstractImageConverter::doConvertToFile(image, filename); + + /* Restore the previous format and return the result */ + _format = previousFormat; + return out; +} + }} CORRADE_PLUGIN_REGISTER(BasisImageConverter, Magnum::Trade::BasisImageConverter, diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h index 82d658dec..7d74b32ac 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h @@ -57,6 +57,8 @@ namespace Magnum { namespace Trade { @brief Basis Universal image converter plugin @m_since_{plugins,2019,10} +@m_keywords{BasisKtxImageConverter} + Creates [Basis Universal](https://github.com/binomialLLC/basis_universal) compressed image files (`*.basis` or `*.ktx2`) from images with format @ref PixelFormat::R8Unorm, @ref PixelFormat::R8Srgb, @@ -65,6 +67,8 @@ compressed image files (`*.basis` or `*.ktx2`) from images with format @ref PixelFormat::RGBA8Unorm or @ref PixelFormat::RGBA8Srgb. Use @ref BasisImporter to import images in this format. +This plugin provides `BasisKtxImageConverter`. + @m_class{m-block m-success} @thirdparty This plugin makes use of the @@ -124,6 +128,14 @@ rounded down. Incomplete mip chains are supported. To generate mip levels from a single top-level image instead, you can use the @cb{.ini} mip_gen @ce @ref Trade-BasisImageConverter-configuration "configuration option". +@subsection Trade-BasisImageConverter-behavior-ktx Converting to KTX2 + +To create Khronos Texture 2.0 (`*.ktx2`) files, either load the plugin as +`BasisKtxImageConverter`, call @ref convertToFile() with the `.ktx2` extension +or pass @ref Format::Ktx to the constructor. + +In all other cases, a Basis Universal (`*.basis`) file is created. + @subsection Trade-BasisImageConverter-behavior-loading Loading the plugin fails with undefined symbol: pthread_create On Linux it may happen that loading the plugin will fail with @@ -155,8 +167,25 @@ to edit the configuration values. */ class MAGNUM_BASISIMAGECONVERTER_EXPORT BasisImageConverter: public AbstractImageConverter { public: - /** @brief Default constructor */ - explicit BasisImageConverter(); + /** + * @brief Output file format + * + * @see @ref BasisImageConverter(Format) + */ + enum class Format: Int { + /* 0 used for default value, Basis unless overridden by + convertToFile */ + + Basis = 1, /**< Output Basis images */ + Ktx, /**< Output KTX2 images */ + }; + + /** + * @brief Default constructor + * + * The converter outputs files in format defined by @ref Format. + */ + explicit BasisImageConverter(Format format = Format{}); /** @brief Plugin manager constructor */ explicit BasisImageConverter(PluginManager::AbstractManager& manager, const std::string& plugin); @@ -164,6 +193,9 @@ class MAGNUM_BASISIMAGECONVERTER_EXPORT BasisImageConverter: public AbstractImag private: MAGNUM_BASISIMAGECONVERTER_LOCAL ImageConverterFeatures doFeatures() const override; MAGNUM_BASISIMAGECONVERTER_LOCAL Containers::Array doConvertToData(Containers::ArrayView imageLevels) override; + MAGNUM_BASISIMAGECONVERTER_LOCAL bool doConvertToFile(const ImageView2D& image, Containers::StringView filename) override; + + Format _format; }; }} diff --git a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp index 3b727170e..a691305fc 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp +++ b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp @@ -52,6 +52,8 @@ struct BasisImageConverterTest: TestSuite::Tester { explicit BasisImageConverterTest(); void wrongFormat(); + void unknownOutputFormatData(); + void unknownOutputFormatFile(); void invalidSwizzle(); void tooManyLevels(); void levelWrongSize(); @@ -65,6 +67,8 @@ struct BasisImageConverterTest: TestSuite::Tester { void rgb(); void rgba(); + void convertToFile(); + void threads(); void ktx(); void customLevels(); @@ -76,6 +80,8 @@ struct BasisImageConverterTest: TestSuite::Tester { PluginManager::Manager _manager; }; +using namespace Containers::Literals; + enum TransferFunction: std::size_t { Linear, Srgb @@ -94,6 +100,20 @@ constexpr struct { {"Srgb", TransferFunction::Srgb} }; +constexpr Containers::StringView BasisPrefix = "sB"_s; +constexpr Containers::StringView KtxPrefix = "\xabKTX"_s; + +constexpr struct { + const char* name; + const char* pluginName; + const char* filename; + const Containers::StringView prefix; +} ConvertToFileData[] { + {"Basis", "BasisImageConverter", "image.basis", BasisPrefix}, + {"KTX2", "BasisImageConverter", "image.ktx2", KtxPrefix}, + {"KTX2 with explicit plugin name", "BasisKtxImageConverter", "image.foo", KtxPrefix} +}; + constexpr struct { const char* name; const char* threads; @@ -112,6 +132,8 @@ constexpr struct { BasisImageConverterTest::BasisImageConverterTest() { addTests({&BasisImageConverterTest::wrongFormat, + &BasisImageConverterTest::unknownOutputFormatData, + &BasisImageConverterTest::unknownOutputFormatFile, &BasisImageConverterTest::invalidSwizzle, &BasisImageConverterTest::tooManyLevels, &BasisImageConverterTest::levelWrongSize, @@ -126,6 +148,9 @@ BasisImageConverterTest::BasisImageConverterTest() { &BasisImageConverterTest::rgba}, Containers::arraySize(FormatTransferFunctionData)); + addInstancedTests({&BasisImageConverterTest::convertToFile}, + Containers::arraySize(ConvertToFileData)); + addInstancedTests({&BasisImageConverterTest::threads}, Containers::arraySize(ThreadsData)); @@ -150,11 +175,13 @@ BasisImageConverterTest::BasisImageConverterTest() { #ifdef BASISIMPORTER_PLUGIN_FILENAME CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(BASISIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); #endif + + /* Create the output directory if it doesn't exist yet */ + CORRADE_INTERNAL_ASSERT_OUTPUT(Utility::Directory::mkpath(BASISIMAGECONVERTER_TEST_OUTPUT_DIR)); } void BasisImageConverterTest::wrongFormat() { - Containers::Pointer converter = - _converterManager.instantiate("BasisImageConverter"); + Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); const char data[8]{}; std::ostringstream out; @@ -163,6 +190,41 @@ void BasisImageConverterTest::wrongFormat() { CORRADE_COMPARE(out.str(), "Trade::BasisImageConverter::convertToData(): unsupported format PixelFormat::RG32F\n"); } +void BasisImageConverterTest::unknownOutputFormatData() { + Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); + + /* The converter defaults to .basis output, conversion should succeed */ + + const char data[4]{}; + const auto converted = converter->convertToData(ImageView2D{PixelFormat::RGB8Unorm, {1, 1}, data}); + CORRADE_VERIFY(converted); + + if(_manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("BasisImporter plugin not found, cannot test"); + + Containers::Pointer importer = + _manager.instantiate("BasisImporterRGBA8"); + CORRADE_VERIFY(importer->openData(converted)); +} + +void BasisImageConverterTest::unknownOutputFormatFile() { + Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); + + /* The converter defaults to .basis output, conversion should succeed */ + + const char data[4]{}; + const ImageView2D image{PixelFormat::RGB8Unorm, {1, 1}, data}; + const std::string filename = Utility::Directory::join(BASISIMAGECONVERTER_TEST_OUTPUT_DIR, "file.foo"); + CORRADE_VERIFY(converter->convertToFile(image, filename)); + + if(_manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("BasisImporter plugin not found, cannot test"); + + Containers::Pointer importer = + _manager.instantiate("BasisImporterRGBA8"); + CORRADE_VERIFY(importer->openFile(filename)); +} + void BasisImageConverterTest::invalidSwizzle() { Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); @@ -488,6 +550,47 @@ void BasisImageConverterTest::rgba() { (DebugTools::CompareImageToFile{_manager, 97.25f, 8.547f})); } +void BasisImageConverterTest::convertToFile() { + if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); + + auto&& data = ConvertToFileData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer pngImporter = _manager.instantiate("PngImporter"); + CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"))); + const auto originalImage = pngImporter->image2D(0); + CORRADE_VERIFY(originalImage); + + Containers::Pointer converter = _converterManager.instantiate(data.pluginName); + std::string filename = Utility::Directory::join(BASISIMAGECONVERTER_TEST_OUTPUT_DIR, data.filename); + CORRADE_VERIFY(converter->convertToFile(*originalImage, filename)); + + /* Verify it's actually the right format */ + /** @todo use TestSuite::Compare::StringHasPrefix once it exists */ + CORRADE_VERIFY(Containers::StringView{Containers::ArrayView(Utility::Directory::read(filename))}.hasPrefix(data.prefix)); + + if(_manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("BasisImporter plugin not found, cannot test"); + + Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); + CORRADE_VERIFY(importer->openFile(filename)); + Containers::Optional converted = importer->image2D(0); + CORRADE_VERIFY(converted); + CORRADE_COMPARE_WITH(originalImage->pixels(), + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), + /* There are moderately significant compression artifacts */ + (DebugTools::CompareImageToFile{_manager, 97.25f, 7.914f})); + + /* The format should get reset again after so convertToData() isn't left + with some random format after */ + if(data.pluginName == "BasisImageConverter"_s) { + const auto compressedData = converter->convertToData(*originalImage); + CORRADE_VERIFY(compressedData); + CORRADE_VERIFY(Containers::StringView{Containers::arrayView(compressedData)}.hasPrefix(BasisPrefix)); + } +} + void BasisImageConverterTest::threads() { if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("PngImporter plugin not found, cannot test contents"); @@ -544,15 +647,18 @@ void BasisImageConverterTest::ktx() { const Image2D imageWithSkip = copyImageWithSkip( *originalImage, {7, 8, 0}, PixelFormat::RGBA8Unorm); - Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); + Containers::Pointer converter = _converterManager.instantiate("BasisKtxImageConverter"); converter->configuration().setValue("create_ktx2_file", true); converter->configuration().setValue("y_flip", data.yFlip); const auto compressedData = converter->convertToData(imageWithSkip); CORRADE_VERIFY(compressedData); + const Containers::StringView compressedView{Containers::arrayView(compressedData)}; + + CORRADE_VERIFY(compressedView.hasPrefix(KtxPrefix)); char KTXorientation[] = "KTXorientation\0r?"; KTXorientation[sizeof(KTXorientation) - 1] = data.yFlip ? 'u' : 'd'; - CORRADE_VERIFY(Containers::StringView{compressedData, compressedData.size()}.contains(KTXorientation)); + CORRADE_VERIFY(compressedView.contains(KTXorientation)); if(_manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) CORRADE_SKIP("BasisImporter plugin not found, cannot test"); diff --git a/src/MagnumPlugins/BasisImageConverter/Test/CMakeLists.txt b/src/MagnumPlugins/BasisImageConverter/Test/CMakeLists.txt index d221fff58..84fcd0d92 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImageConverter/Test/CMakeLists.txt @@ -33,6 +33,12 @@ find_package(Magnum COMPONENTS AnyImageImporter) # pthread, the app has to be instead find_package(Threads REQUIRED) +if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) + set(BASISIMAGECONVERTER_TEST_OUTPUT_DIR "write") +else() + set(BASISIMAGECONVERTER_TEST_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) +endif() + if(WITH_BASISIMPORTER) if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(BASISIMPORTER_TEST_DIR ".") diff --git a/src/MagnumPlugins/BasisImageConverter/Test/configure.h.cmake b/src/MagnumPlugins/BasisImageConverter/Test/configure.h.cmake index 9e6a76abb..0aff7ab2c 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/configure.h.cmake +++ b/src/MagnumPlugins/BasisImageConverter/Test/configure.h.cmake @@ -28,3 +28,4 @@ #cmakedefine BASISIMPORTER_PLUGIN_FILENAME "${BASISIMPORTER_PLUGIN_FILENAME}" #cmakedefine STBIMAGEIMPORTER_PLUGIN_FILENAME "${STBIMAGEIMPORTER_PLUGIN_FILENAME}" #cmakedefine BASISIMPORTER_TEST_DIR "${BASISIMPORTER_TEST_DIR}" +#define BASISIMAGECONVERTER_TEST_OUTPUT_DIR "$BASISIMAGECONVERTER_TEST_OUTPUT_DIR}" From 68bcaa9e93d7b00f342a2d2d2f24e66fe4907182 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:44:40 +0200 Subject: [PATCH 54/76] BasisImageConverter: clearly mention that config options follow the C++ API and link to the basisu tool help text for *some* information on the options. They're not a perfect match, but close --- .../BasisImageConverter/BasisImageConverter.conf | 5 ++--- .../BasisImageConverter/BasisImageConverter.h | 7 +++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf index b376dfd14..beb4c140b 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.conf @@ -2,9 +2,8 @@ provides=BasisKtxImageConverter # [configuration_] [configuration] -# All following options correspond to options of the basisu tool, grouped in -# the same way. Names follow the Basis C++ API and may differ from what the -# tool exposes. +# All following options correspond to parameters of the `basis_compressor` C++ +# API and may differ from what the basisu tool exposes. # Options quality_level=128 diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h index 7d74b32ac..84430b432 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h @@ -157,8 +157,11 @@ target_link_libraries(your-application PRIVATE Threads::Threads) @section Trade-BasisImageConverter-configuration Plugin-specific configuration Basis compression can be configured to produce better quality or reduce -encoding time. Configuration options are equivalent to options of the `basisu` -tool. The full form of the configuration is shown below: +encoding time. Configuration options are equivalent to parameters of the C++ +encoder API in `basis_compressor`. The `basisu` tool options mostly match the +encoder API parameters and its [help text](https://github.com/BinomialLLC/basis_universal/blob/v1_15_update2/basisu_tool.cpp#L76) +provides useful descriptions of most of the parameters, their ranges and the +impact on quality/speed. The full form of the configuration is shown below: @snippet MagnumPlugins/BasisImageConverter/BasisImageConverter.conf configuration_ From 0f295d50aa92e032f93c3c3c01a1facde0a8c638 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:45:10 +0200 Subject: [PATCH 55/76] BasisImageConverter: mention supported basis_universal version in the docs --- src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h index 84430b432..44f6c8414 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h @@ -83,7 +83,9 @@ This plugin provides `BasisKtxImageConverter`. This plugin depends on the @ref Trade and [Basis Universal](https://github.com/binomialLLC/basis_universal) libraries and is built if `WITH_BASISIMAGECONVERTER` is enabled when building Magnum Plugins. To use as a dynamic plugin, load @cpp "BasisImageConverter" @ce -via @ref Corrade::PluginManager::Manager. +via @ref Corrade::PluginManager::Manager. Current version of the plugin is +tested against the [`v1_15_update2` tag](https://github.com/BinomialLLC/basis_universal/tree/v1_15_update2), +but could possibly compile against newer versions as well. Additionally, if you're using Magnum as a CMake subproject, bundle the [magnum-plugins](https://github.com/mosra/magnum-plugins) and From a2e9d496e1e09ed215fe37c901acb341f57c81a4 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:46:55 +0200 Subject: [PATCH 56/76] Basis{Importer,ImageConverter}: enable CMake C language support in the plugins We need it in the find module, but can't enable it there --- modules/FindBasisUniversal.cmake | 15 +++++++++------ .../BasisImageConverter/CMakeLists.txt | 3 +++ src/MagnumPlugins/BasisImporter/CMakeLists.txt | 3 +++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/modules/FindBasisUniversal.cmake b/modules/FindBasisUniversal.cmake index 501fa97b5..3c3e24da0 100644 --- a/modules/FindBasisUniversal.cmake +++ b/modules/FindBasisUniversal.cmake @@ -52,6 +52,15 @@ # DEALINGS IN THE SOFTWARE. # +# Several places in this find module assume that the C language is enabled: +# - test_big_endian() assumes C is enabled, configuration fails without. +# CMake says to call enable_language() in the highest directory using the +# language, so we can't do that here. +# - both the transcoder and encoder link to .c files that would just not be +# compiled without the language enabled, or the source file language being +# changed to CXX +# Currently both BasisImporter and BasisImageConverter call enable_language(C). + list(FIND BasisUniversal_FIND_COMPONENTS "Encoder" _index) if(${_index} GREATER -1) list(APPEND BasisUniversal_FIND_COMPONENTS "Transcoder") @@ -62,12 +71,6 @@ include(TestBigEndian) test_big_endian(BIG_ENDIAN) macro(_basis_setup_source_file source) - # Compile any .c files as C++ since we can't guarantee that C is enabled - # in the calling scope, either inside project() or with enable_language(). - # Otherwise, they won't get compiled at all, leading to undefined symbols. - set_property(SOURCE ${source} PROPERTY LANGUAGE - CXX) - # Tell Basis if we're on a big endian system. It currently doesn't figure # this out by itself. if(BIG_ENDIAN) diff --git a/src/MagnumPlugins/BasisImageConverter/CMakeLists.txt b/src/MagnumPlugins/BasisImageConverter/CMakeLists.txt index ee76ed04c..d6042e281 100644 --- a/src/MagnumPlugins/BasisImageConverter/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImageConverter/CMakeLists.txt @@ -24,6 +24,9 @@ # DEALINGS IN THE SOFTWARE. # +# Required by FindBasisUniversal.cmake, check the comment there for more info +enable_language(C) + # To help Homebrew and Vcpkg packages, Basis Universal sources can be cloned to # src/external and we will use those without any extra effort from the outside. # Note that, because I hate underscores, the name has to contain a dash, *not* diff --git a/src/MagnumPlugins/BasisImporter/CMakeLists.txt b/src/MagnumPlugins/BasisImporter/CMakeLists.txt index d82a007ea..169626cf7 100644 --- a/src/MagnumPlugins/BasisImporter/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImporter/CMakeLists.txt @@ -24,6 +24,9 @@ # DEALINGS IN THE SOFTWARE. # +# Required by FindBasisUniversal.cmake, check the comment there for more info +enable_language(C) + # To help Homebrew and Vcpkg packages, Basis Universal sources can be cloned to # src/external and we will use those without any extra effort from the outside. # Note that, because I hate underscores, the name has to contain a dash, *not* From 6594d79a8acb46d49813035ba7bc3c899505a504 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:47:28 +0200 Subject: [PATCH 57/76] Basis{Importer,ImageConverter}: add copyright notice --- src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp | 1 + src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h | 1 + src/MagnumPlugins/BasisImporter/BasisImporter.cpp | 1 + src/MagnumPlugins/BasisImporter/BasisImporter.h | 1 + 4 files changed, 4 insertions(+) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index 03baf00ff..69020dd71 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -4,6 +4,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Vladimír Vondruš Copyright © 2019 Jonathan Hale + Copyright © 2021 Pablo Escobar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h index 44f6c8414..fd038cd50 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h @@ -6,6 +6,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Vladimír Vondruš Copyright © 2019 Jonathan Hale + Copyright © 2021 Pablo Escobar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index 114fcad80..eb0426c7c 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -4,6 +4,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Vladimír Vondruš Copyright © 2019, 2021 Jonathan Hale + Copyright © 2021 Pablo Escobar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index 61adf93ac..9b96765ec 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -6,6 +6,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Vladimír Vondruš Copyright © 2019, 2021 Jonathan Hale + Copyright © 2021 Pablo Escobar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), From b06f6e97e4e666db61d39f92e7c7fde2ba6a775d Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:48:41 +0200 Subject: [PATCH 58/76] BasisImageConverter: comments++ --- modules/FindBasisUniversal.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/FindBasisUniversal.cmake b/modules/FindBasisUniversal.cmake index 3c3e24da0..69869b75f 100644 --- a/modules/FindBasisUniversal.cmake +++ b/modules/FindBasisUniversal.cmake @@ -145,6 +145,11 @@ foreach(_component ${BasisUniversal_FIND_COMPONENTS}) "Set BASIS_UNIVERSAL_DIR to the root of a directory containing basis_universal source.") endif() + # @todo Disable file loading at compile time and get rid of the + # BMP/JPG/PNG libraries, we don't use those at all. Hopefully + # this becomes a preprocessor define upstream at some point. + # Alternatively, look into creating stubs for the library + # functions used by basis_universal. set(BasisUniversalEncoder_SOURCES ${BasisUniversalEncoder_DIR}/apg_bmp.c ${BasisUniversalEncoder_DIR}/basisu_astc_decomp.cpp From 3e03f27899d07db183275d5ddcc90ab166297f62 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:49:04 +0200 Subject: [PATCH 59/76] BasisImageConverter: cleanup tests --- .../BasisImageConverter/Test/BasisImageConverterTest.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp index a691305fc..caeee290d 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp +++ b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp @@ -233,10 +233,10 @@ void BasisImageConverterTest::invalidSwizzle() { std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(converter->configuration().setValue("swizzle", "gbgbg")); + converter->configuration().setValue("swizzle", "gbgbg"); CORRADE_VERIFY(!converter->convertToData(ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, data})); - CORRADE_VERIFY(converter->configuration().setValue("swizzle", "xaaa")); + converter->configuration().setValue("swizzle", "xaaa"); CORRADE_VERIFY(!converter->convertToData(ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, data})); CORRADE_COMPARE(out.str(), @@ -304,7 +304,7 @@ void BasisImageConverterTest::configPerceptual() { const auto compressedDataAutomatic = converter->convertToData(originalImage); CORRADE_VERIFY(compressedDataAutomatic); - CORRADE_VERIFY(converter->configuration().setValue("perceptual", true)); + converter->configuration().setValue("perceptual", true); const auto compressedDataOverridden = converter->convertToData(originalImage); CORRADE_VERIFY(compressedDataOverridden); @@ -338,7 +338,7 @@ void BasisImageConverterTest::configMipGen() { _converterManager.instantiate("BasisImageConverter"); /* Empty by default */ CORRADE_COMPARE(converter->configuration().value("mip_gen"), false); - CORRADE_VERIFY(converter->configuration().setValue("mip_gen", "")); + converter->configuration().setValue("mip_gen", ""); const auto compressedDataGenerated = converter->convertToData({originalLevel0}); CORRADE_VERIFY(compressedDataGenerated); From fef35be24287898d2151727d1efd636262e74264 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Sun, 24 Oct 2021 21:49:35 +0200 Subject: [PATCH 60/76] Basis{Importer,ImageConverter}: fix AppVeyor basis_universal extraction --- package/ci/appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/ci/appveyor.yml b/package/ci/appveyor.yml index db343368f..b0c84c57e 100644 --- a/package/ci/appveyor.yml +++ b/package/ci/appveyor.yml @@ -84,7 +84,7 @@ install: # Basis Universal - set BASIS_VERSION=v1_15_update2 - IF NOT EXIST %APPVEYOR_BUILD_FOLDER%\basis_universal-%BASIS_VERSION%.zip appveyor DownloadFile https://github.com/BinomialLLC/basis_universal/archive/%BASIS_VERSION%.zip -- 7z x %BASIS_VERSION%.zip && ren basis_universal-%BASIS_VERSION% basis_universal +- 7z x basis_universal-%BASIS_VERSION%.zip && ren basis_universal-%BASIS_VERSION% basis_universal # SPIRV-Tools, for MSVC 2019, 2017 and clang-cl only # This line REQUIRES the COMPILER variable to be set on rt builds, otherwise it From d19123327c2006998419251558b1b015852cbd03 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 25 Oct 2021 00:02:10 +0200 Subject: [PATCH 61/76] Basis{Importer,ImageConverter}: fix find module for CI --- modules/FindBasisUniversal.cmake | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/FindBasisUniversal.cmake b/modules/FindBasisUniversal.cmake index 69869b75f..bca12ac93 100644 --- a/modules/FindBasisUniversal.cmake +++ b/modules/FindBasisUniversal.cmake @@ -67,10 +67,21 @@ if(${_index} GREATER -1) list(REMOVE_DUPLICATES BasisUniversal_FIND_COMPONENTS) endif() -include(TestBigEndian) -test_big_endian(BIG_ENDIAN) +# This fails under Emscripten. WebAssembly is always little-endian. +if(NOT CORRADE_TARGET_EMSCRIPTEN) + include(TestBigEndian) + test_big_endian(BIG_ENDIAN) +endif() macro(_basis_setup_source_file source) + # Compile any .c files as C++. Originally I thought enable_language(C) + # being required (see above) means we don't need to do this, but things get + # complicated with CXX_STANDARD. Some compilers don't like -std=c++11 on C + # files. Even if we manage to remove that flag, some of the files require + # C99 features and it's just not worth the trouble. + set_property(SOURCE ${source} PROPERTY LANGUAGE + CXX) + # Tell Basis if we're on a big endian system. It currently doesn't figure # this out by itself. if(BIG_ENDIAN) From ebfb048beac7928253bb90e176ca01df75d118da Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 25 Oct 2021 16:14:18 +0200 Subject: [PATCH 62/76] BasisImporter: remove all remaining traces of Bc7RGB --- src/MagnumPlugins/BasisImporter/BasisImporter.conf | 1 - src/MagnumPlugins/BasisImporter/BasisImporter.cpp | 4 ++-- src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.conf b/src/MagnumPlugins/BasisImporter/BasisImporter.conf index a7865a7e0..1e11ee403 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.conf +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.conf @@ -6,7 +6,6 @@ provides=BasisImporterBc1RGB provides=BasisImporterBc3RGBA provides=BasisImporterBc4R provides=BasisImporterBc5RG -provides=BasisImporterBc7RGB provides=BasisImporterBc7RGBA provides=BasisImporterPvrtcRGB4bpp provides=BasisImporterPvrtcRGBA4bpp diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp index eb0426c7c..3a6a33fbe 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.cpp +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.cpp @@ -89,7 +89,7 @@ CompressedPixelFormat compressedPixelFormat(BasisImporter::TargetFormat type, bo constexpr const char* FormatNames[]{ "Etc1RGB", "Etc2RGBA", - "Bc1RGB", "Bc3RGBA", "Bc4R", "Bc5RG", nullptr, "Bc7RGBA", + "Bc1RGB", "Bc3RGBA", "Bc4R", "Bc5RG", "Bc7RGBA", nullptr, /* BC7_ALT */ "PvrtcRGB4bpp", "PvrtcRGBA4bpp", "Astc4x4RGBA", nullptr, nullptr, /* ATC formats */ @@ -469,7 +469,7 @@ Containers::Optional> BasisImporter::doImage(const Unsigne targetFormat = configuration().value("format"); if(UnsignedInt(targetFormat) == ~UnsignedInt{}) { Error{} << prefix << "invalid transcoding target format" << targetFormatStr << Debug::nospace - << ", expected to be one of EacR, EacRG, Etc1RGB, Etc2RGBA, Bc1RGB, Bc3RGBA, Bc4R, Bc5RG, Bc7RGB, Bc7RGBA, Pvrtc1RGB4bpp, Pvrtc1RGBA4bpp, Astc4x4RGBA, RGBA8"; + << ", expected to be one of EacR, EacRG, Etc1RGB, Etc2RGBA, Bc1RGB, Bc3RGBA, Bc4R, Bc5RG, Bc7RGBA, Pvrtc1RGB4bpp, Pvrtc1RGBA4bpp, Astc4x4RGBA, RGBA8"; return Containers::NullOpt; } } diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index d3d92c6fb..bb6de6c15 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -376,7 +376,7 @@ void BasisImporterTest::invalidConfiguredFormat() { importer->configuration().setValue("format", "Banana"); CORRADE_VERIFY(!importer->image2D(0)); - CORRADE_COMPARE(out.str(), "Trade::BasisImporter::image2D(): invalid transcoding target format Banana, expected to be one of EacR, EacRG, Etc1RGB, Etc2RGBA, Bc1RGB, Bc3RGBA, Bc4R, Bc5RG, Bc7RGB, Bc7RGBA, Pvrtc1RGB4bpp, Pvrtc1RGBA4bpp, Astc4x4RGBA, RGBA8\n"); + CORRADE_COMPARE(out.str(), "Trade::BasisImporter::image2D(): invalid transcoding target format Banana, expected to be one of EacR, EacRG, Etc1RGB, Etc2RGBA, Bc1RGB, Bc3RGBA, Bc4R, Bc5RG, Bc7RGBA, Pvrtc1RGB4bpp, Pvrtc1RGBA4bpp, Astc4x4RGBA, RGBA8\n"); } void BasisImporterTest::unsupportedFormat() { From e97ed9ce5b5f15cd6e8e613a5e2483609f668c70 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 25 Oct 2021 16:27:32 +0200 Subject: [PATCH 63/76] BasisImporter: fix array view cast on GCC 4.8, hopefully --- .../BasisImporter/Test/BasisImporterTest.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index bb6de6c15..3941141a4 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -105,10 +105,11 @@ constexpr struct { const Containers::ArrayView data; const char* message; } InvalidHeaderData[] { - {"Invalid", "NotAValidFile", "invalid basis header"}, - {"Invalid basis header", "sB\xff\xff", "invalid basis header"}, - {"Invalid KTX2 identifier", "\xabKTX 30\xbb\r\n\x1a\n", "invalid basis header"}, - {"Invalid KTX2 header", "\xabKTX 20\xbb\r\n\x1a\n\xff\xff\xff\xff", "invalid KTX2 header, or not Basis compressed"} + /* GCC 4.8 needs the explicit cast :( */ + {"Invalid", Containers::arrayView("NotAValidFile"), "invalid basis header"}, + {"Invalid basis header", Containers::arrayView("sB\xff\xff"), "invalid basis header"}, + {"Invalid KTX2 identifier", Containers::arrayView("\xabKTX 30\xbb\r\n\x1a\n"), "invalid basis header"}, + {"Invalid KTX2 header", Containers::arrayView("\xabKTX 20\xbb\r\n\x1a\n\xff\xff\xff\xff"), "invalid KTX2 header, or not Basis compressed"} }; constexpr struct { From 08f9abdf7b705fc4c7778c57caace28f26959bcf Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 25 Oct 2021 16:28:59 +0200 Subject: [PATCH 64/76] Basis{Importer,ImageConverter}: restrict and clarify enable_language(C) --- src/MagnumPlugins/BasisImageConverter/CMakeLists.txt | 6 ++++-- src/MagnumPlugins/BasisImporter/CMakeLists.txt | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/CMakeLists.txt b/src/MagnumPlugins/BasisImageConverter/CMakeLists.txt index d6042e281..81a2f563d 100644 --- a/src/MagnumPlugins/BasisImageConverter/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImageConverter/CMakeLists.txt @@ -24,8 +24,10 @@ # DEALINGS IN THE SOFTWARE. # -# Required by FindBasisUniversal.cmake, check the comment there for more info -enable_language(C) +# C is needed on < 3.9 by TestBigEndian, which is used by FindBasisUniversal +if(CMAKE_VERSION VERSION_LESS 3.9) + enable_language(C) +endif() # To help Homebrew and Vcpkg packages, Basis Universal sources can be cloned to # src/external and we will use those without any extra effort from the outside. diff --git a/src/MagnumPlugins/BasisImporter/CMakeLists.txt b/src/MagnumPlugins/BasisImporter/CMakeLists.txt index 169626cf7..156f17c9f 100644 --- a/src/MagnumPlugins/BasisImporter/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImporter/CMakeLists.txt @@ -24,8 +24,10 @@ # DEALINGS IN THE SOFTWARE. # -# Required by FindBasisUniversal.cmake, check the comment there for more info -enable_language(C) +# C is needed on < 3.9 by TestBigEndian, which is used by FindBasisUniversal +if(CMAKE_VERSION VERSION_LESS 3.9) + enable_language(C) +endif() # To help Homebrew and Vcpkg packages, Basis Universal sources can be cloned to # src/external and we will use those without any extra effort from the outside. From be76be902c39308f682a738205e0331424fb0f10 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 25 Oct 2021 16:29:20 +0200 Subject: [PATCH 65/76] Basis{Importer,ImageConverter}: fix AppVeyor basis_universal extraction, second attempt --- package/ci/appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/ci/appveyor.yml b/package/ci/appveyor.yml index b0c84c57e..7e8c25102 100644 --- a/package/ci/appveyor.yml +++ b/package/ci/appveyor.yml @@ -83,8 +83,8 @@ install: # Basis Universal - set BASIS_VERSION=v1_15_update2 -- IF NOT EXIST %APPVEYOR_BUILD_FOLDER%\basis_universal-%BASIS_VERSION%.zip appveyor DownloadFile https://github.com/BinomialLLC/basis_universal/archive/%BASIS_VERSION%.zip -- 7z x basis_universal-%BASIS_VERSION%.zip && ren basis_universal-%BASIS_VERSION% basis_universal +- IF NOT EXIST %APPVEYOR_BUILD_FOLDER%\%BASIS_VERSION%.zip appveyor DownloadFile https://github.com/BinomialLLC/basis_universal/archive/%BASIS_VERSION%.zip +- 7z x %BASIS_VERSION%.zip && ren basis_universal-%BASIS_VERSION% basis_universal # SPIRV-Tools, for MSVC 2019, 2017 and clang-cl only # This line REQUIRES the COMPILER variable to be set on rt builds, otherwise it From 319a050b97bc21e7a10f735d93e956c604aba15b Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 25 Oct 2021 21:35:16 +0200 Subject: [PATCH 66/76] BasisImporter: fix test image treshold --- src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp index bbdddecf9..70cbff499 100644 --- a/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp +++ b/src/MagnumPlugins/BasisImporter/Test/BasisImporterTest.cpp @@ -1202,7 +1202,7 @@ void BasisImporterTest::openMemory() { CORRADE_COMPARE_WITH(image->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), /* There are moderately significant compression artifacts */ - (DebugTools::CompareImageToFile{_manager, 78.3f, 8.31f})); + (DebugTools::CompareImageToFile{_manager, 94.0f, 8.039f})); } void BasisImporterTest::openSameTwice() { From 4ff073700d8b1036a73663d64cf36663dd8a6b9f Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 25 Oct 2021 22:17:35 +0200 Subject: [PATCH 67/76] Basis{Importer,ImageConverter}: don't test for endianness on CMake < 3.9 This is way too broken --- modules/FindBasisUniversal.cmake | 21 ++++++++++++------- .../BasisImageConverter/CMakeLists.txt | 5 ----- .../BasisImporter/CMakeLists.txt | 5 ----- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/modules/FindBasisUniversal.cmake b/modules/FindBasisUniversal.cmake index bca12ac93..338169ebf 100644 --- a/modules/FindBasisUniversal.cmake +++ b/modules/FindBasisUniversal.cmake @@ -67,18 +67,25 @@ if(${_index} GREATER -1) list(REMOVE_DUPLICATES BasisUniversal_FIND_COMPONENTS) endif() -# This fails under Emscripten. WebAssembly is always little-endian. -if(NOT CORRADE_TARGET_EMSCRIPTEN) +# Figure out endianness for Basis Universal. test_big_endian() fails on +# Emscripten, but WebAssembly is always little-endian. On CMake 3.8 and below, +# test_big_endian() requires C support which breaks compilation in funny ways +# (see comment below) so we skip that. +if(NOT CORRADE_TARGET_EMSCRIPTEN AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.9) include(TestBigEndian) test_big_endian(BIG_ENDIAN) endif() macro(_basis_setup_source_file source) - # Compile any .c files as C++. Originally I thought enable_language(C) - # being required (see above) means we don't need to do this, but things get - # complicated with CXX_STANDARD. Some compilers don't like -std=c++11 on C - # files. Even if we manage to remove that flag, some of the files require - # C99 features and it's just not worth the trouble. + # Compile any .c files as C++. Otherwise the files are just ignored because + # the C language is not enabled by project() or enable_language(). Calling + # enable_language in a find module is not a good idea, and even if we do + # this higher up in one of the Basis* plugins, some compilers will require + # the C99 standard being enabled and/or complain about -std=c++11 (done by + # CORRADE_CXX_STANDARD) being set on a C compiler. Doing both (enabling C + # and setting LANGUAGE CXX) still results in static libraries compiling + # with C and producing above-mentioned errors, possibly to do with + # LINKER_LANGUAGE. What a horrible mess. set_property(SOURCE ${source} PROPERTY LANGUAGE CXX) diff --git a/src/MagnumPlugins/BasisImageConverter/CMakeLists.txt b/src/MagnumPlugins/BasisImageConverter/CMakeLists.txt index 81a2f563d..ee76ed04c 100644 --- a/src/MagnumPlugins/BasisImageConverter/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImageConverter/CMakeLists.txt @@ -24,11 +24,6 @@ # DEALINGS IN THE SOFTWARE. # -# C is needed on < 3.9 by TestBigEndian, which is used by FindBasisUniversal -if(CMAKE_VERSION VERSION_LESS 3.9) - enable_language(C) -endif() - # To help Homebrew and Vcpkg packages, Basis Universal sources can be cloned to # src/external and we will use those without any extra effort from the outside. # Note that, because I hate underscores, the name has to contain a dash, *not* diff --git a/src/MagnumPlugins/BasisImporter/CMakeLists.txt b/src/MagnumPlugins/BasisImporter/CMakeLists.txt index 156f17c9f..d82a007ea 100644 --- a/src/MagnumPlugins/BasisImporter/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImporter/CMakeLists.txt @@ -24,11 +24,6 @@ # DEALINGS IN THE SOFTWARE. # -# C is needed on < 3.9 by TestBigEndian, which is used by FindBasisUniversal -if(CMAKE_VERSION VERSION_LESS 3.9) - enable_language(C) -endif() - # To help Homebrew and Vcpkg packages, Basis Universal sources can be cloned to # src/external and we will use those without any extra effort from the outside. # Note that, because I hate underscores, the name has to contain a dash, *not* From a5aca85f0c421478039bec7aac38a05cf63900ae Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 25 Oct 2021 22:19:35 +0200 Subject: [PATCH 68/76] Basis{Importer,ImageConverter}: fix basis_universal download, 3rd attempt WSL is amazingly helpful for this --- package/archlinux/magnum-plugins-git/PKGBUILD | 6 +++--- package/ci/appveyor.yml | 6 +++--- package/ci/travis.yml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package/archlinux/magnum-plugins-git/PKGBUILD b/package/archlinux/magnum-plugins-git/PKGBUILD index 533c820ff..ef2518256 100644 --- a/package/archlinux/magnum-plugins-git/PKGBUILD +++ b/package/archlinux/magnum-plugins-git/PKGBUILD @@ -1,7 +1,7 @@ # Author: mosra pkgname=magnum-plugins-git pkgver=2020.06.r119.g15b8cac9 -_basis_pkgver=v1_15_update2 +_basis_pkgver=1_15_update2 pkgrel=1 pkgdesc="Plugins for the Magnum C++11/C++14 graphics engine (Git version)" arch=('i686' 'x86_64') @@ -12,9 +12,9 @@ makedepends=('cmake' 'git' 'ninja') provides=('magnum-plugins') conflicts=('magnum-plugins') source=("git+git://github.com/mosra/magnum-plugins.git" - "https://github.com/BinomialLLC/basis_universal/archive/${_basis_pkgver}.tar.gz") + "https://github.com/BinomialLLC/basis_universal/archive/v${_basis_pkgver}.tar.gz") sha1sums=('SKIP' - 'b8d3995292c2c0bbedea943250087b0a9a92ca96') + '3caff917d63ed0255fc56bcdf80d305bb47ac9315a4a45409a1264a0e5e0e572') pkgver() { cd "$srcdir/${pkgname%-git}" diff --git a/package/ci/appveyor.yml b/package/ci/appveyor.yml index 7e8c25102..822bf1f02 100644 --- a/package/ci/appveyor.yml +++ b/package/ci/appveyor.yml @@ -82,9 +82,9 @@ install: - IF "%TARGET%" == "desktop" IF "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2017" IF "%COMPILER:~0,4%" == "msvc" appveyor DownloadFile https://ci.magnum.graphics/freetype-2.10.4-windows-2016.zip && 7z x freetype-2.10.4-windows-2016.zip -o%APPVEYOR_BUILD_FOLDER%\deps # Basis Universal -- set BASIS_VERSION=v1_15_update2 -- IF NOT EXIST %APPVEYOR_BUILD_FOLDER%\%BASIS_VERSION%.zip appveyor DownloadFile https://github.com/BinomialLLC/basis_universal/archive/%BASIS_VERSION%.zip -- 7z x %BASIS_VERSION%.zip && ren basis_universal-%BASIS_VERSION% basis_universal +- set BASIS_VERSION=1_15_update2 +- IF NOT EXIST %APPVEYOR_BUILD_FOLDER%\v%BASIS_VERSION%.zip appveyor DownloadFile https://github.com/BinomialLLC/basis_universal/archive/v%BASIS_VERSION%.zip +- 7z x v%BASIS_VERSION%.zip && ren basis_universal-%BASIS_VERSION% basis_universal # SPIRV-Tools, for MSVC 2019, 2017 and clang-cl only # This line REQUIRES the COMPILER variable to be set on rt builds, otherwise it diff --git a/package/ci/travis.yml b/package/ci/travis.yml index ade1e8d71..787e5ebfa 100644 --- a/package/ci/travis.yml +++ b/package/ci/travis.yml @@ -196,7 +196,7 @@ install: - if [ "$TRAVIS_OS_NAME" == "linux" ] && [ "$TARGET" == "desktop-sanitizers" ]; then cd $HOME ; wget https://ci.magnum.graphics/spirv-tools-2020.4-ubuntu-16.04-gcc5.zip && cd $HOME/deps && unzip $HOME/spirv-tools-2020.4-ubuntu-16.04-gcc5.zip && cd $TRAVIS_BUILD_DIR ; fi # Basis Universal -- export BASIS_VERSION=v1_15_update2 && wget -nc https://github.com/BinomialLLC/basis_universal/archive/$BASIS_VERSION.zip && unzip -q $BASIS_VERSION; mv basis_universal-$BASIS_VERSION $HOME/basis_universal +- export BASIS_VERSION=1_15_update2 && wget -nc https://github.com/BinomialLLC/basis_universal/archive/v$BASIS_VERSION.zip && unzip -q v$BASIS_VERSION; mv basis_universal-$BASIS_VERSION $HOME/basis_universal script: - if [ "$TRAVIS_OS_NAME" == "linux" ] && ( [ "$TARGET" == "desktop" ] || [ "$TARGET" == "desktop-sanitizers" ] ); then ./package/ci/unix-desktop.sh; fi From 4597fb1453150b1868ff2db6254e2ee8d5b2d936 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 25 Oct 2021 22:25:35 +0200 Subject: [PATCH 69/76] Basis{Importer,ImageConverter}: don't use VERSION_GREATER_EQUAL it requires CMake 3.7 --- modules/FindBasisUniversal.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/FindBasisUniversal.cmake b/modules/FindBasisUniversal.cmake index 338169ebf..ead53af14 100644 --- a/modules/FindBasisUniversal.cmake +++ b/modules/FindBasisUniversal.cmake @@ -71,7 +71,7 @@ endif() # Emscripten, but WebAssembly is always little-endian. On CMake 3.8 and below, # test_big_endian() requires C support which breaks compilation in funny ways # (see comment below) so we skip that. -if(NOT CORRADE_TARGET_EMSCRIPTEN AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.9) +if(NOT CORRADE_TARGET_EMSCRIPTEN AND NOT CMAKE_VERSION VERSION_LESS 3.9) include(TestBigEndian) test_big_endian(BIG_ENDIAN) endif() From 1ac0592465649176fc542b1ee7dbb1f699a63304 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 25 Oct 2021 22:35:22 +0200 Subject: [PATCH 70/76] Basis{Importer,ImageConverter}: fix basis_universal SHA1 in archlinux package recipe --- package/archlinux/magnum-plugins-git/PKGBUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/archlinux/magnum-plugins-git/PKGBUILD b/package/archlinux/magnum-plugins-git/PKGBUILD index ef2518256..3370abba8 100644 --- a/package/archlinux/magnum-plugins-git/PKGBUILD +++ b/package/archlinux/magnum-plugins-git/PKGBUILD @@ -14,7 +14,7 @@ conflicts=('magnum-plugins') source=("git+git://github.com/mosra/magnum-plugins.git" "https://github.com/BinomialLLC/basis_universal/archive/v${_basis_pkgver}.tar.gz") sha1sums=('SKIP' - '3caff917d63ed0255fc56bcdf80d305bb47ac9315a4a45409a1264a0e5e0e572') + 'b9615d48ebfc62a53f333ebf8a582558a058b0e9') pkgver() { cd "$srcdir/${pkgname%-git}" From 4f791ed5efae0f1338d289c15f1487ecab7e6b1d Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 25 Oct 2021 22:38:36 +0200 Subject: [PATCH 71/76] BasisImporter: oops --- src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt b/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt index 82b4df3af..63a39a570 100644 --- a/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/BasisImporter/Test/CMakeLists.txt @@ -95,14 +95,14 @@ corrade_add_test(BasisImporterTest BasisImporterTest.cpp rgb-63x27.png rgba-63x27.png rgba-63x27-slice1.png - rgba-63x27-slice1.png + rgba-63x27-slice2.png rgba-31x13.png rgba-13x31.png rgba-15x6.png rgba-6x15.png rgba-27x27.png rgba-27x27-slice1.png - rgba-27x27-slice1.png + rgba-27x27-slice2.png ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/KtxImporter/Test/2d-rgba.ktx2) target_include_directories(BasisImporterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_BASISIMPORTER_BUILD_STATIC) From 439a883d345eb06cc89f055846bbe6e9780aee25 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Wed, 27 Oct 2021 12:39:43 +0200 Subject: [PATCH 72/76] BasisImageConverter: accept multiple levels in doOpenFile and test the converted image, not the original(??) image --- .../BasisImageConverter.cpp | 4 +-- .../BasisImageConverter/BasisImageConverter.h | 2 +- .../Test/BasisImageConverterTest.cpp | 31 ++++++++++++------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index 69020dd71..74f954e1e 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -363,7 +363,7 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi return fileData; } -bool BasisImageConverter::doConvertToFile(const ImageView2D& image, const Containers::StringView filename) { +bool BasisImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { /** @todo once Directory is std::string-free, use splitExtension() */ const Containers::String normalized = Utility::String::lowercase(filename); @@ -378,7 +378,7 @@ bool BasisImageConverter::doConvertToFile(const ImageView2D& image, const Contai } /* Delegate to the base implementation which calls doConvertToData() */ - const bool out = AbstractImageConverter::doConvertToFile(image, filename); + const bool out = AbstractImageConverter::doConvertToFile(imageLevels, filename); /* Restore the previous format and return the result */ _format = previousFormat; diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h index fd038cd50..55b534843 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h @@ -199,7 +199,7 @@ class MAGNUM_BASISIMAGECONVERTER_EXPORT BasisImageConverter: public AbstractImag private: MAGNUM_BASISIMAGECONVERTER_LOCAL ImageConverterFeatures doFeatures() const override; MAGNUM_BASISIMAGECONVERTER_LOCAL Containers::Array doConvertToData(Containers::ArrayView imageLevels) override; - MAGNUM_BASISIMAGECONVERTER_LOCAL bool doConvertToFile(const ImageView2D& image, Containers::StringView filename) override; + MAGNUM_BASISIMAGECONVERTER_LOCAL bool doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) override; Format _format; }; diff --git a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp index caeee290d..ac941d0fe 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp +++ b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp @@ -500,10 +500,6 @@ void BasisImageConverterTest::rgb() { CORRADE_VERIFY(!image->isCompressed()); CORRADE_COMPARE(image->format(), TransferFunctionFormats[data.transferFunction][3]); - /* CompareImage doesn't support Srgb formats, so we need to create a view - on the original image, but with a Unorm format */ - const ImageView2D imageViewUnorm{imageWithSkip.storage(), - TransferFunctionFormats[TransferFunction::Linear][2], imageWithSkip.size(), imageWithSkip.data()}; CORRADE_COMPARE_WITH(Containers::arrayCast(image->pixels()), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgb-63x27.png"), /* There are moderately significant compression artifacts */ @@ -559,12 +555,17 @@ void BasisImageConverterTest::convertToFile() { Containers::Pointer pngImporter = _manager.instantiate("PngImporter"); CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"))); - const auto originalImage = pngImporter->image2D(0); - CORRADE_VERIFY(originalImage); + const auto originalLevel0 = pngImporter->image2D(0); + CORRADE_VERIFY(pngImporter->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-31x13.png"))); + const auto originalLevel1 = pngImporter->image2D(0); + CORRADE_VERIFY(originalLevel0); + CORRADE_VERIFY(originalLevel1); + + const ImageView2D originalLevels[2]{*originalLevel0, *originalLevel1}; Containers::Pointer converter = _converterManager.instantiate(data.pluginName); std::string filename = Utility::Directory::join(BASISIMAGECONVERTER_TEST_OUTPUT_DIR, data.filename); - CORRADE_VERIFY(converter->convertToFile(*originalImage, filename)); + CORRADE_VERIFY(converter->convertToFile(originalLevels, filename)); /* Verify it's actually the right format */ /** @todo use TestSuite::Compare::StringHasPrefix once it exists */ @@ -575,17 +576,25 @@ void BasisImageConverterTest::convertToFile() { Containers::Pointer importer = _manager.instantiate("BasisImporterRGBA8"); CORRADE_VERIFY(importer->openFile(filename)); - Containers::Optional converted = importer->image2D(0); - CORRADE_VERIFY(converted); - CORRADE_COMPARE_WITH(originalImage->pixels(), + CORRADE_COMPARE(importer->image2DCount(), 1); + CORRADE_COMPARE(importer->image2DLevelCount(0), 2); + Containers::Optional level0 = importer->image2D(0, 0); + Containers::Optional level1 = importer->image2D(0, 1); + CORRADE_VERIFY(level0); + CORRADE_VERIFY(level1); + CORRADE_COMPARE_WITH(level0->pixels(), Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-63x27.png"), /* There are moderately significant compression artifacts */ (DebugTools::CompareImageToFile{_manager, 97.25f, 7.914f})); + CORRADE_COMPARE_WITH(level1->pixels(), + Utility::Directory::join(BASISIMPORTER_TEST_DIR, "rgba-31x13.png"), + /* There are moderately significant compression artifacts */ + (DebugTools::CompareImageToFile{_manager, 81.0f, 14.302f})); /* The format should get reset again after so convertToData() isn't left with some random format after */ if(data.pluginName == "BasisImageConverter"_s) { - const auto compressedData = converter->convertToData(*originalImage); + const auto compressedData = converter->convertToData(originalLevels); CORRADE_VERIFY(compressedData); CORRADE_VERIFY(Containers::StringView{Containers::arrayView(compressedData)}.hasPrefix(BasisPrefix)); } From 94b17989f770798927f46f52099df7b3ecd68e87 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Wed, 27 Oct 2021 16:37:18 +0200 Subject: [PATCH 73/76] BasisImporter: document two-channel format source G is taken from the alpha channel, matching the recommended basisu -swizzle rrrg for two channels. We don't control this, it's done by the transcoder. --- src/MagnumPlugins/BasisImporter/BasisImporter.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/MagnumPlugins/BasisImporter/BasisImporter.h b/src/MagnumPlugins/BasisImporter/BasisImporter.h index c4ffff374..78ccc7c7a 100644 --- a/src/MagnumPlugins/BasisImporter/BasisImporter.h +++ b/src/MagnumPlugins/BasisImporter/BasisImporter.h @@ -276,7 +276,10 @@ class MAGNUM_BASISIMPORTER_EXPORT BasisImporter: public AbstractImporter { /** BC4 R. Loaded as @ref CompressedPixelFormat::Bc4RUnorm. */ Bc4R = 4, - /** BC5 RG. Loaded as @ref CompressedPixelFormat::Bc5RGUnorm. */ + /** + * BC5 RG. Taken from the input red and alpha channel. Loaded as + * @ref CompressedPixelFormat::Bc5RGUnorm. + */ Bc5RG = 5, /* Bc7RGB (=6) used to be the mode 6 transcoder which went away @@ -329,13 +332,12 @@ class MAGNUM_BASISIMPORTER_EXPORT BasisImporter: public AbstractImporter { have enums for those */ /** - * EAC unsigned red component. Loaded as - * @ref CompressedPixelFormat::EacR11Unorm. + * EAC R. Loaded as @ref CompressedPixelFormat::EacR11Unorm. */ EacR = 20, /** - * EAC unsigned red and green component. Loaded as + * EAC RG. Taken from the input red and alpha channel. Loaded as * @ref CompressedPixelFormat::EacRG11Unorm. */ EacRG = 21, From d1ef9ab842990d24f644b265702eca4c3a2d6d5e Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Wed, 27 Oct 2021 17:11:29 +0200 Subject: [PATCH 74/76] BasisImageConverter: disable implicit swizzle if the user supplied their own This fixes the use case of feeding an RG image + swizzle rrrg (as recommended by the basis docs). This would lead to the output becoming rrrr since BasisImageConverter already performed that swizzle. Also document the behaviour. --- .../BasisImageConverter.cpp | 29 +++++++---- .../BasisImageConverter/BasisImageConverter.h | 16 +++++++ .../Test/BasisImageConverterTest.cpp | 48 +++++++++++++++++++ 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp index 74f954e1e..9b172c8b6 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.cpp @@ -295,17 +295,30 @@ Containers::Array BasisImageConverter::doConvertToData(Containers::ArrayVi } else if(channels == 2) { auto src = image.pixels>(); - for(std::size_t y = 0; y != src.size()[0]; ++y) - for(std::size_t x = 0; x != src.size()[1]; ++x) - /** @todo Doesn't this break if swizzle is rrrg? -> output - would be rrrr */ - dst[y][x] = Math::gather<'r', 'r', 'r', 'g'>(src[y][x]); + /* If the user didn't specify a custom swizzle, assume they want + the two channels compressed in separate slices, R in RGB and G + in Alpha. This significantly improves quality. */ + if(swizzle.empty()) + for(std::size_t y = 0; y != src.size()[0]; ++y) + for(std::size_t x = 0; x != src.size()[1]; ++x) + dst[y][x] = Math::gather<'r', 'r', 'r', 'g'>(src[y][x]); + else + for(std::size_t y = 0; y != src.size()[0]; ++y) + for(std::size_t x = 0; x != src.size()[1]; ++x) + dst[y][x] = Vector3ub::pad(src[y][x]); /* Alpha implicitly 255 */ } else if(channels == 1) { auto src = image.pixels>(); - for(std::size_t y = 0; y != src.size()[0]; ++y) - for(std::size_t x = 0; x != src.size()[1]; ++x) - dst[y][x] = Math::gather<'r', 'r', 'r'>(src[y][x]); + /* If the user didn't specify a custom swizzle, assume they want + a gray-scale image. Alpha is always implicitly 255. */ + if(swizzle.empty()) + for(std::size_t y = 0; y != src.size()[0]; ++y) + for(std::size_t x = 0; x != src.size()[1]; ++x) + dst[y][x] = Math::gather<'r', 'r', 'r'>(src[y][x]); + else + for(std::size_t y = 0; y != src.size()[0]; ++y) + for(std::size_t x = 0; x != src.size()[1]; ++x) + dst[y][x] = Vector3ub::pad(src[y][x]); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); } diff --git a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h index 55b534843..44d1a592d 100644 --- a/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h +++ b/src/MagnumPlugins/BasisImageConverter/BasisImageConverter.h @@ -131,6 +131,22 @@ rounded down. Incomplete mip chains are supported. To generate mip levels from a single top-level image instead, you can use the @cb{.ini} mip_gen @ce @ref Trade-BasisImageConverter-configuration "configuration option". +@subsection Trade-BasisImageConverter-behavior-swizzling Implicit swizzling + +If no user-specified channel mapping is supplied through the +@cb{.ini} swizzle @ce @ref Trade-BasisImageConverter-configuration "configuration option", +the converter swizzles 1- and 2-channel formats before compression as follows: + +- 1-channel formats (@ref PixelFormat::R8Unorm / @ref PixelFormat::R8Srgb) + are remapped as RRR, producing an opaque gray-scale image +- 2-channel formats (@ref PixelFormat::RG8Unorm / @ref PixelFormat::RG8Srgb) + are remapped as RRRG, ie. G becomes the alpha channel. This significantly + improves compressed image quality because RGB and alpha get separate slices + instead of the two channels being compressed into a single slice. + +To disable this behaviour and keep the original channels, set +@cb{.ini} swizzle @ce to "rgba". + @subsection Trade-BasisImageConverter-behavior-ktx Converting to KTX2 To create Khronos Texture 2.0 (`*.ktx2`) files, either load the plugin as diff --git a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp index ac941d0fe..3853a1c82 100644 --- a/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp +++ b/src/MagnumPlugins/BasisImageConverter/Test/BasisImageConverterTest.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,7 @@ struct BasisImageConverterTest: TestSuite::Tester { void threads(); void ktx(); void customLevels(); + void swizzle(); /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _converterManager{"nonexistent"}; @@ -130,6 +132,19 @@ constexpr struct { {"no y-flip", false} }; +constexpr struct { + const char* name; + const PixelFormat format; + const Color4ub input; + const char* swizzle; + const Color4ub output; +} SwizzleData[] { + {"R implicit", PixelFormat::R8Unorm, Color4ub{128, 0, 0}, "", Color4ub{128, 128, 128}}, + {"R none", PixelFormat::R8Unorm, Color4ub{128, 0, 0}, "rgba", Color4ub{128, 0, 0}}, + {"RG implicit", PixelFormat::RG8Unorm, Color4ub{64, 128, 0}, "", Color4ub{64, 64, 64, 128}}, + {"RG none", PixelFormat::RG8Unorm, Color4ub{64, 128, 0}, "rgba", Color4ub{64, 128, 0}} +}; + BasisImageConverterTest::BasisImageConverterTest() { addTests({&BasisImageConverterTest::wrongFormat, &BasisImageConverterTest::unknownOutputFormatData, @@ -159,6 +174,9 @@ BasisImageConverterTest::BasisImageConverterTest() { addTests({&BasisImageConverterTest::customLevels}); + addInstancedTests({&BasisImageConverterTest::swizzle}, + Containers::arraySize(SwizzleData)); + /* Pull in the AnyImageImporter dependency for image comparison, load StbImageImporter from the build tree, if defined. Otherwise it's static and already loaded. */ @@ -764,6 +782,36 @@ void BasisImageConverterTest::customLevels() { (DebugTools::CompareImageToFile{_manager, 76.25f, 24.5f})); } +void BasisImageConverterTest::swizzle() { + auto&& data = SwizzleData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer converter = _converterManager.instantiate("BasisImageConverter"); + /* Default is empty */ + CORRADE_COMPARE(converter->configuration().value("swizzle"), ""); + converter->configuration().setValue("swizzle", data.swizzle); + + const Color4ub pixel[1]{data.input}; + const ImageView2D originalImage{data.format, {1, 1}, Containers::arrayCast(pixel)}; + + const auto compressedData = converter->convertToData(originalImage); + CORRADE_VERIFY(compressedData); + + if(_manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("BasisImporter plugin not found, cannot test"); + + Containers::Pointer importer = + _manager.instantiate("BasisImporterRGBA8"); + CORRADE_VERIFY(importer->openData(compressedData)); + CORRADE_COMPARE(importer->image2DCount(), 1); + + const auto image = importer->image2D(0); + CORRADE_VERIFY(image); + CORRADE_COMPARE(image->size(), (Vector2i{1, 1})); + /* There are very minor compression artifacts */ + CORRADE_COMPARE_WITH(Color4{image->pixels()[0][0]}, Color4{data.output}, (TestSuite::Compare::Around{Vector4{2.0f}})); +} + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::BasisImageConverterTest) From 4ad91fb42de330915d6adccbb258d463de64cdfe Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Mon, 25 Oct 2021 23:54:49 +0200 Subject: [PATCH 75/76] KtxImporter: doc++ --- src/MagnumPlugins/KtxImporter/KtxImporter.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/MagnumPlugins/KtxImporter/KtxImporter.h b/src/MagnumPlugins/KtxImporter/KtxImporter.h index 270a5eab9..52f421f79 100644 --- a/src/MagnumPlugins/KtxImporter/KtxImporter.h +++ b/src/MagnumPlugins/KtxImporter/KtxImporter.h @@ -120,10 +120,11 @@ multiple mip levels. The image type can be determined from @ref texture() and For layered images and (layered) cube maps, the array layers and faces are exposed as an additional image dimension. 1D array textures import -@ref ImageData2D with n y-slices, (layered) 2D textures and cube maps import -@ref ImageData3D with 6*n z-slices. 3D array textures behave differently: -because there is no `ImageData4D`, each layer is imported as a separate -@ref ImageData3D, with @ref image3DCount() determining the number of layers. +@ref ImageData2D with n y-slices, 2D array textures import @ref ImageData3D +with n z-slices and (layered) cube maps import @ref ImageData3D with 6*n +z-slices. 3D array textures behave differently: because there is no +`ImageData4D`, each layer is imported as a separate @ref ImageData3D, with +@ref image3DCount() determining the number of layers. @subsection Trade-KtxImporter-behavior-multilevel Multilevel images From 8ed062142e26e543edc387e4df82935b3653aef5 Mon Sep 17 00:00:00 2001 From: Pablo Escobar Date: Fri, 29 Oct 2021 14:56:39 +0200 Subject: [PATCH 76/76] KtxImporter: forward Basis Universal compressed files to BasisImporter Config options and flags are propagated to an internal instance of BasisImporter. All functions are then proxied to that internal instance until this instance is closed. --- src/MagnumPlugins/KtxImporter/KtxImporter.cpp | 239 +++++++++++++++--- src/MagnumPlugins/KtxImporter/KtxImporter.h | 29 ++- .../KtxImporter/Test/CMakeLists.txt | 17 +- .../KtxImporter/Test/KtxImporterTest.cpp | 173 ++++++++++++- .../KtxImporter/Test/configure.h.cmake | 2 + 5 files changed, 412 insertions(+), 48 deletions(-) diff --git a/src/MagnumPlugins/KtxImporter/KtxImporter.cpp b/src/MagnumPlugins/KtxImporter/KtxImporter.cpp index 1043eb6f2..ca25b1478 100644 --- a/src/MagnumPlugins/KtxImporter/KtxImporter.cpp +++ b/src/MagnumPlugins/KtxImporter/KtxImporter.cpp @@ -29,11 +29,16 @@ #include #include #include +#include +#include #include #include #include +#include #include +#include #include +#include #include #include #include @@ -251,6 +256,32 @@ Containers::Optional decodeFormat(Implementation::VkFormat vkFormat) { return {}; } +/* Used to propagate configuration to BasisImporter. Assumes that this plugin + itself doesn't have any configuration options and so propagates all groups + and values that were set, emitting a warning if the target doesn't have such + option in its default configuration. Copied from propagateConfiguration() in + magnum/src/MagnumPlugins/Implementation/propagateConfiguration.h */ + +void propagateConfiguration(const char* warningPrefix, const Containers::String& groupPrefix, const Containers::StringView plugin, const Utility::ConfigurationGroup& src, Utility::ConfigurationGroup& dst) { + using namespace Containers::Literals; + + /* Propagate values */ + for(Containers::Pair value: src.values()) { + if(!dst.hasValue(value.first())) { + Warning{} << warningPrefix << "option" << "/"_s.joinWithoutEmptyParts({groupPrefix, value.first()}) << "not recognized by" << plugin; + } + + dst.setValue(value.first(), value.second()); + } + + /* Recursively propagate groups */ + for(Containers::Pair> group: src.groups()) { + Utility::ConfigurationGroup* dstGroup = dst.group(group.first()); + if(!dstGroup) dstGroup = dst.addGroup(group.first()); + propagateConfiguration(warningPrefix, "/"_s.joinWithoutEmptyParts({groupPrefix, group.first()}), plugin, group.second(), *dstGroup); + } +} + } struct KtxImporter::File { @@ -282,9 +313,19 @@ KtxImporter::~KtxImporter() = default; ImporterFeatures KtxImporter::doFeatures() const { return ImporterFeature::OpenData; } -bool KtxImporter::doIsOpened() const { return !!_f; } +bool KtxImporter::doIsOpened() const { + /* Only one of these can be populated at a time */ + CORRADE_INTERNAL_ASSERT(!_f || !_basisImporter); + return _f || (_basisImporter && _basisImporter->isOpened()); +} -void KtxImporter::doClose() { _f = nullptr; } +void KtxImporter::doClose() { + _f = nullptr; + if(_basisImporter) { + _basisImporter->close(); + _basisImporter = nullptr; + } +} void KtxImporter::doOpenData(Containers::Array&& data, DataFlags dataFlags) { /* Check if the file is long enough for the header */ @@ -303,6 +344,7 @@ void KtxImporter::doOpenData(Containers::Array&& data, DataFlags dataFlags header.imageSize[0], header.imageSize[1], header.imageSize[2], header.layerCount, header.faceCount, header.levelCount, header.supercompressionScheme, + header.dfdByteOffset, header.dfdByteLength, header.kvdByteOffset, header.kvdByteLength); using namespace Containers::Literals; @@ -316,46 +358,24 @@ void KtxImporter::doOpenData(Containers::Array&& data, DataFlags dataFlags if(identifier.hasPrefix(expected.prefix(Implementation::KtxFileVersionOffset))) { const Containers::StringView version = identifier.suffix(Implementation::KtxFileVersionOffset).prefix(Implementation::KtxFileVersionLength); if(version != "20"_s) { - Error() << "Trade::KtxImporter::openData(): unsupported KTX version, expected 20 but got" << version; + Error{} << "Trade::KtxImporter::openData(): unsupported KTX version, expected 20 but got" << version; return; } } - Error() << "Trade::KtxImporter::openData(): wrong file signature"; + Error{} << "Trade::KtxImporter::openData(): wrong file signature"; return; } /* Read header data and perform some sanity checks, including byte ranges */ - /** @todo Support Basis compression */ - if(header.vkFormat == Implementation::VK_FORMAT_UNDEFINED) { - Error{} << "Trade::KtxImporter::openData(): custom formats are not supported"; - return; - } - - /** @todo Support supercompression */ - if(header.supercompressionScheme != Implementation::SuperCompressionScheme::None) { - Error{} << "Trade::KtxImporter::openData(): supercompression is currently not supported"; - return; - } - - /* typeSize is the size of the format's underlying type, not the texel - size, e.g. 2 for RG16F. For any sane format it should be a - power-of-two between 1 and 8. */ - if(header.typeSize < 1 || header.typeSize > 8 || - (header.typeSize & (header.typeSize - 1))) - { - Error{} << "Trade::KtxImporter::openData(): unsupported type size" << header.typeSize; - return; - } + Containers::Pointer f{InPlaceInit}; if(header.imageSize.x() == 0) { Error{} << "Trade::KtxImporter::openData(): invalid image size, width is 0"; return; } - Containers::Pointer f{InPlaceInit}; - /* Number of array layers, imported as extra image dimensions (except for 3D images, there it's one Image3D per layer). @@ -435,6 +455,92 @@ void KtxImporter::doOpenData(Containers::Array&& data, DataFlags dataFlags return; } + /* Detect Basis-compressed files and forward to BasisImporter. Any other + custom format is not supported. */ + if(header.vkFormat == Implementation::VK_FORMAT_UNDEFINED) { + /* BasisLZ (Basis ETC1 + LZ compression) is indicated by the + supercompression scheme. Basis UASTC on the other hand is indicated + by the DFD color model. */ + if(header.supercompressionScheme != Implementation::SuperCompressionScheme::BasisLZ) { + /* This is the only place we need to read the DFD so all checks + can reside here */ + const std::size_t dfdEnd = header.dfdByteOffset + header.dfdByteLength; + if(data.size() < dfdEnd) { + Error{} << "Trade::KtxImporter::openData(): file too short, expected" << + dfdEnd << "bytes for data format descriptor but got only" << data.size(); + return; + } + + if(header.dfdByteLength < sizeof(UnsignedInt) + sizeof(Implementation::KdfBasicBlockHeader)) { + Error{} << "Trade::KtxImporter::openData(): data format descriptor too short, " + "expected at least" << sizeof(UnsignedInt) + sizeof(Implementation::KdfBasicBlockHeader) << + "bytes but got" << header.dfdByteLength; + return; + } + + const auto& dfd = *reinterpret_cast( + data.suffix(header.dfdByteOffset + sizeof(UnsignedInt)).data()); + + /* colorModel is a byte, no need to endian-swap */ + if(dfd.colorModel != Implementation::KdfBasicBlockHeader::ColorModel::BasisUastc) { + Error{} << "Trade::KtxImporter::openData(): custom formats are not supported"; + return; + } + } + + /* Try to load the BasisImporter plugin */ + const std::string plugin = "BasisImporter"; + if(!(manager()->load(plugin) & PluginManager::LoadState::Loaded)) { + Error{} << "Trade::KtxImporter::openData(): image is compressed with Basis Universal, can't forward to BasisImporter because it's not loaded"; + return; + } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); + + if(flags() & ImporterFlag::Verbose) + Debug{} << "Trade::KtxImporter::openData(): image is compressed with Basis Universal, forwarding to" << metadata->name(); + + /* Instantiate the plugin, propagate flags */ + Containers::Pointer basisImporter = static_cast*>(manager())->instantiate(plugin); + basisImporter->setFlags(flags()); + + /* Propagate configuration */ + propagateConfiguration("Trade::KtxImporter::openData():", {}, metadata->name(), configuration(), basisImporter->configuration()); + + /* Try to open the data with BasisImporter (error output should be + printed by the plugin itself). All other functions transparently + forward to that importer instance if it's populated. */ + bool opened = false; + if(dataFlags >= DataFlag::ExternallyOwned) + opened = basisImporter->openMemory(data); + else + /** @todo This causes an extra copy for DataFlag::Owned */ + opened = basisImporter->openData(data); + + if(!opened) return; + + /* Success, save the instance */ + _basisImporter = std::move(basisImporter); + return; + } + + /** @todo Support supercompression */ + if(header.supercompressionScheme != Implementation::SuperCompressionScheme::None) { + Error{} << "Trade::KtxImporter::openData(): supercompression is currently not supported"; + return; + } + + /* typeSize is the size of the format's underlying type, not the texel + size, e.g. 2 for RG16F. For any sane format it should be a + power-of-two between 1 and 8. */ + if(header.typeSize < 1 || header.typeSize > 8 || + (header.typeSize & (header.typeSize - 1))) + { + Error{} << "Trade::KtxImporter::openData(): unsupported type size" << header.typeSize; + return; + } + /* Get generic format info from Vulkan format */ const auto pixelFormat = decodeFormat(header.vkFormat); if(!pixelFormat) { @@ -653,7 +759,7 @@ void KtxImporter::doOpenData(Containers::Array&& data, DataFlags dataFlags /** @todo KTX spec seems to really insist on rd for cube maps but the wording is odd, I can't tell if they're saying it's mandatory or not: - https://github.khronos.org/KTX-Specification/#cubemapOrientation + https://www.khronos.org/registry/KTX/specs/2.0/ktxspec_v2.html#cubemapOrientation The toktx tool from Khronos Texture Tools also forces rd for cube maps, so we might want to do that in the converter as well. */ @@ -779,35 +885,88 @@ ImageData KtxImporter::doImage(UnsignedInt id, UnsignedInt level) { return ImageData{storage, _f->pixelFormat.uncompressed, size, std::move(data)}; } -UnsignedInt KtxImporter::doImage1DCount() const { return (_f->numDataDimensions == 1) ? _f->imageData.size() : 0; } +UnsignedInt KtxImporter::doImage1DCount() const { + if(_basisImporter) + return _basisImporter->image1DCount(); + else if(_f->numDataDimensions == 1) + return _f->imageData.size(); + else + return 0; +} -UnsignedInt KtxImporter::doImage1DLevelCount(UnsignedInt id) { return _f->imageData[id].size(); } +UnsignedInt KtxImporter::doImage1DLevelCount(UnsignedInt id) { + if(_basisImporter) + return _basisImporter->image1DLevelCount(id); + else + return _f->imageData[id].size(); +} Containers::Optional KtxImporter::doImage1D(UnsignedInt id, UnsignedInt level) { - return doImage<1>(id, level); + if(_basisImporter) + return _basisImporter->image1D(id, level); + else + return doImage<1>(id, level); } -UnsignedInt KtxImporter::doImage2DCount() const { return (_f->numDataDimensions == 2) ? _f->imageData.size() : 0; } +UnsignedInt KtxImporter::doImage2DCount() const { + if(_basisImporter) + return _basisImporter->image2DCount(); + else if(_f->numDataDimensions == 2) + return _f->imageData.size(); + else + return 0; +} -UnsignedInt KtxImporter::doImage2DLevelCount(UnsignedInt id) { return _f->imageData[id].size(); } +UnsignedInt KtxImporter::doImage2DLevelCount(UnsignedInt id) { + if(_basisImporter) + return _basisImporter->image2DLevelCount(id); + else + return _f->imageData[id].size(); +} Containers::Optional KtxImporter::doImage2D(UnsignedInt id, UnsignedInt level) { - return doImage<2>(id, level); + if(_basisImporter) + return _basisImporter->image2D(id, level); + else + return doImage<2>(id, level); } -UnsignedInt KtxImporter::doImage3DCount() const { return (_f->numDataDimensions == 3) ? _f->imageData.size() : 0; } +UnsignedInt KtxImporter::doImage3DCount() const { + if(_basisImporter) + return _basisImporter->image3DCount(); + else if(_f->numDataDimensions == 3) + return _f->imageData.size(); + else + return 0; +} -UnsignedInt KtxImporter::doImage3DLevelCount(UnsignedInt id) { return _f->imageData[id].size(); } +UnsignedInt KtxImporter::doImage3DLevelCount(UnsignedInt id) { + if(_basisImporter) + return _basisImporter->image3DLevelCount(id); + else + return _f->imageData[id].size(); +} Containers::Optional KtxImporter::doImage3D(UnsignedInt id, const UnsignedInt level) { - return doImage<3>(id, level); + if(_basisImporter) + return _basisImporter->image3D(id, level); + else + return doImage<3>(id, level); } -UnsignedInt KtxImporter::doTextureCount() const { return _f->imageData.size(); } +UnsignedInt KtxImporter::doTextureCount() const { + if(_basisImporter) + return _basisImporter->textureCount(); + else + return _f->imageData.size(); +} Containers::Optional KtxImporter::doTexture(UnsignedInt id) { - return TextureData{_f->type, SamplerFilter::Linear, SamplerFilter::Linear, - SamplerMipmap::Linear, SamplerWrapping::Repeat, id}; + if(_basisImporter) + return _basisImporter->texture(id); + else + return TextureData{_f->type, SamplerFilter::Linear, SamplerFilter::Linear, + SamplerMipmap::Linear, SamplerWrapping::Repeat, id}; } }} diff --git a/src/MagnumPlugins/KtxImporter/KtxImporter.h b/src/MagnumPlugins/KtxImporter/KtxImporter.h index 52f421f79..ca6bdf69c 100644 --- a/src/MagnumPlugins/KtxImporter/KtxImporter.h +++ b/src/MagnumPlugins/KtxImporter/KtxImporter.h @@ -144,11 +144,6 @@ Incomplete cube maps (determined by the `KTXcubemapIncomplete` metadata entry) are imported as a 2D array image, but information about which faces it contains can't be imported. -@subsection Trade-KtxImporter-behavior-supercompression Supercompression - -Importing files with [supercompression](https://github.khronos.org/KTX-Specification/#supercompressionSchemes) -is not supported. - @subsection Trade-KtxImporter-behavior-swizzle Swizzle support Explicit swizzling via the KTXswizzle header entry supports BGR and BGRA. Any @@ -157,6 +152,29 @@ other non-identity channel remapping is unsupported and results in an error. For reasons similar to the restriction on axis-flips, compressed formats don't support any swizzling, and the import fails if an image with a compressed format contains a swizzle that isn't RGBA. + +@subsection Trade-KtxImporter-behavior-basis Basis Universal compression + +When the importer detects a Basis Universal compressed file, it will forward +the file to @ref BasisImporter, if it's loaded. + +Flags set via @ref setFlags() and options set through @ref configuration() are +propagated to `BasisImporter`, with a warning emitted in case given option is +not present in the `BasisImporter` default configuration. + +Calls to the @ref image1DCount() / @ref image2DCount() / @ref image3DCount(), +@ref image1DLevelCount() / @ref image2DLevelCount() / @ref image3DLevelCount(), +@ref image1D() / @ref image2D() / @ref image3D() and @ref textureCount() / +@ref texture() functions are then proxied to `BasisImporter`. The @ref close() +function closes and discards the internally instantiated plugin; +@ref isOpened() works as usual. + +@subsection Trade-KtxImporter-behavior-supercompression Supercompression + +Importing files with [supercompression](https://www.khronos.org/registry/KTX/specs/2.0/ktxspec_v2.html#supercompressionSchemes) +is not supported. When @ref Trade-KtxImporter-behavior-basis "forwarding Basis +Universal compressed files", some supercompression schemes like BasisLZ and +Zstandard can be handled by `BasisImporter`. */ class MAGNUM_KTXIMPORTER_EXPORT KtxImporter: public AbstractImporter { public: @@ -189,6 +207,7 @@ class MAGNUM_KTXIMPORTER_EXPORT KtxImporter: public AbstractImporter { private: struct File; Containers::Pointer _f; + Containers::Pointer _basisImporter; template ImageData doImage(UnsignedInt id, UnsignedInt level); diff --git a/src/MagnumPlugins/KtxImporter/Test/CMakeLists.txt b/src/MagnumPlugins/KtxImporter/Test/CMakeLists.txt index 1d67de4fd..183c66253 100644 --- a/src/MagnumPlugins/KtxImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/KtxImporter/Test/CMakeLists.txt @@ -25,8 +25,10 @@ # if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) + set(BASISIMPORTER_TEST_DIR ".") set(KTXIMPORTER_TEST_DIR ".") else() + set(BASISIMPORTER_TEST_DIR ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/BasisImporter/Test) set(KTXIMPORTER_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) endif() @@ -36,6 +38,9 @@ endif() # be revisited when updating Travis to newer Xcode (xcode7.3 has CMake 3.6). if(NOT MAGNUM_KTXIMPORTER_BUILD_STATIC) set(KTXIMPORTER_PLUGIN_FILENAME $) + if(WITH_BASISIMPORTER) + set(BASISIMPORTER_PLUGIN_FILENAME $) + endif() endif() # First replace ${} variables, then $<> generator expressions @@ -109,15 +114,25 @@ corrade_add_test(KtxImporterTest KtxImporterTest.cpp swizzle-bgra.ktx2 swizzle-identity.ktx2 swizzle-unsupported.ktx2 - version1.ktx) + version1.ktx + ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/BasisImporter/Test/rgba.ktx2 + ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/BasisImporter/Test/rgba-array.ktx2 + ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/BasisImporter/Test/rgba-uastc.ktx2 + ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/BasisImporter/Test/rgba-video.ktx2) target_include_directories(KtxImporterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$ ${PROJECT_SOURCE_DIR}/src) if(MAGNUM_KTXIMPORTER_BUILD_STATIC) target_link_libraries(KtxImporterTest PRIVATE KtxImporter) + if(WITH_BASISIMPORTER) + target_link_libraries(KtxImporterTest PRIVATE BasisImporter) + endif() else() # So the plugins get properly built when building the test add_dependencies(KtxImporterTest KtxImporter) + if(WITH_BASISIMPORTER) + add_dependencies(KtxImporterTest BasisImporter) + endif() endif() set_target_properties(KtxImporterTest PROPERTIES FOLDER "MagnumPlugins/KtxImporter/Test") if(CORRADE_BUILD_STATIC AND NOT MAGNUM_KTXIMPORTER_BUILD_STATIC) diff --git a/src/MagnumPlugins/KtxImporter/Test/KtxImporterTest.cpp b/src/MagnumPlugins/KtxImporter/Test/KtxImporterTest.cpp index 660afb5be..2a9931c63 100644 --- a/src/MagnumPlugins/KtxImporter/Test/KtxImporterTest.cpp +++ b/src/MagnumPlugins/KtxImporter/Test/KtxImporterTest.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -95,6 +96,9 @@ struct KtxImporterTest: TestSuite::Tester { void image3DCompressed(); void image3DCompressedMipmaps(); + void forwardBasis(); + void forwardBasisInvalid(); + void keyValueDataEmpty(); void keyValueDataInvalid(); void keyValueDataInvalidIgnored(); @@ -300,6 +304,45 @@ const struct { {"ASTC", "2d-compressed-astc.ktx2", CompressedPixelFormat::Astc12x10RGBASrgb, {9, 10}} }; +const struct { + const char* name; + const ImporterFlags flags; + const char* file; + const TextureType type; + const UnsignedInt images; + const Vector3i size; + const char* verboseMessage; +} ForwardBasisData[]{ + {"2D", ImporterFlags{}, "rgba.ktx2", TextureType::Texture2D, 1, {63, 27, 0}, + ""}, + {"2D verbose", ImporterFlag::Verbose, "rgba.ktx2", TextureType::Texture2D, 1, {63, 27, 0}, + "Trade::KtxImporter::openData(): image is compressed with Basis Universal, forwarding to BasisImporter\n"}, + {"2D array", ImporterFlags{}, "rgba-array.ktx2", TextureType::Texture2DArray, 1, {63, 27, 3}, + ""}, + {"video", ImporterFlags{}, "rgba-video.ktx2", TextureType::Texture2D, 3, {63, 27, 0}, + ""}, + {"video verbose", ImporterFlag::Verbose, "rgba-video.ktx2", TextureType::Texture2D, 3, {63, 27, 0}, + "Trade::KtxImporter::openData(): image is compressed with Basis Universal, forwarding to BasisImporter\n" + "Trade::BasisImporter::openData(): file contains video frames, images must be transcoded sequentially\n"} +}; + +const struct { + const char* name; + const char* file; + const bool requiresBasisImporter; + const std::size_t offset; + const char value; + const char* message; +} ForwardBasisInvalidData[]{ + /* Change ktx2_etc1s_global_data_header::m_endpoint_count */ + {"fails in BasisImporter", "rgba.ktx2", true, 200, 0, + "Trade::BasisImporter::openData(): bad KTX2 file\n"}, + {"file too short for DFD", "rgba-uastc.ktx2", false, offsetof(Implementation::KtxHeader, dfdByteOffset) + 2, 1, + "Trade::KtxImporter::openData(): file too short, expected 65684 bytes for data format descriptor but got only 789\n"}, + {"DFD too short", "rgba-uastc.ktx2", false, offsetof(Implementation::KtxHeader, dfdByteLength), sizeof(UnsignedInt) + sizeof(Implementation::KdfBasicBlockHeader) - 1, + "Trade::KtxImporter::openData(): data format descriptor too short, expected at least 28 bytes but got 27\n"} +}; + using namespace Containers::Literals; const struct { @@ -495,9 +538,15 @@ KtxImporterTest::KtxImporterTest() { &KtxImporterTest::image3DMipmaps, &KtxImporterTest::image3DLayers, &KtxImporterTest::image3DCompressed, - &KtxImporterTest::image3DCompressedMipmaps, + &KtxImporterTest::image3DCompressedMipmaps}); + + addInstancedTests({&KtxImporterTest::forwardBasis}, + Containers::arraySize(ForwardBasisData)); + + addInstancedTests({&KtxImporterTest::forwardBasisInvalid}, + Containers::arraySize(ForwardBasisInvalidData)); - &KtxImporterTest::keyValueDataEmpty}); + addTests({&KtxImporterTest::keyValueDataEmpty}); addInstancedTests({&KtxImporterTest::keyValueDataInvalid}, Containers::arraySize(InvalidKeyValueData)); @@ -532,6 +581,9 @@ KtxImporterTest::KtxImporterTest() { #ifdef KTXIMPORTER_PLUGIN_FILENAME CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(KTXIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); #endif + #ifdef BASISIMPORTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(BASISIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif } void KtxImporterTest::openShort() { @@ -1519,6 +1571,123 @@ void KtxImporterTest::image3DCompressedMipmaps() { } } +void KtxImporterTest::forwardBasis() { + auto&& data = ForwardBasisData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer importer = _manager.instantiate("KtxImporter"); + + if(_manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) { + std::ostringstream outError; + Error redirectError{&outError}; + CORRADE_VERIFY(!importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, data.file))); + CORRADE_COMPARE(outError.str(), "Trade::KtxImporter::openData(): image is compressed with Basis Universal, can't forward to BasisImporter because it's not loaded\n"); + CORRADE_SKIP("BasisImporter plugin not found, cannot test"); + } + + /* Making sure that importer flags and config options are propagated, + including a warning for unknown options */ + importer->setFlags(data.flags); + importer->configuration().setValue("format", "Etc2RGBA"); + importer->configuration().setValue("noSuchOption", "isHere"); + + std::ostringstream outDebug; + Debug redirectDebug{&outDebug}; + std::ostringstream outWarning; + Warning redirectWarning{&outWarning}; + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(BASISIMPORTER_TEST_DIR, data.file))); + CORRADE_COMPARE(outDebug.str(), data.verboseMessage); + CORRADE_COMPARE(outWarning.str(), "Trade::KtxImporter::openData(): option noSuchOption not recognized by BasisImporter\n"); + + const UnsignedInt dimensions = data.size[2] == 0 ? (data.size[1] == 0 ? 1 : 2) : 3; + /* All of these should forward to the correct instance and not crash */ + CORRADE_COMPARE(importer->image1DCount(), dimensions == 1 ? data.images : 0); + CORRADE_COMPARE(importer->image2DCount(), dimensions == 2 ? data.images : 0); + CORRADE_COMPARE(importer->image3DCount(), dimensions == 3 ? data.images : 0); + CORRADE_COMPARE(importer->textureCount(), data.images); + + for(UnsignedInt i = 0; i != data.images; ++i) { + const auto texture = importer->texture(i); + CORRADE_VERIFY(texture); + CORRADE_COMPARE(texture->type(), data.type); + + if(dimensions == 1) { + CORRADE_COMPARE(importer->image1DLevelCount(i), 1); + const auto image = importer->image1D(i); + CORRADE_VERIFY(image); + CORRADE_VERIFY(image->isCompressed()); + CORRADE_COMPARE(image->compressedFormat(), CompressedPixelFormat::Etc2RGBA8Srgb); + CORRADE_COMPARE(Vector3i::pad(image->size()), data.size); + } else if(dimensions == 2) { + CORRADE_COMPARE(importer->image2DLevelCount(i), 1); + const auto image = importer->image2D(i); + CORRADE_VERIFY(image); + CORRADE_VERIFY(image->isCompressed()); + CORRADE_COMPARE(image->compressedFormat(), CompressedPixelFormat::Etc2RGBA8Srgb); + CORRADE_COMPARE(Vector3i::pad(image->size()), data.size); + } else if(dimensions == 3) { + CORRADE_COMPARE(importer->image3DLevelCount(i), 1); + const auto image = importer->image3D(i); + CORRADE_VERIFY(image); + CORRADE_VERIFY(image->isCompressed()); + CORRADE_COMPARE(image->compressedFormat(), CompressedPixelFormat::Etc2RGBA8Srgb); + CORRADE_COMPARE(Vector3i::pad(image->size()), data.size); + } else + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + /* Loading a normal KTX afterwards should work */ + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(KTXIMPORTER_TEST_DIR, "2d-rgb.ktx2"))); + + CORRADE_COMPARE(importer->image2DCount(), 1); + CORRADE_COMPARE(importer->image2DLevelCount(0), 1); + + const auto image = importer->image2D(0); + CORRADE_VERIFY(image); + + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), PixelFormat::RGB8Srgb); + CORRADE_COMPARE(image->size(), (Vector2i{4, 3})); +} + +void KtxImporterTest::forwardBasisInvalid() { + auto&& data = ForwardBasisInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(data.requiresBasisImporter && + _manager.loadState("BasisImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("BasisImporter plugin not found, cannot test"); + + Containers::Pointer importer = _manager.instantiate("KtxImporter"); + + auto fileData = Utility::Directory::read(Utility::Directory::join(BASISIMPORTER_TEST_DIR, data.file)); + CORRADE_VERIFY(fileData); + CORRADE_VERIFY(fileData.size() >= data.offset); + fileData[data.offset] = data.value; + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_VERIFY(!importer->openData(fileData)); + CORRADE_COMPARE(out.str(), data.message); + + /* Loading a normal KTX afterwards should work */ + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(KTXIMPORTER_TEST_DIR, "2d-rgb.ktx2"))); + + CORRADE_COMPARE(importer->image2DCount(), 1); + CORRADE_COMPARE(importer->image2DLevelCount(0), 1); + + const auto image = importer->image2D(0); + CORRADE_VERIFY(image); + + CORRADE_VERIFY(!image->isCompressed()); + CORRADE_COMPARE(image->format(), PixelFormat::RGB8Srgb); + CORRADE_COMPARE(image->size(), (Vector2i{4, 3})); +} + void KtxImporterTest::keyValueDataEmpty() { Containers::Pointer importer = _manager.instantiate("KtxImporter"); auto fileData = Utility::Directory::read(Utility::Directory::join(KTXIMPORTER_TEST_DIR, "2d-rgb.ktx2")); diff --git a/src/MagnumPlugins/KtxImporter/Test/configure.h.cmake b/src/MagnumPlugins/KtxImporter/Test/configure.h.cmake index b10586f4e..069102b39 100644 --- a/src/MagnumPlugins/KtxImporter/Test/configure.h.cmake +++ b/src/MagnumPlugins/KtxImporter/Test/configure.h.cmake @@ -25,4 +25,6 @@ */ #cmakedefine KTXIMPORTER_PLUGIN_FILENAME "${KTXIMPORTER_PLUGIN_FILENAME}" +#cmakedefine BASISIMPORTER_PLUGIN_FILENAME "${BASISIMPORTER_PLUGIN_FILENAME}" +#cmakedefine BASISIMPORTER_TEST_DIR "${BASISIMPORTER_TEST_DIR}" #define KTXIMPORTER_TEST_DIR "${KTXIMPORTER_TEST_DIR}"