From d1f283d962995673f11c5d8b247f9e5a4e9f4801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 3 Aug 2022 12:32:24 +0200 Subject: [PATCH 01/93] Math: fix batch min()/max()/minmax() to work with const input views. Apart from returning const T instead of T it kinda worked, but in case of floating-point vectors it tried to operate with `std::pair` internally and failed miserably. --- src/Magnum/Math/FunctionsBatch.h | 6 +++--- src/Magnum/Math/Test/FunctionsBatchTest.cpp | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Magnum/Math/FunctionsBatch.h b/src/Magnum/Math/FunctionsBatch.h index c52f03811..736a21d30 100644 --- a/src/Magnum/Math/FunctionsBatch.h +++ b/src/Magnum/Math/FunctionsBatch.h @@ -40,9 +40,9 @@ namespace Implementation { /** @todo Utility/Algorithms.h has a similar (but different) variant of this, maybe turn that into some public utility once we have one more use case? */ -template::type>::from(std::declval()))> static auto stridedArrayViewTypeFor(T&&) -> typename View::Type; -template static T stridedArrayViewTypeFor(const Corrade::Containers::ArrayView&); -template static T stridedArrayViewTypeFor(const Corrade::Containers::StridedArrayView1D&); +template::type>::from(std::declval()))> static auto stridedArrayViewTypeFor(T&&) -> typename std::remove_const::type; +template static typename std::remove_const::type stridedArrayViewTypeFor(const Corrade::Containers::ArrayView&); +template static typename std::remove_const::type stridedArrayViewTypeFor(const Corrade::Containers::StridedArrayView1D&); } diff --git a/src/Magnum/Math/Test/FunctionsBatchTest.cpp b/src/Magnum/Math/Test/FunctionsBatchTest.cpp index 5043edda8..253eb9b43 100644 --- a/src/Magnum/Math/Test/FunctionsBatchTest.cpp +++ b/src/Magnum/Math/Test/FunctionsBatchTest.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include @@ -44,6 +45,8 @@ struct FunctionsBatchTest: Corrade::TestSuite::Tester { void nanIgnoring(); void nanIgnoringVector(); + + void constIterable(); }; using namespace Literals; @@ -62,7 +65,9 @@ FunctionsBatchTest::FunctionsBatchTest() { &FunctionsBatchTest::minmax, &FunctionsBatchTest::nanIgnoring, - &FunctionsBatchTest::nanIgnoringVector}); + &FunctionsBatchTest::nanIgnoringVector, + + &FunctionsBatchTest::constIterable}); } void FunctionsBatchTest::isInf() { @@ -284,6 +289,20 @@ void FunctionsBatchTest::nanIgnoringVector() { CORRADE_COMPARE(Math::minmax(allNan).second[1], Constants::nan()); } +void FunctionsBatchTest::constIterable() { + const Vector2 data[]{{5, -3}, {-2, 14}, {9, -5}}; + + /* It shouldn't try to operate with a const type (such as trying to to + assign to `std::pair`) internally, instead + it should remove the const */ + CORRADE_COMPARE(Math::min(Corrade::Containers::arrayView(data)), + (Vector2{-2, -5})); + CORRADE_COMPARE(Math::max(Corrade::Containers::stridedArrayView(data)), + (Vector2{9, 14})); + CORRADE_COMPARE(Math::minmax(Corrade::Containers::Array{data, 3, [](const Vector2*, std::size_t){}}), + std::make_pair(Vector2{-2, -5}, Vector2{9, 14})); +} + }}}} CORRADE_TEST_MAIN(Magnum::Math::Test::FunctionsBatchTest) From fb7e4101f6093c34739678bfca35690d2daea79a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 13 Aug 2022 16:29:43 +0200 Subject: [PATCH 02/93] package/ci: go back to lcov 1.13 on MinGW. Same change as in Corrade, went unnoticed for almost a year. This partially reverts commit c07e35e31c1b2e983150916174c5f94e2c084a2e. --- package/ci/appveyor-lcov.sh | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/package/ci/appveyor-lcov.sh b/package/ci/appveyor-lcov.sh index f0bf24f36..9e5f681c5 100644 --- a/package/ci/appveyor-lcov.sh +++ b/package/ci/appveyor-lcov.sh @@ -20,12 +20,16 @@ set -ev # AppVeyor ships Perl on its own and since we fetch our own lcov anyway, the # MSYS insanity is not needed for ANYTHING AT ALL, in fact. -wget https://github.com/linux-test-project/lcov/archive/v1.15.tar.gz -tar -xzf v1.15.tar.gz +# Important: 1.13 is the only version that actually works. 1.15 doesn't, tries +# to find the original source files in build/.../CMakeFiles/src/Magnum and +# results in a zero-byte coverage being happily uploaded, with no error message +# produced whatsoever. How nice. +wget https://github.com/linux-test-project/lcov/archive/v1.13.tar.gz +tar -xzf v1.13.tar.gz # Keep in sync with PKBUILD-coverage and circleci.yml, please -lcov-1.15/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --directory . --capture --output-file coverage.info > /dev/null -lcov-1.15/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --extract coverage.info "*/src/Magnum*/*" --output-file coverage.info > /dev/null -lcov-1.15/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --remove coverage.info "*/src/MagnumExternal/*" --output-file coverage.info > /dev/null -lcov-1.15/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --remove coverage.info "*/Test/*" --output-file coverage.info > /dev/null -lcov-1.15/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --remove coverage.info "*/build/src/*" --output-file coverage.info > /dev/null +lcov-1.13/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --directory . --capture --output-file coverage.info > /dev/null +lcov-1.13/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --extract coverage.info "*/src/Magnum*/*" --output-file coverage.info > /dev/null +lcov-1.13/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --remove coverage.info "*/src/MagnumExternal/*" --output-file coverage.info > /dev/null +lcov-1.13/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --remove coverage.info "*/Test/*" --output-file coverage.info > /dev/null +lcov-1.13/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --remove coverage.info "*/build/src/*" --output-file coverage.info > /dev/null From 87fd89c66f576204041ef5b7aa7a26b50975a8bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 16 Aug 2022 15:52:45 +0200 Subject: [PATCH 03/93] MeshTools: use Containers::Iterable in concatenate*() and combine*(). This allows people to directly pass Containers::Array there, without having to put them to an annoying temporary Containers::Array. The Iterable header is included for backwards compatibility, apart from that there should be no breaking change. --- doc/changelog.dox | 2 +- src/Magnum/MeshTools/Combine.cpp | 40 ++++++++++------------- src/Magnum/MeshTools/Combine.h | 15 +++++---- src/Magnum/MeshTools/Concatenate.cpp | 38 ++++++++++----------- src/Magnum/MeshTools/Concatenate.h | 26 ++++----------- src/Magnum/MeshTools/InterleaveFlags.h | 6 ++-- src/Magnum/MeshTools/Test/CombineTest.cpp | 2 +- 7 files changed, 53 insertions(+), 76 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 50e188a76..347ff6d51 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -428,7 +428,7 @@ See also: - @ref MeshTools::interleavedLayout(const Trade::MeshData&, UnsignedInt, Containers::ArrayView, InterleaveFlags), @ref MeshTools::interleave(const Trade::MeshData&, Containers::ArrayView, InterleaveFlags) and - @ref MeshTools::concatenate(Containers::ArrayView>, InterleaveFlags) + @ref MeshTools::concatenate(Containers::Iterable, InterleaveFlags) optionally take a @ref MeshTools::InterleaveFlags parameter affecting the output, in particular whether to preserve the original interleaved layout. diff --git a/src/Magnum/MeshTools/Combine.cpp b/src/Magnum/MeshTools/Combine.cpp index 0ca545e2d..fcf08792f 100644 --- a/src/Magnum/MeshTools/Combine.cpp +++ b/src/Magnum/MeshTools/Combine.cpp @@ -26,8 +26,7 @@ #include "Combine.h" #include -#include -#include +#include #include #include "Magnum/Math/Functions.h" @@ -44,20 +43,20 @@ Trade::MeshData combineIndexedImplementation( #ifndef CORRADE_NO_ASSERT const char* assertPrefix, #endif - const MeshPrimitive primitive, Containers::Array& combinedIndices, const UnsignedInt indexCount, const UnsignedInt indexStride, const Containers::ArrayView> data) + const MeshPrimitive primitive, Containers::Array& combinedIndices, const UnsignedInt indexCount, const UnsignedInt indexStride, const Containers::Iterable data) { /* Calculate attribute count and vertex stride */ UnsignedInt attributeCount = 0; UnsignedInt vertexStride = 0; for(std::size_t i = 0; i != data.size(); ++i) { - attributeCount += data[i]->attributeCount(); - for(UnsignedInt j = 0; j != data[i]->attributeCount(); ++j) { - const VertexFormat format = data[i]->attributeFormat(j); + attributeCount += data[i].attributeCount(); + for(UnsignedInt j = 0; j != data[i].attributeCount(); ++j) { + const VertexFormat format = data[i].attributeFormat(j); CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), assertPrefix << "attribute" << j << "of mesh" << i << "has an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), (Trade::MeshData{MeshPrimitive::Points, 0})); - vertexStride += vertexFormatSize(format)*Math::max(data[i]->attributeArraySize(j), UnsignedShort{1}); + vertexStride += vertexFormatSize(format)*Math::max(data[i].attributeArraySize(j), UnsignedShort{1}); } } @@ -111,7 +110,7 @@ Trade::MeshData combineIndexedImplementation( } -Trade::MeshData combineIndexedAttributes(const Containers::ArrayView> data) { +Trade::MeshData combineIndexedAttributes(const Containers::Iterable data) { CORRADE_ASSERT(!data.isEmpty(), "MeshTools::combineIndexedAttributes(): no meshes passed", (Trade::MeshData{MeshPrimitive{}, 0})); @@ -124,21 +123,21 @@ Trade::MeshData combineIndexedAttributes(const Containers::ArrayViewisIndexed(), + CORRADE_ASSERT(data[i].isIndexed(), "MeshTools::combineIndexedAttributes(): data" << i << "is not indexed", (Trade::MeshData{MeshPrimitive{}, 0})); - const MeshIndexType indexType = data[i]->indexType(); + const MeshIndexType indexType = data[i].indexType(); CORRADE_ASSERT(!isMeshIndexTypeImplementationSpecific(indexType), "MeshTools::combineIndexedAttributes(): data" << i << "has an implementation-specific index type" << reinterpret_cast(meshIndexTypeUnwrap(indexType)), (Trade::MeshData{MeshPrimitive{}, 0})); if(i == 0) { - primitive = data[i]->primitive(); - indexCount = data[i]->indexCount(); + primitive = data[i].primitive(); + indexCount = data[i].indexCount(); } else { - CORRADE_ASSERT(data[i]->primitive() == primitive, - "MeshTools::combineIndexedAttributes(): data" << i << "is" << data[i]->primitive() << "but expected" << primitive, (Trade::MeshData{MeshPrimitive{}, 0})); - CORRADE_ASSERT(data[i]->indexCount() == indexCount, - "MeshTools::combineIndexedAttributes(): data" << i << "has" << data[i]->indexCount() << "indices but expected" << indexCount, (Trade::MeshData{MeshPrimitive{}, 0})); + CORRADE_ASSERT(data[i].primitive() == primitive, + "MeshTools::combineIndexedAttributes(): data" << i << "is" << data[i].primitive() << "but expected" << primitive, (Trade::MeshData{MeshPrimitive{}, 0})); + CORRADE_ASSERT(data[i].indexCount() == indexCount, + "MeshTools::combineIndexedAttributes(): data" << i << "has" << data[i].indexCount() << "indices but expected" << indexCount, (Trade::MeshData{MeshPrimitive{}, 0})); } indexStride += meshIndexTypeSize(indexType); } @@ -171,10 +170,6 @@ Trade::MeshData combineIndexedAttributes(const Containers::ArrayView> data) { - return combineIndexedAttributes(Containers::arrayView(data)); -} - Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, const Trade::MeshData& faceAttributes) { CORRADE_ASSERT(mesh.isIndexed(), "MeshTools::combineFaceAttributes(): vertex mesh is not indexed", @@ -236,10 +231,9 @@ Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, const Trade:: #ifndef CORRADE_NO_ASSERT "MeshTools::combineFaceAttributes():", #endif - mesh.primitive(), combinedIndices, meshIndexCount, indexStride, - Containers::arrayView>({ + mesh.primitive(), combinedIndices, meshIndexCount, indexStride, { mesh, faceAttributes - })); + }); } Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, Containers::ArrayView faceAttributes) { diff --git a/src/Magnum/MeshTools/Combine.h b/src/Magnum/MeshTools/Combine.h index bbceef152..fb0341802 100644 --- a/src/Magnum/MeshTools/Combine.h +++ b/src/Magnum/MeshTools/Combine.h @@ -35,6 +35,13 @@ #include "Magnum/MeshTools/visibility.h" #include "Magnum/Trade/Trade.h" +#ifdef MAGNUM_BUILD_DEPRECATED +/* combineIndexedAttributes() used to take an ArrayView>, + now it's through the Iterable class. Include it explicitly until people + learn to include it themselves. */ +#include +#endif + namespace Magnum { namespace MeshTools { /** @@ -88,13 +95,7 @@ implementation-specific format. @see @ref isMeshIndexTypeImplementationSpecific(), @ref isVertexFormatImplementationSpecific() */ -MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(const Containers::ArrayView> data); - -/** - * @overload - * @m_since{2020,06} - */ -MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(std::initializer_list> data); +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(const Containers::Iterable data); /** @brief Combine per-face attributes into an existing mesh diff --git a/src/Magnum/MeshTools/Concatenate.cpp b/src/Magnum/MeshTools/Concatenate.cpp index 34a0f4e73..67517cc7f 100644 --- a/src/Magnum/MeshTools/Concatenate.cpp +++ b/src/Magnum/MeshTools/Concatenate.cpp @@ -33,7 +33,7 @@ namespace Magnum { namespace MeshTools { namespace Implementation { -std::pair concatenateIndexVertexCount(Containers::ArrayView> meshes) { +std::pair concatenateIndexVertexCount(Containers::Iterable meshes) { UnsignedInt indexCount = 0; UnsignedInt vertexCount = 0; for(const Trade::MeshData& mesh: meshes) { @@ -62,7 +62,7 @@ struct MeshAttributeHash: std::hash&& indexData, const UnsignedInt vertexCount, Containers::Array&& vertexData, Containers::Array&& attributeData, const Containers::ArrayView> meshes, const char* const assertPrefix) { +Trade::MeshData concatenate(Containers::Array&& indexData, const UnsignedInt vertexCount, Containers::Array&& vertexData, Containers::Array&& attributeData, const Containers::Iterable meshes, const char* const assertPrefix) { #ifdef CORRADE_NO_ASSERT static_cast(assertPrefix); #endif @@ -82,17 +82,17 @@ Trade::MeshData concatenate(Containers::Array&& indexData, const UnsignedI /** @todo delegate to `indexTriangleStrip()` (`duplicate*()`?) etc when those are done */ CORRADE_ASSERT( - meshes.front()->primitive() != MeshPrimitive::LineStrip && - meshes.front()->primitive() != MeshPrimitive::LineLoop && - meshes.front()->primitive() != MeshPrimitive::TriangleStrip && - meshes.front()->primitive() != MeshPrimitive::TriangleFan, - assertPrefix << meshes.front()->primitive() << "is not supported, turn it into a plain indexed mesh first", + meshes.front().primitive() != MeshPrimitive::LineStrip && + meshes.front().primitive() != MeshPrimitive::LineLoop && + meshes.front().primitive() != MeshPrimitive::TriangleStrip && + meshes.front().primitive() != MeshPrimitive::TriangleFan, + assertPrefix << meshes.front().primitive() << "is not supported, turn it into a plain indexed mesh first", (Trade::MeshData{MeshPrimitive{}, 0})); /* Populate the resulting instance with what we have. It'll be used below for convenient access to vertex / index data */ auto indices = Containers::arrayCast(indexData); - Trade::MeshData out{meshes.front()->primitive(), + Trade::MeshData out{meshes.front().primitive(), /* If the index array is empty, we're creating a non-indexed mesh (not an indexed mesh with zero indices) */ std::move(indexData), indices.isEmpty() ? @@ -189,13 +189,13 @@ Trade::MeshData concatenate(Containers::Array&& indexData, const UnsignedI } -Trade::MeshData concatenate(const Containers::ArrayView> meshes, const InterleaveFlags flags) { +Trade::MeshData concatenate(const Containers::Iterable meshes, const InterleaveFlags flags) { CORRADE_ASSERT(!meshes.isEmpty(), "MeshTools::concatenate(): expected at least one mesh", (Trade::MeshData{MeshPrimitive::Points, 0})); #ifndef CORRADE_NO_ASSERT - for(std::size_t i = 0; i != meshes.front()->attributeCount(); ++i) { - const VertexFormat format = meshes.front()->attributeFormat(i); + for(std::size_t i = 0; i != meshes.front().attributeCount(); ++i) { + const VertexFormat format = meshes.front().attributeFormat(i); CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), "MeshTools::concatenate(): attribute" << i << "of the first mesh has an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), (Trade::MeshData{MeshPrimitive::Points, 0})); @@ -208,13 +208,13 @@ Trade::MeshData concatenate(const Containers::ArrayView attributeData; - if(meshes.front()->attributeCount()) - attributeData = Implementation::interleavedLayout(Trade::MeshData{meshes.front()->primitive(), - {}, meshes.front()->vertexData(), - Trade::meshAttributeDataNonOwningArray(meshes.front()->attributeData())}, {}, flags); + if(meshes.front().attributeCount()) + attributeData = Implementation::interleavedLayout(Trade::MeshData{meshes.front().primitive(), + {}, meshes.front().vertexData(), + Trade::meshAttributeDataNonOwningArray(meshes.front().attributeData())}, {}, flags); else attributeData = - Implementation::interleavedLayout(Trade::MeshData{meshes.front()->primitive(), - meshes.front()->vertexCount()}, {}, flags); + Implementation::interleavedLayout(Trade::MeshData{meshes.front().primitive(), + meshes.front().vertexCount()}, {}, flags); /* Calculate total index/vertex count and allocate the target memory. Index data are allocated with NoInit as the whole array will be written, @@ -227,8 +227,4 @@ Trade::MeshData concatenate(const Containers::ArrayView> meshes, const InterleaveFlags flags) { - return concatenate(Containers::arrayView(meshes), flags); -} - }} diff --git a/src/Magnum/MeshTools/Concatenate.h b/src/Magnum/MeshTools/Concatenate.h index 5ba5c5d43..3f5323baf 100644 --- a/src/Magnum/MeshTools/Concatenate.h +++ b/src/Magnum/MeshTools/Concatenate.h @@ -31,7 +31,7 @@ */ #include -#include +#include #include "Magnum/MeshTools/Interleave.h" #include "Magnum/Trade/MeshData.h" @@ -39,8 +39,8 @@ namespace Magnum { namespace MeshTools { namespace Implementation { - MAGNUM_MESHTOOLS_EXPORT std::pair concatenateIndexVertexCount(Containers::ArrayView> meshes); - MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Containers::Array&& indexData, UnsignedInt vertexCount, Containers::Array&& vertexData, Containers::Array&& attributeData, Containers::ArrayView> meshes, const char* assertPrefix); + MAGNUM_MESHTOOLS_EXPORT std::pair concatenateIndexVertexCount(Containers::Iterable meshes); + MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Containers::Array&& indexData, UnsignedInt vertexCount, Containers::Array&& vertexData, Containers::Array&& attributeData, Containers::Iterable meshes, const char* assertPrefix); } /** @@ -82,13 +82,7 @@ to compress it to a smaller type, if desired. @ref SceneTools::flattenMeshHierarchy2D(), @ref SceneTools::flattenMeshHierarchy3D() */ -MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Containers::ArrayView> meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes); - -/** - * @overload - * @m_since{2020,06} - */ -MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(std::initializer_list> meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes); +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Containers::Iterable meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes); /** @brief Concatenate a list of meshes into a pre-existing destination, enlarging it if necessary @@ -99,14 +93,14 @@ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(std::initializer_list>, InterleaveFlags) +Compared to @ref concatenate(Containers::Iterable, InterleaveFlags) this function resizes existing index and vertex buffers in @p destination using @ref Containers::arrayResize() and given @p allocator, and reuses its atttribute data array instead of always allocating new ones. Only the attribute layout from @p destination is used, all vertex/index data are taken from @p meshes. Expects that @p meshes contains at least one item. */ -template class Allocator = Containers::ArrayAllocator> void concatenateInto(Trade::MeshData& destination, Containers::ArrayView> meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes) { +template class Allocator = Containers::ArrayAllocator> void concatenateInto(Trade::MeshData& destination, Containers::Iterable meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes) { CORRADE_ASSERT(!meshes.isEmpty(), "MeshTools::concatenateInto(): no meshes passed", ); #ifndef CORRADE_NO_ASSERT @@ -142,14 +136,6 @@ template class Allocator = Containers::ArrayAllocator> void conc destination = Implementation::concatenate(std::move(indexData), indexVertexCount.second, std::move(vertexData), std::move(attributeData), meshes, "MeshTools::concatenateInto():"); } -/** - * @overload - * @m_since{2020,06} - */ -template class Allocator = Containers::ArrayAllocator> void concatenateInto(Trade::MeshData& destination, std::initializer_list> meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes) { - concatenateInto(destination, Containers::arrayView(meshes), flags); -} - }} #endif diff --git a/src/Magnum/MeshTools/InterleaveFlags.h b/src/Magnum/MeshTools/InterleaveFlags.h index 2bf4387d5..d9437980a 100644 --- a/src/Magnum/MeshTools/InterleaveFlags.h +++ b/src/Magnum/MeshTools/InterleaveFlags.h @@ -43,7 +43,7 @@ namespace Magnum { namespace MeshTools { @see @ref InterleaveFlags, @ref interleavedLayout(const Trade::MeshData&, UnsignedInt, Containers::ArrayView, InterleaveFlags), @ref interleave(const Trade::MeshData&, Containers::ArrayView, InterleaveFlags), - @ref concatenate(Containers::ArrayView>, InterleaveFlags) + @ref concatenate(Containers::Iterable, InterleaveFlags) */ enum class InterleaveFlag: UnsignedInt { /** @@ -72,7 +72,7 @@ enum class InterleaveFlag: UnsignedInt { * * Has no effect when passed to @ref interleavedLayout(const Trade::MeshData&, UnsignedInt, Containers::ArrayView, InterleaveFlags) "interleavedLayout()" * as that function doesn't preserve the index buffer. Has no effect when - * passed to @ref concatenate(Containers::ArrayView>, InterleaveFlags) "concatenate()" + * passed to @ref concatenate(Containers::Iterable, InterleaveFlags) "concatenate()" * as that function allocates a new combined index buffer anyway. * @see @ref isMeshIndexTypeImplementationSpecific() */ @@ -85,7 +85,7 @@ enum class InterleaveFlag: UnsignedInt { @see @ref interleavedLayout(const Trade::MeshData&, UnsignedInt, Containers::ArrayView, InterleaveFlags), @ref interleave(const Trade::MeshData&, Containers::ArrayView, InterleaveFlags), - @ref concatenate(Containers::ArrayView>, InterleaveFlags) + @ref concatenate(Containers::Iterable, InterleaveFlags) */ typedef Containers::EnumSet InterleaveFlags; diff --git a/src/Magnum/MeshTools/Test/CombineTest.cpp b/src/Magnum/MeshTools/Test/CombineTest.cpp index 0f40e4639..cda4d9485 100644 --- a/src/Magnum/MeshTools/Test/CombineTest.cpp +++ b/src/Magnum/MeshTools/Test/CombineTest.cpp @@ -24,7 +24,7 @@ */ #include -#include +#include #include #include #include From d958d73df6d5ba78b9f46a08b292e8643c771a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 16 Aug 2022 16:08:48 +0200 Subject: [PATCH 04/93] Vk: add TODOs for when Containers::Iterable is a thing. --- src/Magnum/Vk/DeviceCreateInfo.h | 3 +++ src/Magnum/Vk/FramebufferCreateInfo.h | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/Magnum/Vk/DeviceCreateInfo.h b/src/Magnum/Vk/DeviceCreateInfo.h index e7a1fd9b9..c451c1030 100644 --- a/src/Magnum/Vk/DeviceCreateInfo.h +++ b/src/Magnum/Vk/DeviceCreateInfo.h @@ -320,6 +320,9 @@ class MAGNUM_VK_EXPORT DeviceCreateInfo { * @see @ref DeviceProperties::pickQueueFamily() * @todoc link to addQueues(QueueFlags) once doxygen finally GROWS UP * and can link to &-qualified functions FFS + * @todo switch to Iterable once Iterable supports references + * so people can pass a whole array of queues without having to + * build a temporary list of references */ DeviceCreateInfo& addQueues(UnsignedInt family, Containers::ArrayView priorities, Containers::ArrayView> output) &; /** @overload */ diff --git a/src/Magnum/Vk/FramebufferCreateInfo.h b/src/Magnum/Vk/FramebufferCreateInfo.h index 4b0ee3a9f..033b15fa8 100644 --- a/src/Magnum/Vk/FramebufferCreateInfo.h +++ b/src/Magnum/Vk/FramebufferCreateInfo.h @@ -94,6 +94,10 @@ class MAGNUM_VK_EXPORT FramebufferCreateInfo { * - `attachmentCount` and `pAttachments` to a copy of * @p attachments * - `width`, `height` and `layers` to @p size + * + * @todo switch to Iterable once Iterable supports + * references so people can pass a whole array of images without + * having to build a temporary list of references */ explicit FramebufferCreateInfo(VkRenderPass renderPass, Containers::ArrayView> attachments, const Vector3i& size, Flags flags = {}); From 0f4f14bb0049c87c6d24eef6097476691fd4b37e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 16 Aug 2022 16:16:04 +0200 Subject: [PATCH 05/93] Vk: use Containers::Iterable in DescriptorSetLayoutCreateInfo. Same as the previous commit in MeshTools -- allows to pass a Containers::Array to it without having to form a list of references first. --- src/Magnum/Vk/DescriptorSetLayout.cpp | 5 +++-- src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h | 14 +++++++++++++- src/Magnum/Vk/Test/DescriptorSetLayoutTest.cpp | 1 + src/Magnum/Vk/Test/DescriptorSetLayoutVkTest.cpp | 2 ++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Magnum/Vk/DescriptorSetLayout.cpp b/src/Magnum/Vk/DescriptorSetLayout.cpp index 31cbac41e..2d604a14a 100644 --- a/src/Magnum/Vk/DescriptorSetLayout.cpp +++ b/src/Magnum/Vk/DescriptorSetLayout.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include "Magnum/Vk/Assert.h" @@ -90,7 +91,7 @@ DescriptorSetLayoutBinding& DescriptorSetLayoutBinding::operator=(DescriptorSetL return *this; } -DescriptorSetLayoutCreateInfo::DescriptorSetLayoutCreateInfo(const Containers::ArrayView> bindings, const Flags flags): _info{} { +DescriptorSetLayoutCreateInfo::DescriptorSetLayoutCreateInfo(const Containers::Iterable bindings, const Flags flags): _info{} { /* Check the total count of immutable samplers to allocate them all in a contiguous memory location. Also check if we have any binding flags. If yes, we have to create an additional array and put a structure into the @@ -146,7 +147,7 @@ DescriptorSetLayoutCreateInfo::DescriptorSetLayoutCreateInfo(const Containers::A } } -DescriptorSetLayoutCreateInfo::DescriptorSetLayoutCreateInfo(const std::initializer_list> bindings, const Flags flags): DescriptorSetLayoutCreateInfo{Containers::arrayView(bindings), flags} {} +DescriptorSetLayoutCreateInfo::DescriptorSetLayoutCreateInfo(const std::initializer_list> bindings, const Flags flags): DescriptorSetLayoutCreateInfo{Containers::Iterable{bindings}, flags} {} DescriptorSetLayoutCreateInfo::DescriptorSetLayoutCreateInfo(NoInitT) noexcept {} diff --git a/src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h b/src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h index 1ba013349..0d07ed378 100644 --- a/src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h +++ b/src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h @@ -46,6 +46,14 @@ #include "Magnum/Vk/Vk.h" #include "Magnum/Vk/Vulkan.h" +#ifdef MAGNUM_BUILD_DEPRECATED +/* DescriptorSetLayoutCreateInfo constructor used to take an + ArrayView>, now it's through the + Iterable class. Include it explicitly until people learn to include it + themselves. */ +#include +#endif + namespace Magnum { namespace Vk { /** @@ -315,8 +323,12 @@ class MAGNUM_VK_EXPORT DescriptorSetLayoutCreateInfo { * - `pBindingFlags` to a list of all * @ref DescriptorSetLayoutBinding::flags() from @p bindings */ - explicit DescriptorSetLayoutCreateInfo(Containers::ArrayView> bindings, Flags flags = {}); + explicit DescriptorSetLayoutCreateInfo(Containers::Iterable bindings, Flags flags = {}); + /** @overload */ + /* Iterable takes std::initializer_list itself but having it also here + allows to do stuff like `DescriptorSetLayoutCreateInfo{a, b}` and + (admittedly weird) `DescriptorSetLayoutCreateInfo{}` as well. */ explicit DescriptorSetLayoutCreateInfo(std::initializer_list> bindings, Flags flags = {}); /** diff --git a/src/Magnum/Vk/Test/DescriptorSetLayoutTest.cpp b/src/Magnum/Vk/Test/DescriptorSetLayoutTest.cpp index 7e229626c..1ba7b9752 100644 --- a/src/Magnum/Vk/Test/DescriptorSetLayoutTest.cpp +++ b/src/Magnum/Vk/Test/DescriptorSetLayoutTest.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include "Magnum/Vk/DescriptorSetLayoutCreateInfo.h" diff --git a/src/Magnum/Vk/Test/DescriptorSetLayoutVkTest.cpp b/src/Magnum/Vk/Test/DescriptorSetLayoutVkTest.cpp index 4600ba25f..2795abbda 100644 --- a/src/Magnum/Vk/Test/DescriptorSetLayoutVkTest.cpp +++ b/src/Magnum/Vk/Test/DescriptorSetLayoutVkTest.cpp @@ -23,6 +23,8 @@ DEALINGS IN THE SOFTWARE. */ +#include + #include "Magnum/Vk/DescriptorSetLayoutCreateInfo.h" #include "Magnum/Vk/DescriptorType.h" #include "Magnum/Vk/Result.h" From b418a8fc87f8e0fa004c9cf054cef73167d21ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 16 Aug 2022 16:24:37 +0200 Subject: [PATCH 06/93] Vk: add a clickable reference to Queue::submit() from CommandBuffer docs. Too hard / annoying to figure out what the APIs and types are otherwise. --- src/Magnum/Vk/CommandBuffer.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Magnum/Vk/CommandBuffer.h b/src/Magnum/Vk/CommandBuffer.h index a6bf6af20..7c059f464 100644 --- a/src/Magnum/Vk/CommandBuffer.h +++ b/src/Magnum/Vk/CommandBuffer.h @@ -200,9 +200,10 @@ delimited with @ref beginRenderPass() and @ref endRenderPass(), see @snippet MagnumVk.cpp CommandBuffer-usage -Once recorded, the command buffer can be submitted to a compatible @ref Queue -that was set up at @ref Vk-Device-creation "device creation time". Usually -you'd want to wait on the submit completion with a @link Fence @endlink: +Once recorded, call @ref Queue::submit() to submit the command buffer to a +compatible @ref Queue that was set up at +@ref Vk-Device-creation "device creation time". Usually you'd want to wait on +the submit completion with a @link Fence @endlink: @snippet MagnumVk.cpp CommandBuffer-usage-submit */ From 0588c1eefeb599a1fceb00fedfef096244081677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 17 Aug 2022 13:18:21 +0200 Subject: [PATCH 07/93] AnyImageConverter: adapt test to changes in OpenExrImageConverter. It now relies on ImageFlag::CubeMap instead of the custom envmap=cube configuration option. --- .../AnyImageConverter/Test/AnyImageConverterTest.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp b/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp index afbbcb47b..c01643c92 100644 --- a/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp +++ b/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp @@ -422,7 +422,7 @@ const ImageView1D Image1D{PixelFormat::RGB8Unorm, 2, Data}; const ImageView2D Image2D{PixelFormat::RGB8Unorm, {2, 3}, Data}; const ImageView2D Image2DFloat{PixelFormat::Depth32F, {3, 2}, FloatData}; const ImageView3D Image3D{PixelFormat::RGB8Unorm, {2, 3, 2}, Data}; -const ImageView3D ImageCube{PixelFormat::Depth32F, {1, 1, 6}, CubeData}; +const ImageView3D ImageCube{PixelFormat::Depth32F, {1, 1, 6}, CubeData, ImageFlag3D::CubeMap}; const CompressedImageView1D CompressedImage1D{CompressedPixelFormat::Bc1RGBAUnorm, 3, Data}; const CompressedImageView2D CompressedImage2D{CompressedPixelFormat::Bc1RGBAUnorm, {1, 3}, Data}; const CompressedImageView3D CompressedImage3D{CompressedPixelFormat::Bc1RGBAUnorm, {1, 1, 3}, Data}; @@ -1096,7 +1096,6 @@ void AnyImageConverterTest::propagateFlags3D() { CORRADE_VERIFY(Utility::Path::remove(filename)); Containers::Pointer converter = manager.instantiate("AnyImageConverter"); - converter->configuration().setValue("envmap", "cube"); /* This will make the verbose output print the detected hardware thread count, but also the info about updating global thread count for the first time. Thus run it once w/o a verbose flag and then again with to @@ -1193,7 +1192,6 @@ void AnyImageConverterTest::propagateFlagsLevels3D() { CORRADE_VERIFY(Utility::Path::remove(filename)); Containers::Pointer converter = manager.instantiate("AnyImageConverter"); - converter->configuration().setValue("envmap", "cube"); /* This will make the verbose output print the detected hardware thread count, but also the info about updating global thread count for the first time. Thus run it once w/o a verbose flag and then again with to From 8b45a5170c9e21ad2a29a3c8224fc5b5995fe9b0 Mon Sep 17 00:00:00 2001 From: Hugo Amiard Date: Wed, 10 Aug 2022 12:11:49 +0200 Subject: [PATCH 08/93] Use Corrade String[View] in some ConfigurationValue specializations. These headers are included quite often and because expands to the whole , it has quite a significant impact. Other cases like Math/ConfigurationValue.h are not changed at the moment, as that header is only included together with Utility::Arguments or Utility::Configuration -- and these classes are still STL-heavy, so avoiding std::string usage in just the ConfigurationValue specializations doesn't help anything at the moment. --- src/Magnum/Mesh.cpp | 10 +++++----- src/Magnum/Mesh.h | 9 ++++----- src/Magnum/PixelFormat.cpp | 10 +++++----- src/Magnum/PixelFormat.h | 9 ++++----- src/Magnum/VertexFormat.cpp | 6 +++--- src/Magnum/VertexFormat.h | 5 ++--- 6 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/Magnum/Mesh.cpp b/src/Magnum/Mesh.cpp index 7fbcf5b8f..0e30d0538 100644 --- a/src/Magnum/Mesh.cpp +++ b/src/Magnum/Mesh.cpp @@ -25,7 +25,7 @@ #include "Mesh.h" -#include +#include #include #include #include @@ -105,28 +105,28 @@ UnsignedInt meshIndexTypeSize(const MeshIndexType type) { namespace Corrade { namespace Utility { -std::string ConfigurationValue::toString(Magnum::MeshPrimitive value, ConfigurationValueFlags) { +Containers::String ConfigurationValue::toString(Magnum::MeshPrimitive value, ConfigurationValueFlags) { if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::MeshPrimitiveNames)) return Magnum::MeshPrimitiveNames[Magnum::UnsignedInt(value) - 1]; return {}; } -Magnum::MeshPrimitive ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { +Magnum::MeshPrimitive ConfigurationValue::fromString(Containers::StringView stringValue, ConfigurationValueFlags) { for(std::size_t i = 0; i != Containers::arraySize(Magnum::MeshPrimitiveNames); ++i) if(stringValue == Magnum::MeshPrimitiveNames[i]) return Magnum::MeshPrimitive(i + 1); return {}; } -std::string ConfigurationValue::toString(Magnum::MeshIndexType value, ConfigurationValueFlags) { +Containers::String ConfigurationValue::toString(Magnum::MeshIndexType value, ConfigurationValueFlags) { if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::MeshIndexTypeNames)) return Magnum::MeshIndexTypeNames[Magnum::UnsignedInt(value) - 1]; return {}; } -Magnum::MeshIndexType ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { +Magnum::MeshIndexType ConfigurationValue::fromString(Containers::StringView stringValue, ConfigurationValueFlags) { for(std::size_t i = 0; i != Containers::arraySize(Magnum::MeshIndexTypeNames); ++i) if(stringValue == Magnum::MeshIndexTypeNames[i]) return Magnum::MeshIndexType(i + 1); diff --git a/src/Magnum/Mesh.h b/src/Magnum/Mesh.h index 3902ba5c5..331754793 100644 --- a/src/Magnum/Mesh.h +++ b/src/Magnum/Mesh.h @@ -30,7 +30,6 @@ */ #include -#include #include "Magnum/Magnum.h" #include "Magnum/visibility.h" @@ -365,14 +364,14 @@ template<> struct MAGNUM_EXPORT ConfigurationValue { * * If the value is invalid, returns empty string. */ - static std::string toString(Magnum::MeshPrimitive value, ConfigurationValueFlags); + static Containers::String toString(Magnum::MeshPrimitive value, ConfigurationValueFlags); /** * @brief Reads enum value as string * * If the value is invalid, returns a zero (invalid) primitive. */ - static Magnum::MeshPrimitive fromString(const std::string& stringValue, ConfigurationValueFlags); + static Magnum::MeshPrimitive fromString(Containers::StringView stringValue, ConfigurationValueFlags); }; /** @configurationvalue{Magnum::MeshIndexType} */ @@ -384,14 +383,14 @@ template<> struct MAGNUM_EXPORT ConfigurationValue { * * If the value is invalid, returns empty string. */ - static std::string toString(Magnum::MeshIndexType value, ConfigurationValueFlags); + static Containers::String toString(Magnum::MeshIndexType value, ConfigurationValueFlags); /** * @brief Read enum value as string * * If the value is invalid, returns a zero (invalid) type. */ - static Magnum::MeshIndexType fromString(const std::string& stringValue, ConfigurationValueFlags); + static Magnum::MeshIndexType fromString(Containers::StringView stringValue, ConfigurationValueFlags); }; }} diff --git a/src/Magnum/PixelFormat.cpp b/src/Magnum/PixelFormat.cpp index da887735a..40afec253 100644 --- a/src/Magnum/PixelFormat.cpp +++ b/src/Magnum/PixelFormat.cpp @@ -25,7 +25,7 @@ #include "PixelFormat.h" -#include +#include #include #include #include @@ -556,14 +556,14 @@ Debug& operator<<(Debug& debug, const CompressedPixelFormat value) { namespace Corrade { namespace Utility { -std::string ConfigurationValue::toString(Magnum::PixelFormat value, ConfigurationValueFlags) { +Containers::String ConfigurationValue::toString(Magnum::PixelFormat value, ConfigurationValueFlags) { if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::PixelFormatNames)) return Magnum::PixelFormatNames[Magnum::UnsignedInt(value) - 1]; return {}; } -Magnum::PixelFormat ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { +Magnum::PixelFormat ConfigurationValue::fromString(Containers::StringView stringValue, ConfigurationValueFlags) { /** @todo This is extremely slow with >100 values. Do a binary search on a sorted index list instead (extracted into a common utility) */ for(std::size_t i = 0; i != Containers::arraySize(Magnum::PixelFormatNames); ++i) @@ -572,14 +572,14 @@ Magnum::PixelFormat ConfigurationValue::fromString(const st return {}; } -std::string ConfigurationValue::toString(Magnum::CompressedPixelFormat value, ConfigurationValueFlags) { +Containers::String ConfigurationValue::toString(Magnum::CompressedPixelFormat value, ConfigurationValueFlags) { if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::CompressedPixelFormatNames)) return Magnum::CompressedPixelFormatNames[Magnum::UnsignedInt(value) - 1]; return {}; } -Magnum::CompressedPixelFormat ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { +Magnum::CompressedPixelFormat ConfigurationValue::fromString(Containers::StringView stringValue, ConfigurationValueFlags) { /** @todo This is extremely slow with >100 values. Do a binary search on a sorted index list instead (extracted into a common utility) */ for(std::size_t i = 0; i != Containers::arraySize(Magnum::CompressedPixelFormatNames); ++i) diff --git a/src/Magnum/PixelFormat.h b/src/Magnum/PixelFormat.h index 1cf1cd298..290f5aa28 100644 --- a/src/Magnum/PixelFormat.h +++ b/src/Magnum/PixelFormat.h @@ -30,7 +30,6 @@ */ #include -#include #include "Magnum/Magnum.h" #include "Magnum/visibility.h" @@ -2491,14 +2490,14 @@ template<> struct MAGNUM_EXPORT ConfigurationValue { * * If the value is invalid, returns empty string. */ - static std::string toString(Magnum::PixelFormat value, ConfigurationValueFlags); + static Containers::String toString(Magnum::PixelFormat value, ConfigurationValueFlags); /** * @brief Reads enum value as string * * If the value is invalid, returns a zero (invalid) format. */ - static Magnum::PixelFormat fromString(const std::string& stringValue, ConfigurationValueFlags); + static Magnum::PixelFormat fromString(Containers::StringView stringValue, ConfigurationValueFlags); }; /** @@ -2513,14 +2512,14 @@ template<> struct MAGNUM_EXPORT ConfigurationValue +#include #include #include #include @@ -930,14 +930,14 @@ Debug& operator<<(Debug& debug, const VertexFormat value) { namespace Corrade { namespace Utility { -std::string ConfigurationValue::toString(Magnum::VertexFormat value, ConfigurationValueFlags) { +Containers::String ConfigurationValue::toString(Magnum::VertexFormat value, ConfigurationValueFlags) { if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::VertexFormatNames)) return Magnum::VertexFormatNames[Magnum::UnsignedInt(value) - 1]; return {}; } -Magnum::VertexFormat ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { +Magnum::VertexFormat ConfigurationValue::fromString(Containers::StringView stringValue, ConfigurationValueFlags) { for(std::size_t i = 0; i != Containers::arraySize(Magnum::VertexFormatNames); ++i) if(stringValue == Magnum::VertexFormatNames[i]) return Magnum::VertexFormat(i + 1); diff --git a/src/Magnum/VertexFormat.h b/src/Magnum/VertexFormat.h index 9cbcc9108..56dcbc3d2 100644 --- a/src/Magnum/VertexFormat.h +++ b/src/Magnum/VertexFormat.h @@ -30,7 +30,6 @@ */ #include -#include #include "Magnum/Magnum.h" #include "Magnum/visibility.h" @@ -1473,14 +1472,14 @@ template<> struct MAGNUM_EXPORT ConfigurationValue { * * If the value is invalid, returns empty string. */ - static std::string toString(Magnum::VertexFormat value, ConfigurationValueFlags); + static Containers::String toString(Magnum::VertexFormat value, ConfigurationValueFlags); /** * @brief Read enum value as string * * If the value is invalid, returns a zero (invalid) format. */ - static Magnum::VertexFormat fromString(const std::string& stringValue, ConfigurationValueFlags); + static Magnum::VertexFormat fromString(Containers::StringView stringValue, ConfigurationValueFlags); }; }} From 61ea2c7eca3cdbefe464d0a2e1701974c2d7aa99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 18 Aug 2022 11:13:12 +0200 Subject: [PATCH 09/93] doc: updated credits and changelog. --- doc/changelog.dox | 3 +++ doc/credits.dox | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 347ff6d51..f80f4bc71 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -341,6 +341,9 @@ See also: - @ref Image, @ref CompressedImage, @ref ImageView, @ref CompressedImageView as well as @ref Trade::ImageData are now able to annotate array, cube map and cube map array images using @ref ImageFlags +- Removed unnecessary @ref std::string usage from certain + @relativeref{Corrade,Utility::ConfigurationValue} specializations (see + [mosra/magnum#582](https://github.com/mosra/magnum/pull/582)) @subsubsection changelog-latest-changes-debugtools DebugTools library diff --git a/doc/credits.dox b/doc/credits.dox index f5927f862..2a5b8ba1d 100644 --- a/doc/credits.dox +++ b/doc/credits.dox @@ -147,7 +147,7 @@ Are the below lists missing your name or something's wrong? - **Hilario Pérez Corona** ([\@hpcorona](https://github.com/hpcorona)) --- improvements to @cb{.cmake} android_create_apk() @ce - **Hugo Amnov** ([\@hugoam](https://github.com/hugoam)) --- buildsystem - improvements + improvements and STL usage cleanup - **Ivan P.** ([\@uzername](https://github.com/uzername)) --- documentation improvements - **Ivan Sanz Carasa** ([\@isc30](https://github.com/isc30)) --- buildsystem From 150d045275b0de877d13818b9e605c76e5f552f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 12 Apr 2022 22:10:51 +0200 Subject: [PATCH 10/93] Trade: full scene conversion support in AbstractSceneConverter. Wasn't really possible to split this into multiple commits, so here's the whole thing including delegation from and to the single-mesh APIs. What's not done here and postponed for later is: - an ability to feed the whole importer to it, filtering away data that aren't supported by the converter - corresponding changes in AbstractImageConverter, where it would now primarily accept ImageData to future-proof for arbitrary extra key/value data --- doc/changelog.dox | 3 + src/Magnum/Trade/AbstractSceneConverter.cpp | 1044 +++- src/Magnum/Trade/AbstractSceneConverter.h | 1754 ++++++- .../Trade/Test/AbstractSceneConverterTest.cpp | 4438 ++++++++++++++++- .../AnySceneConverter/AnySceneConverter.cpp | 2 +- 5 files changed, 7134 insertions(+), 107 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index f80f4bc71..b57171cd5 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -285,6 +285,9 @@ See also: - New @ref Trade::SkinData class and @ref Trade::AbstractImporter::skin2D() / @ref Trade::AbstractImporter::skin3D() family of APIs for skin import, as well as support in @ref Trade::AnySceneImporter "AnySceneImporter" +- The @ref Trade::AbstractSceneConverter plugin interface gained support for + batch conversion of whole scenes --- meshes, hierarchies, materials, + textures, animations and other data - 1D and 3D image support in @ref Trade::AbstractImageConverter - @ref Trade::LightData got extended to support light attenuation and range parameters as well and spot light inner and outer angle diff --git a/src/Magnum/Trade/AbstractSceneConverter.cpp b/src/Magnum/Trade/AbstractSceneConverter.cpp index b72541173..a364759eb 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.cpp +++ b/src/Magnum/Trade/AbstractSceneConverter.cpp @@ -25,15 +25,23 @@ #include "AbstractSceneConverter.h" +#include #include #include +#include #include +#include #include #include #include +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ArrayAllocator.h" +#include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/SceneData.h" #ifdef MAGNUM_BUILD_DEPRECATED /* needed by deprecated convertToFile() that takes a std::string */ @@ -54,10 +62,75 @@ namespace Magnum { namespace Trade { using namespace Containers::Literals; +/* Gets allocated in begin*() and deallocated in end*() or abort(). The direct + conversion functions such as convert(const MeshData&) don't directly need + this state, but can indirectly delegate to it, such as when + convert(const MeshData&) is emulated with a sequence of begin(), + add(const MeshData&) and end(). */ +struct AbstractSceneConverter::State { + enum class Type { + Convert, + ConvertToData, + ConvertToFile + }; + + explicit State(Type type): type{type} { + if(type == Type::Convert) + new(&converted.mesh) Containers::Optional{}; + else if(type == Type::ConvertToData) + new(&converted.meshToData) Containers::Optional>{}; + else if(type == Type::ConvertToFile) + new(&converted.meshToFile) bool{}; + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + ~State() { + if(type == Type::Convert) + converted.mesh.~Optional(); + else if(type == Type::ConvertToData) + converted.meshToData.~Optional(); + else if(type == Type::ConvertToFile) + ; + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + Type type; + + UnsignedInt sceneCount = 0; + UnsignedInt animationCount = 0; + UnsignedInt lightCount = 0; + UnsignedInt cameraCount = 0; + UnsignedInt skin2DCount = 0; + UnsignedInt skin3DCount = 0; + UnsignedInt meshCount = 0; + UnsignedInt materialCount = 0; + UnsignedInt textureCount = 0; + UnsignedInt image1DCount = 0; + UnsignedInt image2DCount = 0; + UnsignedInt image3DCount = 0; + + /* Used if type == Type::ConvertToFile. Could theoretically be in the same + allocation as State (ArrayTuple?), or at least reusing the space in + `converted`, but I don't think a single allocation matters that much. */ + Containers::String filename; + + union Converted { + /* C++, FUCK OFF, what's the point of requiring me to create an + explicit constructor and destructor if I have no way to store or + know the information about the active field at this point?! */ + Converted() noexcept {} + ~Converted() {} + + Containers::Optional mesh; + Containers::Optional> meshToData; + bool meshToFile; + } converted; +}; + Containers::StringView AbstractSceneConverter::pluginInterface() { return /* [interface] */ -"cz.mosra.magnum.Trade.AbstractSceneConverter/0.1.2"_s +"cz.mosra.magnum.Trade.AbstractSceneConverter/0.2"_s /* [interface] */ ; } @@ -148,19 +221,29 @@ Containers::Optional> Implementation::SceneConverterOptionalButAlsoArray #endif AbstractSceneConverter::convertToData(const MeshData& mesh) { - CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToData, - "Trade::AbstractSceneConverter::convertToData(): mesh conversion not supported", {}); + if(features() >= SceneConverterFeature::ConvertMeshToData) { + Containers::Optional> out = doConvertToData(mesh); + CORRADE_ASSERT(!out || !out->deleter() || out->deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || out->deleter() == ArrayAllocator::deleter, + "Trade::AbstractSceneConverter::convertToData(): implementation is not allowed to use a custom Array deleter", {}); - Containers::Optional> out = doConvertToData(mesh); - CORRADE_ASSERT(!out || !out->deleter() || out->deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || out->deleter() == ArrayAllocator::deleter, - "Trade::AbstractSceneConverter::convertToData(): implementation is not allowed to use a custom Array deleter", {}); + /* GCC 4.8 needs an explicit conversion here */ + #ifdef MAGNUM_BUILD_DEPRECATED + return Implementation::SceneConverterOptionalButAlsoArray{std::move(out)}; + #else + return out; + #endif - /* GCC 4.8 needs an explicit conversion here */ - #ifdef MAGNUM_BUILD_DEPRECATED - return Implementation::SceneConverterOptionalButAlsoArray{std::move(out)}; - #else - return out; - #endif + } else if(features() >= (SceneConverterFeature::ConvertMultipleToData|SceneConverterFeature::AddMeshes)) { + beginData(); + + if(add(mesh)) return endData(); + + /* Finish the conversion even if add() fails -- this API shouldn't + leave it in an in-progress state */ + abort(); + return {}; + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convertToData(): mesh conversion not supported", {}); } Containers::Optional> AbstractSceneConverter::doConvertToData(const MeshData&) { @@ -168,10 +251,20 @@ Containers::Optional> AbstractSceneConverter::doConvertT } bool AbstractSceneConverter::convertToFile(const MeshData& mesh, const Containers::StringView filename) { - CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToFile, - "Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported", {}); + if(features() >= SceneConverterFeature::ConvertMeshToFile) { + return doConvertToFile(mesh, filename); + + } else if(features() & (SceneConverterFeature::ConvertMultipleToFile|SceneConverterFeature::AddMeshes)) { + beginFile(filename); + + if(add(mesh)) return endFile(); + + /* Finish the conversion even if add() fails -- this API shouldn't + leave it in an in-progress state */ + abort(); + return false; - return doConvertToFile(mesh, filename); + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported", {}); } #ifdef MAGNUM_BUILD_DEPRECATED @@ -183,18 +276,890 @@ bool AbstractSceneConverter::convertToFile(const std::string& filename, const Me bool AbstractSceneConverter::doConvertToFile(const MeshData& mesh, const Containers::StringView filename) { CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToData, "Trade::AbstractSceneConverter::convertToFile(): mesh conversion advertised but not implemented", false); - const Containers::Optional> data = doConvertToData(mesh); + const Containers::Optional> out = doConvertToData(mesh); + /* No deleter checks as it doesn't matter here */ + if(!out) return false; + + if(!Utility::Path::write(filename, *out)) { + Error() << "Trade::AbstractSceneConverter::convertToFile(): cannot write to file" << filename; + return false; + } + + return true; +} + +bool AbstractSceneConverter::isConverting() const { + return !!_state; +} + +void AbstractSceneConverter::abort() { + if(!_state) return; + + doAbort(); + _state = {}; +} + +void AbstractSceneConverter::doAbort() {} + +void AbstractSceneConverter::begin() { + if(_state) abort(); + + _state.emplace(State::Type::Convert); + + if(features() >= SceneConverterFeature::ConvertMultiple) { + doBegin(); + + } else if(features() & SceneConverterFeature::ConvertMesh) { + /* Actual operation performed in doAdd(const MeshData&) */ + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::begin(): feature not supported", ); +} + +void AbstractSceneConverter::doBegin() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::begin(): feature advertised but not implemented", ); +} + +Containers::Pointer AbstractSceneConverter::end() { + CORRADE_ASSERT(_state && _state->type == State::Type::Convert, + "Trade::AbstractSceneConverter::end(): no conversion in progress", {}); + + Containers::ScopeGuard deleteState{this, [](AbstractSceneConverter* self) { + self->_state = {}; + }}; + + if(features() & SceneConverterFeature::ConvertMesh) { + if(_state->meshCount != 1) { + Error{} << "Trade::AbstractSceneConverter::end(): the converter requires exactly one mesh"; + return {}; + } + + struct SingleMeshImporter: AbstractImporter { + explicit SingleMeshImporter(Containers::Optional&& mesh) noexcept: _mesh{std::move(mesh)} {} + + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return _opened; } + void doClose() override { + _opened = false; + _mesh = {}; + } + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { + /* To avoid complicated logic (such as returning non-owned + data and then having to specify the lifetime guarantees), + the mesh can be retrieved only once. Second time it's an + error. Another option would be to behave like if the + importer is closed afterwards, but that would result in + assertions which isn't nice. */ + if(!_mesh) { + Error{} << "Trade::AbstractSceneConverter::end(): mesh can be retrieved only once from a converter with just Trade::SceneConverterFeature::ConvertMesh"; + return {}; + } + + Containers::Optional out = std::move(_mesh); + _mesh = {}; + return out; + } + + private: + bool _opened = true; + Containers::Optional _mesh; + }; + + return Containers::Pointer(new SingleMeshImporter{std::move(_state->converted.mesh)}); + + } else if(features() & SceneConverterFeature::ConvertMultiple) { + return doEnd(); + + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Pointer AbstractSceneConverter::doEnd() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::end(): feature advertised but not implemented", {}); +} + +void AbstractSceneConverter::beginData() { + if(_state) abort(); + + _state.emplace(State::Type::ConvertToData); + + if(features() >= SceneConverterFeature::ConvertMultipleToData) { + doBeginData(); + + } else if(features() >= SceneConverterFeature::ConvertMeshToData) { + /* Actual operation performed in doAdd(const MeshData&) */ + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginData(): feature not supported", ); +} + +void AbstractSceneConverter::doBeginData() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginData(): feature advertised but not implemented", ); +} + +Containers::Optional> AbstractSceneConverter::endData() { + CORRADE_ASSERT(_state && _state->type == State::Type::ConvertToData, + "Trade::AbstractSceneConverter::endData(): no data conversion in progress", {}); + + Containers::ScopeGuard deleteState{this, [](AbstractSceneConverter* self) { + self->_state = {}; + }}; + + if(features() >= SceneConverterFeature::ConvertMultipleToData) { + Containers::Optional> out = doEndData(); + CORRADE_ASSERT(!out || !out->deleter() || out->deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || out->deleter() == ArrayAllocator::deleter, + "Trade::AbstractSceneConverter::endData(): implementation is not allowed to use a custom Array deleter", {}); + + return out; + + } else if(features() >= SceneConverterFeature::ConvertMeshToData) { + if(_state->meshCount != 1) { + Error{} << "Trade::AbstractSceneConverter::endData(): the converter requires exactly one mesh"; + return {}; + } + + /* No deleter validity checks here, those were performed in + convertToData(const MeshData&) already */ + + return std::move(_state->converted.meshToData); + + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Optional> AbstractSceneConverter::doEndData() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::endData(): feature advertised but not implemented", {}); +} + +void AbstractSceneConverter::beginFile(const Containers::StringView filename) { + if(_state) abort(); + + _state.emplace(State::Type::ConvertToFile); + _state->filename = Containers::String::nullTerminatedGlobalView(filename); + + if(features() >= SceneConverterFeature::ConvertMultipleToFile) { + doBeginFile(_state->filename); + + } else if(features() >= SceneConverterFeature::ConvertMeshToFile) { + /* Actual operation performed in doAdd(const MeshData&) */ + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginFile(): feature not supported", ); +} + +void AbstractSceneConverter::doBeginFile(Containers::StringView) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMultipleToData, + "Trade::AbstractSceneConverter::beginFile(): feature advertised but not implemented", ); + + doBeginData(); +} + +bool AbstractSceneConverter::endFile() { + CORRADE_ASSERT(_state && _state->type == State::Type::ConvertToFile, + "Trade::AbstractSceneConverter::endFile(): no file conversion in progress", {}); + + Containers::ScopeGuard deleteState{this, [](AbstractSceneConverter* self) { + self->_state = {}; + }}; + + if(features() >= SceneConverterFeature::ConvertMultipleToFile) { + return doEndFile(_state->filename); + + } else if(features() & SceneConverterFeature::ConvertMeshToFile) { + if(_state->meshCount != 1) { + Error{} << "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh"; + return {}; + } + + return _state->converted.meshToFile; + + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +bool AbstractSceneConverter::doEndFile(const Containers::StringView filename) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMultipleToData, + "Trade::AbstractSceneConverter::endFile(): feature advertised but not implemented", {}); + + const Containers::Optional> data = doEndData(); /* No deleter checks as it doesn't matter here */ if(!data) return false; if(!Utility::Path::write(filename, *data)) { - Error() << "Trade::AbstractSceneConverter::convertToFile(): cannot write to file" << filename; + Error{} << "Trade::AbstractSceneConverter::endFile(): cannot write to file" << filename; return false; } return true; } +UnsignedInt AbstractSceneConverter::sceneCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::sceneCount(): no conversion in progress", {}); + return _state->sceneCount; +} + +Containers::Optional AbstractSceneConverter::add(const SceneData& scene, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddScenes, + "Trade::AbstractSceneConverter::add(): scene conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->sceneCount, scene, name)) + return _state->sceneCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const SceneData& scene) { + return add(scene, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const SceneData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): scene conversion advertised but not implemented", {}); +} + +void AbstractSceneConverter::setSceneFieldName(const SceneField field, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddScenes, + "Trade::AbstractSceneConverter::setSceneFieldName(): feature not supported", ); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::setSceneFieldName(): no conversion in progress", ); + CORRADE_ASSERT(isSceneFieldCustom(field), + "Trade::AbstractSceneConverter::setSceneFieldName():" << field << "is not custom", ); + + doSetSceneFieldName(sceneFieldCustom(field), name); +} + +void AbstractSceneConverter::doSetSceneFieldName(UnsignedInt, Containers::StringView) {} + +void AbstractSceneConverter::setObjectName(const UnsignedLong object, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddScenes, + "Trade::AbstractSceneConverter::setObjectName(): feature not supported", ); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::setObjectName(): no conversion in progress", ); + + doSetObjectName(object, name); +} + +void AbstractSceneConverter::doSetObjectName(UnsignedLong, Containers::StringView) {} + +void AbstractSceneConverter::setDefaultScene(const UnsignedInt id) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddScenes, + "Trade::AbstractSceneConverter::setDefaultScene(): feature not supported", ); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::setDefaultScene(): no conversion in progress", ); + CORRADE_ASSERT(id < _state->sceneCount, + "Trade::AbstractSceneConverter::setDefaultScene(): index" << id << "out of range for" << _state->sceneCount << "scenes", ); + + doSetDefaultScene(id); +} + +void AbstractSceneConverter::doSetDefaultScene(UnsignedInt) {} + +UnsignedInt AbstractSceneConverter::animationCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::animationCount(): no conversion in progress", {}); + return _state->animationCount; +} + +Containers::Optional AbstractSceneConverter::add(const AnimationData& animation, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddAnimations, + "Trade::AbstractSceneConverter::add(): animation conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->animationCount, animation, name)) + return _state->animationCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const AnimationData& animation) { + return add(animation, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const AnimationData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): animation conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::lightCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::lightCount(): no conversion in progress", {}); + return _state->lightCount; +} + +Containers::Optional AbstractSceneConverter::add(const LightData& light, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddLights, + "Trade::AbstractSceneConverter::add(): light conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->lightCount, light, name)) + return _state->lightCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const LightData& light) { + return add(light, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const LightData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): light conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::cameraCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::cameraCount(): no conversion in progress", {}); + return _state->cameraCount; +} + +Containers::Optional AbstractSceneConverter::add(const CameraData& camera, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddCameras, + "Trade::AbstractSceneConverter::add(): camera conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->cameraCount, camera, name)) + return _state->cameraCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const CameraData& camera) { + return add(camera, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const CameraData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): camera conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::skin2DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::skin2DCount(): no conversion in progress", {}); + return _state->skin2DCount; +} + +Containers::Optional AbstractSceneConverter::add(const SkinData2D& skin, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddSkins2D, + "Trade::AbstractSceneConverter::add(): 2D skin conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->skin2DCount, skin, name)) + return _state->skin2DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const SkinData2D& skin) { + return add(skin, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const SkinData2D&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): 2D skin conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::skin3DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::skin3DCount(): no conversion in progress", {}); + return _state->skin3DCount; +} + +Containers::Optional AbstractSceneConverter::add(const SkinData3D& skin, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddSkins3D, + "Trade::AbstractSceneConverter::add(): 3D skin conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->skin3DCount, skin, name)) + return _state->skin3DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const SkinData3D& skin) { + return add(skin, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const SkinData3D&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): 3D skin conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::meshCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::meshCount(): no conversion in progress", {}); + return _state->meshCount; +} + +Containers::Optional AbstractSceneConverter::add(const MeshData& mesh, const Containers::StringView name) { + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(features() >= SceneConverterFeature::AddMeshes) { + if(!doAdd(_state->meshCount, mesh, name)) return {}; + + } else if(features() & (SceneConverterFeature::ConvertMesh| + SceneConverterFeature::ConvertMeshToData| + SceneConverterFeature::ConvertMeshToFile)) { + if(_state->meshCount != 0) { + Error{} << "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh"; + return {}; + } + + if(_state->type == State::Type::Convert) { + CORRADE_INTERNAL_ASSERT(features() & SceneConverterFeature::ConvertMesh); + if(!(_state->converted.mesh = doConvert(mesh))) + return {}; + } else if(_state->type == State::Type::ConvertToData) { + CORRADE_INTERNAL_ASSERT(features() >= SceneConverterFeature::ConvertMeshToData); + if(!(_state->converted.meshToData = doConvertToData(mesh))) + return {}; + } else if(_state->type == State::Type::ConvertToFile) { + CORRADE_INTERNAL_ASSERT(features() & SceneConverterFeature::ConvertMeshToFile); + if(!(_state->converted.meshToFile = doConvertToFile(mesh, _state->filename))) + return {}; + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): mesh conversion not supported", {}); + + return _state->meshCount++; +} + +Containers::Optional AbstractSceneConverter::add(const MeshData& mesh) { + return add(mesh, {}); +} + +bool AbstractSceneConverter::doAdd(const UnsignedInt id, const MeshData& mesh, const Containers::StringView name) { + CORRADE_ASSERT(features() >= SceneConverterFeature::MeshLevels, "Trade::AbstractSceneConverter::add(): mesh conversion advertised but not implemented", {}); + + return doAdd(id, Containers::Iterable{mesh}, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable meshLevels, const Containers::StringView name) { + CORRADE_ASSERT(features() >= (SceneConverterFeature::AddMeshes|SceneConverterFeature::MeshLevels), + "Trade::AbstractSceneConverter::add(): multi-level mesh conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + CORRADE_ASSERT(!meshLevels.isEmpty(), + "Trade::AbstractSceneConverter::add(): at least one mesh level has to be specified", false); + + if(doAdd(_state->meshCount, meshLevels, name)) + return _state->meshCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable meshLevels) { + return add(meshLevels, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): multi-level mesh conversion advertised but not implemented", {}); +} + +void AbstractSceneConverter::setMeshAttributeName(const MeshAttribute attribute, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddMeshes, + "Trade::AbstractSceneConverter::setMeshAttributeName(): feature not supported", ); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::setMeshAttributeName(): no conversion in progress", ); + CORRADE_ASSERT(isMeshAttributeCustom(attribute), + "Trade::AbstractSceneConverter::setMeshAttributeName():" << attribute << "is not custom", ); + + doSetMeshAttributeName(meshAttributeCustom(attribute), name); +} + +void AbstractSceneConverter::doSetMeshAttributeName(UnsignedShort, Containers::StringView) {} + +UnsignedInt AbstractSceneConverter::materialCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::materialCount(): no conversion in progress", {}); + return _state->materialCount; +} + +Containers::Optional AbstractSceneConverter::add(const MaterialData& material, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddMaterials, + "Trade::AbstractSceneConverter::add(): material conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->materialCount, material, name)) + return _state->materialCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const MaterialData& material) { + return add(material, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const MaterialData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): material conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::textureCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::textureCount(): no conversion in progress", {}); + return _state->textureCount; +} + +Containers::Optional AbstractSceneConverter::add(const TextureData& texture, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddTextures, + "Trade::AbstractSceneConverter::add(): texture conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->textureCount, texture, name)) + return _state->textureCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const TextureData& texture) { + return add(texture, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const TextureData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): texture conversion advertised but not implemented", {}); +} + +#ifndef CORRADE_NO_ASSERT +namespace { + +template bool checkImageValidity(const char* const messagePrefix, const ImageData& image) { + /* At some point there might be a file format that allows zero-sized + images, but so far I don't know about any. When such format appears, + this check will get moved to plugin implementations that can't work with + zero-sized images. + + Also note that this check isn't done for the Image->Image conversion + above, there zero-sized images and nullptr *could* make sense. */ + CORRADE_ASSERT(image.size().product(), + messagePrefix << "can't add image with a zero size:" << image.size(), false); + CORRADE_ASSERT(image.data(), + messagePrefix << "can't add image with a nullptr view", false); + return true; +} + +template bool checkImageValidity(const char* const messagePrefix, const Containers::Iterable> imageLevels) { + CORRADE_ASSERT(!imageLevels.isEmpty(), + messagePrefix << "at least one image level has to be specified", false); + + const bool isCompressed = imageLevels[0].isCompressed(); + const PixelFormat format = isCompressed ? PixelFormat{} : imageLevels[0].format(); + const UnsignedInt formatExtra = isCompressed ? 0 : imageLevels[0].formatExtra(); + const CompressedPixelFormat compressedFormat = isCompressed ? imageLevels[0].compressedFormat() : CompressedPixelFormat{}; + const ImageFlags flags = imageLevels[0].flags(); + /* Going through *all* levels although the format assertion is never fired + in the first iteration in order to properly check also the first one for + zero size / nullptr. */ + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + CORRADE_ASSERT(imageLevels[i].size().product(), + messagePrefix << "can't add image level" << i << "with a zero size:" << imageLevels[i].size(), false); + CORRADE_ASSERT(imageLevels[i].data(), + messagePrefix << "can't add image level" << i << "with a nullptr view", false); + CORRADE_ASSERT(imageLevels[i].isCompressed() == isCompressed, + messagePrefix << "image level" << i << (isCompressed ? "is not" : "is") << "compressed but previous" << (isCompressed ? "are" : "aren't"), false); + if(!isCompressed) { + CORRADE_ASSERT(imageLevels[i].format() == format, + messagePrefix << "image levels don't have the same format, expected" << format << "but got" << imageLevels[i].format() << "for level" << i, false); + CORRADE_ASSERT(imageLevels[i].formatExtra() == formatExtra, + messagePrefix << "image levels don't have the same extra format field, expected" << formatExtra << "but got" << imageLevels[i].formatExtra() << "for level" << i, false); + } else { + CORRADE_ASSERT(imageLevels[i].compressedFormat() == compressedFormat, + messagePrefix << "image levels don't have the same format, expected" << compressedFormat << "but got" << imageLevels[i].compressedFormat() << "for level" << i, false); + } + CORRADE_ASSERT(imageLevels[i].flags() == flags, + messagePrefix << "image levels don't have the same flags, expected" << flags << "but got" << imageLevels[i].flags() << "for level" << i, false); + } + + return true; +} + +} +#endif + +UnsignedInt AbstractSceneConverter::image1DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::image1DCount(): no conversion in progress", {}); + return _state->image1DCount; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData1D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() & (image.isCompressed() ? SceneConverterFeature::AddCompressedImages1D : SceneConverterFeature::AddImages1D), + "Trade::AbstractSceneConverter::add():" << (image.isCompressed() ? "compressed 1D" : "1D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", image)) + return {}; + #endif + + if(doAdd(_state->image1DCount, image, name)) + return _state->image1DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData1D& image) { + return add(image, {}); +} + +bool AbstractSceneConverter::doAdd(const UnsignedInt id, const ImageData1D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ImageLevels, "Trade::AbstractSceneConverter::add(): 1D image conversion advertised but not implemented", {}); + + return doAdd(id, Containers::Iterable{image}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView1D& image, const Containers::StringView name) { + return add(ImageData1D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView1D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView1D& image, const Containers::StringView name) { + return add(ImageData1D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView1D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds. + Has to be first so we can safely ask for the first item in asserts + below. */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", imageLevels)) + return {}; + #endif + CORRADE_ASSERT(features() >= ((imageLevels.front().isCompressed() ? SceneConverterFeature::AddCompressedImages1D : SceneConverterFeature::AddImages1D)|SceneConverterFeature::ImageLevels), + "Trade::AbstractSceneConverter::add(): multi-level" << (imageLevels.front().isCompressed() ? "compressed 1D" : "1D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->image1DCount, imageLevels, name)) + return _state->image1DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): multi-level 1D image conversion advertised but not implemented", {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const ImageView1D& image = imageLevels[i]; + new(&data[i]) ImageData1D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const CompressedImageView1D& image = imageLevels[i]; + new(&data[i]) ImageData1D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +UnsignedInt AbstractSceneConverter::image2DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::image2DCount(): no conversion in progress", {}); + return _state->image2DCount; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData2D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() & (image.isCompressed() ? SceneConverterFeature::AddCompressedImages2D : SceneConverterFeature::AddImages2D), + "Trade::AbstractSceneConverter::add():" << (image.isCompressed() ? "compressed 2D" : "2D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", image)) + return {}; + #endif + + if(doAdd(_state->image2DCount, image, name)) + return _state->image2DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData2D& image) { + return add(image, {}); +} + +bool AbstractSceneConverter::doAdd(const UnsignedInt id, const ImageData2D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ImageLevels, "Trade::AbstractSceneConverter::add(): 2D image conversion advertised but not implemented", {}); + + return doAdd(id, Containers::Iterable{image}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView2D& image, const Containers::StringView name) { + return add(ImageData2D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView2D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView2D& image, const Containers::StringView name) { + return add(ImageData2D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView2D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds. + Has to be first so we can safely ask for the first item in asserts + below. */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", imageLevels)) + return {}; + #endif + CORRADE_ASSERT(features() >= ((imageLevels.front().isCompressed() ? SceneConverterFeature::AddCompressedImages2D : SceneConverterFeature::AddImages2D)|SceneConverterFeature::ImageLevels), + "Trade::AbstractSceneConverter::add(): multi-level" << (imageLevels.front().isCompressed() ? "compressed 2D" : "2D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->image2DCount, imageLevels, name)) + return _state->image2DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): multi-level 2D image conversion advertised but not implemented", {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const ImageView2D& image = imageLevels[i]; + new(&data[i]) ImageData2D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const CompressedImageView2D& image = imageLevels[i]; + new(&data[i]) ImageData2D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +UnsignedInt AbstractSceneConverter::image3DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::image3DCount(): no conversion in progress", {}); + return _state->image3DCount; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData3D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() & (image.isCompressed() ? SceneConverterFeature::AddCompressedImages3D : SceneConverterFeature::AddImages3D), + "Trade::AbstractSceneConverter::add():" << (image.isCompressed() ? "compressed 3D" : "3D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", image)) + return {}; + #endif + + if(doAdd(_state->image3DCount, image, name)) + return _state->image3DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData3D& image) { + return add(image, {}); +} + +bool AbstractSceneConverter::doAdd(const UnsignedInt id, const ImageData3D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ImageLevels, "Trade::AbstractSceneConverter::add(): 3D image conversion advertised but not implemented", {}); + + return doAdd(id, Containers::Iterable{image}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView3D& image, const Containers::StringView name) { + return add(ImageData3D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView3D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView3D& image, const Containers::StringView name) { + return add(ImageData3D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView3D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds. + Has to be first so we can safely ask for the first item in asserts + below. */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", imageLevels)) + return {}; + #endif + CORRADE_ASSERT(features() >= ((imageLevels.front().isCompressed() ? SceneConverterFeature::AddCompressedImages3D : SceneConverterFeature::AddImages3D)|SceneConverterFeature::ImageLevels), + "Trade::AbstractSceneConverter::add(): multi-level" << (imageLevels.front().isCompressed() ? "compressed 3D" : "3D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->image3DCount, imageLevels, name)) + return _state->image3DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): multi-level 3D image conversion advertised but not implemented", {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const ImageView3D& image = imageLevels[i]; + new(&data[i]) ImageData3D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const CompressedImageView3D& image = imageLevels[i]; + new(&data[i]) ImageData3D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + Debug& operator<<(Debug& debug, const SceneConverterFeature value) { debug << "Trade::SceneConverterFeature" << Debug::nospace; @@ -205,11 +1170,31 @@ Debug& operator<<(Debug& debug, const SceneConverterFeature value) { _c(ConvertMeshInPlace) _c(ConvertMeshToData) _c(ConvertMeshToFile) + _c(ConvertMultiple) + _c(ConvertMultipleToData) + _c(ConvertMultipleToFile) + _c(AddScenes) + _c(AddAnimations) + _c(AddLights) + _c(AddCameras) + _c(AddSkins2D) + _c(AddSkins3D) + _c(AddMeshes) + _c(AddMaterials) + _c(AddTextures) + _c(AddImages1D) + _c(AddImages2D) + _c(AddImages3D) + _c(AddCompressedImages1D) + _c(AddCompressedImages2D) + _c(AddCompressedImages3D) + _c(MeshLevels) + _c(ImageLevels) #undef _c /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; } Debug& operator<<(Debug& debug, const SceneConverterFeatures value) { @@ -218,7 +1203,28 @@ Debug& operator<<(Debug& debug, const SceneConverterFeatures value) { SceneConverterFeature::ConvertMeshInPlace, SceneConverterFeature::ConvertMeshToData, /* Implied by ConvertMeshToData, has to be after */ - SceneConverterFeature::ConvertMeshToFile}); + SceneConverterFeature::ConvertMeshToFile, + SceneConverterFeature::ConvertMultiple, + SceneConverterFeature::ConvertMultipleToData, + /* Implied by ConvertMultipleToData, has to be after */ + SceneConverterFeature::ConvertMultipleToFile, + SceneConverterFeature::AddScenes, + SceneConverterFeature::AddAnimations, + SceneConverterFeature::AddLights, + SceneConverterFeature::AddCameras, + SceneConverterFeature::AddSkins2D, + SceneConverterFeature::AddSkins3D, + SceneConverterFeature::AddMeshes, + SceneConverterFeature::AddMaterials, + SceneConverterFeature::AddTextures, + SceneConverterFeature::AddImages1D, + SceneConverterFeature::AddImages2D, + SceneConverterFeature::AddImages3D, + SceneConverterFeature::AddCompressedImages1D, + SceneConverterFeature::AddCompressedImages2D, + SceneConverterFeature::AddCompressedImages3D, + SceneConverterFeature::MeshLevels, + SceneConverterFeature::ImageLevels}); } Debug& operator<<(Debug& debug, const SceneConverterFlag value) { diff --git a/src/Magnum/Trade/AbstractSceneConverter.h b/src/Magnum/Trade/AbstractSceneConverter.h index f5cac1b5b..226df8035 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.h +++ b/src/Magnum/Trade/AbstractSceneConverter.h @@ -30,6 +30,7 @@ * @m_since{2020,06} */ +#include #include #include "Magnum/Magnum.h" @@ -53,31 +54,214 @@ namespace Magnum { namespace Trade { @see @ref SceneConverterFeatures, @ref AbstractSceneConverter::features() */ -enum class SceneConverterFeature: UnsignedByte { +enum class SceneConverterFeature: UnsignedInt { /** - * Convert a mesh with - * @ref AbstractSceneConverter::convert(const MeshData&). + * Convert a single mesh instance with + * @ref AbstractSceneConverter::convert(const MeshData&). The function can + * be also used if both @ref SceneConverterFeature::ConvertMultiple and + * @ref SceneConverterFeature::AddMeshes are supported. */ ConvertMesh = 1 << 0, /** - * Convert a mesh in-place with + * Convert a single mesh instance in-place with * @ref AbstractSceneConverter::convertInPlace(MeshData&). */ ConvertMeshInPlace = 1 << 1, /** - * Converting a mesh to a file with - * @ref AbstractSceneConverter::convertToFile(const MeshData&, Containers::StringView). + * Convert a single mesh instance to a file with + * @ref AbstractSceneConverter::convertToFile(const MeshData&, Containers::StringView). The function can be also used if both + * @ref SceneConverterFeature::ConvertMultipleToFile and + * @ref SceneConverterFeature::AddMeshes are supported. */ ConvertMeshToFile = 1 << 2, /** - * Converting a mesh to raw data with + * Convert a single mesh instance to raw data with * @ref AbstractSceneConverter::convertToData(const MeshData&). Implies - * @ref SceneConverterFeature::ConvertMeshToFile. + * @ref SceneConverterFeature::ConvertMeshToFile. The function can be also + * used if both @ref SceneConverterFeature::ConvertMultipleToData and + * @ref SceneConverterFeature::AddMeshes are supported. */ - ConvertMeshToData = ConvertMeshToFile|(1 << 3) + ConvertMeshToData = ConvertMeshToFile|(1 << 3), + + /** + * Convert multiple data with + * @ref AbstractSceneConverter::begin() and + * @relativeref{AbstractSceneConverter,end()}. + * @m_since_latest + */ + ConvertMultiple = 1 << 4, + + /** + * Convert multiple data to a file with + * @ref AbstractSceneConverter::beginFile() and + * @relativeref{AbstractSceneConverter,endFile()}. The functions can be + * also used if @ref SceneConverterFeature::ConvertMeshToFile is supported. + * @m_since_latest + */ + ConvertMultipleToFile = 1 << 5, + + /** + * Convert multiple data to raw data with + * @ref AbstractSceneConverter::beginData() and + * @relativeref{AbstractSceneConverter,endData()}. Implies + * @ref SceneConverterFeature::ConvertMultipleToFile. The functions can be + * also used if @ref SceneConverterFeature::ConvertMeshToData is supported. + * @m_since_latest + */ + ConvertMultipleToData = ConvertMultipleToFile|(1 << 6), + + /** + * Add scene instances with + * @ref AbstractSceneConverter::add(const SceneData&, Containers::StringView), + * together with @relativeref{AbstractSceneConverter,setSceneFieldName()}, + * @relativeref{AbstractSceneConverter,setObjectName()} and + * @relativeref{AbstractSceneConverter,setDefaultScene()}. + * @m_since_latest + */ + AddScenes = 1 << 7, + + /** + * Add animation instances with + * @ref AbstractSceneConverter::add(const AnimationData&, Containers::StringView). + * @m_since_latest + */ + AddAnimations = 1 << 8, + + /** + * Add light instances with + * @ref AbstractSceneConverter::add(const LightData&, Containers::StringView). + * @m_since_latest + */ + AddLights = 1 << 9, + + /** + * Add camera instances with + * @ref AbstractSceneConverter::add(const CameraData&, Containers::StringView). + * @m_since_latest + */ + AddCameras = 1 << 10, + + /** + * Add 2D skin instances with + * @ref AbstractSceneConverter::add(const SkinData2D&, Containers::StringView). + * @m_since_latest + */ + AddSkins2D = 1 << 11, + + /** + * Add 3D skin instances with + * @ref AbstractSceneConverter::add(const SkinData3D&, Containers::StringView). + * @m_since_latest + */ + AddSkins3D = 1 << 12, + + /** + * Add single-level mesh instances with + * @ref AbstractSceneConverter::add(const MeshData&, Containers::StringView), + * together with @relativeref{AbstractSceneConverter,setMeshAttributeName()}. + * This function can be also used if + * @ref SceneConverterFeature::ConvertMesh, + * @ref SceneConverterFeature::ConvertMeshToFile or + * @ref SceneConverterFeature::ConvertMeshToData is supported. + * @m_since_latest + * @see @ref SceneConverterFeature::MeshLevels + */ + AddMeshes = 1 << 13, + + /** + * Add material instances with + * @ref AbstractSceneConverter::add(const MaterialData&, Containers::StringView). + * @m_since_latest + */ + AddMaterials = 1 << 14, + + /** + * Add texture instances with + * @ref AbstractSceneConverter::add(const TextureData&, Containers::StringView). + * @m_since_latest + */ + AddTextures = 1 << 15, + + /** + * Add single-level 1D image instances with + * @ref AbstractSceneConverter::add(const ImageData1D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const ImageView1D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddImages1D = 1 << 16, + + /** + * Add single-level 2D image instances with + * @ref AbstractSceneConverter::add(const ImageData2D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const ImageView2D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddImages2D = 1 << 17, + + /** + * Add single-level 3D image instances with + * @ref AbstractSceneConverter::add(const ImageData3D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const ImageView3D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddImages3D = 1 << 18, + + /** + * Add single-level compressed 1D image instances with + * @ref AbstractSceneConverter::add(const ImageData1D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const CompressedImageView1D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddCompressedImages1D = 1 << 19, + + /** + * Add single-level compressed 2D image instances with + * @ref AbstractSceneConverter::add(const ImageData2D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const CompressedImageView2D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddCompressedImages2D = 1 << 20, + + /** + * Add single-level compressed 3D image instances with + * @ref AbstractSceneConverter::add(const ImageData3D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const CompressedImageView3D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddCompressedImages3D = 1 << 21, + + /** + * Add multiple mesh levels with + * @ref AbstractSceneConverter::add(Containers::Iterable, Containers::StringView) + * if @ref SceneConverterFeature::AddMeshes is also supported. + * @m_since_latest + */ + MeshLevels = 1 << 22, + + /** + * Add multiple image levels with + * @ref AbstractSceneConverter::add(Containers::Iterable, Containers::StringView) + * if @ref SceneConverterFeature::AddImages1D or + * @relativeref{SceneConverterFeature,AddCompressedImages1D} is also + * supported; with @ref AbstractSceneConverter::add(Containers::Iterable, Containers::StringView) + * if @ref SceneConverterFeature::AddImages2D or + * @relativeref{SceneConverterFeature,AddCompressedImages2D} is also + * supported; or with @ref AbstractSceneConverter::add(Containers::Iterable, Containers::StringView) + * if @ref SceneConverterFeature::AddImages3D or + * @relativeref{SceneConverterFeature,AddCompressedImages3D} is also + * supported. + * @m_since_latest + */ + ImageLevels = 1 << 23 }; /** @@ -162,15 +346,16 @@ namespace Implementation { Provides functionality for converting meshes and other scene data between various formats or performing optimizations and other operations on them. -The interface supports three main kinds of operation, with implementations -advertising support for a subset of them via @ref features(): +The interface provides a direct and a batch interface, with implementations +advertising support for a subset of them via @ref features(). The direct +interface has three main kinds of operation: - Saving a mesh to a file / data using @ref convertToFile(const MeshData&, Containers::StringView) / @ref convertToData(const MeshData&). This is mostly for exporting the mesh data to a common format like OBJ or PLY in order to be used with an external tool. Advertised with @ref SceneConverterFeature::ConvertMeshToFile - or @ref SceneConverterFeature::ConvertMeshToData + / @relativeref{SceneConverterFeature,ConvertMeshToData}. - Performing an operation on the mesh data itself using @ref convert(const MeshData&), from which you get a @ref MeshData again. This includes operations like mesh decimation or topology cleanup. @@ -181,23 +366,48 @@ advertising support for a subset of them via @ref features(): shuffle the data around. Advertised with @ref SceneConverterFeature::ConvertMeshInPlace. +The batch interface allows converting a whole scene consisting of multiple +meshes, materials, images, ... instead of a single mesh. Similarly, it has two +main kinds of operation: + +- Saving a scene to a file / data using @ref beginFile() / @ref beginData() + and @ref endFile() / @ref endData(). This is the usual process for + exporting a whole scene to file formats such as glTF. Advertised with + @ref SceneConverterFeature::ConvertMultipleToFile / + @relativeref{SceneConverterFeature,ConvertMultipleToData}. +- Performing an operation on the whole scene using @ref begin() and + @ref end(), getting a live @ref AbstractImporter instance as a result. This + includes more complex operations such as scale-aware mesh decimation or + texture downsampling, joining meshes with the same material, or exploding + large meshes into smaller and easier to cull chunks. Advertised with + @ref SceneConverterFeature::ConvertMultiple. + +The actual data is then supplied to the converter in between the begin and end +calls using various @ref add() overloads and related APIs. Support for +particular data types is then advertised with +@ref SceneConverterFeature::AddScenes, +@relativeref{SceneConverterFeature,AddMeshes}, +@relativeref{SceneConverterFeature,AddCameras}, +@relativeref{SceneConverterFeature,AddImages2D} etc. + @section Trade-AbstractSceneConverter-usage Usage Scene converters are commonly implemented as plugins, which means the concrete converter implementation is loaded and instantiated through a @relativeref{Corrade,PluginManager::Manager}. Then, based on the intent and on -what the particular converter supports, @ref convertToFile(), -@ref convertToData(), @ref convert() or @ref convertInPlace() gets called. +what the particular converter supports, either a single-mesh conversion with +@ref convert() and related functions is performed, or a whole-scene conversion +with @ref begin(), various @ref add() APIs and @ref end() is done. -As each converter has different requirements on the input data layout and -vertex formats, you're expected to perform error handling on the application -side --- if a conversion fails, you get an empty +As each converter has different requirements on the input data, their layout +and formats, you're expected to perform error handling on the application side +--- if a conversion fails, you get an empty @relativeref{Corrade,Containers::Optional} or @cpp false @ce and a reason printed to @relativeref{Magnum,Error}. Everything else (using a feature not implemented in the converter, ...) is treated as a programmer error and will produce the usual assertions. -@subsection Trade-AbstractSceneConverter-usage-file Saving a mesh to a file +@subsection Trade-AbstractSceneConverter-usage-mesh-file Converting a single mesh to a file In the following example a mesh is saved to a PLY file using the @ref AnySceneConverter plugin, together with all needed error handling. In this @@ -219,7 +429,7 @@ scene converter plugins. that exposes functionality of all scene converter plugins through a command line interface. -@subsection Trade-AbstractSceneConverter-usage-mesh Converting mesh data +@subsection Trade-AbstractSceneConverter-usage-mesh-data Converting a single mesh data In the following snippet we use the @ref MeshOptimizerSceneConverter to perform a set of optimizations on the mesh to make it render faster. While @@ -234,7 +444,7 @@ of configuration options to specify what actually gets done and how, and the default setup may not even do anything. See @ref plugins-configuration for details and a usage example. -@subsection Trade-AbstractSceneConverter-usage-mesh-in-place Converting mesh data in-place +@subsection Trade-AbstractSceneConverter-usage-mesh-in-place Converting a single mesh data in-place Certain operations such as buffer reordering can be performed by directly modifying the input data instead of having to allocate a copy of the whole @@ -249,6 +459,16 @@ following: @snippet MagnumTrade.cpp AbstractSceneConverter-usage-mesh-in-place +@subsection Trade-AbstractSceneConverter-usage-multiple Converting multiple data + +While the operations shown above are convenient enough for simple cases +involving just a single mesh, general conversion of a whole scene needs much +more than that. Such conversion is done in a batch way --- first initializing +the conversion of desired kind, adding particular data, and finalizing the +conversion together with getting the output back. + +@todoc usage example once a batch converter exists + @section Trade-AbstractSceneConverter-data-dependency Data dependency The instances returned from various functions *by design* have no dependency on @@ -263,8 +483,11 @@ plugin module has been unloaded. @section Trade-AbstractSceneConverter-subclassing Subclassing The plugin needs to implement the @ref doFeatures() function and one or more of -@ref doConvert(), @ref doConvertInPlace(), @ref doConvertToData() or -@ref doConvertToFile() functions based on what features are supported. +@ref doConvert(), @ref doConvertInPlace(), @ref doConvertToData(), +@ref doConvertToFile() functions based on what single-mesh conversion features +are supported, or pairs of @ref doBegin() / @ref doEnd(), @ref doBeginData() / +@ref doEndData(), @ref doBeginFile() / @ref doEndFile() functions and one or +more @ref doAdd() functions based on what multiple-data features are supported. You don't need to do most of the redundant sanity checks, these things are checked by the implementation: @@ -278,6 +501,58 @@ checked by the implementation: - The @ref doConvertToFile(const MeshData&, Containers::StringView) function is called only if @ref SceneConverterFeature::ConvertMeshToFile is supported. +- The @ref doBegin() and @ref doEnd() functions are called only if + @ref SceneConverterFeature::ConvertMultiple is supported. +- The @ref doBeginData() and @ref doEndData() functions are called only if + @ref SceneConverterFeature::ConvertMultipleToData is supported. +- The @ref doBeginFile() and @ref doEndFile() functions are called only if + @ref SceneConverterFeature::ConvertMultipleToFile is supported. +- The @ref doEnd(), @ref doEndData(), @ref doEndFile(), @ref doAbort() and + @ref doAdd() functions are called only if a corresponding @ref begin(), + @ref beginData() or @ref beginFile() was called before and @ref abort() + wasn't called in the meantime. +- The @ref doAdd() and various `doSet*()` functions are called only if a + corresponding @ref SceneConverterFeature is supported. +- The @ref doAdd((UnsignedInt, Containers::Iterable, Containers::StringView) + function is called only if the list has at least one mesh +- All @ref doAdd() functions taking a single image are called only if the + image has a non-zero size in all dimensions and the data is not + @cpp nullptr @ce. +- All @ref doAdd() functions taking multiple images are called only if the + list has at least one image, each of the images has a non-zero size, the + data are not @cpp nullptr @ce and additionally all images are either + uncompressed or all compressed and they have the same pixel format. + Since file formats have varying requirements on image level sizes and their + order and some don't impose any requirements at all, the plugin + implementation is expected to check the sizes on its own. + +For user convenience it's possible to use a single-mesh converter through the +multi-mesh interface as well as use a multi-mesh converter through the +single-mesh interface. The base class can proxy one to the other and does all +necessary edge-case checks: + +- If you have a multi-mesh interface implemented using @ref doBeginFile() / + @ref doBeginData(), @ref doEndFile() / @ref doEndData() and + @ref doAdd(UnsignedInt, const MeshData&, Containers::StringView), it's + automatically delegated to from + @ref convertToFile(const MeshData&, Containers::StringView) + and @ref convertToData(const MeshData&). +- If you have a single-mesh interface implemented using + @ref doConvert(const MeshData&) / @ref doConvertToFile(const MeshData&, Containers::StringView) + / @ref doConvertToData(const MeshData&), it's automatically delegated to + from @ref begin() / @ref beginFile() / @ref beginData(), + @ref end() / @ref endFile() / @ref endData() and + @ref add(const MeshData&, Containers::StringView), succeeding only if + exactly one mesh is added. +- As an exception, a multi-mesh interface implemented using @ref doBegin() + and @ref doEnd() can't be automatically delegated to from + @ref convert(const MeshData&), as the returned @ref AbstractImporter + instance is allowed to have any number of meshes and the delegation logic + would be too complex. In that case, you need to implement + @ref doConvert(const MeshData&) and advertise + @ref SceneConverterFeature::ConvertMesh in @ref doFeatures() yourself. + + @m_class{m-block m-warning} @@ -289,6 +564,10 @@ checked by the implementation: could cause dangling function pointer call on array destruction if the plugin gets unloaded before the array is destroyed. This is asserted by the base implementation on return. +@par + The only exception is the @ref AbstractImporter instance returned by + @ref end() --- since its implementation is in the plugin module itself, the + plugin can't be unloaded until the returned instance is destroyed. */ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::AbstractManagingPlugin { public: @@ -373,11 +652,15 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract /** * @brief Convert a mesh * - * Depending on the plugin, can perform for example vertex format - * conversion, overdraw optimization or decimation / subdivision. - * Available only if @ref SceneConverterFeature::ConvertMesh is - * supported. On failure prints a message to @relativeref{Magnum,Error} - * and returns @ref Containers::NullOpt. + * On failure prints a message to @relativeref{Magnum,Error} and + * returns @ref Containers::NullOpt. + * + * Expects that @ref SceneConverterFeature::ConvertMesh is supported. + * If @ref SceneConverterFeature::AddMeshes is supported instead, you + * have to use @ref begin(), @ref add(const MeshData&, Containers::StringView) + * and retrieve the output from the importer returned by @ref end() --- + * in such case the process can also return zero or more than one mesh + * instead of always exactly one. * @see @ref features(), @ref convertInPlace(MeshData&) */ Containers::Optional convert(const MeshData& mesh); @@ -385,11 +668,11 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract /** * @brief Convert a mesh in-place * - * Depending on the plugin, can perform for example index buffer - * reordering for better vertex cache use or overdraw optimization. - * Available only if @ref SceneConverterFeature::ConvertMeshInPlace is - * supported. On failure prints a message to @relativeref{Magnum,Error} - * and returns @cpp false @ce, @p mesh is guaranteed to stay unchanged. + * On failure prints a message to @relativeref{Magnum,Error} and + * returns @cpp false @ce, @p mesh is guaranteed to stay unchanged. + * + * Expects that @ref SceneConverterFeature::ConvertMeshInPlace is + * supported. * @see @ref features(), @ref convert(const MeshData&) */ bool convertInPlace(MeshData& mesh); @@ -397,11 +680,15 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract /** * @brief Convert a mesh to a raw data * - * Depending on the plugin, can convert the mesh to a file format that - * can be saved to disk. Available only if - * @ref SceneConverterFeature::ConvertMeshToData is supported. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @ref Containers::NullOpt. + * On failure prints a message to @relativeref{Magnum,Error} and + * returns @ref Containers::NullOpt. + * + * Expects that @ref SceneConverterFeature::ConvertMeshToData is + * supported. If not and both + * @ref SceneConverterFeature::ConvertMultipleToData and + * @ref SceneConverterFeature::AddMeshes is supported instead, + * delegates to a sequence of @ref beginData(), + * @ref add(const MeshData&, Containers::StringView) and @ref endData(). * @see @ref features(), @ref convertToFile() */ #if !defined(MAGNUM_BUILD_DEPRECATED) || defined(DOXYGEN_GENERATING_OUTPUT) @@ -415,10 +702,16 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @brief Convert a mesh to a file * @m_since_latest * - * Available only if @ref SceneConverterFeature::ConvertMeshToFile or - * @ref SceneConverterFeature::ConvertMeshToData is supported. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @cpp false @ce. + * On failure prints a message to @relativeref{Magnum,Error} and + * returns @cpp false @ce. + * + * Expects that @ref SceneConverterFeature::ConvertMeshToFile is + * supported. If not and both + * @ref SceneConverterFeature::ConvertMultipleToFile and + * @ref SceneConverterFeature::AddMeshes is supported instead, + * delegates to a sequence of @ref beginFile(), + * @ref add(const MeshData&, Containers::StringView) and + * @ref endFile(). * @see @ref features(), @ref convertToData() */ bool convertToFile(const MeshData& mesh, Containers::StringView filename); @@ -432,52 +725,1373 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract CORRADE_DEPRECATED("use convertToFile(const MeshData&, Containers::StringView) instead") bool convertToFile(const std::string& filename, const MeshData& mesh); #endif - protected: /** - * @brief Implementation for @ref convertToFile(const MeshData&, Containers::StringView) + * @brief Whether any conversion is in progress + * @m_since_latest * - * If @ref SceneConverterFeature::ConvertMeshToData is supported, - * default implementation calls @ref doConvertToData(const MeshData&) - * and saves the result to given file. It is allowed to call this - * function from your @ref doConvertToFile() implementation, for - * example when you only need to do format detection based on file - * extension. + * Returns @cpp true @ce if any conversion started by @ref begin(), + * @ref beginData() or @ref beginFile() has not ended yet and + * @ref abort() wasn't called; @cpp false @ce otherwise. */ - virtual bool doConvertToFile(const MeshData& mesh, Containers::StringView filename); + bool isConverting() const; - private: /** - * @brief Implementation for @ref features() + * @brief Abort any in-progress conversion + * @m_since_latest * - * The implementation is expected to support at least one feature. + * On particular implementations an explicit call to this function may + * result in freed memory. If no conversion is currently in progress, + * does nothing. After this function is called, @ref isConverting() + * returns @cpp false @ce. */ - virtual SceneConverterFeatures doFeatures() const = 0; + void abort(); /** - * @brief Implementation for @ref setFlags() + * @brief Begin converting a scene + * @m_since_latest * - * Useful when the converter needs to modify some internal state on - * flag setup. Default implementation does nothing and this - * function doesn't need to be implemented --- the flags are available - * through @ref flags(). + * If a conversion is currently in progress, calls @ref abort() first. + * The converted output of data supplied via various @ref add() and + * `set*()` APIs is returned via an importer instance upon calling + * @ref end(). * - * To reduce the amount of error checking on user side, this function - * isn't expected to fail --- if a flag combination is invalid / - * unsuported, error reporting should be delayed to various conversion - * functions, where the user is expected to do error handling anyway. + * Expects that @ref SceneConverterFeature::ConvertMultiple is + * supported. If not and @ref SceneConverterFeature::ConvertMesh is + * supported instead, sets up internal state in order to delegate + * @ref add(const MeshData&, Containers::StringView) to + * @ref convert(const MeshData&) and return the result from + * @ref end(). + * @see @ref features(), @ref beginData(), @ref beginFile() */ - virtual void doSetFlags(SceneConverterFlags flags); + void begin(); - /** @brief Implementation for @ref convert(const MeshData&) */ - virtual Containers::Optional doConvert(const MeshData& mesh); + /** + * @brief End converting a scene + * @m_since_latest + * + * Expects that @ref begin() was called before. The returned + * @ref AbstractImporter may contain arbitrary amounts of data + * depending on the particular converter plugin. On failure prints a + * message to @relativeref{Magnum,Error} and returns @cpp nullptr @ce. + * + * @m_class{m-note m-warning} + * + * @par Data dependency + * The returned importer instance is fully self-contained, with no + * dependency on the originating converter instance or its + * internal state, meaning you can immediately reuse the instance + * for another conversion without corrupting the importer instance + * returned earlier. + * @par + * Its *code*, however, is coming from the plugin binary and thus + * the plugin should not be unloaded (or its originating + * @relativeref{Corrade,PluginManager::Manager} destroyed) before + * the importer instance is destroyed. + * + * If @ref SceneConverterFeature::ConvertMultiple is not supported and + * @ref SceneConverterFeature::ConvertMesh is supported instead, + * returns an importer exposing a single mesh that was converted via + * the @ref add(const MeshData&, Containers::StringView) call, which + * delegated to @ref convert(const MeshData&). To simplify data + * ownership logic, the mesh can be extracted from the returned + * importer only once, subsequent calls to @ref AbstractImporter::mesh() + * will fail. If no mesh was added, prints a message to + * @relativeref{Magnum,Error} and returns @cpp nullptr @ce. + */ + Containers::Pointer end(); - /** @brief Implementation for @ref convertInPlace(MeshData&) */ - virtual bool doConvertInPlace(MeshData& mesh); + /** + * @brief Begin converting a scene to raw data + * @m_since_latest + * + * If a conversion is currently in progress, calls @ref abort() first. + * The converted output of data supplied via various @ref add() and + * `set*()` APIs is returned upon calling @ref endData(). + * + * Expects that @ref SceneConverterFeature::ConvertMultipleToData is + * supported. If not and @ref SceneConverterFeature::ConvertMeshToData + * is supported instead, sets up internal state in order to delegate + * @ref add(const MeshData&, Containers::StringView) to + * @ref convertToData(const MeshData&) and return the result from + * @ref endData(). + * @see @ref features(), @ref begin(), @ref beginFile() + */ + void beginData(); - /** @brief Implementation for @ref convertToData(const MeshData&) */ - virtual Containers::Optional> doConvertToData(const MeshData& mesh); + /** + * @brief End converting a scene to raw data + * @m_since_latest + * + * Expects that @ref beginData() was called before. On failure prints a + * message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt. + * + * If @ref SceneConverterFeature::ConvertMultipleToData is not + * supported and @ref SceneConverterFeature::ConvertMeshToData is + * supported instead, returns data converted via the + * @ref add(const MeshData&, Containers::StringView) call, which + * delegated to @ref convertToData(const MeshData&). If no mesh was + * added, prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt. + */ + Containers::Optional> endData(); + + /** + * @brief Begin converting a scene to a file + * @m_since_latest + * + * If a conversion is currently in progress, calls @ref abort() first. + * The converted output of data supplied via various @ref add() and + * `set*()` APIs is returned upon calling @ref endFile(). + * + * Expects that @ref SceneConverterFeature::ConvertMultipleToFile is + * supported. If not and @ref SceneConverterFeature::ConvertMeshToFile + * is supported instead, sets up internal state in order to delegate + * @ref add(const MeshData&, Containers::StringView) to + * @ref convertToFile(const MeshData&, Containers::StringView) and + * return the result from @ref endFile(). + * @see @ref features(), @ref begin(), @ref beginData() + */ + void beginFile(Containers::StringView filename); + + /** + * @brief End converting a scene to raw data + * @m_since_latest + * + * Expects that @ref beginData() was called before. On failure prints a + * message to @relativeref{Magnum,Error} and returns @cpp false @ce. + * + * If @ref SceneConverterFeature::ConvertMultipleToFile is not + * supported and @ref SceneConverterFeature::ConvertMeshToFile is + * supported instead, returns a result of the + * @ref add(const MeshData&, Containers::StringView) call, which + * delegated to @ref convertToFile(const MeshData&, Containers::StringView). + * If no mesh was added, prints a message to @relativeref{Magnum,Error} + * and returns @ref Containers::NullOpt. + */ + bool endFile(); + + /** + * @brief Count of added scenes + * @m_since_latest + * + * Count of scenes successfully added with + * @ref add(const SceneData&, Containers::StringView) since the initial + * @ref begin(), @ref beginData() or @ref beginFile() call. Expects + * that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddScenes is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt sceneCount() const; + + /** + * @brief Add a scene + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddScenes is supported. The returned ID + * is implicitly equal to @ref sceneCount() before calling this + * function and can be subsequently used in functions like + * @ref setDefaultScene(). On failure prints a message to + * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt --- + * the count of added animations doesn't change in that case. + * + * If the converter doesn't support scene naming, @p name is ignored. + * + * Because a scene directly or indirectly references majority of other + * data, it's recommended to be added only after all data it uses are + * added as well. Particular converter plugins may have looser + * requirements, but adding it last guarantees that the conversion + * process doesn't fail due to the scene referencing yet-unknown data. + * @see @ref isConverting(), @ref features(), + * @ref setSceneFieldName(), @ref setObjectName(), + * @ref setDefaultScene() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const SceneData& data, Containers::StringView name = {}); + #else + Containers::Optional add(const SceneData& data, Containers::StringView name); + Containers::Optional add(const SceneData& data); + #endif + + /** + * @brief Set name of a custom scene field + * @m_since_latest + * + * Expects that a conversion is currently in progress, + * @ref SceneConverterFeature::AddScenes is supported and @p field is a + * custom field. The field name will get used only for scene data added + * after this function has been called. If the converter doesn't + * support custom scene fields or doesn't support naming them, the call + * is ignored. + * @see @ref isConverting(), @ref features(), @ref isSceneFieldCustom(), + * @ref setMeshAttributeName() + */ + void setSceneFieldName(SceneField field, Containers::StringView name); + + /** + * @brief Set object name + * @m_since_latest + * + * Expects that a conversion is currently in progress, + * @ref SceneConverterFeature::AddScenes is supported and @p field is a + * custom field. The object name will get used only for scene data + * added after this function has been called. If the converter doesn't + * support naming objects, the call is ignored. + * @see @ref isConverting(), @ref features() + */ + void setObjectName(UnsignedLong object, Containers::StringView name); + + /** + * @brief Set default scene + * @m_since_latest + * + * Expects that a conversion is currently in progress, + * @ref SceneConverterFeature::AddScenes is supported and + * @ref sceneCount() is greater than @p id. If the converter doesn't + * support multiple scenes or default scene selection, the call is + * ignored. + * @see @ref isConverting(), @ref features() + */ + void setDefaultScene(UnsignedInt id); + + /** + * @brief Count of added animations + * @m_since_latest + * + * Count of animations successfully added with + * @ref add(const AnimationData&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddAnimations is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt animationCount() const; + + /** + * @brief Add an animation + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddAnimations is supported. The returned + * ID is implicitly equal to @ref animationCount() before calling this + * function. On failure prints a message to @relativeref{Magnum,Error} + * and returns @ref Containers::NullOpt --- the count of added + * animations doesn't change in that case. + * + * If the converter doesn't support animation naming, @p name is + * ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const AnimationData& animation, Containers::StringView name = {}); + #else + Containers::Optional add(const AnimationData& animation, Containers::StringView name); + Containers::Optional add(const AnimationData& animation); + #endif + + /** + * @brief Count of added lights + * @m_since_latest + * + * Count of lights successfully added with + * @ref add(const LightData&, Containers::StringView) since the initial + * @ref begin(), @ref beginData() or @ref beginFile() call. Expects + * that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddLights is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt lightCount() const; + + /** + * @brief Add a light + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddLights is supported. The returned ID + * is implicitly equal to @ref lightCount() before calling this + * function and can be subsequently used to for example reference a + * light from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added lights doesn't + * change in that case. + * + * If the converter doesn't support light naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const LightData& light, Containers::StringView name = {}); + #else + Containers::Optional add(const LightData& light, Containers::StringView name); + Containers::Optional add(const LightData& light); + #endif + + /** + * @brief Count of added cameras + * @m_since_latest + * + * Count of cameras successfully added with + * @ref add(const CameraData&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddCameras is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt cameraCount() const; + + /** + * @brief Add a camera + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddCameras is supported. The returned ID + * is implicitly equal to @ref cameraCount() before calling this + * function and can be subsequently used to for example reference a + * camera from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added cameras doesn't + * change in that case. + * + * If the converter doesn't support camera naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const CameraData& camera, Containers::StringView name = {}); + #else + Containers::Optional add(const CameraData& camera, Containers::StringView name); + Containers::Optional add(const CameraData& camera); + #endif + + /** + * @brief Count of added 2D skins + * @m_since_latest + * + * Count of skins successfully added with + * @ref add(const SkinData2D&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddSkins2D is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt skin2DCount() const; + + /** + * @brief Add a 2D skin + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddSkins2D is supported. The returned ID + * is implicitly equal to @ref skin2DCount() before calling this + * function. On failure prints a message to @relativeref{Magnum,Error} + * and returns @ref Containers::NullOpt --- the count of added skins + * doesn't change in that case. + * + * If the converter doesn't support skin naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const SkinData2D& skin, Containers::StringView name = {}); + #else + Containers::Optional add(const SkinData2D& skin, Containers::StringView name); + Containers::Optional add(const SkinData2D& skin); + #endif + + /** + * @brief Count of added 3D skins + * @m_since_latest + * + * Count of skins successfully added with + * @ref add(const SkinData3D&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddSkins3D is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt skin3DCount() const; + + /** + * @brief Add a 3D skin + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddSkins3D is supported. The returned ID + * is implicitly equal to @ref skin3DCount() before calling this + * function. On failure prints a message to @relativeref{Magnum,Error} + * and returns @ref Containers::NullOpt --- the count of added skins + * doesn't change in that case. + * + * If the converter doesn't support skin naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const SkinData3D& skin, Containers::StringView name = {}); + #else + Containers::Optional add(const SkinData3D& skin, Containers::StringView name); + Containers::Optional add(const SkinData3D& skin); + #endif + + /** + * @brief Count of added meshes + * @m_since_latest + * + * Count of meshes successfully added with + * @ref add(const MeshData&, Containers::StringView) or + * @ref add(Containers::Iterable, Containers::StringView) + * since the initial @ref begin(), @ref beginData() or @ref beginFile() + * call. Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddMeshes is not supported and only the + * singular @relativeref{SceneConverterFeature,ConvertMesh}, + * @relativeref{SceneConverterFeature,ConvertMeshToData} or + * @relativeref{SceneConverterFeature,ConvertMeshToFile} is supported, + * returns always either @cpp 0 @ce or @cpp 1 @ce. Otherwise returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt meshCount() const; + + /** + * @brief Add a mesh + * @m_since_latest + * + * Expects that a conversion is currently in progress and either + * @ref SceneConverterFeature::AddMeshes, + * @relativeref{SceneConverterFeature,ConvertMesh}, + * @relativeref{SceneConverterFeature,ConvertMeshToData} or + * @relativeref{SceneConverterFeature,ConvertMeshToFile} is + * supported. The returned ID is implicitly equal to @ref meshCount() + * before calling this function and can be subsequently used to for + * example reference a mesh from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added meshes doesn't + * change in that case. + * + * If the converter doesn't support mesh naming, @p name is ignored. + * + * If only the singular @ref SceneConverterFeature::ConvertMesh, + * @relativeref{SceneConverterFeature,ConvertMeshToData} or + * @relativeref{SceneConverterFeature,ConvertMeshToFile} is supported, + * the function can be called only exactly once to successfully produce + * an output, and the process is equivalent to + * @ref convert(const MeshData&), + * @ref convertToData(const MeshData&) or + * @ref convertToFile(const MeshData&, Containers::StringView), with + * the @p name ignored. + * @see @ref isConverting(), @ref features(), + * @ref setMeshAttributeName() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const MeshData& mesh, Containers::StringView name = {}); + #else + Containers::Optional add(const MeshData& mesh, Containers::StringView name); + Containers::Optional add(const MeshData& mesh); + #endif + + /** + * @brief Add a set of mesh levels + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::MeshLevels together with + * @relativeref{SceneConverterFeature,AddMeshes} is supported. The + * returned ID is implicitly equal to @ref meshCount() before calling + * this function and can be subsequently used to for example reference + * a mesh from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView); all levels + * together are treated as a single mesh. On failure prints a message + * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt + * --- the count of added meshes doesn't change in that case. + * + * If the converter doesn't support mesh naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref setMeshAttributeName() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable meshLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable meshLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable meshLevels); + #endif + + /** + * @brief Set name of a custom mesh attribute + * @m_since_latest + * + * Expects that a conversion is currently in progress, + * @ref SceneConverterFeature::AddMeshes is supported and @p attribute + * is a custom attribute. The attribute name will get used only mesh + * data added after this function has been called. If the converter + * doesn't support custom mesh attributes or doesn't support naming + * them, the call is ignored. + * @see @ref isConverting(), @ref features(), + * @ref isMeshAttributeCustom(), @ref setSceneFieldName() + */ + void setMeshAttributeName(MeshAttribute attribute, Containers::StringView name); + + /** + * @brief Count of added materials + * @m_since_latest + * + * Count of materials successfully added with + * @ref add(const MaterialData&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddMaterials is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt materialCount() const; + + /** + * @brief Add a material + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddMaterials is supported. The returned + * ID is implicitly equal to @ref materialCount() before calling this + * function and can be subsequently used to for example reference a + * material from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added materials doesn't + * change in that case. + * + * If the converter doesn't support material naming, @p name is + * ignored. + * + * Because a material directly or indirectly references textures and + * images, it's recommended to be added only after all data it uses + * are added as well. Particular converter plugins may have looser + * requirements, but adding it last guarantees that the conversion + * process doesn't fail due to the material referencing yet-unknown + * data. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const MaterialData& material, Containers::StringView name = {}); + #else + Containers::Optional add(const MaterialData& material, Containers::StringView name); + Containers::Optional add(const MaterialData& material); + #endif + + /** + * @brief Count of added textures + * @m_since_latest + * + * Count of textures successfully added with + * @ref add(const TextureData&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddTextures is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt textureCount() const; + + /** + * @brief Add a texture + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddTextures is supported. The returned + * ID is implicitly equal to @ref textureCount() before calling this + * function and can be subsequently used to for example reference a + * texture from a @ref MaterialData passed to + * @ref add(const MaterialData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added textures doesn't + * change in that case. + * + * If the converter doesn't support texture naming, @p name is ignored. + * + * Because a texture references an image, it's recommended to be added + * only after the image it uses is added as well. Particular converter + * plugins may have looser requirements, but adding it last guarantees + * that the conversion process doesn't fail due to the texture + * referencing yet-unknown data. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const TextureData& texture, Containers::StringView name = {}); + #else + Containers::Optional add(const TextureData& texture, Containers::StringView name); + Containers::Optional add(const TextureData& texture); + #endif + + /** + * @brief Count of added 1D images + * @m_since_latest + * + * Count of images successfully added with + * @ref add(const ImageData1D&, Containers::StringView) or + * @ref add(Containers::Iterable, Containers::StringView) + * and overloads since the initial @ref begin(), @ref beginData() or + * @ref beginFile() call. Expects that a conversion is currently in + * progress. If neither @ref SceneConverterFeature::AddImages1D nor + * @relativeref{SceneConverterFeature,AddCompressedImages1D} is + * supported, returns @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt image1DCount() const; + + /** + * @brief Add a 1D image + * @m_since_latest + * + * Expects that a conversion is currently in progress and either + * @ref SceneConverterFeature::AddImages1D or + * @relativeref{SceneConverterFeature,AddCompressedImages1D} is + * supported based on whether @p image is compressed. The image is + * expected to not be @cpp nullptr @ce and to have a non-zero size. The + * returned ID is implicitly equal to @ref image1DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added images doesn't + * change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref ImageData::isCompressed() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageData1D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageData1D& image, Containers::StringView name); + Containers::Optional add(const ImageData1D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageView1D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageView1D& image, Containers::StringView name); + Containers::Optional add(const ImageView1D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const CompressedImageView1D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const CompressedImageView1D& image, Containers::StringView name); + Containers::Optional add(const CompressedImageView1D& image); + #endif + + /** + * @brief Add a set of 1D image levels + * @m_since_latest + * + * The @p imageLevels are expected to have at least one image, with the + * images either all uncompressed or all compressed, none having + * @cpp nullptr @ce data or zero size in any dimension, and all sharing + * the same pixel format and layout flags. Expects that a conversion is + * currently in progress and + * @ref SceneConverterFeature::ImageLevels together with either + * @relativeref{SceneConverterFeature,AddImages1D} or + * @relativeref{SceneConverterFeature,AddCompressedImages1D} is + * supported based on whether @p imageLevels are compressed. The + * returned ID is implicitly equal to @ref image1DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView); all levels + * together are treated as a single image. On failure prints a message + * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt + * --- the count of added images doesn't change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref ImageData::isCompressed() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @brief Count of added 2D images + * @m_since_latest + * + * Count of images successfully added with + * @ref add(const ImageData2D&, Containers::StringView) or + * @ref add(Containers::Iterable, Containers::StringView) + * and overloads since the initial @ref begin(), @ref beginData() or + * @ref beginFile() call. Expects that a conversion is currently in + * progress. If neither @ref SceneConverterFeature::AddImages2D nor + * @relativeref{SceneConverterFeature,AddCompressedImages2D} is + * supported, returns @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt image2DCount() const; + + /** + * @brief Add a 2D image + * @m_since_latest + * + * Expects that a conversion is currently in progress and either + * @ref SceneConverterFeature::AddImages2D or + * @relativeref{SceneConverterFeature,AddCompressedImages2D} is + * supported based on whether @p image is compressed. The image is + * expected to not be @cpp nullptr @ce and to have a non-zero size. The + * returned ID is implicitly equal to @ref image2DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added images doesn't + * change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref ImageData::isCompressed() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageData2D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageData2D& image, Containers::StringView name); + Containers::Optional add(const ImageData2D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageView2D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageView2D& image, Containers::StringView name); + Containers::Optional add(const ImageView2D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const CompressedImageView2D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const CompressedImageView2D& image, Containers::StringView name); + Containers::Optional add(const CompressedImageView2D& image); + #endif + + /** + * @brief Add a set of 2D image levels + * @m_since_latest + * + * The @p imageLevels are expected to have at least one image, with the + * images either all uncompressed or all compressed, none having + * @cpp nullptr @ce data or zero size in any dimension, and all sharing + * the same pixel format and layout flags. Expects that a conversion is + * currently in progress and + * @ref SceneConverterFeature::ImageLevels together with either + * @relativeref{SceneConverterFeature,AddImages2D} or + * @relativeref{SceneConverterFeature,AddCompressedImages2D} is + * supported based on whether @p imageLevels are compressed. The + * returned ID is implicitly equal to @ref image2DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView); all levels + * together are treated as a single image. On failure prints a message + * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt + * --- the count of added images doesn't change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @brief Count of added 3D images + * @m_since_latest + * + * Count of images successfully added with + * @ref add(const ImageData3D&, Containers::StringView) or + * @ref add(Containers::Iterable, Containers::StringView) + * and overloads since the initial @ref begin(), @ref beginData() or + * @ref beginFile() call. Expects that a conversion is currently in + * progress. If neither @ref SceneConverterFeature::AddImages3D nor + * @relativeref{SceneConverterFeature,AddCompressedImages3D} is + * supported, returns @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt image3DCount() const; + + /** + * @brief Add a 3D image + * @m_since_latest + * + * Expects that a conversion is currently in progress and either + * @ref SceneConverterFeature::AddImages3D or + * @relativeref{SceneConverterFeature,AddCompressedImages3D} is + * supported based on whether @p image is compressed. The image is + * expected to not be @cpp nullptr @ce and to have a non-zero size. The + * returned ID is implicitly equal to @ref image3DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added images doesn't + * change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref ImageData::isCompressed() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageData3D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageData3D& image, Containers::StringView name); + Containers::Optional add(const ImageData3D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageView3D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageView3D& image, Containers::StringView name); + Containers::Optional add(const ImageView3D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const CompressedImageView3D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const CompressedImageView3D& image, Containers::StringView name); + Containers::Optional add(const CompressedImageView3D& image); + #endif + + /** + * @brief Add a set of 3D image levels + * @m_since_latest + * + * The @p imageLevels are expected to have at least one image, with the + * images either all uncompressed or all compressed, none having + * @cpp nullptr @ce data or zero size in any dimension, and all sharing + * the same pixel format and layout flags. Expects that a conversion is + * currently in progress and + * @ref SceneConverterFeature::ImageLevels together with either + * @relativeref{SceneConverterFeature,AddImages3D} or + * @relativeref{SceneConverterFeature,AddCompressedImages3D} is + * supported based on whether @p imageLevels are compressed. The + * returned ID is implicitly equal to @ref image3DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView); all levels + * together are treated as a single image. On failure prints a message + * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt + * --- the count of added images doesn't change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + protected: + /** + * @brief Implementation for @ref convertToFile(const MeshData&, Containers::StringView) + * + * If @ref SceneConverterFeature::ConvertMeshToData is supported, + * default implementation calls @ref doConvertToData(const MeshData&) + * and saves the result to given file. It is allowed to call this + * function from your @ref doConvertToFile() implementation, for + * example when you only need to do format detection based on file + * extension. + * + * Otherwise, if both @ref SceneConverterFeature::ConvertMultipleToFile + * and @ref SceneConverterFeature::AddMeshes is supported, default + * implementation calls @ref beginData(), + * @ref add(const MeshData&, Containers::StringView) and returns the + * output of @ref endData(), or @ref Containers::NullOpt if any of + * those failed. + */ + virtual bool doConvertToFile(const MeshData& mesh, Containers::StringView filename); + + /** + * @brief Implementation for @ref beginFile() + * @m_since_latest + * + * If @ref SceneConverterFeature::ConvertMultipleToData is supported, + * default implementation delegates to @ref doBeginData(). + * + * It is allowed to call this function from your @ref doBeginFile() + * implementation, for example when you only need to do format + * detection based on file extension. + * + * The @p filename string is guaranteed to stay in scope until a call + * to @ref doEndFile(). It's not guaranteed to be + * @relativeref{Corrade,Containers::StringViewFlag::Global} or + * @relativeref{Corrade,Containers::StringViewFlag::NullTerminated}, + * however. + */ + virtual void doBeginFile(Containers::StringView filename); + + /** + * @brief Implementation for @ref endFile() + * @m_since_latest + * + * Receives the same @p filename as was passed to @ref doBeginFile() + * earlier. Expected to save the output data and reset the internal + * state for a potential new conversion to happen. + * + * If @ref SceneConverterFeature::ConvertMultipleToData is supported, + * default implementation calls @ref doEndData() and saves the result + * to given file. + * + * It is allowed to call this function from your @ref doEndFile() + * implementation, for example when you only need to do format + * detection based on file extension. + */ + virtual bool doEndFile(Containers::StringView filename); + + private: + struct State; + + /** + * @brief Implementation for @ref features() + * + * The implementation is expected to support at least one feature. + */ + virtual SceneConverterFeatures doFeatures() const = 0; + + /** + * @brief Implementation for @ref setFlags() + * + * Useful when the converter needs to modify some internal state on + * flag setup. Default implementation does nothing and this + * function doesn't need to be implemented --- the flags are available + * through @ref flags(). + * + * To reduce the amount of error checking on user side, this function + * isn't expected to fail --- if a flag combination is invalid / + * unsuported, error reporting should be delayed to various conversion + * functions, where the user is expected to do error handling anyway. + */ + virtual void doSetFlags(SceneConverterFlags flags); + + /** @brief Implementation for @ref convert(const MeshData&) */ + virtual Containers::Optional doConvert(const MeshData& mesh); + + /** @brief Implementation for @ref convertInPlace(MeshData&) */ + virtual bool doConvertInPlace(MeshData& mesh); + + /** + * @brief Implementation for @ref convertToData(const MeshData&) + * + * If both @ref SceneConverterFeature::ConvertMultipleToData and + * @ref SceneConverterFeature::AddMeshes is supported, default + * implementation calls @ref beginData(), + * @ref add(const MeshData&, Containers::StringView) and returns the + * output of @ref endData(), or @ref Containers::NullOpt if any of + * those failed. + */ + virtual Containers::Optional> doConvertToData(const MeshData& mesh); + + /** + * @brief Implementation for @ref abort() + * @m_since_latest + * + * Default implementation does nothing. + */ + virtual void doAbort(); + + /** + * @brief Implementation for @ref begin() + * @m_since_latest + */ + virtual void doBegin(); + + /** + * @brief Implementation for @ref end() + * @m_since_latest + * + * Expected to return an importer instance owning all output data and + * reset the internal state for a potential new conversion to happen. + */ + virtual Containers::Pointer doEnd(); + + /** + * @brief Implementation for @ref beginData() + * @m_since_latest + */ + virtual void doBeginData(); + + /** + * @brief Implementation for @ref endData() + * @m_since_latest + * + * Expected to return the output data and reset the internal state for + * a potential new conversion to happen. + */ + virtual Containers::Optional> doEndData(); + + /** + * @brief Implementation for @ref add(const SceneData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref sceneCount() at the time this function is + * called. + */ + virtual bool doAdd(UnsignedInt id, const SceneData& scene, Containers::StringView name); + + /** + * @brief Implementation for @ref setSceneFieldName() + * @m_since_latest + * + * Receives the custom ID extracted via + * @ref sceneFieldCustom(SceneField). Default implementation does + * nothing. + */ + virtual void doSetSceneFieldName(UnsignedInt field, Containers::StringView name); + + /** + * @brief Implementation for @ref setObjectName() + * @m_since_latest + * + * Default implementation does nothing. + */ + virtual void doSetObjectName(UnsignedLong object, Containers::StringView name); + + /** + * @brief Implementation for @ref setDefaultScene() + * @m_since_latest + * + * Default implementation does nothing. + */ + virtual void doSetDefaultScene(UnsignedInt id); + + /** + * @brief Implementation for @ref add(const AnimationData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref animationCount() at the time this + * function is called. + */ + virtual bool doAdd(UnsignedInt id, const AnimationData& animation, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const LightData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref lightCount() at the time this function is + * called. + */ + virtual bool doAdd(UnsignedInt id, const LightData& light, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const CameraData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref cameraCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const CameraData& camera, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const SkinData2D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref skin2DCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const SkinData2D& skin, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const SkinData3D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref skin3DCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const SkinData3D& skin, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const MeshData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref meshCount() at the time this function is + * called. + * + * If @ref SceneConverterFeature::AddMeshes together with + * @relativeref{SceneConverterFeature,MeshLevels} is supported, default + * implementation calls @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + * with just the single @p mesh. + * + * Otherwise, if @ref SceneConverterFeature::ConvertMesh, + * @relativeref{SceneConverterFeature,ConvertMeshToData} or + * @relativeref{SceneConverterFeature,ConvertMeshToFile} is supported, + * default implementation delegates to @ref doConvert(const MeshData&), + * @ref doConvertToData(const MeshData&) or + * @ref doConvertToFile(const MeshData&, Containers::StringView) and + * remembers the result to return it from @ref doEnd(), + * @ref doEndData() or @ref doEndFile(). If the delegated-to function + * fails, returns @cpp false @ce and the subsequent @ref doEnd(), + * @ref doEndData() or @ref doEndFile() call prints a message to + * @relativeref{Magnum,Error} and returns a @cpp nullptr @ce, + * @ref Containers::NullOpt or @cpp false @ce. Since the delegation + * operates just on a single mesh at a time, if this function is called + * more than once after a @ref begin(), @ref beginData() or + * @ref beginFile(), prints a message to @relativeref{Magnum,Error} and + * returns @cpp false @ce. + */ + virtual bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView name); + + /** + * @brief Implementation for @ref add(Containers::Iterable, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref meshCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, Containers::Iterable meshLevels, Containers::StringView name); + + /** + * @brief Implementation for @ref setMeshAttributeName() + * @m_since_latest + * + * Receives the custom ID extracted via + * @ref meshAttributeCustom(MeshAttribute). Default implementation does + * nothing. + */ + virtual void doSetMeshAttributeName(UnsignedShort attribute, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const MaterialData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref materialCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const MaterialData& material, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const TextureData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref textureCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const TextureData& texture, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const ImageData1D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image1DCount() at the time this function + * is called. If @ref add(const ImageView1D&, Containers::StringView) + * or @ref add(const CompressedImageView1D&, Containers::StringView) + * was called, receives the view wrapped in a non-owning + * @ref ImageData1D instance. + * + * If @ref SceneConverterFeature::ImageLevels is supported, default + * implementation calls @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + * with just the single @p image. + */ + virtual bool doAdd(UnsignedInt id, const ImageData1D& image, Containers::StringView name); + + /** + * @brief Implementation for @ref add(Containers::Iterable, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image1DCount() at the time this function + * is called. If @ref add(Containers::Iterable, Containers::StringView) + * or @ref add(Containers::Iterable, Containers::StringView) + * was called, receives the views wrapped in non-owning + * @ref ImageData1D instances. + */ + virtual bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const ImageData2D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image2DCount() at the time this function + * is called. If @ref add(const ImageView2D&, Containers::StringView) + * or @ref add(const CompressedImageView2D&, Containers::StringView) + * was called, receives the view wrapped in a non-owning + * @ref ImageData2D instance. + * + * If @ref SceneConverterFeature::ImageLevels is supported, default + * implementation calls @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + * with just the single @p image. + */ + virtual bool doAdd(UnsignedInt id, const ImageData2D& image, Containers::StringView name); + + /** + * @brief Implementation for @ref add(Containers::Iterable, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image2DCount() at the time this function + * is called. If @ref add(Containers::Iterable, Containers::StringView) + * or @ref add(Containers::Iterable, Containers::StringView) + * was called, receives the views wrapped in non-owning + * @ref ImageData2D instances. + */ + virtual bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const ImageData3D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image3DCount() at the time this function + * is called. If @ref add(const ImageView3D&, Containers::StringView) + * or @ref add(const CompressedImageView3D&, Containers::StringView) + * was called, receives the view wrapped in a non-owning + * @ref ImageData3D instance. + * + * If @ref SceneConverterFeature::ImageLevels is supported, default + * implementation calls @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + * with just the single @p image. + */ + virtual bool doAdd(UnsignedInt id, const ImageData3D& image, Containers::StringView name); + + /** + * @brief Implementation for @ref add(Containers::Iterable, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image3DCount() at the time this function + * is called. If @ref add(Containers::Iterable, Containers::StringView) + * or @ref add(Containers::Iterable, Containers::StringView) + * was called, receives the views wrapped in non-owning + * @ref ImageData3D instances. + */ + virtual bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name); SceneConverterFlags _flags; + Containers::Pointer _state; }; }} diff --git a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp index 5cee66b72..675b20adc 100644 --- a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include /** @todo remove once Debug is stream-free */ #include @@ -34,10 +35,23 @@ #include #include +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" #include "Magnum/Math/Vector3.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" #include "Magnum/Trade/ArrayAllocator.h" +#include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/AbstractSceneConverter.h" +#include "Magnum/Trade/AnimationData.h" +#include "Magnum/Trade/CameraData.h" +#include "Magnum/Trade/ImageData.h" +#include "Magnum/Trade/LightData.h" +#include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/SceneData.h" +#include "Magnum/Trade/SkinData.h" +#include "Magnum/Trade/TextureData.h" #include "configure.h" @@ -52,6 +66,8 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void setFlagsNotImplemented(); void thingNotSupported(); + /* Certain features need a combination of flags, test them explicitly */ + void thingLevelsNotSupported(); void convertMesh(); void convertMeshFailed(); @@ -68,6 +84,9 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void convertMeshToData(); void convertMeshToDataFailed(); + void convertMeshToDataThroughBatch(); + void convertMeshToDataThroughBatchAddFailed(); + void convertMeshToDataThroughBatchEndFailed(); void convertMeshToDataNotImplemented(); void convertMeshToDataNonOwningDeleter(); void convertMeshToDataGrowableDeleter(); @@ -78,8 +97,173 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void convertMeshToFileThroughData(); void convertMeshToFileThroughDataFailed(); void convertMeshToFileThroughDataNotWritable(); + void convertMeshToFileThroughBatch(); + void convertMeshToFileThroughBatchAddFailed(); + void convertMeshToFileThroughBatchEndFailed(); void convertMeshToFileNotImplemented(); + void beginEnd(); + void beginEndFailed(); + void beginNotImplemented(); + void endNotImplemented(); + + void beginEndData(); + void beginEndDataFailed(); + void beginDataNotImplemented(); + void endDataNotImplemented(); + void beginEndDataCustomDeleter(); + + void beginEndFile(); + void beginEndFileFailed(); + void beginEndFileThroughData(); + void beginEndFileThroughDataFailed(); + void beginEndFileThroughDataNotWritable(); + void beginFileNotImplemented(); + void endFileNotImplemented(); + + void abort(); + void abortNotImplemented(); + + void thingNoBegin(); + void endMismatchedBegin(); + void endDataMismatchedBegin(); + void endFileMismatchedBegin(); + + void addScene(); + void addSceneFailed(); + void addSceneNotImplemented(); + + void setSceneFieldName(); + void setSceneFieldNameNotImplemented(); + void setSceneFieldNameNotCustom(); + + void setObjectName(); + void setObjectNameNotImplemented(); + + void setDefaultScene(); + void setDefaultSceneNotImplemented(); + void setDefaultSceneOutOfRange(); + + void addAnimation(); + void addAnimationFailed(); + void addAnimationNotImplemented(); + + void addLight(); + void addLightFailed(); + void addLightNotImplemented(); + + void addCamera(); + void addCameraFailed(); + void addCameraNotImplemented(); + + void addSkin2D(); + void addSkin2DFailed(); + void addSkin2DNotImplemented(); + + void addSkin3D(); + void addSkin3DFailed(); + void addSkin3DNotImplemented(); + + void addMesh(); + void addMeshFailed(); + void addMeshThroughConvertMesh(); + void addMeshThroughConvertMeshFailed(); + void addMeshThroughConvertMeshZeroMeshes(); + void addMeshThroughConvertMeshTwoMeshes(); + void addMeshThroughConvertMeshToData(); + void addMeshThroughConvertMeshToDataFailed(); + void addMeshThroughConvertMeshToDataZeroMeshes(); + void addMeshThroughConvertMeshToDataTwoMeshes(); + void addMeshThroughConvertMeshToFile(); + void addMeshThroughConvertMeshToFileThroughData(); + void addMeshThroughConvertMeshToFileFailed(); + void addMeshThroughConvertMeshToFileZeroMeshes(); + void addMeshThroughConvertMeshToFileTwoMeshes(); + void addMeshNotImplemented(); + + void addMeshLevels(); + void addMeshLevelsFailed(); + void addMeshLevelsNoLevels(); + void addMeshLevelsNotImplemented(); + + void addMeshThroughLevels(); + + void setMeshAttributeName(); + void setMeshAttributeNameNotImplemented(); + void setMeshAttributeNameNotCustom(); + + void addMaterial(); + void addMaterialFailed(); + void addMaterialNotImplemented(); + + void addTexture(); + void addTextureFailed(); + void addTextureNotImplemented(); + + void addImage1D(); + void addImage1DView(); + void addImage1DCompressedView(); + void addImage1DFailed(); + /* 1D/2D/3D share the same image validity check function, so it's verified + only for 2D thoroughly and for others just that the check is used */ + void addImage1DInvalidImage(); + void addImage1DNotImplemented(); + + void addImage2D(); + void addImage2DView(); + void addImage2DCompressedView(); + void addImage2DFailed(); + void addImage2DZeroSize(); + void addImage2DNullptr(); + void addImage2DNotImplemented(); + + void addImage3D(); + void addImage3DView(); + void addImage3DCompressedView(); + void addImage3DFailed(); + /* 1D/2D/3D share the same image validity check function, so it's verified + only for 2D thoroughly and for others just that the check is used */ + void addImage3DInvalidImage(); + void addImage3DNotImplemented(); + + void addImageLevels1D(); + void addImageLevels1DView(); + void addImageLevels1DCompressedView(); + void addImageLevels1DFailed(); + /* 1D/2D/3D share the same image list validity check function, so it's + verified only for 2D thoroughly and for others just that the check is + used */ + void addImageLevels1DInvalidImage(); + void addImageLevels1DNotImplemented(); + + void addImageLevels2D(); + void addImageLevels2DView(); + void addImageLevels2DCompressedView(); + void addImageLevels2DFailed(); + void addImageLevels2DNoLevels(); + void addImageLevels2DZeroSize(); + void addImageLevels2DNullptr(); + void addImageLevels2DInconsistentCompressed(); + void addImageLevels2DInconsistentFormat(); + void addImageLevels2DInconsistentFormatExtra(); + void addImageLevels2DInconsistentCompressedFormat(); + void addImageLevels2DInconsistentFlags(); + void addImageLevels2DNotImplemented(); + + void addImageLevels3D(); + void addImageLevels3DView(); + void addImageLevels3DCompressedView(); + void addImageLevels3DFailed(); + /* 1D/2D/3D share the same image list validity check function, so it's + verified only for 2D thoroughly and for others just that the check is + used */ + void addImageLevels3DInvalidImage(); + void addImageLevels3DNotImplemented(); + + void addImage1DThroughLevels(); + void addImage2DThroughLevels(); + void addImage3DThroughLevels(); + void debugFeature(); void debugFeatures(); void debugFeaturesSupersets(); @@ -87,6 +271,8 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void debugFlags(); }; +using namespace Containers::Literals; + AbstractSceneConverterTest::AbstractSceneConverterTest() { addTests({&AbstractSceneConverterTest::featuresNone, @@ -94,6 +280,7 @@ AbstractSceneConverterTest::AbstractSceneConverterTest() { &AbstractSceneConverterTest::setFlagsNotImplemented, &AbstractSceneConverterTest::thingNotSupported, + &AbstractSceneConverterTest::thingLevelsNotSupported, &AbstractSceneConverterTest::convertMesh, &AbstractSceneConverterTest::convertMeshFailed, @@ -110,6 +297,9 @@ AbstractSceneConverterTest::AbstractSceneConverterTest() { &AbstractSceneConverterTest::convertMeshToData, &AbstractSceneConverterTest::convertMeshToDataFailed, + &AbstractSceneConverterTest::convertMeshToDataThroughBatch, + &AbstractSceneConverterTest::convertMeshToDataThroughBatchAddFailed, + &AbstractSceneConverterTest::convertMeshToDataThroughBatchEndFailed, &AbstractSceneConverterTest::convertMeshToDataNotImplemented, &AbstractSceneConverterTest::convertMeshToDataNonOwningDeleter, &AbstractSceneConverterTest::convertMeshToDataGrowableDeleter, @@ -120,8 +310,163 @@ AbstractSceneConverterTest::AbstractSceneConverterTest() { &AbstractSceneConverterTest::convertMeshToFileThroughData, &AbstractSceneConverterTest::convertMeshToFileThroughDataFailed, &AbstractSceneConverterTest::convertMeshToFileThroughDataNotWritable, + &AbstractSceneConverterTest::convertMeshToFileThroughBatch, + &AbstractSceneConverterTest::convertMeshToFileThroughBatchAddFailed, + &AbstractSceneConverterTest::convertMeshToFileThroughBatchEndFailed, &AbstractSceneConverterTest::convertMeshToFileNotImplemented, + &AbstractSceneConverterTest::beginEnd, + &AbstractSceneConverterTest::beginEndFailed, + &AbstractSceneConverterTest::beginNotImplemented, + &AbstractSceneConverterTest::endNotImplemented, + + &AbstractSceneConverterTest::beginEndData, + &AbstractSceneConverterTest::beginEndDataFailed, + &AbstractSceneConverterTest::beginDataNotImplemented, + &AbstractSceneConverterTest::endDataNotImplemented, + &AbstractSceneConverterTest::beginEndDataCustomDeleter, + + &AbstractSceneConverterTest::beginEndFile, + &AbstractSceneConverterTest::beginEndFileFailed, + &AbstractSceneConverterTest::beginEndFileThroughData, + &AbstractSceneConverterTest::beginEndFileThroughDataFailed, + &AbstractSceneConverterTest::beginEndFileThroughDataNotWritable, + &AbstractSceneConverterTest::beginFileNotImplemented, + &AbstractSceneConverterTest::endFileNotImplemented, + + &AbstractSceneConverterTest::abort, + &AbstractSceneConverterTest::abortNotImplemented, + + &AbstractSceneConverterTest::thingNoBegin, + &AbstractSceneConverterTest::endMismatchedBegin, + &AbstractSceneConverterTest::endDataMismatchedBegin, + &AbstractSceneConverterTest::endFileMismatchedBegin, + + &AbstractSceneConverterTest::addScene, + &AbstractSceneConverterTest::addSceneFailed, + &AbstractSceneConverterTest::addSceneNotImplemented, + + &AbstractSceneConverterTest::setSceneFieldName, + &AbstractSceneConverterTest::setSceneFieldNameNotImplemented, + &AbstractSceneConverterTest::setSceneFieldNameNotCustom, + + &AbstractSceneConverterTest::setObjectName, + &AbstractSceneConverterTest::setObjectNameNotImplemented, + + &AbstractSceneConverterTest::setDefaultScene, + &AbstractSceneConverterTest::setDefaultSceneNotImplemented, + &AbstractSceneConverterTest::setDefaultSceneOutOfRange, + + &AbstractSceneConverterTest::addAnimation, + &AbstractSceneConverterTest::addAnimationFailed, + &AbstractSceneConverterTest::addAnimationNotImplemented, + + &AbstractSceneConverterTest::addLight, + &AbstractSceneConverterTest::addLightFailed, + &AbstractSceneConverterTest::addLightNotImplemented, + + &AbstractSceneConverterTest::addCamera, + &AbstractSceneConverterTest::addCameraFailed, + &AbstractSceneConverterTest::addCameraNotImplemented, + + &AbstractSceneConverterTest::addSkin2D, + &AbstractSceneConverterTest::addSkin2DFailed, + &AbstractSceneConverterTest::addSkin2DNotImplemented, + + &AbstractSceneConverterTest::addSkin3D, + &AbstractSceneConverterTest::addSkin3DFailed, + &AbstractSceneConverterTest::addSkin3DNotImplemented, + + &AbstractSceneConverterTest::addMesh, + &AbstractSceneConverterTest::addMeshFailed, + &AbstractSceneConverterTest::addMeshThroughConvertMesh, + &AbstractSceneConverterTest::addMeshThroughConvertMeshFailed, + &AbstractSceneConverterTest::addMeshThroughConvertMeshZeroMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshTwoMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToData, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToDataFailed, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToDataZeroMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToDataTwoMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFile, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFileThroughData, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFileFailed, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFileZeroMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFileTwoMeshes, + &AbstractSceneConverterTest::addMeshNotImplemented, + + &AbstractSceneConverterTest::addMeshLevels, + &AbstractSceneConverterTest::addMeshLevelsFailed, + &AbstractSceneConverterTest::addMeshLevelsNoLevels, + &AbstractSceneConverterTest::addMeshLevelsNotImplemented, + + &AbstractSceneConverterTest::addMeshThroughLevels, + + &AbstractSceneConverterTest::setMeshAttributeName, + &AbstractSceneConverterTest::setMeshAttributeNameNotImplemented, + &AbstractSceneConverterTest::setMeshAttributeNameNotCustom, + + &AbstractSceneConverterTest::addMaterial, + &AbstractSceneConverterTest::addMaterialFailed, + &AbstractSceneConverterTest::addMaterialNotImplemented, + + &AbstractSceneConverterTest::addTexture, + &AbstractSceneConverterTest::addTextureFailed, + &AbstractSceneConverterTest::addTextureNotImplemented, + + &AbstractSceneConverterTest::addImage1D, + &AbstractSceneConverterTest::addImage1DView, + &AbstractSceneConverterTest::addImage1DCompressedView, + &AbstractSceneConverterTest::addImage1DFailed, + &AbstractSceneConverterTest::addImage1DInvalidImage, + &AbstractSceneConverterTest::addImage1DNotImplemented, + + &AbstractSceneConverterTest::addImage2D, + &AbstractSceneConverterTest::addImage2DView, + &AbstractSceneConverterTest::addImage2DCompressedView, + &AbstractSceneConverterTest::addImage2DFailed, + &AbstractSceneConverterTest::addImage2DZeroSize, + &AbstractSceneConverterTest::addImage2DNullptr, + &AbstractSceneConverterTest::addImage2DNotImplemented, + + &AbstractSceneConverterTest::addImage3D, + &AbstractSceneConverterTest::addImage3DView, + &AbstractSceneConverterTest::addImage3DCompressedView, + &AbstractSceneConverterTest::addImage3DFailed, + &AbstractSceneConverterTest::addImage3DInvalidImage, + &AbstractSceneConverterTest::addImage3DNotImplemented, + + &AbstractSceneConverterTest::addImageLevels1D, + &AbstractSceneConverterTest::addImageLevels1DView, + &AbstractSceneConverterTest::addImageLevels1DCompressedView, + &AbstractSceneConverterTest::addImageLevels1DFailed, + &AbstractSceneConverterTest::addImageLevels1DInvalidImage, + &AbstractSceneConverterTest::addImageLevels1DNotImplemented, + + &AbstractSceneConverterTest::addImageLevels2D, + &AbstractSceneConverterTest::addImageLevels2DView, + &AbstractSceneConverterTest::addImageLevels2DCompressedView, + &AbstractSceneConverterTest::addImageLevels2DFailed, + &AbstractSceneConverterTest::addImageLevels2DNoLevels, + &AbstractSceneConverterTest::addImageLevels2DZeroSize, + &AbstractSceneConverterTest::addImageLevels2DNullptr, + &AbstractSceneConverterTest::addImageLevels2DInconsistentCompressed, + &AbstractSceneConverterTest::addImageLevels2DInconsistentFormat, + &AbstractSceneConverterTest::addImageLevels2DInconsistentFormatExtra, + &AbstractSceneConverterTest::addImageLevels2DInconsistentCompressedFormat, + &AbstractSceneConverterTest::addImageLevels2DInconsistentFlags, + &AbstractSceneConverterTest::addImageLevels2DNotImplemented, + + &AbstractSceneConverterTest::addImageLevels3D, + &AbstractSceneConverterTest::addImageLevels3DView, + &AbstractSceneConverterTest::addImageLevels3DCompressedView, + &AbstractSceneConverterTest::addImageLevels3DFailed, + &AbstractSceneConverterTest::addImageLevels3DInvalidImage, + &AbstractSceneConverterTest::addImageLevels3DNotImplemented, + + &AbstractSceneConverterTest::addImage1DThroughLevels, + &AbstractSceneConverterTest::addImage2DThroughLevels, + &AbstractSceneConverterTest::addImage3DThroughLevels, + &AbstractSceneConverterTest::debugFeature, &AbstractSceneConverterTest::debugFeatures, &AbstractSceneConverterTest::debugFeaturesSupersets, @@ -194,11 +539,19 @@ void AbstractSceneConverterTest::thingNotSupported() { struct: AbstractSceneConverter { SceneConverterFeatures doFeatures() const override { /* Assuming this bit is unused */ - return SceneConverterFeature(1 << 7); + return SceneConverterFeature(1u << 31); } } converter; - MeshData mesh{MeshPrimitive::Triangles, 3}; + MeshData mesh{MeshPrimitive::Triangles, 0}; + + const char imageData[4*4]{}; + ImageData1D image1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}; + ImageData1D compressedImage1D{CompressedPixelFormat::Astc4x4RGBAF, 1, DataFlags{}, imageData}; + ImageData2D image2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}; + ImageData2D compressedImage2D{CompressedPixelFormat::Astc4x4RGBAF, {1, 1}, DataFlags{}, imageData}; + ImageData3D image3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}; + ImageData3D compressedImage3D{CompressedPixelFormat::Astc4x4RGBAF, {1, 1, 1}, DataFlags{}, imageData}; std::ostringstream out; Error redirectError{&out}; @@ -206,11 +559,131 @@ void AbstractSceneConverterTest::thingNotSupported() { converter.convertInPlace(mesh); converter.convertToData(mesh); converter.convertToFile(mesh, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out")); + + converter.begin(); + converter.beginData(); + converter.beginFile(Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out")); + + converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}); + converter.setSceneFieldName({}, {}); + converter.setObjectName(0, {}); + converter.setDefaultScene(0); + + converter.add(AnimationData{nullptr, nullptr}); + converter.add(LightData{LightData::Type::Point, {}, 0.0f}); + converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f}); + converter.add(SkinData2D{nullptr, nullptr}); + converter.add(SkinData3D{nullptr, nullptr}); + + converter.add(mesh); + converter.add({mesh, mesh}); + converter.setMeshAttributeName({}, {}); + + converter.add(MaterialData{{}, nullptr}); + converter.add(TextureData{{}, {}, {}, {}, {}, 0}); + + converter.add(image1D); + converter.add(compressedImage1D); + converter.add({image1D, image1D}); + converter.add({compressedImage1D, compressedImage1D}); + + converter.add(image2D); + converter.add(compressedImage2D); + converter.add({image2D, image2D}); + converter.add({compressedImage2D, compressedImage2D}); + + converter.add(image3D); + converter.add(compressedImage3D); + converter.add({image3D, image3D}); + converter.add({compressedImage3D, compressedImage3D}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convert(): mesh conversion not supported\n" "Trade::AbstractSceneConverter::convertInPlace(): mesh conversion not supported\n" "Trade::AbstractSceneConverter::convertToData(): mesh conversion not supported\n" - "Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported\n"); + "Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported\n" + + "Trade::AbstractSceneConverter::begin(): feature not supported\n" + "Trade::AbstractSceneConverter::beginData(): feature not supported\n" + "Trade::AbstractSceneConverter::beginFile(): feature not supported\n" + + "Trade::AbstractSceneConverter::add(): scene conversion not supported\n" + "Trade::AbstractSceneConverter::setSceneFieldName(): feature not supported\n" + "Trade::AbstractSceneConverter::setObjectName(): feature not supported\n" + "Trade::AbstractSceneConverter::setDefaultScene(): feature not supported\n" + + "Trade::AbstractSceneConverter::add(): animation conversion not supported\n" + "Trade::AbstractSceneConverter::add(): light conversion not supported\n" + "Trade::AbstractSceneConverter::add(): camera conversion not supported\n" + "Trade::AbstractSceneConverter::add(): 2D skin conversion not supported\n" + "Trade::AbstractSceneConverter::add(): 3D skin conversion not supported\n" + + "Trade::AbstractSceneConverter::add(): mesh conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level mesh conversion not supported\n" + "Trade::AbstractSceneConverter::setMeshAttributeName(): feature not supported\n" + + "Trade::AbstractSceneConverter::add(): material conversion not supported\n" + "Trade::AbstractSceneConverter::add(): texture conversion not supported\n" + + "Trade::AbstractSceneConverter::add(): 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): compressed 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 1D image conversion not supported\n" + + "Trade::AbstractSceneConverter::add(): 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): compressed 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 2D image conversion not supported\n" + + "Trade::AbstractSceneConverter::add(): 3D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): compressed 3D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 3D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 3D image conversion not supported\n"); +} + +void AbstractSceneConverterTest::thingLevelsNotSupported() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::AddCompressedImages1D| + SceneConverterFeature::AddCompressedImages2D| + SceneConverterFeature::AddCompressedImages3D; + } + } converter; + + MeshData mesh{MeshPrimitive::Triangles, 3}; + + const char imageData[4*4]{}; + ImageData1D image1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}; + ImageData1D compressedImage1D{CompressedPixelFormat::Astc4x4RGBAF, 1, DataFlags{}, imageData}; + ImageData2D image2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}; + ImageData2D compressedImage2D{CompressedPixelFormat::Astc4x4RGBAF, {1, 1}, DataFlags{}, imageData}; + ImageData3D image3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}; + ImageData3D compressedImage3D{CompressedPixelFormat::Astc4x4RGBAF, {1, 1, 1}, DataFlags{}, imageData}; + + std::ostringstream out; + Error redirectError{&out}; + converter.add({mesh, mesh}); + converter.add({image1D, image1D}); + converter.add({compressedImage1D, compressedImage1D}); + converter.add({image2D, image2D}); + converter.add({compressedImage2D, compressedImage2D}); + converter.add({image3D, image3D}); + converter.add({compressedImage3D, compressedImage3D}); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::add(): multi-level mesh conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 3D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 3D image conversion not supported\n"); } void AbstractSceneConverterTest::convertMesh() { @@ -461,6 +934,95 @@ void AbstractSceneConverterTest::convertMeshToDataFailed() { CORRADE_COMPARE(out.str(), ""); } +void AbstractSceneConverterTest::convertMeshToDataThroughBatch() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData| + SceneConverterFeature::AddMeshes; + } + + Containers::Optional> doConvertToData(const Magnum::Trade::MeshData&) override { + CORRADE_FAIL_IF(true, "doConvertToData() should not be called"); + return {}; + } + + void doBeginData() override { + _vertexCount = 42; + } + + bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView) override { + CORRADE_COMPARE(id, 0); + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + _vertexCount *= mesh.vertexCount(); + return true; + } + + Containers::Optional> doEndData() override { + return Containers::Array{nullptr, _vertexCount}; + } + + std::size_t _vertexCount = 0; + } converter; + + Containers::Optional> data = converter.convertToData(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->size(), 42*6); +} + +void AbstractSceneConverterTest::convertMeshToDataThroughBatchAddFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData| + SceneConverterFeature::AddMeshes; + } + + void doBeginData() override {} + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return false; + } + + Containers::Optional> doEndData() override { + CORRADE_FAIL_IF(true, "doEndData() shouldn't be called"); + return {}; + } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToData(MeshData{MeshPrimitive::Triangles, 6})); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), ""); +} + +void AbstractSceneConverterTest::convertMeshToDataThroughBatchEndFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData| + SceneConverterFeature::AddMeshes; + } + + void doBeginData() override {} + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return true; + } + + Containers::Optional> doEndData() override { + return {}; + } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToData(MeshData{MeshPrimitive::Triangles, 6})); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), ""); +} + void AbstractSceneConverterTest::convertMeshToDataNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -536,7 +1098,7 @@ void AbstractSceneConverterTest::convertMeshToFile() { SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToFile; } bool doConvertToFile(const MeshData& mesh, Containers::StringView filename) override { - return Utility::Path::write(filename, Containers::arrayView( {char(mesh.vertexCount())})); + return Utility::Path::write(filename, Containers::arrayView({char(mesh.vertexCount())})); } } converter; @@ -629,6 +1191,102 @@ void AbstractSceneConverterTest::convertMeshToFileThroughDataNotWritable() { TestSuite::Compare::StringHasSuffix); } +void AbstractSceneConverterTest::convertMeshToFileThroughBatch() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile| + SceneConverterFeature::AddMeshes; + } + + bool doConvertToFile(const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doConvertToFile() should not be called"); + return {}; + } + + void doBeginFile(Containers::StringView filename) override { + _filename = filename; + } + + bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView) override { + CORRADE_COMPARE(id, 0); + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + _vertexCount = mesh.vertexCount(); + return true; + } + + bool doEndFile(Containers::StringView filename) override { + CORRADE_COMPARE(filename, _filename); + return Utility::Path::write(filename, Containers::arrayView({char(_vertexCount)})); + } + + std::size_t _vertexCount = 0; + Containers::StringView _filename; + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + CORRADE_VERIFY(converter.convertToFile(MeshData{MeshPrimitive::Triangles, 0xfc}, filename)); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE_AS(filename, + "\xfc", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::convertMeshToFileThroughBatchAddFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile| + SceneConverterFeature::AddMeshes; + } + + void doBeginFile(Containers::StringView) override {} + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return false; + } + + bool doEndFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doEndFile() shouldn't be called"); + return {}; + } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToFile(MeshData{MeshPrimitive::Triangles, 0xfc}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"))); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), ""); +} + +void AbstractSceneConverterTest::convertMeshToFileThroughBatchEndFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile| + SceneConverterFeature::AddMeshes; + } + + void doBeginFile(Containers::StringView) override {} + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return true; + } + + bool doEndFile(Containers::StringView) override { + return {}; + } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToFile(MeshData{MeshPrimitive::Triangles, 0xfc}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"))); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), ""); +} + void AbstractSceneConverterTest::convertMeshToFileNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -642,27 +1300,3773 @@ void AbstractSceneConverterTest::convertMeshToFileNotImplemented() { CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convertToFile(): mesh conversion advertised but not implemented\n"); } -void AbstractSceneConverterTest::debugFeature() { +void AbstractSceneConverterTest::beginEnd() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override { + CORRADE_VERIFY(!beginCalled); + beginCalled = true; + } + + Containers::Pointer doEnd() override { + CORRADE_VERIFY(!endCalled); + endCalled = true; + + struct Importer: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + void doClose() override {} + bool doIsOpened() const override { return true; } + + const void* doImporterState() const override { + return reinterpret_cast(0xdeadbeef); + } + }; + + return Containers::Pointer{new Importer}; + } + + bool beginCalled = false, endCalled = false; + } converter; + + CORRADE_VERIFY(!converter.isConverting()); + converter.begin(); + CORRADE_VERIFY(converter.beginCalled); + CORRADE_VERIFY(!converter.endCalled); + CORRADE_VERIFY(converter.isConverting()); + + Containers::Pointer out = converter.end(); + CORRADE_VERIFY(out); + CORRADE_COMPARE(out->importerState(), reinterpret_cast(0xdeadbeef)); + CORRADE_VERIFY(converter.endCalled); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginEndFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override {} + + Containers::Pointer doEnd() override { + return nullptr; + } + } converter; + + converter.begin(); + + /* The implementation is expected to print an error message on its own */ std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.end()); + CORRADE_COMPARE(out.str(), ""); - Debug{&out} << SceneConverterFeature::ConvertMeshInPlace << SceneConverterFeature(0xf0); - CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshInPlace Trade::SceneConverterFeature(0xf0)\n"); + CORRADE_VERIFY(!converter.isConverting()); } -void AbstractSceneConverterTest::debugFeatures() { +void AbstractSceneConverterTest::beginNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + } converter; + std::ostringstream out; + Error redirectError{&out}; + converter.begin(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::begin(): feature advertised but not implemented\n"); +} - Debug{&out} << (SceneConverterFeature::ConvertMesh|SceneConverterFeature::ConvertMeshToFile) << SceneConverterFeatures{}; - CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMesh|Trade::SceneConverterFeature::ConvertMeshToFile Trade::SceneConverterFeatures{}\n"); +void AbstractSceneConverterTest::endNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.end(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::end(): feature advertised but not implemented\n"); } -void AbstractSceneConverterTest::debugFeaturesSupersets() { - /* ConvertMeshToData is a superset of ConvertMeshToFile, so only one should - be printed */ - { - std::ostringstream out; - Debug{&out} << (SceneConverterFeature::ConvertMeshToData|SceneConverterFeature::ConvertMeshToFile); - CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshToData\n"); +void AbstractSceneConverterTest::beginEndData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override { + CORRADE_VERIFY(!beginCalled); + beginCalled = true; + } + + Containers::Optional> doEndData() override { + CORRADE_VERIFY(!endCalled); + endCalled = true; + return Containers::array({'h', 'e', 'l', 'l', 'o'}); + } + + bool beginCalled = false, endCalled = false; + } converter; + + CORRADE_VERIFY(!converter.isConverting()); + converter.beginData(); + CORRADE_VERIFY(converter.beginCalled); + CORRADE_VERIFY(!converter.endCalled); + CORRADE_VERIFY(converter.isConverting()); + + Containers::Optional> out = converter.endData(); + CORRADE_VERIFY(out); + CORRADE_COMPARE_AS(*out, + Containers::arrayView({'h', 'e', 'l', 'l', 'o'}), + TestSuite::Compare::Container); + CORRADE_VERIFY(converter.endCalled); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginEndDataFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + + Containers::Optional> doEndData() override { + return {}; + } + } converter; + + converter.beginData(); + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endData()); + CORRADE_COMPARE(out.str(), ""); + + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginDataNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.beginData(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::beginData(): feature advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::endDataNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + } converter; + + converter.beginData(); + + std::ostringstream out; + Error redirectError{&out}; + converter.endData(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::endData(): feature advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::beginEndDataCustomDeleter() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + + Containers::Optional> doEndData() override { + return Containers::Array{data, 1, [](char*, std::size_t) {}}; + } + + char data[1]; + } converter; + + converter.beginData(); + + std::ostringstream out; + Error redirectError{&out}; + converter.endData(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endData(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractSceneConverterTest::beginEndFile() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + + void doBeginFile(Containers::StringView filename) override { + CORRADE_VERIFY(!beginCalled); + beginCalled = true; + CORRADE_COMPARE(filename, "file.gltf"); + filenameDataPointer = filename.data(); + } + + bool doEndFile(Containers::StringView filename) override { + CORRADE_VERIFY(!endCalled); + endCalled = true; + CORRADE_COMPARE(filename, "file.gltf"); + + /* The filename should stay in scope and be the same pointer */ + CORRADE_COMPARE(filename.data(), filenameDataPointer); + return true; + } + + bool beginCalled = false, endCalled = false; + const void* filenameDataPointer; + } converter; + + CORRADE_VERIFY(!converter.isConverting()); + converter.beginFile("file.gltf!"_s.exceptSuffix(1)); + CORRADE_VERIFY(converter.beginCalled); + CORRADE_VERIFY(!converter.endCalled); + CORRADE_VERIFY(converter.isConverting()); + + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(converter.endCalled); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginEndFileFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + + void doBeginFile(Containers::StringView) override {} + + bool doEndFile(Containers::StringView) override { + return false; + } + } converter; + + converter.beginFile("file.gltf"); + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_COMPARE(out.str(), ""); + + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginEndFileThroughData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + + Containers::Optional> doEndData() override { + return Containers::array({'h', 'e', 'l', 'l', 'o'}); + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + + /* doEndFile() should call doEndData() */ + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE_AS(filename, "hello", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::beginEndFileThroughDataFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + + Containers::Optional> doEndData() override { + return {}; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + + /* Function should fail, no file should get written and no error output + should be printed (the base implementation assumes the plugin does it) */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + CORRADE_COMPARE(out.str(), ""); +} + +void AbstractSceneConverterTest::beginEndFileThroughDataNotWritable() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + + Containers::Optional> doEndData() override { + return Containers::array({'h', 'e', 'l', 'l', 'o'}); + } + } converter; + + converter.beginFile("/some/path/that/does/not/exist"); + + /* Function should fail, no file should get written and no error output + should be printed (the base implementation assumes the plugin does it) */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + /* There's an error from Path::write() before */ + CORRADE_COMPARE_AS(out.str(), + "\nTrade::AbstractSceneConverter::endFile(): cannot write to file /some/path/that/does/not/exist\n", + TestSuite::Compare::StringHasSuffix); +} + +void AbstractSceneConverterTest::beginFileNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.beginFile("file.gltf"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::beginFile(): feature advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::endFileNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + + void doBeginFile(Containers::StringView) override {} + } converter; + + converter.beginFile("file.gltf"); + + std::ostringstream out; + Error redirectError{&out}; + converter.endFile(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): feature advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::abort() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override {} + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool abortCalled = false; + } converter; + + CORRADE_VERIFY(!converter.abortCalled); + converter.begin(); + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.isConverting()); + converter.abort(); + CORRADE_VERIFY(converter.abortCalled); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::abortNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override {} + } converter; + + /* This should work, there's no need for a plugin to implement this */ + converter.begin(); + CORRADE_VERIFY(converter.isConverting()); + converter.abort(); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::thingNoBegin() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::AddScenes| + SceneConverterFeature::AddAnimations| + SceneConverterFeature::AddLights| + SceneConverterFeature::AddCameras| + SceneConverterFeature::AddSkins2D| + SceneConverterFeature::AddSkins3D| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::AddMaterials| + SceneConverterFeature::AddTextures| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::AddCompressedImages1D| + SceneConverterFeature::AddCompressedImages2D| + SceneConverterFeature::AddCompressedImages3D| + SceneConverterFeature::MeshLevels| + SceneConverterFeature::ImageLevels; + } + + void doBeginData() override {} + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.end(); + converter.endData(); + converter.endFile(); + + converter.sceneCount(); + converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}); + converter.setSceneFieldName({}, {}); + converter.setObjectName(0, {}); + converter.setDefaultScene(0); + + converter.animationCount(); + converter.add(AnimationData{nullptr, nullptr}); + + converter.lightCount(); + converter.add(LightData{LightData::Type::Point, {}, 0.0f}); + + converter.cameraCount(); + converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f}); + + converter.skin2DCount(); + converter.add(SkinData2D{nullptr, nullptr}); + + converter.skin3DCount(); + converter.add(SkinData3D{nullptr, nullptr}); + + converter.meshCount(); + converter.add(MeshData{MeshPrimitive::Triangles, 0}); + converter.add({MeshData{MeshPrimitive::Triangles, 0}, + MeshData{MeshPrimitive::Triangles, 0}}); + converter.setMeshAttributeName({}, {}); + + converter.materialCount(); + converter.add(MaterialData{{}, nullptr}); + + converter.textureCount(); + converter.add(TextureData{{}, {}, {}, {}, {}, 0}); + + const char imageData[4]{}; + + converter.image1DCount(); + converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}); + converter.add({ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}}); + + converter.image2DCount(); + converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}); + converter.add({ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}}); + + converter.image3DCount(); + converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}); + converter.add({ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}}); + + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::end(): no conversion in progress\n" + "Trade::AbstractSceneConverter::endData(): no data conversion in progress\n" + "Trade::AbstractSceneConverter::endFile(): no file conversion in progress\n" + + "Trade::AbstractSceneConverter::sceneCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::setSceneFieldName(): no conversion in progress\n" + "Trade::AbstractSceneConverter::setObjectName(): no conversion in progress\n" + "Trade::AbstractSceneConverter::setDefaultScene(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::animationCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::lightCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::cameraCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::skin2DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::skin3DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::meshCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::setMeshAttributeName(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::materialCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::textureCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::image1DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::image2DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::image3DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n"); +} + +void AbstractSceneConverterTest::endMismatchedBegin() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + } converter; + + converter.beginData(); + + std::ostringstream out; + Error redirectError{&out}; + converter.end(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::end(): no conversion in progress\n"); +} + +void AbstractSceneConverterTest::endDataMismatchedBegin() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.endData(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endData(): no data conversion in progress\n"); +} + +void AbstractSceneConverterTest::endFileMismatchedBegin() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.endFile(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): no file conversion in progress\n"); +} + +void AbstractSceneConverterTest::addScene() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const SceneData& scene, Containers::StringView name) override { + /* Scene count should not be increased before the function + returns */ + CORRADE_COMPARE(id, sceneCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(scene.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.sceneCount(), 0); + CORRADE_COMPARE(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.sceneCount(), 1); + CORRADE_COMPARE(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.sceneCount(), 2); +} + +void AbstractSceneConverterTest::addSceneFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.sceneCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.sceneCount(), 0); +} + +void AbstractSceneConverterTest::addSceneNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): scene conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::setSceneFieldName() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + void doSetSceneFieldName(UnsignedInt field, Containers::StringView name) override { + CORRADE_COMPARE(field, 1337); + CORRADE_COMPARE(name, "hello!"); + setSceneFieldNameCalled = true; + } + + bool setSceneFieldNameCalled = false; + } converter; + + CORRADE_VERIFY(true); /* capture correct function name */ + + converter.begin(); + converter.setSceneFieldName(sceneFieldCustom(1337), "hello!"); + CORRADE_VERIFY(converter.setSceneFieldNameCalled); +} + +void AbstractSceneConverterTest::setSceneFieldNameNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + } converter; + + /* This should work, there's no need for a plugin to implement this */ + converter.begin(); + converter.setSceneFieldName(sceneFieldCustom(1337), "hello!"); + CORRADE_VERIFY(true); +} + +void AbstractSceneConverterTest::setSceneFieldNameNotCustom() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.setSceneFieldName(SceneField::Transformation, "hello!"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::setSceneFieldName(): Trade::SceneField::Transformation is not custom\n"); +} + +void AbstractSceneConverterTest::setObjectName() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + void doSetObjectName(UnsignedLong object, Containers::StringView name) override { + CORRADE_COMPARE(object, 1337); + CORRADE_COMPARE(name, "hey!"); + setObjectNameCalled = true; + } + + bool setObjectNameCalled = false; + } converter; + + CORRADE_VERIFY(true); /* capture correct function name */ + + converter.begin(); + converter.setObjectName(1337, "hey!"); + CORRADE_VERIFY(converter.setObjectNameCalled); +} + +void AbstractSceneConverterTest::setObjectNameNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + } converter; + + /* This should work, there's no need for a plugin to implement this */ + converter.begin(); + converter.setObjectName(1337, "hey!"); + CORRADE_VERIFY(true); +} + +void AbstractSceneConverterTest::setDefaultScene() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { + return true; + } + + void doSetDefaultScene(UnsignedInt id) override { + CORRADE_COMPARE(id, 2); + setDefaultSceneCalled = true; + } + + bool setDefaultSceneCalled = false; + } converter; + + converter.begin(); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_COMPARE(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}), 2); + + converter.setDefaultScene(2); + CORRADE_VERIFY(converter.setDefaultSceneCalled); +} + +void AbstractSceneConverterTest::setDefaultSceneNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { + return true; + } + } converter; + + converter.begin(); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_COMPARE(converter.sceneCount(), 1); + + /* This should work, there's no need for a plugin to implement this */ + converter.setDefaultScene(0); +} + +void AbstractSceneConverterTest::setDefaultSceneOutOfRange() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { + return true; + } + } converter; + + converter.begin(); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_COMPARE(converter.sceneCount(), 2); + + std::ostringstream out; + Error redirectError{&out}; + converter.setDefaultScene(2); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::setDefaultScene(): index 2 out of range for 2 scenes\n"); +} + +void AbstractSceneConverterTest::addAnimation() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddAnimations; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const AnimationData& animation, Containers::StringView name) override { + /* Animation count should not be increased before the function + returns */ + CORRADE_COMPARE(id, animationCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(animation.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.animationCount(), 0); + CORRADE_COMPARE(converter.add(AnimationData{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.animationCount(), 1); + CORRADE_COMPARE(converter.add(AnimationData{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.animationCount(), 2); +} + +void AbstractSceneConverterTest::addAnimationFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddAnimations; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const AnimationData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.animationCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(AnimationData{nullptr, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.animationCount(), 0); +} + +void AbstractSceneConverterTest::addAnimationNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddAnimations; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(AnimationData{nullptr, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): animation conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addLight() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddLights; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const LightData& light, Containers::StringView name) override { + /* Light count should not be increased before the function + returns */ + CORRADE_COMPARE(id, lightCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(light.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.lightCount(), 0); + CORRADE_COMPARE(converter.add(LightData{LightData::Type::Point, {}, 0.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.lightCount(), 1); + CORRADE_COMPARE(converter.add(LightData{LightData::Type::Point, {}, 0.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.lightCount(), 2); +} + +void AbstractSceneConverterTest::addLightFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddLights; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const LightData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.lightCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(LightData{LightData::Type::Point, {}, 0.0f})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.lightCount(), 0); +} + +void AbstractSceneConverterTest::addLightNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddLights; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(LightData{LightData::Type::Point, {}, 0.0f}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): light conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addCamera() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCameras; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const CameraData& camera, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, cameraCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(camera.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.cameraCount(), 0); + CORRADE_COMPARE(converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.cameraCount(), 1); + CORRADE_COMPARE(converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.cameraCount(), 2); +} + +void AbstractSceneConverterTest::addCameraFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCameras; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const CameraData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.cameraCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.cameraCount(), 0); +} + +void AbstractSceneConverterTest::addCameraNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCameras; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): camera conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addSkin2D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins2D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const SkinData2D& skin, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, skin2DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(skin.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.skin2DCount(), 0); + CORRADE_COMPARE(converter.add(SkinData2D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.skin2DCount(), 1); + CORRADE_COMPARE(converter.add(SkinData2D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.skin2DCount(), 2); +} + +void AbstractSceneConverterTest::addSkin2DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins2D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const SkinData2D&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.skin2DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(SkinData2D{nullptr, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.skin2DCount(), 0); +} + +void AbstractSceneConverterTest::addSkin2DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins2D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(SkinData2D{nullptr, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 2D skin conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addSkin3D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins3D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const SkinData3D& skin, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, skin3DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(skin.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.skin3DCount(), 0); + CORRADE_COMPARE(converter.add(SkinData3D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.skin3DCount(), 1); + CORRADE_COMPARE(converter.add(SkinData3D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.skin3DCount(), 2); +} + +void AbstractSceneConverterTest::addSkin3DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins3D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const SkinData3D&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.skin3DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(SkinData3D{nullptr, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.skin3DCount(), 0); +} + +void AbstractSceneConverterTest::addSkin3DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins3D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(SkinData3D{nullptr, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 3D skin conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addMesh() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, meshCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(mesh.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.meshCount(), 2); +} + +void AbstractSceneConverterTest::addMeshFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Triangles, 0})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMesh() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh; + } + + Containers::Optional doConvert(const MeshData& mesh) override { + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + return MeshData{MeshPrimitive::Lines, mesh.vertexCount()*2}; + } + + void doBegin() override { + CORRADE_FAIL_IF(true, "doBegin() should not be called"); + } + + Containers::Pointer doEnd() override { + CORRADE_FAIL_IF(true, "doEnd() should not be called"); + return {}; + } + + bool doAdd(UnsignedInt, const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doAdd() should not be called"); + return {}; + } + } converter; + + CORRADE_VERIFY(true); /* capture correct function name */ + + converter.begin(); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + Containers::Pointer importer = converter.end(); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(importer); + CORRADE_VERIFY(importer->isOpened()); + CORRADE_COMPARE(importer->meshCount(), 1); + + Containers::Optional mesh = importer->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_COMPARE(mesh->primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(mesh->vertexCount(), 12); + + /* The mesh is returned only once, second time it will fail (but just an + error, not an assert */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->mesh(0)); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::end(): mesh can be retrieved only once from a converter with just Trade::SceneConverterFeature::ConvertMesh\n"); + } + + /* Verify that it's also possible to close the importer without hitting + some nasty assert */ + importer->close(); + CORRADE_VERIFY(!importer->isOpened()); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh; + } + + Containers::Optional doConvert(const MeshData&) override { + return {}; + } + } converter; + + converter.begin(); + + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Lines, 6})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* But the observable behavior is as if no mesh was added */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.end()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::end(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshZeroMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh; + } + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.end()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::end(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshTwoMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh; + } + + Containers::Optional doConvert(const MeshData& mesh) override { + return MeshData{MeshPrimitive::Lines, mesh.vertexCount()*2}; + } + } converter; + + converter.begin(); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Triangles, 7})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh\n"); + } + + /* Getting the result should still work */ + Containers::Pointer out = converter.end(); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(out); + CORRADE_COMPARE(out->meshCount(), 1); + + Containers::Optional mesh = out->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_COMPARE(mesh->primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(mesh->vertexCount(), 12); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + + Containers::Optional> doConvertToData(const MeshData& mesh) override { + return Containers::Array{nullptr, mesh.vertexCount()}; + } + + void doBeginData() override { + CORRADE_FAIL_IF(true, "doBeginData() should not be called"); + } + + Containers::Optional> doEndData() override { + CORRADE_FAIL_IF(true, "doEndData() should not be called"); + return {}; + } + + bool doAdd(UnsignedInt, const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doAdd() should not be called"); + return {}; + } + } converter; + + converter.beginData(); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + Containers::Optional> data = converter.endData(); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->size(), 6); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + + Containers::Optional> doConvertToData(const MeshData&) override { + return {}; + } + } converter; + + converter.beginData(); + + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Lines, 6})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* But the observable behavior is as if no mesh was added */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endData()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endData(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataZeroMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + } converter; + + converter.beginData(); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endData()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endData(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataTwoMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + + Containers::Optional> doConvertToData(const MeshData& mesh) override { + return Containers::Array{nullptr, mesh.vertexCount()}; + } + } converter; + + converter.beginData(); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Triangles, 7})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh\n"); + } + + /* Getting the result should still work */ + Containers::Optional> data = converter.endData(); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->size(), 6); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFile() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile; + } + + bool doConvertToFile(const MeshData& mesh, Containers::StringView filename) override { + return Utility::Path::write(filename, Containers::arrayView({char(mesh.vertexCount())})); + } + + void doBeginFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doBeginFile() should not be called"); + } + + bool doEndFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doEndFile() should not be called"); + return {}; + } + + bool doAdd(UnsignedInt, const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doAdd() should not be called"); + return {}; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0xef}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + /* It's easier to just perform the operation right during add() than to + make a copy of the passed MeshData and do it at endFile(), so the file + exists at this point already */ + CORRADE_VERIFY(Utility::Path::exists(filename)); + + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(Utility::Path::exists(filename)); + CORRADE_COMPARE_AS(filename, + "\xef", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileThroughData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + + Containers::Optional> doConvertToData(const MeshData& mesh) override { + return Containers::array({char(mesh.vertexCount())}); + } + + void doBeginData() override { + CORRADE_FAIL_IF(true, "doBeginData() should not be called"); + } + + Containers::Optional> doEndData() override { + CORRADE_FAIL_IF(true, "doEndData() should not be called"); + return {}; + } + + void doBeginFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doBeginFile() should not be called"); + } + + bool doEndFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doEndFile() should not be called"); + return {}; + } + + bool doAdd(UnsignedInt, const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doAdd() should not be called"); + return {}; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0xef}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + /* It's easier to just perform the operation right during add() than to + make a copy of the passed MeshData and do it at the end(), so the file + exists at this point already */ + CORRADE_VERIFY(Utility::Path::exists(filename)); + + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(Utility::Path::exists(filename)); + CORRADE_COMPARE_AS(filename, + "\xef", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile; + } + + bool doConvertToFile(const MeshData&, Containers::StringView) override { + return false; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Lines, 6})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* But the observable behavior is as if no mesh was added */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileZeroMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileTwoMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile; + } + + bool doConvertToFile(const MeshData& mesh, Containers::StringView filename) override { + return Utility::Path::write(filename, Containers::arrayView({char(mesh.vertexCount())})); + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0xef}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + /* It's easier to just perform the operation right during add() than to + make a copy of the passed MeshData and do it at the endFile(), so the + file exists at this point already */ + CORRADE_VERIFY(Utility::Path::exists(filename)); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Triangles, 0xb0})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh\n"); + } + + /* Getting the result should still work */ + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(Utility::Path::exists(filename)); + CORRADE_COMPARE_AS(filename, + "\xef", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::addMeshNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(MeshData{MeshPrimitive::Triangles, 0}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): mesh conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addMeshLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, Containers::Iterable meshLevels, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, meshCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(meshLevels.size(), 3); + CORRADE_COMPARE(meshLevels[1].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add({ + MeshData{MeshPrimitive::Lines, 0}, + MeshData{MeshPrimitive::Triangles, 3, reinterpret_cast(0xdeadbeef)}, + MeshData{MeshPrimitive::Faces, 0}, + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(converter.add({ + MeshData{MeshPrimitive::Faces, 2}, + MeshData{MeshPrimitive::Meshlets, 1, reinterpret_cast(0xdeadbeef)}, + MeshData{MeshPrimitive::Points, 0}, + }, "hello"), 1); + CORRADE_COMPARE(converter.meshCount(), 2); +} + +void AbstractSceneConverterTest::addMeshLevelsFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add({ + MeshData{MeshPrimitive::Triangles, 0}, + MeshData{MeshPrimitive::Triangles, 0} + })); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); +} + +void AbstractSceneConverterTest::addMeshLevelsNoLevels() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(Containers::Iterable{}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): at least one mesh level has to be specified\n"); +} + +void AbstractSceneConverterTest::addMeshLevelsNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + MeshData{MeshPrimitive::Triangles, 0}, + MeshData{MeshPrimitive::Triangles, 0} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): multi-level mesh conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addMeshThroughLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable meshLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(meshLevels.size(), 1); + CORRADE_COMPARE(meshLevels[0].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.meshCount(), 1); +} + +void AbstractSceneConverterTest::setMeshAttributeName() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + void doBegin() override {} + + void doSetMeshAttributeName(UnsignedShort field, Containers::StringView name) override { + CORRADE_COMPARE(field, 1337); + CORRADE_COMPARE(name, "hello!"); + setMeshAttributeNameCalled = true; + } + + bool setMeshAttributeNameCalled = false; + } converter; + + CORRADE_VERIFY(true); /* capture correct function name */ + + converter.begin(); + converter.setMeshAttributeName(meshAttributeCustom(1337), "hello!"); + CORRADE_VERIFY(converter.setMeshAttributeNameCalled); +} + +void AbstractSceneConverterTest::setMeshAttributeNameNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + void doBegin() override {} + } converter; + + /* This should work, there's no need for a plugin to implement this */ + converter.begin(); + converter.setMeshAttributeName(meshAttributeCustom(1337), "hello!"); + CORRADE_VERIFY(true); +} + +void AbstractSceneConverterTest::setMeshAttributeNameNotCustom() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.setMeshAttributeName(MeshAttribute::ObjectId, "hello!"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::setMeshAttributeName(): Trade::MeshAttribute::ObjectId is not custom\n"); +} + +void AbstractSceneConverterTest::addMaterial() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMaterials; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const MaterialData& material, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, materialCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(material.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.materialCount(), 0); + CORRADE_COMPARE(converter.add(MaterialData{{}, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.materialCount(), 1); + CORRADE_COMPARE(converter.add(MaterialData{{}, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.materialCount(), 2); +} + +void AbstractSceneConverterTest::addMaterialFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMaterials; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const MaterialData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.materialCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MaterialData{{}, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.materialCount(), 0); +} + +void AbstractSceneConverterTest::addMaterialNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMaterials; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(MaterialData{{}, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): material conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addTexture() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddTextures; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const TextureData& texture, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, textureCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(texture.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.textureCount(), 0); + CORRADE_COMPARE(converter.add(TextureData{{}, {}, {}, {}, {}, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.textureCount(), 1); + CORRADE_COMPARE(converter.add(TextureData{{}, {}, {}, {}, {}, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.textureCount(), 2); +} + +void AbstractSceneConverterTest::addTextureFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddTextures; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const TextureData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.textureCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(TextureData{{}, {}, {}, {}, {}, 0})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.textureCount(), 0); +} + +void AbstractSceneConverterTest::addTextureNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddTextures; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(TextureData{{}, {}, {}, {}, {}, 0}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): texture conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImage1D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const ImageData1D& image, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image1DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(image.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, {}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); + CORRADE_COMPARE(converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, {}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.image1DCount(), 2); +} + +void AbstractSceneConverterTest::addImage1DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData1D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(!image.isCompressed()); + CORRADE_COMPARE(image.storage().alignment(), 2); + CORRADE_COMPARE(image.format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(image.size(), 3); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add(ImageView1D{ + PixelStorage{}.setAlignment(2), + PixelFormat::RG8Snorm, 3, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImage1DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages1D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData1D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(image.isCompressed()); + CORRADE_COMPARE(image.compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(image.compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(image.size(), 3); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add(CompressedImageView1D{ + CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), + CompressedPixelFormat::Astc3x3x3RGBASrgb, 3, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImage1DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::AddCompressedImages1D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData1D&, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData})); + CORRADE_VERIFY(!converter.add(ImageView1D{PixelFormat::RGBA8Unorm, 1, imageData})); + CORRADE_VERIFY(!converter.add(CompressedImageView1D{CompressedPixelFormat::Astc4x4RGBAUnorm, 1, imageData})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image1DCount(), 0); +} + +void AbstractSceneConverterTest::addImage1DInvalidImage() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData1D{PixelFormat::RGBA8Unorm, {}, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image with a zero size: Vector(0)\n"); +} + +void AbstractSceneConverterTest::addImage1DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + const char imageData[4]{}; + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 1D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImage2D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const ImageData2D& image, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image2DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(image.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, {}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); + CORRADE_COMPARE(converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, {}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.image2DCount(), 2); +} + +void AbstractSceneConverterTest::addImage2DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData2D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(!image.isCompressed()); + CORRADE_COMPARE(image.storage().alignment(), 2); + CORRADE_COMPARE(image.format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(image.size(), (Vector2i{3, 1})); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add(ImageView2D{ + PixelStorage{}.setAlignment(2), + PixelFormat::RG8Snorm, {3, 1}, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImage2DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages2D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData2D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(image.isCompressed()); + CORRADE_COMPARE(image.compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(image.compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(image.size(), (Vector2i{3, 2})); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add(CompressedImageView2D{ + CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), + CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 2}, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImage2DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::AddCompressedImages2D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData2D&, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData})); + CORRADE_VERIFY(!converter.add(ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, imageData})); + CORRADE_VERIFY(!converter.add(CompressedImageView2D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1}, imageData})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image2DCount(), 0); +} + +void AbstractSceneConverterTest::addImage2DZeroSize() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + void doBegin() override {} + } converter; + + const char imageData[16]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {4, 0}, DataFlags{}, imageData}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image with a zero size: Vector(4, 0)\n"); +} + +void AbstractSceneConverterTest::addImage2DNullptr() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, {nullptr, 4}}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image with a nullptr view\n"); +} + +void AbstractSceneConverterTest::addImage2DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + const char imageData[4]{}; + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 2D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImage3D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const ImageData3D& image, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image3DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(image.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, {}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); + CORRADE_COMPARE(converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, {}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.image3DCount(), 2); +} + +void AbstractSceneConverterTest::addImage3DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData3D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(!image.isCompressed()); + CORRADE_COMPARE(image.storage().alignment(), 2); + CORRADE_COMPARE(image.format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(image.size(), (Vector3i{1, 3, 1})); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add(ImageView3D{ + PixelStorage{}.setAlignment(2), + PixelFormat::RG8Snorm, {1, 3, 1}, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::addImage3DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages3D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData3D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(image.isCompressed()); + CORRADE_COMPARE(image.compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(image.compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(image.size(), (Vector3i{1, 3, 1})); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add(CompressedImageView3D{ + CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), + CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 3, 1}, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::addImage3DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::AddCompressedImages3D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData3D&, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData})); + CORRADE_VERIFY(!converter.add(ImageView3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, imageData})); + CORRADE_VERIFY(!converter.add(CompressedImageView3D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1, 1}, imageData})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image3DCount(), 0); +} + +void AbstractSceneConverterTest::addImage3DInvalidImage() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {}, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image with a zero size: Vector(0, 0, 0)\n"); +} + +void AbstractSceneConverterTest::addImage3DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D; + } + + void doBegin() override {} + } converter; + + const char imageData[4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 3D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImageLevels1D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image1DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 3); + CORRADE_COMPARE(imageLevels[1].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add({ + /* Arbitrary dimensions should be fine */ + ImageData1D{PixelFormat::RGBA8Unorm, 4, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 2, DataFlags{}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, + ImageData1D{PixelFormat::RGBA8Unorm, 3, DataFlags{}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); + CORRADE_COMPARE(converter.add({ + ImageData1D{PixelFormat::RGBA8Unorm, 2, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, + ImageData1D{PixelFormat::RGBA8Unorm, 4, DataFlags{}, imageData} + }, "hello"), 1); + CORRADE_COMPARE(converter.image1DCount(), 2); +} + +void AbstractSceneConverterTest::addImageLevels1DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(!imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].storage().alignment(), 2); + CORRADE_COMPARE(imageLevels[1].format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(imageLevels[1].size(), 3); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add({ + ImageView1D{PixelFormat::RG8Snorm, 1, imageData}, + ImageView1D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, 3, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels1DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(imageLevels[1].compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(imageLevels[1].size(), 3); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add({ + CompressedImageView1D{CompressedPixelFormat::Astc3x3x3RGBASrgb, 1, imageData}, + CompressedImageView1D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, 3, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels1DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::AddCompressedImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add({ + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + ImageView1D{PixelFormat::RGBA8Unorm, 1, imageData}, + ImageView1D{PixelFormat::RGBA8Unorm, 1, imageData}, + })); + CORRADE_VERIFY(!converter.add({ + CompressedImageView1D{CompressedPixelFormat::Astc4x4RGBAUnorm, 1, imageData}, + CompressedImageView1D{CompressedPixelFormat::Astc4x4RGBAUnorm, 1, imageData}, + })); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image1DCount(), 0); +} + +void AbstractSceneConverterTest::addImageLevels1DInvalidImage() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(std::initializer_list>{}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): at least one image level has to be specified\n"); +} + +void AbstractSceneConverterTest::addImageLevels1DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): multi-level 1D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImageLevels2D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image2DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 3); + CORRADE_COMPARE(imageLevels[1].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add({ + /* Arbitrary dimensions should be fine */ + ImageData2D{PixelFormat::RGBA8Unorm, {4, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 3}, DataFlags{}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); + CORRADE_COMPARE(converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 3}, DataFlags{}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, + ImageData2D{PixelFormat::RGBA8Unorm, {4, 1}, DataFlags{}, imageData} + }, "hello"), 1); + CORRADE_COMPARE(converter.image2DCount(), 2); +} + +void AbstractSceneConverterTest::addImageLevels2DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(!imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].storage().alignment(), 2); + CORRADE_COMPARE(imageLevels[1].format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(imageLevels[1].size(), (Vector2i{1, 3})); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add({ + ImageView2D{PixelFormat::RG8Snorm, {1, 1}, imageData}, + ImageView2D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, {1, 3}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels2DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(imageLevels[1].compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(imageLevels[1].size(), (Vector2i{3, 1})); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add({ + CompressedImageView2D{CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 1}, imageData}, + CompressedImageView2D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 1}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels2DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::AddCompressedImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, imageData}, + ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + CompressedImageView2D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1}, imageData}, + CompressedImageView2D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1}, imageData} + })); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image2DCount(), 0); +} + +void AbstractSceneConverterTest::addImageLevels2DNoLevels() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(std::initializer_list>{}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): at least one image level has to be specified\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DZeroSize() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {4, 0}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image level 1 with a zero size: Vector(4, 0)\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DNullptr() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, {nullptr, 4}} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image level 1 with a nullptr view\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentCompressed() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + ImageData2D a{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}; + ImageData2D b{CompressedPixelFormat::Astc10x10RGBAF, {1, 1}, DataFlags{}, imageData}; + + std::ostringstream out; + Error redirectError{&out}; + converter.add({a, b}); + converter.add({b, b, a}); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::add(): image level 1 is compressed but previous aren't\n" + "Trade::AbstractSceneConverter::add(): image level 2 is not compressed but previous are\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentFormat() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Srgb, {4, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): image levels don't have the same format, expected PixelFormat::RGBA8Unorm but got PixelFormat::RGBA8Srgb for level 2\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentFormatExtra() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelStorage{}, 252, 1037, 4, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelStorage{}, 252, 1037, 4, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelStorage{}, 252, 4467, 4, {4, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): image levels don't have the same extra format field, expected 1037 but got 4467 for level 2\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentCompressedFormat() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{CompressedPixelFormat::Bc1RGBAUnorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{CompressedPixelFormat::Bc1RGBAUnorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{CompressedPixelFormat::Bc1RGBASrgb, {4, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): image levels don't have the same format, expected CompressedPixelFormat::Bc1RGBAUnorm but got CompressedPixelFormat::Bc1RGBASrgb for level 2\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentFlags() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData, ImageFlag2D::Array}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData, ImageFlag2D::Array}, + ImageData2D{PixelFormat::RGBA8Unorm, {4, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): image levels don't have the same flags, expected ImageFlag2D::Array but got ImageFlags2D{} for level 2\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): multi-level 2D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImageLevels3D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image3DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 3); + CORRADE_COMPARE(imageLevels[1].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add({ + /* Arbitrary dimensions should be fine */ + ImageData3D{PixelFormat::RGBA8Unorm, {4, 1, 1}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {2, 2, 1}, DataFlags{}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 3}, DataFlags{}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); + CORRADE_COMPARE(converter.add({ + ImageData3D{PixelFormat::RGBA8Unorm, {2, 1, 2}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 3, 1}, DataFlags{}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 4, 1}, DataFlags{}, imageData} + }, "hello"), 1); + CORRADE_COMPARE(converter.image3DCount(), 2); +} + +void AbstractSceneConverterTest::addImageLevels3DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(!imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].storage().alignment(), 2); + CORRADE_COMPARE(imageLevels[1].format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(imageLevels[1].size(), (Vector3i{1, 3, 1})); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add({ + ImageView3D{PixelFormat::RG8Snorm, {1, 1, 1}, imageData}, + ImageView3D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, {1, 3, 1}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels3DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(imageLevels[1].compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(imageLevels[1].size(), (Vector3i{3, 1, 1})); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add({ + CompressedImageView3D{CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 1, 1}, imageData}, + CompressedImageView3D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 1, 1}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels3DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::AddCompressedImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add({ + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + ImageView3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, imageData}, + ImageView3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + CompressedImageView3D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1, 1}, imageData}, + CompressedImageView3D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1, 1}, imageData} + })); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image3DCount(), 0); +} + +void AbstractSceneConverterTest::addImageLevels3DInvalidImage() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(std::initializer_list>{}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): at least one image level has to be specified\n"); +} + +void AbstractSceneConverterTest::addImageLevels3DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): multi-level 3D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImage1DThroughLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 1); + CORRADE_COMPARE(imageLevels[0].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, {}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImage2DThroughLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 1); + CORRADE_COMPARE(imageLevels[0].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, {}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImage3DThroughLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 1); + CORRADE_COMPARE(imageLevels[0].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, {}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::debugFeature() { + std::ostringstream out; + + Debug{&out} << SceneConverterFeature::ConvertMeshInPlace << SceneConverterFeature(0xdeaddead); + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshInPlace Trade::SceneConverterFeature(0xdeaddead)\n"); +} + +void AbstractSceneConverterTest::debugFeatures() { + std::ostringstream out; + + Debug{&out} << (SceneConverterFeature::ConvertMesh|SceneConverterFeature::ConvertMeshToFile) << SceneConverterFeatures{}; + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMesh|Trade::SceneConverterFeature::ConvertMeshToFile Trade::SceneConverterFeatures{}\n"); +} + +void AbstractSceneConverterTest::debugFeaturesSupersets() { + /* ConvertMeshToData is a superset of ConvertMeshToFile, so only one should + be printed */ + { + std::ostringstream out; + Debug{&out} << (SceneConverterFeature::ConvertMeshToData|SceneConverterFeature::ConvertMeshToFile); + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshToData\n"); + + /* ConvertMultipleToData is a superset of ConvertMultipleToFile, so only + one should be printed */ + } { + std::ostringstream out; + Debug{&out} << (SceneConverterFeature::ConvertMultipleToData|SceneConverterFeature::ConvertMultipleToFile); + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMultipleToData\n"); } } diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp index 9c5934767..bcde3cf34 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp @@ -98,4 +98,4 @@ bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers:: }} CORRADE_PLUGIN_REGISTER(AnySceneConverter, Magnum::Trade::AnySceneConverter, - "cz.mosra.magnum.Trade.AbstractSceneConverter/0.1.2") + "cz.mosra.magnum.Trade.AbstractSceneConverter/0.2") From 2b5cb6d4096798da0ebe41be09d1eebfb7515632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 17 Aug 2022 19:38:41 +0200 Subject: [PATCH 11/93] package/ci: yes, tests got even heavier, so reduce the parallelism again. --- package/ci/circleci.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/package/ci/circleci.yml b/package/ci/circleci.yml index f82254895..939233540 100644 --- a/package/ci/circleci.yml +++ b/package/ci/circleci.yml @@ -271,7 +271,7 @@ jobs: extra: libgl1-mesa-dev libsdl2-dev libglfw3-dev libopenal-dev libvulkan-dev # In this case it gets stuck even with 24 jobs. Only on GCC, usually when # the huge TradeAbstractImporterTest / TradeMaterialDataTest get involved. - # TODO: revisit when we get rid of more STL + # TODO: revisit when we get rid of more STL / deprecated includes - cap-ninja-jobs: count: 20 - install-gcc-4_8 @@ -310,7 +310,7 @@ jobs: # It crashes with the default setting. Only on GCC, usually when the huge # TradeAbstractImporterTest / TradeMaterialDataTest / TradeSceneDataTest # get involved. - # TODO: revisit when we get rid of more STL + # TODO: revisit when we get rid of more STL / deprecated includes - cap-ninja-jobs - install-gcc-4_8 - install-swiftshader-vulkan: @@ -331,11 +331,11 @@ jobs: steps: - install-base-linux: extra: libsdl2-dev libglfw3-dev wget unzip - # It crashes with the default setting. Only on GCC, usually when the huge - # TradeAbstractImporterTest / TradeMaterialDataTest / TradeSceneDataTest - # get involved. - # TODO: revisit when we get rid of more STL - - cap-ninja-jobs + # In this case it gets stuck even with 24 jobs. Only on GCC, usually when + # the huge TradeAbstractImporterTest / TradeMaterialDataTest get involved. + # TODO: revisit when we get rid of more STL / deprecated includes + - cap-ninja-jobs: + count: 20 - install-gcc-4_8 - install-cmake: version: "3.4.3" @@ -357,11 +357,11 @@ jobs: steps: - install-base-linux: extra: libsdl2-dev libglfw3-dev wget unzip - # It crashes with the default setting. Only on GCC, usually when the huge - # TradeAbstractImporterTest / TradeMaterialDataTest / TradeSceneDataTest - # get involved. - # TODO: revisit when we get rid of more STL - - cap-ninja-jobs + # In this case it gets stuck even with 24 jobs. Only on GCC, usually when + # the huge TradeAbstractImporterTest / TradeMaterialDataTest get involved. + # TODO: revisit when we get rid of more STL / deprecated includes + - cap-ninja-jobs: + count: 20 - install-gcc-4_8 - install-cmake: version: "3.4.3" @@ -385,7 +385,7 @@ jobs: - install-base-linux: extra: libgl1-mesa-dev libsdl2-dev libglfw3-dev libopenal-dev libvulkan-dev # 24 is not enough, unlike the other GCC-based builds - # TODO: revisit when we get rid of more STL + # TODO: revisit when we get rid of more STL / deprecated includes - cap-ninja-jobs: count: 20 - install-gcc-4_8 From cc8440184acc54093c90f1a1afbed0d5801d4d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 17 Aug 2022 22:08:10 +0200 Subject: [PATCH 12/93] Trade: AbstractSceneConverter::begin*() should have a way to fail. Initially I thought there was no reason for this to fail, but then realized AnySceneConverter would need it. And also any other plugin relying on something external that might fail during initialization -- there's no other moment after plugin instantiation where it could signalize a failure, and deferring that to the first add() call, whichever would it be, is really not a sane idea. --- src/Magnum/Trade/AbstractSceneConverter.cpp | 50 +- src/Magnum/Trade/AbstractSceneConverter.h | 23 +- .../Trade/Test/AbstractSceneConverterTest.cpp | 572 ++++++++++-------- 3 files changed, 368 insertions(+), 277 deletions(-) diff --git a/src/Magnum/Trade/AbstractSceneConverter.cpp b/src/Magnum/Trade/AbstractSceneConverter.cpp index a364759eb..a789c1fde 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.cpp +++ b/src/Magnum/Trade/AbstractSceneConverter.cpp @@ -301,22 +301,28 @@ void AbstractSceneConverter::abort() { void AbstractSceneConverter::doAbort() {} -void AbstractSceneConverter::begin() { +bool AbstractSceneConverter::begin() { if(_state) abort(); _state.emplace(State::Type::Convert); if(features() >= SceneConverterFeature::ConvertMultiple) { - doBegin(); + if(!doBegin()) { + _state = {}; + return false; + } + + return true; } else if(features() & SceneConverterFeature::ConvertMesh) { /* Actual operation performed in doAdd(const MeshData&) */ + return true; - } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::begin(): feature not supported", ); + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::begin(): feature not supported", {}); } -void AbstractSceneConverter::doBegin() { - CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::begin(): feature advertised but not implemented", ); +bool AbstractSceneConverter::doBegin() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::begin(): feature advertised but not implemented", {}); } Containers::Pointer AbstractSceneConverter::end() { @@ -378,22 +384,28 @@ Containers::Pointer AbstractSceneConverter::doEnd() { CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::end(): feature advertised but not implemented", {}); } -void AbstractSceneConverter::beginData() { +bool AbstractSceneConverter::beginData() { if(_state) abort(); _state.emplace(State::Type::ConvertToData); if(features() >= SceneConverterFeature::ConvertMultipleToData) { - doBeginData(); + if(!doBeginData()) { + _state = {}; + return false; + } + + return true; } else if(features() >= SceneConverterFeature::ConvertMeshToData) { /* Actual operation performed in doAdd(const MeshData&) */ + return true; - } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginData(): feature not supported", ); + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginData(): feature not supported", {}); } -void AbstractSceneConverter::doBeginData() { - CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginData(): feature advertised but not implemented", ); +bool AbstractSceneConverter::doBeginData() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginData(): feature advertised but not implemented", {}); } Containers::Optional> AbstractSceneConverter::endData() { @@ -429,26 +441,32 @@ Containers::Optional> AbstractSceneConverter::doEndData( CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::endData(): feature advertised but not implemented", {}); } -void AbstractSceneConverter::beginFile(const Containers::StringView filename) { +bool AbstractSceneConverter::beginFile(const Containers::StringView filename) { if(_state) abort(); _state.emplace(State::Type::ConvertToFile); _state->filename = Containers::String::nullTerminatedGlobalView(filename); if(features() >= SceneConverterFeature::ConvertMultipleToFile) { - doBeginFile(_state->filename); + if(!doBeginFile(_state->filename)) { + _state = {}; + return false; + } + + return true; } else if(features() >= SceneConverterFeature::ConvertMeshToFile) { /* Actual operation performed in doAdd(const MeshData&) */ + return true; - } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginFile(): feature not supported", ); + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginFile(): feature not supported", {}); } -void AbstractSceneConverter::doBeginFile(Containers::StringView) { +bool AbstractSceneConverter::doBeginFile(Containers::StringView) { CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMultipleToData, - "Trade::AbstractSceneConverter::beginFile(): feature advertised but not implemented", ); + "Trade::AbstractSceneConverter::beginFile(): feature advertised but not implemented", {}); - doBeginData(); + return doBeginData(); } bool AbstractSceneConverter::endFile() { diff --git a/src/Magnum/Trade/AbstractSceneConverter.h b/src/Magnum/Trade/AbstractSceneConverter.h index 226df8035..3d25bb31b 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.h +++ b/src/Magnum/Trade/AbstractSceneConverter.h @@ -753,7 +753,8 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * If a conversion is currently in progress, calls @ref abort() first. * The converted output of data supplied via various @ref add() and * `set*()` APIs is returned via an importer instance upon calling - * @ref end(). + * @ref end(). On failure prints a message to @relativeref{Magnum,Error} + * and returns @cpp false @ce. * * Expects that @ref SceneConverterFeature::ConvertMultiple is * supported. If not and @ref SceneConverterFeature::ConvertMesh is @@ -763,7 +764,7 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @ref end(). * @see @ref features(), @ref beginData(), @ref beginFile() */ - void begin(); + bool begin(); /** * @brief End converting a scene @@ -806,7 +807,9 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * * If a conversion is currently in progress, calls @ref abort() first. * The converted output of data supplied via various @ref add() and - * `set*()` APIs is returned upon calling @ref endData(). + * `set*()` APIs is returned upon calling @ref endData(). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @cpp false @ce. * * Expects that @ref SceneConverterFeature::ConvertMultipleToData is * supported. If not and @ref SceneConverterFeature::ConvertMeshToData @@ -816,7 +819,7 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @ref endData(). * @see @ref features(), @ref begin(), @ref beginFile() */ - void beginData(); + bool beginData(); /** * @brief End converting a scene to raw data @@ -842,7 +845,9 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * * If a conversion is currently in progress, calls @ref abort() first. * The converted output of data supplied via various @ref add() and - * `set*()` APIs is returned upon calling @ref endFile(). + * `set*()` APIs is returned upon calling @ref endFile(). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @cpp false @ce. * * Expects that @ref SceneConverterFeature::ConvertMultipleToFile is * supported. If not and @ref SceneConverterFeature::ConvertMeshToFile @@ -852,7 +857,7 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * return the result from @ref endFile(). * @see @ref features(), @ref begin(), @ref beginData() */ - void beginFile(Containers::StringView filename); + bool beginFile(Containers::StringView filename); /** * @brief End converting a scene to raw data @@ -1757,7 +1762,7 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @relativeref{Corrade,Containers::StringViewFlag::NullTerminated}, * however. */ - virtual void doBeginFile(Containers::StringView filename); + virtual bool doBeginFile(Containers::StringView filename); /** * @brief Implementation for @ref endFile() @@ -1832,7 +1837,7 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @brief Implementation for @ref begin() * @m_since_latest */ - virtual void doBegin(); + virtual bool doBegin(); /** * @brief Implementation for @ref end() @@ -1847,7 +1852,7 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @brief Implementation for @ref beginData() * @m_since_latest */ - virtual void doBeginData(); + virtual bool doBeginData(); /** * @brief Implementation for @ref endData() diff --git a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp index 675b20adc..a6a4f003b 100644 --- a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp @@ -103,18 +103,21 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void convertMeshToFileNotImplemented(); void beginEnd(); - void beginEndFailed(); + void beginFailed(); + void endFailed(); void beginNotImplemented(); void endNotImplemented(); void beginEndData(); - void beginEndDataFailed(); + void beginDataFailed(); + void endDataFailed(); void beginDataNotImplemented(); void endDataNotImplemented(); void beginEndDataCustomDeleter(); void beginEndFile(); - void beginEndFileFailed(); + void beginFileFailed(); + void endFileFailed(); void beginEndFileThroughData(); void beginEndFileThroughDataFailed(); void beginEndFileThroughDataNotWritable(); @@ -316,18 +319,21 @@ AbstractSceneConverterTest::AbstractSceneConverterTest() { &AbstractSceneConverterTest::convertMeshToFileNotImplemented, &AbstractSceneConverterTest::beginEnd, - &AbstractSceneConverterTest::beginEndFailed, + &AbstractSceneConverterTest::beginFailed, + &AbstractSceneConverterTest::endFailed, &AbstractSceneConverterTest::beginNotImplemented, &AbstractSceneConverterTest::endNotImplemented, &AbstractSceneConverterTest::beginEndData, - &AbstractSceneConverterTest::beginEndDataFailed, + &AbstractSceneConverterTest::beginDataFailed, + &AbstractSceneConverterTest::endDataFailed, &AbstractSceneConverterTest::beginDataNotImplemented, &AbstractSceneConverterTest::endDataNotImplemented, &AbstractSceneConverterTest::beginEndDataCustomDeleter, &AbstractSceneConverterTest::beginEndFile, - &AbstractSceneConverterTest::beginEndFileFailed, + &AbstractSceneConverterTest::beginFileFailed, + &AbstractSceneConverterTest::endFileFailed, &AbstractSceneConverterTest::beginEndFileThroughData, &AbstractSceneConverterTest::beginEndFileThroughDataFailed, &AbstractSceneConverterTest::beginEndFileThroughDataNotWritable, @@ -946,8 +952,9 @@ void AbstractSceneConverterTest::convertMeshToDataThroughBatch() { return {}; } - void doBeginData() override { + bool doBeginData() override { _vertexCount = 42; + return true; } bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView) override { @@ -977,7 +984,7 @@ void AbstractSceneConverterTest::convertMeshToDataThroughBatchAddFailed() { SceneConverterFeature::AddMeshes; } - void doBeginData() override {} + bool doBeginData() override { return true; } bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { return false; @@ -1004,7 +1011,7 @@ void AbstractSceneConverterTest::convertMeshToDataThroughBatchEndFailed() { SceneConverterFeature::AddMeshes; } - void doBeginData() override {} + bool doBeginData() override { return true; } bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { return true; @@ -1203,8 +1210,9 @@ void AbstractSceneConverterTest::convertMeshToFileThroughBatch() { return {}; } - void doBeginFile(Containers::StringView filename) override { + bool doBeginFile(Containers::StringView filename) override { _filename = filename; + return true; } bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView) override { @@ -1241,7 +1249,7 @@ void AbstractSceneConverterTest::convertMeshToFileThroughBatchAddFailed() { SceneConverterFeature::AddMeshes; } - void doBeginFile(Containers::StringView) override {} + bool doBeginFile(Containers::StringView) override { return true; } bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { return false; @@ -1268,7 +1276,7 @@ void AbstractSceneConverterTest::convertMeshToFileThroughBatchEndFailed() { SceneConverterFeature::AddMeshes; } - void doBeginFile(Containers::StringView) override {} + bool doBeginFile(Containers::StringView) override { return true; } bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { return true; @@ -1306,9 +1314,10 @@ void AbstractSceneConverterTest::beginEnd() { return SceneConverterFeature::ConvertMultiple; } - void doBegin() override { + bool doBegin() override { CORRADE_VERIFY(!beginCalled); beginCalled = true; + return true; } Containers::Pointer doEnd() override { @@ -1332,7 +1341,7 @@ void AbstractSceneConverterTest::beginEnd() { } converter; CORRADE_VERIFY(!converter.isConverting()); - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_VERIFY(converter.beginCalled); CORRADE_VERIFY(!converter.endCalled); CORRADE_VERIFY(converter.isConverting()); @@ -1344,20 +1353,38 @@ void AbstractSceneConverterTest::beginEnd() { CORRADE_VERIFY(!converter.isConverting()); } -void AbstractSceneConverterTest::beginEndFailed() { +void AbstractSceneConverterTest::beginFailed() { struct: AbstractSceneConverter { SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMultiple; } - void doBegin() override {} + bool doBegin() override { return false; } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.begin()); + CORRADE_COMPARE(out.str(), ""); + + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::endFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + bool doBegin() override { return true; } Containers::Pointer doEnd() override { return nullptr; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); /* The implementation is expected to print an error message on its own */ std::ostringstream out; @@ -1392,10 +1419,10 @@ void AbstractSceneConverterTest::endNotImplemented() { return SceneConverterFeature::ConvertMultiple; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -1410,9 +1437,10 @@ void AbstractSceneConverterTest::beginEndData() { return SceneConverterFeature::ConvertMultipleToData; } - void doBeginData() override { + bool doBeginData() override { CORRADE_VERIFY(!beginCalled); beginCalled = true; + return true; } Containers::Optional> doEndData() override { @@ -1425,7 +1453,7 @@ void AbstractSceneConverterTest::beginEndData() { } converter; CORRADE_VERIFY(!converter.isConverting()); - converter.beginData(); + CORRADE_VERIFY(converter.beginData()); CORRADE_VERIFY(converter.beginCalled); CORRADE_VERIFY(!converter.endCalled); CORRADE_VERIFY(converter.isConverting()); @@ -1439,20 +1467,38 @@ void AbstractSceneConverterTest::beginEndData() { CORRADE_VERIFY(!converter.isConverting()); } -void AbstractSceneConverterTest::beginEndDataFailed() { +void AbstractSceneConverterTest::beginDataFailed() { struct: AbstractSceneConverter { SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMultipleToData; } - void doBeginData() override {} + bool doBeginData() override { return false; } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.beginData()); + CORRADE_COMPARE(out.str(), ""); + + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::endDataFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + bool doBeginData() override { return true; } Containers::Optional> doEndData() override { return {}; } } converter; - converter.beginData(); + CORRADE_VERIFY(converter.beginData()); /* The implementation is expected to print an error message on its own */ std::ostringstream out; @@ -1487,10 +1533,10 @@ void AbstractSceneConverterTest::endDataNotImplemented() { return SceneConverterFeature::ConvertMultipleToData; } - void doBeginData() override {} + bool doBeginData() override { return true; } } converter; - converter.beginData(); + CORRADE_VERIFY(converter.beginData()); std::ostringstream out; Error redirectError{&out}; @@ -1507,7 +1553,7 @@ void AbstractSceneConverterTest::beginEndDataCustomDeleter() { return SceneConverterFeature::ConvertMultipleToData; } - void doBeginData() override {} + bool doBeginData() override { return true; } Containers::Optional> doEndData() override { return Containers::Array{data, 1, [](char*, std::size_t) {}}; @@ -1516,7 +1562,7 @@ void AbstractSceneConverterTest::beginEndDataCustomDeleter() { char data[1]; } converter; - converter.beginData(); + CORRADE_VERIFY(converter.beginData()); std::ostringstream out; Error redirectError{&out}; @@ -1530,11 +1576,12 @@ void AbstractSceneConverterTest::beginEndFile() { return SceneConverterFeature::ConvertMultipleToFile; } - void doBeginFile(Containers::StringView filename) override { + bool doBeginFile(Containers::StringView filename) override { CORRADE_VERIFY(!beginCalled); beginCalled = true; CORRADE_COMPARE(filename, "file.gltf"); filenameDataPointer = filename.data(); + return true; } bool doEndFile(Containers::StringView filename) override { @@ -1552,7 +1599,7 @@ void AbstractSceneConverterTest::beginEndFile() { } converter; CORRADE_VERIFY(!converter.isConverting()); - converter.beginFile("file.gltf!"_s.exceptSuffix(1)); + CORRADE_VERIFY(converter.beginFile("file.gltf!"_s.exceptSuffix(1))); CORRADE_VERIFY(converter.beginCalled); CORRADE_VERIFY(!converter.endCalled); CORRADE_VERIFY(converter.isConverting()); @@ -1562,20 +1609,36 @@ void AbstractSceneConverterTest::beginEndFile() { CORRADE_VERIFY(!converter.isConverting()); } -void AbstractSceneConverterTest::beginEndFileFailed() { +void AbstractSceneConverterTest::beginFileFailed() { struct: AbstractSceneConverter { SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMultipleToFile; } - void doBeginFile(Containers::StringView) override {} + bool doBeginFile(Containers::StringView) override { return false; } + } converter; - bool doEndFile(Containers::StringView) override { - return false; + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.beginFile("file.gltf")); + CORRADE_COMPARE(out.str(), ""); + + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::endFileFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; } + + bool doBeginFile(Containers::StringView) override { return true; } + + bool doEndFile(Containers::StringView) override { return false; } } converter; - converter.beginFile("file.gltf"); + CORRADE_VERIFY(converter.beginFile("file.gltf")); /* The implementation is expected to print an error message on its own */ std::ostringstream out; @@ -1592,7 +1655,7 @@ void AbstractSceneConverterTest::beginEndFileThroughData() { return SceneConverterFeature::ConvertMultipleToData; } - void doBeginData() override {} + bool doBeginData() override { return true; } Containers::Optional> doEndData() override { return Containers::array({'h', 'e', 'l', 'l', 'o'}); @@ -1604,7 +1667,7 @@ void AbstractSceneConverterTest::beginEndFileThroughData() { if(Utility::Path::exists(filename)) CORRADE_VERIFY(Utility::Path::remove(filename)); - converter.beginFile(filename); + CORRADE_VERIFY(converter.beginFile(filename)); /* doEndFile() should call doEndData() */ CORRADE_VERIFY(converter.endFile()); @@ -1618,7 +1681,7 @@ void AbstractSceneConverterTest::beginEndFileThroughDataFailed() { return SceneConverterFeature::ConvertMultipleToData; } - void doBeginData() override {} + bool doBeginData() override { return true; } Containers::Optional> doEndData() override { return {}; @@ -1630,7 +1693,7 @@ void AbstractSceneConverterTest::beginEndFileThroughDataFailed() { if(Utility::Path::exists(filename)) CORRADE_VERIFY(Utility::Path::remove(filename)); - converter.beginFile(filename); + CORRADE_VERIFY(converter.beginFile(filename)); /* Function should fail, no file should get written and no error output should be printed (the base implementation assumes the plugin does it) */ @@ -1648,14 +1711,14 @@ void AbstractSceneConverterTest::beginEndFileThroughDataNotWritable() { return SceneConverterFeature::ConvertMultipleToData; } - void doBeginData() override {} + bool doBeginData() override { return true; } Containers::Optional> doEndData() override { return Containers::array({'h', 'e', 'l', 'l', 'o'}); } } converter; - converter.beginFile("/some/path/that/does/not/exist"); + CORRADE_VERIFY(converter.beginFile("/some/path/that/does/not/exist")); /* Function should fail, no file should get written and no error output should be printed (the base implementation assumes the plugin does it) */ @@ -1692,10 +1755,10 @@ void AbstractSceneConverterTest::endFileNotImplemented() { return SceneConverterFeature::ConvertMultipleToFile; } - void doBeginFile(Containers::StringView) override {} + bool doBeginFile(Containers::StringView) override { return true; } } converter; - converter.beginFile("file.gltf"); + CORRADE_VERIFY(converter.beginFile("file.gltf")); std::ostringstream out; Error redirectError{&out}; @@ -1709,7 +1772,7 @@ void AbstractSceneConverterTest::abort() { return SceneConverterFeature::ConvertMultiple; } - void doBegin() override {} + bool doBegin() override { return true; } void doAbort() override { CORRADE_VERIFY(!abortCalled); @@ -1720,7 +1783,7 @@ void AbstractSceneConverterTest::abort() { } converter; CORRADE_VERIFY(!converter.abortCalled); - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_VERIFY(!converter.abortCalled); CORRADE_VERIFY(converter.isConverting()); converter.abort(); @@ -1734,11 +1797,11 @@ void AbstractSceneConverterTest::abortNotImplemented() { return SceneConverterFeature::ConvertMultiple; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; /* This should work, there's no need for a plugin to implement this */ - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_VERIFY(converter.isConverting()); converter.abort(); CORRADE_VERIFY(!converter.isConverting()); @@ -1768,7 +1831,7 @@ void AbstractSceneConverterTest::thingNoBegin() { SceneConverterFeature::ImageLevels; } - void doBeginData() override {} + bool doBeginData() override { return true; } } converter; std::ostringstream out; @@ -1885,10 +1948,10 @@ void AbstractSceneConverterTest::endMismatchedBegin() { return SceneConverterFeature::ConvertMultipleToData; } - void doBeginData() override {} + bool doBeginData() override { return true; } } converter; - converter.beginData(); + CORRADE_VERIFY(converter.beginData()); std::ostringstream out; Error redirectError{&out}; @@ -1904,10 +1967,10 @@ void AbstractSceneConverterTest::endDataMismatchedBegin() { return SceneConverterFeature::ConvertMultiple; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -1923,10 +1986,10 @@ void AbstractSceneConverterTest::endFileMismatchedBegin() { return SceneConverterFeature::ConvertMultiple; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -1941,7 +2004,7 @@ void AbstractSceneConverterTest::addScene() { SceneConverterFeature::AddScenes; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, const SceneData& scene, Containers::StringView name) override { /* Scene count should not be increased before the function @@ -1958,7 +2021,7 @@ void AbstractSceneConverterTest::addScene() { bool addCalled = false; } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.sceneCount(), 0); CORRADE_COMPARE(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); @@ -1974,14 +2037,14 @@ void AbstractSceneConverterTest::addSceneFailed() { SceneConverterFeature::AddScenes; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { return false; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.sceneCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -2006,10 +2069,10 @@ void AbstractSceneConverterTest::addSceneNotImplemented() { SceneConverterFeature::AddScenes; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -2024,7 +2087,7 @@ void AbstractSceneConverterTest::setSceneFieldName() { SceneConverterFeature::AddScenes; } - void doBegin() override {} + bool doBegin() override { return true; } void doSetSceneFieldName(UnsignedInt field, Containers::StringView name) override { CORRADE_COMPARE(field, 1337); @@ -2037,7 +2100,7 @@ void AbstractSceneConverterTest::setSceneFieldName() { CORRADE_VERIFY(true); /* capture correct function name */ - converter.begin(); + CORRADE_VERIFY(converter.begin()); converter.setSceneFieldName(sceneFieldCustom(1337), "hello!"); CORRADE_VERIFY(converter.setSceneFieldNameCalled); } @@ -2049,11 +2112,11 @@ void AbstractSceneConverterTest::setSceneFieldNameNotImplemented() { SceneConverterFeature::AddScenes; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; /* This should work, there's no need for a plugin to implement this */ - converter.begin(); + CORRADE_VERIFY(converter.begin()); converter.setSceneFieldName(sceneFieldCustom(1337), "hello!"); CORRADE_VERIFY(true); } @@ -2067,10 +2130,10 @@ void AbstractSceneConverterTest::setSceneFieldNameNotCustom() { SceneConverterFeature::AddScenes; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -2085,7 +2148,7 @@ void AbstractSceneConverterTest::setObjectName() { SceneConverterFeature::AddScenes; } - void doBegin() override {} + bool doBegin() override { return true; } void doSetObjectName(UnsignedLong object, Containers::StringView name) override { CORRADE_COMPARE(object, 1337); @@ -2098,7 +2161,7 @@ void AbstractSceneConverterTest::setObjectName() { CORRADE_VERIFY(true); /* capture correct function name */ - converter.begin(); + CORRADE_VERIFY(converter.begin()); converter.setObjectName(1337, "hey!"); CORRADE_VERIFY(converter.setObjectNameCalled); } @@ -2110,11 +2173,11 @@ void AbstractSceneConverterTest::setObjectNameNotImplemented() { SceneConverterFeature::AddScenes; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; /* This should work, there's no need for a plugin to implement this */ - converter.begin(); + CORRADE_VERIFY(converter.begin()); converter.setObjectName(1337, "hey!"); CORRADE_VERIFY(true); } @@ -2126,7 +2189,7 @@ void AbstractSceneConverterTest::setDefaultScene() { SceneConverterFeature::AddScenes; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { return true; @@ -2140,7 +2203,7 @@ void AbstractSceneConverterTest::setDefaultScene() { bool setDefaultSceneCalled = false; } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); CORRADE_COMPARE(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}), 2); @@ -2158,14 +2221,14 @@ void AbstractSceneConverterTest::setDefaultSceneNotImplemented() { SceneConverterFeature::AddScenes; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); CORRADE_COMPARE(converter.sceneCount(), 1); @@ -2182,14 +2245,14 @@ void AbstractSceneConverterTest::setDefaultSceneOutOfRange() { SceneConverterFeature::AddScenes; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); CORRADE_COMPARE(converter.sceneCount(), 2); @@ -2207,7 +2270,7 @@ void AbstractSceneConverterTest::addAnimation() { SceneConverterFeature::AddAnimations; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, const AnimationData& animation, Containers::StringView name) override { /* Animation count should not be increased before the function @@ -2224,7 +2287,7 @@ void AbstractSceneConverterTest::addAnimation() { bool addCalled = false; } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.animationCount(), 0); CORRADE_COMPARE(converter.add(AnimationData{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); @@ -2240,14 +2303,14 @@ void AbstractSceneConverterTest::addAnimationFailed() { SceneConverterFeature::AddAnimations; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const AnimationData&, Containers::StringView) override { return false; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.animationCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -2272,10 +2335,10 @@ void AbstractSceneConverterTest::addAnimationNotImplemented() { SceneConverterFeature::AddAnimations; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -2290,7 +2353,7 @@ void AbstractSceneConverterTest::addLight() { SceneConverterFeature::AddLights; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, const LightData& light, Containers::StringView name) override { /* Light count should not be increased before the function @@ -2307,7 +2370,7 @@ void AbstractSceneConverterTest::addLight() { bool addCalled = false; } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.lightCount(), 0); CORRADE_COMPARE(converter.add(LightData{LightData::Type::Point, {}, 0.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); @@ -2323,14 +2386,14 @@ void AbstractSceneConverterTest::addLightFailed() { SceneConverterFeature::AddLights; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const LightData&, Containers::StringView) override { return false; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.lightCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -2355,10 +2418,10 @@ void AbstractSceneConverterTest::addLightNotImplemented() { SceneConverterFeature::AddLights; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -2373,7 +2436,7 @@ void AbstractSceneConverterTest::addCamera() { SceneConverterFeature::AddCameras; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, const CameraData& camera, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -2390,7 +2453,7 @@ void AbstractSceneConverterTest::addCamera() { bool addCalled = false; } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.cameraCount(), 0); CORRADE_COMPARE(converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); @@ -2406,14 +2469,14 @@ void AbstractSceneConverterTest::addCameraFailed() { SceneConverterFeature::AddCameras; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const CameraData&, Containers::StringView) override { return false; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.cameraCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -2438,10 +2501,10 @@ void AbstractSceneConverterTest::addCameraNotImplemented() { SceneConverterFeature::AddCameras; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -2456,7 +2519,7 @@ void AbstractSceneConverterTest::addSkin2D() { SceneConverterFeature::AddSkins2D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, const SkinData2D& skin, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -2473,7 +2536,7 @@ void AbstractSceneConverterTest::addSkin2D() { bool addCalled = false; } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.skin2DCount(), 0); CORRADE_COMPARE(converter.add(SkinData2D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); @@ -2489,14 +2552,14 @@ void AbstractSceneConverterTest::addSkin2DFailed() { SceneConverterFeature::AddSkins2D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const SkinData2D&, Containers::StringView) override { return false; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.skin2DCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -2521,10 +2584,10 @@ void AbstractSceneConverterTest::addSkin2DNotImplemented() { SceneConverterFeature::AddSkins2D; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -2539,7 +2602,7 @@ void AbstractSceneConverterTest::addSkin3D() { SceneConverterFeature::AddSkins3D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, const SkinData3D& skin, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -2556,7 +2619,7 @@ void AbstractSceneConverterTest::addSkin3D() { bool addCalled = false; } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.skin3DCount(), 0); CORRADE_COMPARE(converter.add(SkinData3D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); @@ -2572,14 +2635,14 @@ void AbstractSceneConverterTest::addSkin3DFailed() { SceneConverterFeature::AddSkins3D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const SkinData3D&, Containers::StringView) override { return false; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.skin3DCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -2604,10 +2667,10 @@ void AbstractSceneConverterTest::addSkin3DNotImplemented() { SceneConverterFeature::AddSkins3D; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -2622,7 +2685,7 @@ void AbstractSceneConverterTest::addMesh() { SceneConverterFeature::AddMeshes; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -2639,7 +2702,7 @@ void AbstractSceneConverterTest::addMesh() { bool addCalled = false; } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.meshCount(), 0); CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); @@ -2655,14 +2718,14 @@ void AbstractSceneConverterTest::addMeshFailed() { SceneConverterFeature::AddMeshes; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { return false; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.meshCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -2689,8 +2752,9 @@ void AbstractSceneConverterTest::addMeshThroughConvertMesh() { return MeshData{MeshPrimitive::Lines, mesh.vertexCount()*2}; } - void doBegin() override { + bool doBegin() override { CORRADE_FAIL_IF(true, "doBegin() should not be called"); + return {}; } Containers::Pointer doEnd() override { @@ -2706,7 +2770,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMesh() { CORRADE_VERIFY(true); /* capture correct function name */ - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.meshCount(), 0); CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); @@ -2749,7 +2813,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshFailed() { } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Lines, 6})); /* It shouldn't abort the whole process */ @@ -2771,7 +2835,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshZeroMeshes() { } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -2791,7 +2855,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshTwoMeshes() { } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.meshCount(), 0); CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); @@ -2829,8 +2893,9 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToData() { return Containers::Array{nullptr, mesh.vertexCount()}; } - void doBeginData() override { + bool doBeginData() override { CORRADE_FAIL_IF(true, "doBeginData() should not be called"); + return {}; } Containers::Optional> doEndData() override { @@ -2844,7 +2909,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToData() { } } converter; - converter.beginData(); + CORRADE_VERIFY(converter.beginData()); CORRADE_COMPARE(converter.meshCount(), 0); CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); @@ -2867,7 +2932,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataFailed() { } } converter; - converter.beginData(); + CORRADE_VERIFY(converter.beginData()); CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Lines, 6})); /* It shouldn't abort the whole process */ @@ -2889,7 +2954,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataZeroMeshes() { } } converter; - converter.beginData(); + CORRADE_VERIFY(converter.beginData()); std::ostringstream out; Error redirectError{&out}; @@ -2909,7 +2974,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataTwoMeshes() { } } converter; - converter.beginData(); + CORRADE_VERIFY(converter.beginData()); CORRADE_COMPARE(converter.meshCount(), 0); CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); @@ -2942,8 +3007,9 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToFile() { return Utility::Path::write(filename, Containers::arrayView({char(mesh.vertexCount())})); } - void doBeginFile(Containers::StringView) override { + bool doBeginFile(Containers::StringView) override { CORRADE_FAIL_IF(true, "doBeginFile() should not be called"); + return {}; } bool doEndFile(Containers::StringView) override { @@ -2962,7 +3028,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToFile() { if(Utility::Path::exists(filename)) CORRADE_VERIFY(Utility::Path::remove(filename)); - converter.beginFile(filename); + CORRADE_VERIFY(converter.beginFile(filename)); CORRADE_VERIFY(!Utility::Path::exists(filename)); CORRADE_COMPARE(converter.meshCount(), 0); @@ -2991,8 +3057,9 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileThroughData() { return Containers::array({char(mesh.vertexCount())}); } - void doBeginData() override { + bool doBeginData() override { CORRADE_FAIL_IF(true, "doBeginData() should not be called"); + return {}; } Containers::Optional> doEndData() override { @@ -3000,8 +3067,9 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileThroughData() { return {}; } - void doBeginFile(Containers::StringView) override { + bool doBeginFile(Containers::StringView) override { CORRADE_FAIL_IF(true, "doBeginFile() should not be called"); + return {}; } bool doEndFile(Containers::StringView) override { @@ -3020,7 +3088,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileThroughData() { if(Utility::Path::exists(filename)) CORRADE_VERIFY(Utility::Path::remove(filename)); - converter.beginFile(filename); + CORRADE_VERIFY(converter.beginFile(filename)); CORRADE_VERIFY(!Utility::Path::exists(filename)); CORRADE_COMPARE(converter.meshCount(), 0); @@ -3055,7 +3123,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileFailed() { if(Utility::Path::exists(filename)) CORRADE_VERIFY(Utility::Path::remove(filename)); - converter.beginFile(filename); + CORRADE_VERIFY(converter.beginFile(filename)); CORRADE_VERIFY(!Utility::Path::exists(filename)); CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Lines, 6})); @@ -3084,7 +3152,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileZeroMeshes() { if(Utility::Path::exists(filename)) CORRADE_VERIFY(Utility::Path::remove(filename)); - converter.beginFile(filename); + CORRADE_VERIFY(converter.beginFile(filename)); CORRADE_VERIFY(!Utility::Path::exists(filename)); std::ostringstream out; @@ -3111,7 +3179,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileTwoMeshes() { if(Utility::Path::exists(filename)) CORRADE_VERIFY(Utility::Path::remove(filename)); - converter.beginFile(filename); + CORRADE_VERIFY(converter.beginFile(filename)); CORRADE_VERIFY(!Utility::Path::exists(filename)); CORRADE_COMPARE(converter.meshCount(), 0); @@ -3150,10 +3218,10 @@ void AbstractSceneConverterTest::addMeshNotImplemented() { SceneConverterFeature::AddMeshes; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -3169,7 +3237,7 @@ void AbstractSceneConverterTest::addMeshLevels() { SceneConverterFeature::MeshLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, Containers::Iterable meshLevels, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -3187,7 +3255,7 @@ void AbstractSceneConverterTest::addMeshLevels() { bool addCalled = false; } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.meshCount(), 0); CORRADE_COMPARE(converter.add({ MeshData{MeshPrimitive::Lines, 0}, @@ -3212,14 +3280,14 @@ void AbstractSceneConverterTest::addMeshLevelsFailed() { SceneConverterFeature::MeshLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { return false; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.meshCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -3248,10 +3316,10 @@ void AbstractSceneConverterTest::addMeshLevelsNoLevels() { SceneConverterFeature::MeshLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -3269,10 +3337,10 @@ void AbstractSceneConverterTest::addMeshLevelsNotImplemented() { SceneConverterFeature::MeshLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -3291,7 +3359,7 @@ void AbstractSceneConverterTest::addMeshThroughLevels() { SceneConverterFeature::MeshLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable meshLevels, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -3305,7 +3373,7 @@ void AbstractSceneConverterTest::addMeshThroughLevels() { bool addCalled = false; } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); CORRADE_COMPARE(converter.meshCount(), 1); @@ -3318,7 +3386,7 @@ void AbstractSceneConverterTest::setMeshAttributeName() { SceneConverterFeature::AddMeshes; } - void doBegin() override {} + bool doBegin() override { return true; } void doSetMeshAttributeName(UnsignedShort field, Containers::StringView name) override { CORRADE_COMPARE(field, 1337); @@ -3331,7 +3399,7 @@ void AbstractSceneConverterTest::setMeshAttributeName() { CORRADE_VERIFY(true); /* capture correct function name */ - converter.begin(); + CORRADE_VERIFY(converter.begin()); converter.setMeshAttributeName(meshAttributeCustom(1337), "hello!"); CORRADE_VERIFY(converter.setMeshAttributeNameCalled); } @@ -3343,11 +3411,11 @@ void AbstractSceneConverterTest::setMeshAttributeNameNotImplemented() { SceneConverterFeature::AddMeshes; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; /* This should work, there's no need for a plugin to implement this */ - converter.begin(); + CORRADE_VERIFY(converter.begin()); converter.setMeshAttributeName(meshAttributeCustom(1337), "hello!"); CORRADE_VERIFY(true); } @@ -3361,10 +3429,10 @@ void AbstractSceneConverterTest::setMeshAttributeNameNotCustom() { SceneConverterFeature::AddMeshes; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -3379,7 +3447,7 @@ void AbstractSceneConverterTest::addMaterial() { SceneConverterFeature::AddMaterials; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, const MaterialData& material, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -3396,7 +3464,7 @@ void AbstractSceneConverterTest::addMaterial() { bool addCalled = false; } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.materialCount(), 0); CORRADE_COMPARE(converter.add(MaterialData{{}, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); @@ -3412,14 +3480,14 @@ void AbstractSceneConverterTest::addMaterialFailed() { SceneConverterFeature::AddMaterials; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const MaterialData&, Containers::StringView) override { return false; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.materialCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -3444,10 +3512,10 @@ void AbstractSceneConverterTest::addMaterialNotImplemented() { SceneConverterFeature::AddMaterials; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -3462,7 +3530,7 @@ void AbstractSceneConverterTest::addTexture() { SceneConverterFeature::AddTextures; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, const TextureData& texture, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -3479,7 +3547,7 @@ void AbstractSceneConverterTest::addTexture() { bool addCalled = false; } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.textureCount(), 0); CORRADE_COMPARE(converter.add(TextureData{{}, {}, {}, {}, {}, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); @@ -3495,14 +3563,14 @@ void AbstractSceneConverterTest::addTextureFailed() { SceneConverterFeature::AddTextures; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const TextureData&, Containers::StringView) override { return false; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.textureCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -3527,10 +3595,10 @@ void AbstractSceneConverterTest::addTextureNotImplemented() { SceneConverterFeature::AddTextures; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -3545,7 +3613,7 @@ void AbstractSceneConverterTest::addImage1D() { SceneConverterFeature::AddImages1D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, const ImageData1D& image, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -3564,7 +3632,7 @@ void AbstractSceneConverterTest::addImage1D() { const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image1DCount(), 0); CORRADE_COMPARE(converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, {}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); @@ -3580,7 +3648,7 @@ void AbstractSceneConverterTest::addImage1DView() { SceneConverterFeature::AddImages1D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const ImageData1D& image, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -3599,7 +3667,7 @@ void AbstractSceneConverterTest::addImage1DView() { const char imageData[6]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image1DCount(), 0); CORRADE_COMPARE(converter.add(ImageView1D{ PixelStorage{}.setAlignment(2), @@ -3615,7 +3683,7 @@ void AbstractSceneConverterTest::addImage1DCompressedView() { SceneConverterFeature::AddCompressedImages1D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const ImageData1D& image, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -3634,7 +3702,7 @@ void AbstractSceneConverterTest::addImage1DCompressedView() { const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image1DCount(), 0); CORRADE_COMPARE(converter.add(CompressedImageView1D{ CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), @@ -3651,7 +3719,7 @@ void AbstractSceneConverterTest::addImage1DFailed() { SceneConverterFeature::AddCompressedImages1D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const ImageData1D&, Containers::StringView) override { return false; @@ -3660,7 +3728,7 @@ void AbstractSceneConverterTest::addImage1DFailed() { const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image1DCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -3689,10 +3757,10 @@ void AbstractSceneConverterTest::addImage1DInvalidImage() { SceneConverterFeature::AddImages1D; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -3709,10 +3777,10 @@ void AbstractSceneConverterTest::addImage1DNotImplemented() { SceneConverterFeature::AddImages1D; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); const char imageData[4]{}; @@ -3729,7 +3797,7 @@ void AbstractSceneConverterTest::addImage2D() { SceneConverterFeature::AddImages2D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, const ImageData2D& image, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -3748,7 +3816,7 @@ void AbstractSceneConverterTest::addImage2D() { const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image2DCount(), 0); CORRADE_COMPARE(converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, {}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); @@ -3764,7 +3832,7 @@ void AbstractSceneConverterTest::addImage2DView() { SceneConverterFeature::AddImages2D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const ImageData2D& image, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -3783,7 +3851,7 @@ void AbstractSceneConverterTest::addImage2DView() { const char imageData[6]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image2DCount(), 0); CORRADE_COMPARE(converter.add(ImageView2D{ PixelStorage{}.setAlignment(2), @@ -3799,7 +3867,7 @@ void AbstractSceneConverterTest::addImage2DCompressedView() { SceneConverterFeature::AddCompressedImages2D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const ImageData2D& image, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -3818,7 +3886,7 @@ void AbstractSceneConverterTest::addImage2DCompressedView() { const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image2DCount(), 0); CORRADE_COMPARE(converter.add(CompressedImageView2D{ CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), @@ -3835,7 +3903,7 @@ void AbstractSceneConverterTest::addImage2DFailed() { SceneConverterFeature::AddCompressedImages2D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const ImageData2D&, Containers::StringView) override { return false; @@ -3844,7 +3912,7 @@ void AbstractSceneConverterTest::addImage2DFailed() { const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image2DCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -3873,12 +3941,12 @@ void AbstractSceneConverterTest::addImage2DZeroSize() { SceneConverterFeature::AddImages2D; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; const char imageData[16]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -3895,10 +3963,10 @@ void AbstractSceneConverterTest::addImage2DNullptr() { SceneConverterFeature::AddImages2D; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -3915,10 +3983,10 @@ void AbstractSceneConverterTest::addImage2DNotImplemented() { SceneConverterFeature::AddImages2D; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); const char imageData[4]{}; @@ -3935,7 +4003,7 @@ void AbstractSceneConverterTest::addImage3D() { SceneConverterFeature::AddImages3D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, const ImageData3D& image, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -3954,7 +4022,7 @@ void AbstractSceneConverterTest::addImage3D() { const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image3DCount(), 0); CORRADE_COMPARE(converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, {}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); @@ -3970,7 +4038,7 @@ void AbstractSceneConverterTest::addImage3DView() { SceneConverterFeature::AddImages3D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const ImageData3D& image, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -3989,7 +4057,7 @@ void AbstractSceneConverterTest::addImage3DView() { const char imageData[6]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image3DCount(), 0); CORRADE_COMPARE(converter.add(ImageView3D{ PixelStorage{}.setAlignment(2), @@ -4005,7 +4073,7 @@ void AbstractSceneConverterTest::addImage3DCompressedView() { SceneConverterFeature::AddCompressedImages3D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const ImageData3D& image, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -4024,7 +4092,7 @@ void AbstractSceneConverterTest::addImage3DCompressedView() { const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image3DCount(), 0); CORRADE_COMPARE(converter.add(CompressedImageView3D{ CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), @@ -4041,7 +4109,7 @@ void AbstractSceneConverterTest::addImage3DFailed() { SceneConverterFeature::AddCompressedImages3D; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, const ImageData3D&, Containers::StringView) override { return false; @@ -4050,7 +4118,7 @@ void AbstractSceneConverterTest::addImage3DFailed() { const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image3DCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -4079,10 +4147,10 @@ void AbstractSceneConverterTest::addImage3DInvalidImage() { SceneConverterFeature::AddImages3D; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4099,12 +4167,12 @@ void AbstractSceneConverterTest::addImage3DNotImplemented() { SceneConverterFeature::AddImages3D; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4120,7 +4188,7 @@ void AbstractSceneConverterTest::addImageLevels1D() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -4140,7 +4208,7 @@ void AbstractSceneConverterTest::addImageLevels1D() { const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image1DCount(), 0); CORRADE_COMPARE(converter.add({ /* Arbitrary dimensions should be fine */ @@ -4166,7 +4234,7 @@ void AbstractSceneConverterTest::addImageLevels1DView() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -4186,7 +4254,7 @@ void AbstractSceneConverterTest::addImageLevels1DView() { const char imageData[6]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image1DCount(), 0); CORRADE_COMPARE(converter.add({ ImageView1D{PixelFormat::RG8Snorm, 1, imageData}, @@ -4204,7 +4272,7 @@ void AbstractSceneConverterTest::addImageLevels1DCompressedView() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -4224,7 +4292,7 @@ void AbstractSceneConverterTest::addImageLevels1DCompressedView() { const char imageData[6]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image1DCount(), 0); CORRADE_COMPARE(converter.add({ CompressedImageView1D{CompressedPixelFormat::Astc3x3x3RGBASrgb, 1, imageData}, @@ -4243,7 +4311,7 @@ void AbstractSceneConverterTest::addImageLevels1DFailed() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { return false; @@ -4252,7 +4320,7 @@ void AbstractSceneConverterTest::addImageLevels1DFailed() { const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image1DCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -4291,10 +4359,10 @@ void AbstractSceneConverterTest::addImageLevels1DInvalidImage() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4312,12 +4380,12 @@ void AbstractSceneConverterTest::addImageLevels1DNotImplemented() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4336,7 +4404,7 @@ void AbstractSceneConverterTest::addImageLevels2D() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -4356,7 +4424,7 @@ void AbstractSceneConverterTest::addImageLevels2D() { const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image2DCount(), 0); CORRADE_COMPARE(converter.add({ /* Arbitrary dimensions should be fine */ @@ -4382,7 +4450,7 @@ void AbstractSceneConverterTest::addImageLevels2DView() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -4402,7 +4470,7 @@ void AbstractSceneConverterTest::addImageLevels2DView() { const char imageData[6]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image2DCount(), 0); CORRADE_COMPARE(converter.add({ ImageView2D{PixelFormat::RG8Snorm, {1, 1}, imageData}, @@ -4420,7 +4488,7 @@ void AbstractSceneConverterTest::addImageLevels2DCompressedView() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -4440,7 +4508,7 @@ void AbstractSceneConverterTest::addImageLevels2DCompressedView() { const char imageData[6]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image2DCount(), 0); CORRADE_COMPARE(converter.add({ CompressedImageView2D{CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 1}, imageData}, @@ -4459,7 +4527,7 @@ void AbstractSceneConverterTest::addImageLevels2DFailed() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { return false; @@ -4468,7 +4536,7 @@ void AbstractSceneConverterTest::addImageLevels2DFailed() { const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image2DCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -4507,10 +4575,10 @@ void AbstractSceneConverterTest::addImageLevels2DNoLevels() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4528,12 +4596,12 @@ void AbstractSceneConverterTest::addImageLevels2DZeroSize() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4554,12 +4622,12 @@ void AbstractSceneConverterTest::addImageLevels2DNullptr() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4580,12 +4648,12 @@ void AbstractSceneConverterTest::addImageLevels2DInconsistentCompressed() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); ImageData2D a{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}; ImageData2D b{CompressedPixelFormat::Astc10x10RGBAF, {1, 1}, DataFlags{}, imageData}; @@ -4609,12 +4677,12 @@ void AbstractSceneConverterTest::addImageLevels2DInconsistentFormat() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4636,12 +4704,12 @@ void AbstractSceneConverterTest::addImageLevels2DInconsistentFormatExtra() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4663,12 +4731,12 @@ void AbstractSceneConverterTest::addImageLevels2DInconsistentCompressedFormat() SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4690,12 +4758,12 @@ void AbstractSceneConverterTest::addImageLevels2DInconsistentFlags() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4717,12 +4785,12 @@ void AbstractSceneConverterTest::addImageLevels2DNotImplemented() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4741,7 +4809,7 @@ void AbstractSceneConverterTest::addImageLevels3D() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override { /* Camera count should not be increased before the function @@ -4761,7 +4829,7 @@ void AbstractSceneConverterTest::addImageLevels3D() { const char imageData[4*4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image3DCount(), 0); CORRADE_COMPARE(converter.add({ /* Arbitrary dimensions should be fine */ @@ -4787,7 +4855,7 @@ void AbstractSceneConverterTest::addImageLevels3DView() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -4807,7 +4875,7 @@ void AbstractSceneConverterTest::addImageLevels3DView() { const char imageData[6]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image3DCount(), 0); CORRADE_COMPARE(converter.add({ ImageView3D{PixelFormat::RG8Snorm, {1, 1, 1}, imageData}, @@ -4825,7 +4893,7 @@ void AbstractSceneConverterTest::addImageLevels3DCompressedView() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -4845,7 +4913,7 @@ void AbstractSceneConverterTest::addImageLevels3DCompressedView() { const char imageData[6]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image3DCount(), 0); CORRADE_COMPARE(converter.add({ CompressedImageView3D{CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 1, 1}, imageData}, @@ -4864,7 +4932,7 @@ void AbstractSceneConverterTest::addImageLevels3DFailed() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { return false; @@ -4873,7 +4941,7 @@ void AbstractSceneConverterTest::addImageLevels3DFailed() { const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image3DCount(), 0); /* The implementation is expected to print an error message on its own */ @@ -4912,10 +4980,10 @@ void AbstractSceneConverterTest::addImageLevels3DInvalidImage() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4933,12 +5001,12 @@ void AbstractSceneConverterTest::addImageLevels3DNotImplemented() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } } converter; const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); std::ostringstream out; Error redirectError{&out}; @@ -4957,7 +5025,7 @@ void AbstractSceneConverterTest::addImage1DThroughLevels() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -4973,7 +5041,7 @@ void AbstractSceneConverterTest::addImage1DThroughLevels() { const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, {}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); CORRADE_COMPARE(converter.image1DCount(), 1); @@ -4987,7 +5055,7 @@ void AbstractSceneConverterTest::addImage2DThroughLevels() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -5003,7 +5071,7 @@ void AbstractSceneConverterTest::addImage2DThroughLevels() { const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, {}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); CORRADE_COMPARE(converter.image2DCount(), 1); @@ -5017,7 +5085,7 @@ void AbstractSceneConverterTest::addImage3DThroughLevels() { SceneConverterFeature::ImageLevels; } - void doBegin() override {} + bool doBegin() override { return true; } bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { CORRADE_COMPARE(name, "hello"); @@ -5033,7 +5101,7 @@ void AbstractSceneConverterTest::addImage3DThroughLevels() { const char imageData[4]{}; - converter.begin(); + CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, {}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); CORRADE_COMPARE(converter.image3DCount(), 1); From 424d666bab4abe58da66aded5615e13a32566215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 17 Aug 2022 23:22:08 +0200 Subject: [PATCH 13/93] Any*{Importer,Converter}: no, raw string literals make no sense here. Test code probably from the time when I didn't know about C string literal concatenation yet? --- .../AnyAudioImporter/Test/AnyAudioImporterTest.cpp | 9 ++++++--- .../AnyImageImporter/Test/AnyImageImporterTest.cpp | 9 ++++++--- .../AnySceneConverter/Test/AnySceneConverterTest.cpp | 9 ++++++--- .../AnySceneImporter/Test/AnySceneImporterTest.cpp | 9 ++++++--- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp b/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp index 6e8ce4c32..7cd935683 100644 --- a/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp +++ b/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp @@ -121,13 +121,16 @@ void AnyImporterTest::detect() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->openFile(data.filename)); - /* Can't use raw string literals in macros on GCC 4.8 */ #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\nAudio::AnyImporter::openFile(): cannot load the {0} plugin\n", data.plugin)); + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Audio::AnyImporter::openFile(): cannot load the {0} plugin\n", + data.plugin)); #else CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} was not found\nAudio::AnyImporter::openFile(): cannot load the {0} plugin\n", data.plugin)); + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Audio::AnyImporter::openFile(): cannot load the {0} plugin\n", + data.plugin)); #endif } diff --git a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp index ccc9e24a0..6c6c4ce03 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp +++ b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp @@ -297,13 +297,16 @@ void AnyImageImporterTest::detect() { CORRADE_VERIFY(read); CORRADE_VERIFY(!importer->openData(*read)); } else CORRADE_VERIFY(!importer->openFile(filename)); - /* Can't use raw string literals in macros on GCC 4.8 */ #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\nTrade::AnyImageImporter::{1}(): cannot load the {0} plugin\n", data.plugin, data.asData ? "openData" : "openFile")); + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Trade::AnyImageImporter::{1}(): cannot load the {0} plugin\n", + data.plugin, data.asData ? "openData" : "openFile")); #else CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} was not found\nTrade::AnyImageImporter::{1}(): cannot load the {0} plugin\n", data.plugin, data.asData ? "openData" : "openFile")); + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Trade::AnyImageImporter::{1}(): cannot load the {0} plugin\n", + data.plugin, data.asData ? "openData" : "openFile")); #endif } diff --git a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp index 0844e5764..d4e476d3d 100644 --- a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp +++ b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp @@ -130,13 +130,16 @@ void AnySceneConverterTest::detect() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!converter->convertToFile(MeshData{MeshPrimitive::Triangles, 0}, data.filename)); - /* Can't use raw string literals in macros on GCC 4.8 */ #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\nTrade::AnySceneConverter::convertToFile(): cannot load the {0} plugin\n", data.plugin)); + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Trade::AnySceneConverter::convertToFile(): cannot load the {0} plugin\n", + data.plugin)); #else CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} was not found\nTrade::AnySceneConverter::convertToFile(): cannot load the {0} plugin\n", data.plugin)); + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Trade::AnySceneConverter::convertToFile(): cannot load the {0} plugin\n", + data.plugin)); #endif } diff --git a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp index 4887ae510..1a47fe554 100644 --- a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp +++ b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp @@ -182,13 +182,16 @@ void AnySceneImporterTest::detect() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->openFile(data.filename)); - /* Can't use raw string literals in macros on GCC 4.8 */ #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\nTrade::AnySceneImporter::openFile(): cannot load the {0} plugin\n", data.plugin)); + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Trade::AnySceneImporter::openFile(): cannot load the {0} plugin\n", + data.plugin)); #else CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} was not found\nTrade::AnySceneImporter::openFile(): cannot load the {0} plugin\n", data.plugin)); + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Trade::AnySceneImporter::openFile(): cannot load the {0} plugin\n", + data.plugin)); #endif } From 11cc18486b3e5c582ce5755ca2125852808ca4b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 17 Aug 2022 23:25:32 +0200 Subject: [PATCH 14/93] Any*{Converter,Importer}: clean up ancient log redirection in tests. It's usually just `out` nowadays, and it's restricted to the smallest possible scope to not hide errors coming from elsewhere. --- .../Test/AnyAudioImporterTest.cpp | 9 +++---- .../Test/AnyImageImporterTest.cpp | 27 +++++++++---------- .../Test/AnySceneConverterTest.cpp | 9 +++---- .../Test/AnySceneImporterTest.cpp | 9 +++---- 4 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp b/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp index 7cd935683..26db2bfb0 100644 --- a/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp +++ b/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp @@ -135,13 +135,12 @@ void AnyImporterTest::detect() { } void AnyImporterTest::unknown() { - std::ostringstream output; - Error redirectError{&output}; - Containers::Pointer importer = _manager.instantiate("AnyAudioImporter"); - CORRADE_VERIFY(!importer->openFile("sound.mid")); - CORRADE_COMPARE(output.str(), "Audio::AnyImporter::openFile(): cannot determine the format of sound.mid\n"); + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->openFile("sound.mid")); + CORRADE_COMPARE(out.str(), "Audio::AnyImporter::openFile(): cannot determine the format of sound.mid\n"); } void AnyImporterTest::propagateConfiguration() { diff --git a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp index 6c6c4ce03..112e583ee 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp +++ b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp @@ -311,36 +311,33 @@ void AnyImageImporterTest::detect() { } void AnyImageImporterTest::unknownExtension() { - std::ostringstream output; - Error redirectError{&output}; - Containers::Pointer importer = _manager.instantiate("AnyImageImporter"); - CORRADE_VERIFY(!importer->openFile("image.xcf")); - CORRADE_COMPARE(output.str(), "Trade::AnyImageImporter::openFile(): cannot determine the format of image.xcf\n"); + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->openFile("image.xcf")); + CORRADE_COMPARE(out.str(), "Trade::AnyImageImporter::openFile(): cannot determine the format of image.xcf\n"); } void AnyImageImporterTest::unknownSignature() { auto&& data = DetectUnknownData[testCaseInstanceId()]; setTestCaseDescription(data.name); - std::ostringstream output; - Error redirectError{&output}; - Containers::Pointer importer = _manager.instantiate("AnyImageImporter"); - CORRADE_VERIFY(!importer->openData(data.data)); - CORRADE_COMPARE(output.str(), Utility::formatString("Trade::AnyImageImporter::openData(): cannot determine the format from signature 0x{}\n", data.signature)); + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->openData(data.data)); + CORRADE_COMPARE(out.str(), Utility::formatString("Trade::AnyImageImporter::openData(): cannot determine the format from signature 0x{}\n", data.signature)); } void AnyImageImporterTest::emptyData() { - std::ostringstream output; - Error redirectError{&output}; - Containers::Pointer importer = _manager.instantiate("AnyImageImporter"); - CORRADE_VERIFY(!importer->openData(nullptr)); - CORRADE_COMPARE(output.str(), "Trade::AnyImageImporter::openData(): file is empty\n"); + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->openData(nullptr)); + CORRADE_COMPARE(out.str(), "Trade::AnyImageImporter::openData(): file is empty\n"); } void AnyImageImporterTest::propagateFlags() { diff --git a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp index d4e476d3d..bb7807562 100644 --- a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp +++ b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp @@ -144,13 +144,12 @@ void AnySceneConverterTest::detect() { } void AnySceneConverterTest::unknown() { - std::ostringstream output; - Error redirectError{&output}; - Containers::Pointer converter = _manager.instantiate("AnySceneConverter"); - CORRADE_VERIFY(!converter->convertToFile(MeshData{MeshPrimitive::Triangles, 0}, "mesh.obj")); - CORRADE_COMPARE(output.str(), "Trade::AnySceneConverter::convertToFile(): cannot determine the format of mesh.obj\n"); + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->convertToFile(MeshData{MeshPrimitive::Triangles, 0}, "mesh.obj")); + CORRADE_COMPARE(out.str(), "Trade::AnySceneConverter::convertToFile(): cannot determine the format of mesh.obj\n"); } void AnySceneConverterTest::propagateFlags() { diff --git a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp index 1a47fe554..7091c7edb 100644 --- a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp +++ b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp @@ -196,13 +196,12 @@ void AnySceneImporterTest::detect() { } void AnySceneImporterTest::unknown() { - std::ostringstream output; - Error redirectError{&output}; - Containers::Pointer importer = _manager.instantiate("AnySceneImporter"); - CORRADE_VERIFY(!importer->openFile("mesh.wtf")); - CORRADE_COMPARE(output.str(), "Trade::AnySceneImporter::openFile(): cannot determine the format of mesh.wtf\n"); + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->openFile("mesh.wtf")); + CORRADE_COMPARE(out.str(), "Trade::AnySceneImporter::openFile(): cannot determine the format of mesh.wtf\n"); } void AnySceneImporterTest::propagateFlags() { From 4ce7d8e9f49e3fb4c6840defa60627b8178fb950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 17 Aug 2022 23:59:03 +0200 Subject: [PATCH 15/93] Trade: improve messages for batch->single SceneConverter delegation. --- src/Magnum/Trade/AbstractSceneConverter.cpp | 4 ++-- src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Magnum/Trade/AbstractSceneConverter.cpp b/src/Magnum/Trade/AbstractSceneConverter.cpp index a789c1fde..58409e146 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.cpp +++ b/src/Magnum/Trade/AbstractSceneConverter.cpp @@ -482,7 +482,7 @@ bool AbstractSceneConverter::endFile() { } else if(features() & SceneConverterFeature::ConvertMeshToFile) { if(_state->meshCount != 1) { - Error{} << "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh"; + Error{} << "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh, got" << _state->meshCount; return {}; } @@ -704,7 +704,7 @@ Containers::Optional AbstractSceneConverter::add(const MeshData& me SceneConverterFeature::ConvertMeshToData| SceneConverterFeature::ConvertMeshToFile)) { if(_state->meshCount != 0) { - Error{} << "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh"; + Error{} << "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh, got" << _state->meshCount + 1; return {}; } diff --git a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp index a6a4f003b..6b20c2aad 100644 --- a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp @@ -2868,7 +2868,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshTwoMeshes() { /* It shouldn't abort the whole process */ CORRADE_VERIFY(converter.isConverting()); CORRADE_COMPARE(converter.meshCount(), 1); - CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh\n"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh, got 2\n"); } /* Getting the result should still work */ @@ -2987,7 +2987,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataTwoMeshes() { /* It shouldn't abort the whole process */ CORRADE_VERIFY(converter.isConverting()); CORRADE_COMPARE(converter.meshCount(), 1); - CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh\n"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh, got 2\n"); } /* Getting the result should still work */ @@ -3137,7 +3137,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileFailed() { CORRADE_VERIFY(!converter.endFile()); CORRADE_VERIFY(!converter.isConverting()); CORRADE_VERIFY(!Utility::Path::exists(filename)); - CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh\n"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh, got 0\n"); } void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileZeroMeshes() { @@ -3160,7 +3160,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileZeroMeshes() { CORRADE_VERIFY(!converter.endFile()); CORRADE_VERIFY(!converter.isConverting()); CORRADE_VERIFY(!Utility::Path::exists(filename)); - CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh\n"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh, got 0\n"); } void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileTwoMeshes() { @@ -3198,7 +3198,7 @@ void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileTwoMeshes() { /* It shouldn't abort the whole process */ CORRADE_VERIFY(converter.isConverting()); CORRADE_COMPARE(converter.meshCount(), 1); - CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh\n"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh, got 2\n"); } /* Getting the result should still work */ From a5298a2c08e2222a388e792de92aaebc5e230e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 18 Aug 2022 00:20:12 +0200 Subject: [PATCH 16/93] Any*Converter: clean up unused Corrade/Containers/StringStl.h includes. --- src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp | 1 - src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp | 1 - src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp | 1 - 3 files changed, 3 deletions(-) diff --git a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp index 1a1ba784e..97efdc571 100644 --- a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp +++ b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp @@ -26,7 +26,6 @@ #include "AnyImageConverter.h" #include -#include /* for PluginManager */ #include #include #include diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp index bcde3cf34..2a84c7a4a 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp @@ -26,7 +26,6 @@ #include "AnySceneConverter.h" #include -#include /* for PluginManager */ #include #include #include diff --git a/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp b/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp index d2aa04181..106991eec 100644 --- a/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp +++ b/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include From 89763380e16efe23fa21711fcf5d82171af28fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 18 Aug 2022 00:25:30 +0200 Subject: [PATCH 17/93] AnySceneConverter: support whole scene conversion. --- doc/changelog.dox | 3 +- .../AnySceneConverter/AnySceneConverter.cpp | 186 +++++++++++++- .../AnySceneConverter/AnySceneConverter.h | 55 +++- .../Test/AnySceneConverterTest.cpp | 236 ++++++++++++++++-- 4 files changed, 451 insertions(+), 29 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index b57171cd5..4364d67d2 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -287,7 +287,8 @@ See also: well as support in @ref Trade::AnySceneImporter "AnySceneImporter" - The @ref Trade::AbstractSceneConverter plugin interface gained support for batch conversion of whole scenes --- meshes, hierarchies, materials, - textures, animations and other data + textures, animations and other data; @relativeref{Trade,AnySceneConverter} + is updated to support batch conversion as well - 1D and 3D image support in @ref Trade::AbstractImageConverter - @ref Trade::LightData got extended to support light attenuation and range parameters as well and spot light inner and outer angle diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp index 2a84c7a4a..747350b79 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp @@ -25,15 +25,19 @@ #include "AnySceneConverter.h" +#include +#include #include #include #include #include #include /* for PluginMetadata::name() */ +#include /* CORRADE_UNUSED */ #include #include -#include "Magnum/Trade/ImageData.h" +#include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/SceneData.h" #include "MagnumPlugins/Implementation/propagateConfiguration.h" namespace Magnum { namespace Trade { @@ -47,7 +51,26 @@ AnySceneConverter::AnySceneConverter(PluginManager::AbstractManager& manager, co AnySceneConverter::~AnySceneConverter() = default; SceneConverterFeatures AnySceneConverter::doFeatures() const { - return SceneConverterFeature::ConvertMeshToFile; + /* Report that we can convert meshes and scenes to files, because that the + plugin can do always as it dispatches there. But everything else is + reported only once an actual converter plugin is loaded -- i.e., after beginFile() is called. + + Additionally, to allow using beginFile() + add(MeshData) + endFile() + with converters that only support ConvertMeshToFile, we "fake" report + AddMesh supported as well. If we wouldn't, the base class would try to + go through convertToFile() instead, causing the mesh to not be delegated + to _converter->add() but instead supplied to a whole new plugin + instance. Then, upon delegating to _converter->endFile(), it would fail + due to having 0 meshes. */ + + SceneConverterFeatures delegatedFeatures = + _converter ? _converter->features() : SceneConverterFeatures{}; + if(delegatedFeatures & SceneConverterFeature::ConvertMeshToFile) + delegatedFeatures |= SceneConverterFeature::AddMeshes; + + return SceneConverterFeature::ConvertMeshToFile| + SceneConverterFeature::ConvertMultipleToFile| + delegatedFeatures; } bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers::StringView filename) { @@ -94,6 +117,165 @@ bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers:: return converter->convertToFile(mesh, filename); } +void AnySceneConverter::doAbort() { + _converter->abort(); + _converter = {}; +} + +bool AnySceneConverter::doBeginFile(const Containers::StringView filename) { + CORRADE_INTERNAL_ASSERT(manager()); + + /* We don't detect any double extensions yet, so we can normalize just the + extension. In case we eventually might, it'd have to be split() instead + to save at least by normalizing just the filename and not the path. */ + const Containers::String normalizedExtension = Utility::String::lowercase(Utility::Path::splitExtension(filename).second()); + + /* Detect the plugin from extension */ + Containers::StringView plugin; + if(normalizedExtension == ".ply"_s) + plugin = "StanfordSceneConverter"_s; + else { + Error{} << "Trade::AnySceneConverter::beginFile(): cannot determine the format of" << filename; + return false; + } + + /* Try to load the plugin */ + if(!(manager()->load(plugin) & PluginManager::LoadState::Loaded)) { + Error{} << "Trade::AnySceneConverter::beginFile(): cannot load the" << plugin << "plugin"; + return false; + } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); + if(flags() & SceneConverterFlag::Verbose) { + Debug d; + d << "Trade::AnySceneConverter::beginFile(): using" << plugin; + if(plugin != metadata->name()) + d << "(provided by" << metadata->name() << Debug::nospace << ")"; + } + + /* Instantiate the plugin, propagate flags */ + Containers::Pointer converter = static_cast*>(manager())->instantiate(plugin); + converter->setFlags(flags()); + + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnySceneConverter::beginFile():", {}, metadata->name(), configuration(), converter->configuration()); + + /* Try to begin the file (error output should be printed by the plugin + itself) */ + if(!converter->beginFile(filename)) return false; + + /* Success, save the instance */ + _converter = std::move(converter); + return true; +} + +bool AnySceneConverter::doEndFile(Containers::StringView) { + /* Destroy the converter instance after the operation finishes to avoid + keeping now-useless state around */ + const bool out = _converter->endFile(); + _converter = {}; + return out; +} + +/* It's easier to use CORRADE_UNUSED than to have #ifndef CORRADE_NO_ASSERT */ + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const SceneData& scene, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->sceneCount()); + return !!_converter->add(scene, name); +} + +void AnySceneConverter::doSetSceneFieldName(const UnsignedInt field, const Containers::StringView name) { + return _converter->setSceneFieldName(sceneFieldCustom(field), name); +} + +void AnySceneConverter::doSetObjectName(const UnsignedLong object, const Containers::StringView name) { + return _converter->setObjectName(object, name); +} + +void AnySceneConverter::doSetDefaultScene(const UnsignedInt id) { + return _converter->setDefaultScene(id); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const AnimationData& animation, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->animationCount()); + return !!_converter->add(animation, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const LightData& light, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->lightCount()); + return !!_converter->add(light, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const CameraData& camera, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->cameraCount()); + return !!_converter->add(camera, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const SkinData2D& skin, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->skin2DCount()); + return !!_converter->add(skin, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const SkinData3D& skin, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->skin3DCount()); + return !!_converter->add(skin, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const MeshData& mesh, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->meshCount()); + return !!_converter->add(mesh, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const Containers::Iterable meshLevels, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->meshCount()); + return !!_converter->add(meshLevels, name); +} + +void AnySceneConverter::doSetMeshAttributeName(const UnsignedShort attribute, const Containers::StringView name) { + return _converter->setMeshAttributeName(meshAttributeCustom(attribute), name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const MaterialData& material, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->materialCount()); + return !!_converter->add(material, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const TextureData& texture, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->textureCount()); + return !!_converter->add(texture, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const ImageData1D& image, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->image1DCount()); + return !!_converter->add(image, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const Containers::Iterable imageLevels, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->image1DCount()); + return !!_converter->add(imageLevels, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const ImageData2D& image, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->image2DCount()); + return !!_converter->add(image, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const Containers::Iterable imageLevels, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->image2DCount()); + return !!_converter->add(imageLevels, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const ImageData3D& image, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->image3DCount()); + return !!_converter->add(image, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const Containers::Iterable imageLevels, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->image3DCount()); + return !!_converter->add(imageLevels, name); +} + }} CORRADE_PLUGIN_REGISTER(AnySceneConverter, Magnum::Trade::AnySceneConverter, diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h index c83e1bf04..d11296579 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h @@ -30,6 +30,8 @@ * @m_since{2020,06} */ +#include + #include "Magnum/Trade/AbstractSceneConverter.h" #include "MagnumPlugins/AnySceneConverter/configure.h" @@ -96,14 +98,20 @@ information. @section Trade-AnySceneConverter-proxy Interface proxying and option propagation -On a call to @ref convertToFile(), a target file format is detected from the -extension and a corresponding plugin is loaded. After that, flags set via -@ref setFlags() and options set through @ref configuration() are propagated to -the concrete implementation, with a warning emitted in case given option is not -present in the default configuration of the target plugin. +On a call to @ref convertToFile() or @ref beginFile(), a target file format is +detected from the extension and a corresponding plugin is loaded. After that, +flags set via @ref setFlags() and options set through @ref configuration() are +propagated to the concrete implementation. A warning is emitted in case an +option set is not present in the default configuration of the target plugin. + +The @ref features() initially report just +@ref SceneConverterFeature::ConvertMeshToFile and @relativeref{SceneConverterFeature,ConvertMultipleToFile} --- i.e., not +advertising support for any actual data types. These are included only once +@ref beginFile() is called, taken from the concrete plugin implementation. -The output of the @ref convertToFile() function called on the concrete -implementation is then proxied back. +Calls to the @ref endFile(), @ref add() and related functions are then proxied +to the concrete implementation. The @ref abort() function aborts and destroys +the internally instantiated plugin; @ref isConverting() works as usual. */ class MAGNUM_ANYSCENECONVERTER_EXPORT AnySceneConverter: public AbstractSceneConverter { public: @@ -118,6 +126,39 @@ class MAGNUM_ANYSCENECONVERTER_EXPORT AnySceneConverter: public AbstractSceneCon private: MAGNUM_ANYSCENECONVERTER_LOCAL SceneConverterFeatures doFeatures() const override; MAGNUM_ANYSCENECONVERTER_LOCAL bool doConvertToFile(const MeshData& mesh, Containers::StringView filename) override; + + void doAbort() override; + bool doBeginFile(Containers::StringView filename) override; + bool doEndFile(Containers::StringView filename) override; + + bool doAdd(UnsignedInt id, const SceneData& scene, Containers::StringView name) override; + void doSetSceneFieldName(UnsignedInt field, Containers::StringView name) override; + void doSetObjectName(UnsignedLong object, Containers::StringView name) override; + void doSetDefaultScene(UnsignedInt id) override; + + bool doAdd(UnsignedInt id, const AnimationData& animation, Containers::StringView name) override; + bool doAdd(UnsignedInt id, const LightData& light, Containers::StringView name) override; + bool doAdd(UnsignedInt id, const CameraData& camera, Containers::StringView name) override; + bool doAdd(UnsignedInt id, const SkinData2D& skin, Containers::StringView name) override; + bool doAdd(UnsignedInt id, const SkinData3D& skin, Containers::StringView name) override; + + bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView name) override; + bool doAdd(UnsignedInt id, Containers::Iterable meshLevels, Containers::StringView name) override; + void doSetMeshAttributeName(UnsignedShort attribute, Containers::StringView name) override; + + bool doAdd(UnsignedInt id, const MaterialData& material, Containers::StringView name) override; + bool doAdd(UnsignedInt id, const TextureData& texture, Containers::StringView name) override; + + bool doAdd(UnsignedInt id, const ImageData1D& image, Containers::StringView name) override; + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override; + + bool doAdd(UnsignedInt id, const ImageData2D& image, Containers::StringView name) override; + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override; + + bool doAdd(UnsignedInt id, const ImageData3D& image, Containers::StringView name) override; + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override; + + Containers::Pointer _converter; }; }} diff --git a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp index bb7807562..1b65dda77 100644 --- a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp +++ b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -45,13 +46,21 @@ struct AnySceneConverterTest: TestSuite::Tester { explicit AnySceneConverterTest(); void convert(); - void detect(); + void convertBeginEnd(); - void unknown(); + void detectConvert(); + void detectBeginEnd(); - void propagateFlags(); - void propagateConfiguration(); - void propagateConfigurationUnknown(); + void unknownConvert(); + void unknownBeginEnd(); + + void propagateFlagsConvert(); + void propagateFlagsBeginEnd(); + + void propagateConfigurationConvert(); + void propagateConfigurationBeginEnd(); + void propagateConfigurationUnknownConvert(); + void propagateConfigurationUnknownBeginEnd(); /* configuration propagation fully tested in AnySceneImporter, as there the plugins have configuration subgroups as well */ @@ -63,23 +72,42 @@ constexpr struct { const char* name; const char* filename; const char* plugin; -} DetectData[]{ +} DetectConvertData[]{ + {"Stanford PLY", "bunny.ply", "StanfordSceneConverter"}, + /* Have at least one test case with uppercase */ + {"Stanford PLY uppercase", "ARMADI~1.PLY", "StanfordSceneConverter"} +}; + +constexpr struct { + const char* name; + const char* filename; + const char* plugin; +} DetectBeginEndData[]{ {"Stanford PLY", "bunny.ply", "StanfordSceneConverter"}, /* Have at least one test case with uppercase */ {"Stanford PLY uppercase", "ARMADI~1.PLY", "StanfordSceneConverter"} }; AnySceneConverterTest::AnySceneConverterTest() { - addTests({&AnySceneConverterTest::convert}); + addTests({&AnySceneConverterTest::convert, + &AnySceneConverterTest::convertBeginEnd}); + + addInstancedTests({&AnySceneConverterTest::detectConvert}, + Containers::arraySize(DetectConvertData)); + + addInstancedTests({&AnySceneConverterTest::detectBeginEnd}, + Containers::arraySize(DetectBeginEndData)); - addInstancedTests({&AnySceneConverterTest::detect}, - Containers::arraySize(DetectData)); + addTests({&AnySceneConverterTest::unknownConvert, + &AnySceneConverterTest::unknownBeginEnd, - addTests({&AnySceneConverterTest::unknown, + &AnySceneConverterTest::propagateFlagsConvert, + &AnySceneConverterTest::propagateFlagsBeginEnd, - &AnySceneConverterTest::propagateFlags, - &AnySceneConverterTest::propagateConfiguration, - &AnySceneConverterTest::propagateConfigurationUnknown}); + &AnySceneConverterTest::propagateConfigurationConvert, + &AnySceneConverterTest::propagateConfigurationBeginEnd, + &AnySceneConverterTest::propagateConfigurationUnknownConvert, + &AnySceneConverterTest::propagateConfigurationUnknownBeginEnd}); /* Load the plugin directly from the build tree. Otherwise it's static and already loaded. */ @@ -121,8 +149,41 @@ void AnySceneConverterTest::convert() { CORRADE_COMPARE_AS(filename, Utility::Path::join(ANYSCENEIMPORTER_TEST_DIR, "triangle.ply"), TestSuite::Compare::File); } -void AnySceneConverterTest::detect() { - auto&& data = DetectData[testCaseInstanceId()]; +void AnySceneConverterTest::convertBeginEnd() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* Catch also ABI and interface mismatch errors */ + if(!(manager.load("StanfordSceneConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + Containers::String filename = Utility::Path::join(ANYSCENECONVERTER_TEST_OUTPUT_DIR, "file.ply"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + const Vector3 positions[] { + {-0.5f, -0.5f, 0.0f}, + { 0.5f, -0.5f, 0.0f}, + { 0.0f, 0.5f, 0.0f} + }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Containers::Pointer converter = manager.instantiate("AnySceneConverter"); + CORRADE_VERIFY(converter->beginFile(filename)); + CORRADE_COMPARE(converter->add(mesh), 0); + CORRADE_VERIFY(converter->endFile()); + + /* This file is reused in AnySceneImporter tests, so it's worth to save it + here */ + CORRADE_COMPARE_AS(filename, Utility::Path::join(ANYSCENEIMPORTER_TEST_DIR, "triangle.ply"), TestSuite::Compare::File); +} + +void AnySceneConverterTest::detectConvert() { + auto&& data = DetectConvertData[testCaseInstanceId()]; setTestCaseDescription(data.name); Containers::Pointer converter = _manager.instantiate("AnySceneConverter"); @@ -143,7 +204,28 @@ void AnySceneConverterTest::detect() { #endif } -void AnySceneConverterTest::unknown() { +void AnySceneConverterTest::detectBeginEnd() { + auto&& data = DetectConvertData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Pointer converter = _manager.instantiate("AnySceneConverter"); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->beginFile(data.filename)); + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + CORRADE_COMPARE(out.str(), Utility::formatString( + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Trade::AnySceneConverter::beginFile(): cannot load the {0} plugin\n", + data.plugin)); + #else + CORRADE_COMPARE(out.str(), Utility::formatString( + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Trade::AnySceneConverter::beginFile(): cannot load the {0} plugin\n", data.plugin)); + #endif +} + +void AnySceneConverterTest::unknownConvert() { Containers::Pointer converter = _manager.instantiate("AnySceneConverter"); std::ostringstream out; @@ -152,7 +234,16 @@ void AnySceneConverterTest::unknown() { CORRADE_COMPARE(out.str(), "Trade::AnySceneConverter::convertToFile(): cannot determine the format of mesh.obj\n"); } -void AnySceneConverterTest::propagateFlags() { +void AnySceneConverterTest::unknownBeginEnd() { + Containers::Pointer converter = _manager.instantiate("AnySceneConverter"); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->beginFile("mesh.obj")); + CORRADE_COMPARE(out.str(), "Trade::AnySceneConverter::beginFile(): cannot determine the format of mesh.obj\n"); +} + +void AnySceneConverterTest::propagateFlagsConvert() { PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); @@ -190,7 +281,47 @@ void AnySceneConverterTest::propagateFlags() { CORRADE_SKIP("No plugin with verbose output available to test flag propagation."); } -void AnySceneConverterTest::propagateConfiguration() { +void AnySceneConverterTest::propagateFlagsBeginEnd() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* Catch also ABI and interface mismatch errors */ + if(!(manager.load("StanfordSceneConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + Containers::String filename = Utility::Path::join(ANYSCENECONVERTER_TEST_OUTPUT_DIR, "file.ply"); + + const Vector3 positions[] { + {-0.5f, -0.5f, 0.0f}, + { 0.5f, -0.5f, 0.0f}, + { 0.0f, 0.5f, 0.0f} + }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Containers::Pointer converter = manager.instantiate("AnySceneConverter"); + converter->setFlags(SceneConverterFlag::Verbose); + + std::ostringstream out; + { + Debug redirectOutput{&out}; + CORRADE_VERIFY(converter->beginFile(filename)); + } + CORRADE_VERIFY(converter->add(mesh)); + CORRADE_VERIFY(converter->endFile()); + CORRADE_VERIFY(Utility::Path::exists(filename)); + CORRADE_COMPARE(out.str(), + "Trade::AnySceneConverter::beginFile(): using StanfordSceneConverter\n"); + + /* We tested AnySceneConverter's verbose output, but can't actually test + the flag propagation in any way yet */ + CORRADE_SKIP("No plugin with verbose output available to test flag propagation."); +} + +void AnySceneConverterTest::propagateConfigurationConvert() { PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); @@ -223,7 +354,42 @@ void AnySceneConverterTest::propagateConfiguration() { CORRADE_COMPARE_AS(filename, Utility::Path::join(ANYSCENECONVERTER_TEST_DIR, "objectid.ply"), TestSuite::Compare::File); } -void AnySceneConverterTest::propagateConfigurationUnknown() { +void AnySceneConverterTest::propagateConfigurationBeginEnd() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* Catch also ABI and interface mismatch errors */ + if(!(manager.load("StanfordSceneConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + Containers::String filename = Utility::Path::join(ANYSCENECONVERTER_TEST_OUTPUT_DIR, "file.ply"); + + const struct Data { + Vector3 position; + UnsignedInt objectId; + } data[] { + {{-0.5f, -0.5f, 0.0f}, 4678}, + {{ 0.5f, -0.5f, 0.0f}, 3232}, + {{ 0.0f, 0.5f, 0.0f}, 1536} + }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, {}, data, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::stridedArrayView(data).slice(&Data::position)}, + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, Containers::stridedArrayView(data).slice(&Data::objectId)}, + }}; + + Containers::Pointer converter = manager.instantiate("AnySceneConverter"); + converter->configuration().setValue("objectIdAttribute", "OID"); + CORRADE_VERIFY(converter->beginFile(filename)); + CORRADE_VERIFY(converter->add(mesh)); + CORRADE_VERIFY(converter->endFile()); + /* Compare to an expected output to ensure the custom attribute name was + used */ + CORRADE_COMPARE_AS(filename, Utility::Path::join(ANYSCENECONVERTER_TEST_DIR, "objectid.ply"), TestSuite::Compare::File); +} + +void AnySceneConverterTest::propagateConfigurationUnknownConvert() { PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); @@ -251,6 +417,38 @@ void AnySceneConverterTest::propagateConfigurationUnknown() { CORRADE_COMPARE(out.str(), "Trade::AnySceneConverter::convertToFile(): option noSuchOption not recognized by StanfordSceneConverter\n"); } +void AnySceneConverterTest::propagateConfigurationUnknownBeginEnd() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* Catch also ABI and interface mismatch errors */ + if(!(manager.load("StanfordSceneConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + const Vector3 positions[] { + {-0.5f, -0.5f, 0.0f}, + { 0.5f, -0.5f, 0.0f}, + { 0.0f, 0.5f, 0.0f} + }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Containers::Pointer converter = manager.instantiate("AnySceneConverter"); + converter->configuration().setValue("noSuchOption", "isHere"); + + std::ostringstream out; + { + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->beginFile(Utility::Path::join(ANYSCENECONVERTER_TEST_OUTPUT_DIR, "file.ply"))); + } + CORRADE_VERIFY(converter->add(mesh)); + CORRADE_VERIFY(converter->endFile()); + CORRADE_COMPARE(out.str(), "Trade::AnySceneConverter::beginFile(): option noSuchOption not recognized by StanfordSceneConverter\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::AnySceneConverterTest) From eb382b3096666f320b59f4b098643a4203ccaafa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 18 Aug 2022 11:44:16 +0200 Subject: [PATCH 18/93] Trade: implicitly abort previous conversion in all scene converter APIs. And properly test the behavior as well. While it's rare that a batch and immediate conversions would be mixed on a single converter instance, if it happens by accident it should not lead to unexpected internal state. --- src/Magnum/Trade/AbstractSceneConverter.cpp | 14 +- src/Magnum/Trade/AbstractSceneConverter.h | 35 ++- .../Trade/Test/AbstractSceneConverterTest.cpp | 244 ++++++++++++++++++ 3 files changed, 279 insertions(+), 14 deletions(-) diff --git a/src/Magnum/Trade/AbstractSceneConverter.cpp b/src/Magnum/Trade/AbstractSceneConverter.cpp index 58409e146..d0c676c28 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.cpp +++ b/src/Magnum/Trade/AbstractSceneConverter.cpp @@ -188,6 +188,8 @@ void AbstractSceneConverter::clearFlags(SceneConverterFlags flags) { } Containers::Optional AbstractSceneConverter::convert(const MeshData& mesh) { + abort(); + CORRADE_ASSERT(features() & SceneConverterFeature::ConvertMesh, "Trade::AbstractSceneConverter::convert(): mesh conversion not supported", {}); @@ -205,6 +207,8 @@ Containers::Optional AbstractSceneConverter::doConvert(const MeshData& } bool AbstractSceneConverter::convertInPlace(MeshData& mesh) { + abort(); + CORRADE_ASSERT(features() & SceneConverterFeature::ConvertMeshInPlace, "Trade::AbstractSceneConverter::convertInPlace(): mesh conversion not supported", {}); @@ -221,6 +225,8 @@ Containers::Optional> Implementation::SceneConverterOptionalButAlsoArray #endif AbstractSceneConverter::convertToData(const MeshData& mesh) { + abort(); + if(features() >= SceneConverterFeature::ConvertMeshToData) { Containers::Optional> out = doConvertToData(mesh); CORRADE_ASSERT(!out || !out->deleter() || out->deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || out->deleter() == ArrayAllocator::deleter, @@ -251,6 +257,8 @@ Containers::Optional> AbstractSceneConverter::doConvertT } bool AbstractSceneConverter::convertToFile(const MeshData& mesh, const Containers::StringView filename) { + abort(); + if(features() >= SceneConverterFeature::ConvertMeshToFile) { return doConvertToFile(mesh, filename); @@ -302,7 +310,7 @@ void AbstractSceneConverter::abort() { void AbstractSceneConverter::doAbort() {} bool AbstractSceneConverter::begin() { - if(_state) abort(); + abort(); _state.emplace(State::Type::Convert); @@ -385,7 +393,7 @@ Containers::Pointer AbstractSceneConverter::doEnd() { } bool AbstractSceneConverter::beginData() { - if(_state) abort(); + abort(); _state.emplace(State::Type::ConvertToData); @@ -442,7 +450,7 @@ Containers::Optional> AbstractSceneConverter::doEndData( } bool AbstractSceneConverter::beginFile(const Containers::StringView filename) { - if(_state) abort(); + abort(); _state.emplace(State::Type::ConvertToFile); _state->filename = Containers::String::nullTerminatedGlobalView(filename); diff --git a/src/Magnum/Trade/AbstractSceneConverter.h b/src/Magnum/Trade/AbstractSceneConverter.h index 3d25bb31b..40973ca0a 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.h +++ b/src/Magnum/Trade/AbstractSceneConverter.h @@ -511,6 +511,10 @@ checked by the implementation: @ref doAdd() functions are called only if a corresponding @ref begin(), @ref beginData() or @ref beginFile() was called before and @ref abort() wasn't called in the meantime. +- The @ref doConvert(), @ref doConvertInPlace(), @ref doConvertToData(), + @ref doConvertToFile(), @ref doBegin(), @ref doBeginData() and + @ref doBeginFile() functions are called only after the previous conversion + (if any) was aborted with @ref doAbort(). - The @ref doAdd() and various `doSet*()` functions are called only if a corresponding @ref SceneConverterFeature is supported. - The @ref doAdd((UnsignedInt, Containers::Iterable, Containers::StringView) @@ -652,7 +656,8 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract /** * @brief Convert a mesh * - * On failure prints a message to @relativeref{Magnum,Error} and + * If a (batch) conversion is currently in progress, calls @ref abort() + * first. On failure prints a message to @relativeref{Magnum,Error} and * returns @ref Containers::NullOpt. * * Expects that @ref SceneConverterFeature::ConvertMesh is supported. @@ -661,26 +666,30 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * and retrieve the output from the importer returned by @ref end() --- * in such case the process can also return zero or more than one mesh * instead of always exactly one. - * @see @ref features(), @ref convertInPlace(MeshData&) + * @see @ref isConverting(), @ref features(), + * @ref convertInPlace(MeshData&) */ Containers::Optional convert(const MeshData& mesh); /** * @brief Convert a mesh in-place * - * On failure prints a message to @relativeref{Magnum,Error} and + * If a (batch) conversion is currently in progress, calls @ref abort() + * first. On failure prints a message to @relativeref{Magnum,Error} and * returns @cpp false @ce, @p mesh is guaranteed to stay unchanged. * * Expects that @ref SceneConverterFeature::ConvertMeshInPlace is * supported. - * @see @ref features(), @ref convert(const MeshData&) + * @see @ref isConverting(), @ref features(), + * @ref convert(const MeshData&) */ bool convertInPlace(MeshData& mesh); /** * @brief Convert a mesh to a raw data * - * On failure prints a message to @relativeref{Magnum,Error} and + * If (batch) conversion is currently in progress, calls @ref abort() + * first. On failure prints a message to @relativeref{Magnum,Error} and * returns @ref Containers::NullOpt. * * Expects that @ref SceneConverterFeature::ConvertMeshToData is @@ -689,7 +698,7 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @ref SceneConverterFeature::AddMeshes is supported instead, * delegates to a sequence of @ref beginData(), * @ref add(const MeshData&, Containers::StringView) and @ref endData(). - * @see @ref features(), @ref convertToFile() + * @see @ref isConverting(), @ref features(), @ref convertToFile() */ #if !defined(MAGNUM_BUILD_DEPRECATED) || defined(DOXYGEN_GENERATING_OUTPUT) Containers::Optional> @@ -702,7 +711,8 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @brief Convert a mesh to a file * @m_since_latest * - * On failure prints a message to @relativeref{Magnum,Error} and + * If a (batch) conversion is currently in progress, calls @ref abort() + * first. On failure prints a message to @relativeref{Magnum,Error} and * returns @cpp false @ce. * * Expects that @ref SceneConverterFeature::ConvertMeshToFile is @@ -712,7 +722,7 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * delegates to a sequence of @ref beginFile(), * @ref add(const MeshData&, Containers::StringView) and * @ref endFile(). - * @see @ref features(), @ref convertToData() + * @see @ref isConverting(), @ref features(), @ref convertToData() */ bool convertToFile(const MeshData& mesh, Containers::StringView filename); @@ -762,7 +772,8 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @ref add(const MeshData&, Containers::StringView) to * @ref convert(const MeshData&) and return the result from * @ref end(). - * @see @ref features(), @ref beginData(), @ref beginFile() + * @see @ref isConverting(), @ref features(), @ref beginData(), + * @ref beginFile() */ bool begin(); @@ -817,7 +828,8 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @ref add(const MeshData&, Containers::StringView) to * @ref convertToData(const MeshData&) and return the result from * @ref endData(). - * @see @ref features(), @ref begin(), @ref beginFile() + * @see @ref isConverting(), @ref features(), @ref begin(), + * @ref beginFile() */ bool beginData(); @@ -855,7 +867,8 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @ref add(const MeshData&, Containers::StringView) to * @ref convertToFile(const MeshData&, Containers::StringView) and * return the result from @ref endFile(). - * @see @ref features(), @ref begin(), @ref beginData() + * @see @ref isConverting(), @ref features(), @ref begin(), + * @ref beginData() */ bool beginFile(Containers::StringView filename); diff --git a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp index 6b20c2aad..4b171c762 100644 --- a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp @@ -126,6 +126,13 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void abort(); void abortNotImplemented(); + void abortImplicitlyConvertMesh(); + void abortImplicitlyConvertMeshInPlace(); + void abortImplicitlyConvertMeshToData(); + void abortImplicitlyConvertMeshToFile(); + void abortImplicitlyBegin(); + void abortImplicitlyBeginData(); + void abortImplicitlyBeginFile(); void thingNoBegin(); void endMismatchedBegin(); @@ -342,6 +349,13 @@ AbstractSceneConverterTest::AbstractSceneConverterTest() { &AbstractSceneConverterTest::abort, &AbstractSceneConverterTest::abortNotImplemented, + &AbstractSceneConverterTest::abortImplicitlyConvertMesh, + &AbstractSceneConverterTest::abortImplicitlyConvertMeshInPlace, + &AbstractSceneConverterTest::abortImplicitlyConvertMeshToData, + &AbstractSceneConverterTest::abortImplicitlyConvertMeshToFile, + &AbstractSceneConverterTest::abortImplicitlyBegin, + &AbstractSceneConverterTest::abortImplicitlyBeginData, + &AbstractSceneConverterTest::abortImplicitlyBeginFile, &AbstractSceneConverterTest::thingNoBegin, &AbstractSceneConverterTest::endMismatchedBegin, @@ -1807,6 +1821,236 @@ void AbstractSceneConverterTest::abortNotImplemented() { CORRADE_VERIFY(!converter.isConverting()); } +void AbstractSceneConverterTest::abortImplicitlyConvertMesh() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh| + SceneConverterFeature::ConvertMultiple; + } + + Containers::Optional doConvert(const MeshData&) override { + return MeshData{MeshPrimitive::Lines, 2}; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if there's a batch conversion happening and the + immediate APIs are used */ + CORRADE_VERIFY(converter.convert(MeshData{MeshPrimitive::Triangles, 6})); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::abortImplicitlyConvertMeshInPlace() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshInPlace| + SceneConverterFeature::ConvertMultiple; + } + + bool doConvertInPlace(MeshData&) override { + return true; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if there's a batch conversion happening and the + immediate APIs are used */ + MeshData mesh{MeshPrimitive::Triangles, 6}; + CORRADE_VERIFY(converter.convertInPlace(mesh)); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::abortImplicitlyConvertMeshToData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData| + SceneConverterFeature::ConvertMultiple; + } + + Containers::Optional> doConvertToData(const MeshData&) override { + return Containers::Array{}; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if there's a batch conversion happening and the + immediate APIs are used */ + CORRADE_VERIFY(converter.convertToData(MeshData{MeshPrimitive::Triangles, 6})); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::abortImplicitlyConvertMeshToFile() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile| + SceneConverterFeature::ConvertMultiple; + } + + bool doConvertToFile(const MeshData&, Containers::StringView) override { + return true; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if there's a batch conversion happening and the + immediate APIs are used */ + CORRADE_VERIFY(converter.convertToFile(MeshData{MeshPrimitive::Triangles, 6}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"))); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::abortImplicitlyBegin() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if calling begin() while another conversion is already + happening. Then, what is happening is the new conversion. */ + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::abortImplicitlyBeginData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::ConvertMultipleToData; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool doBeginData() override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if calling beginData() while another conversion is + already happening. Then, what is happening is the new conversion. */ + CORRADE_VERIFY(converter.beginData()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::abortImplicitlyBeginFile() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::ConvertMultipleToFile; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool doBeginFile(Containers::StringView) override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if calling beginData() while another conversion is + already happening. Then, what is happening is the new conversion. */ + CORRADE_VERIFY(converter.beginFile(Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"))); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + void AbstractSceneConverterTest::thingNoBegin() { CORRADE_SKIP_IF_NO_ASSERT(); From a4561716f6689ba38909d30af65e1e0c242dc671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 20 Aug 2022 12:34:08 +0200 Subject: [PATCH 19/93] Trade: doc++ --- src/Magnum/Trade/AbstractImageConverter.h | 57 ++++++++++++----------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/Magnum/Trade/AbstractImageConverter.h b/src/Magnum/Trade/AbstractImageConverter.h index 621e1589e..11e0e7bc5 100644 --- a/src/Magnum/Trade/AbstractImageConverter.h +++ b/src/Magnum/Trade/AbstractImageConverter.h @@ -572,36 +572,39 @@ based on what features are supported. You don't need to do most of the redundant sanity checks, these things are checked by the implementation: -- The function @ref doConvert(const ImageView2D&) is called only if - @ref ImageConverterFeature::Convert2D is supported and equivalently for the +- The @ref doConvert(const ImageView2D&) function is called only if + @ref ImageConverterFeature::Convert2D is supported; equivalently for the 1D and 3D case. -- The function @ref doConvert(const CompressedImageView2D&) is called only if - @ref ImageConverterFeature::ConvertCompressed2D is supported and - equivalently for the 1D and 3D case. -- The function @ref doConvertToData(const ImageView2D&) is called only if - @ref ImageConverterFeature::Convert2DToData is supported and equivalently +- The @ref doConvert(const CompressedImageView2D&) function is called only if + @ref ImageConverterFeature::ConvertCompressed2D is supported; equivalently + for the 1D and 3D case. +- The @ref doConvertToData(const ImageView2D&) function is called only if + @ref ImageConverterFeature::Convert2DToData is supported; equivalently for the 1D and 3D case. -- The function @ref doConvertToData(Containers::ArrayView) +- The @ref doConvertToData(Containers::ArrayView) function is called only if @ref ImageConverterFeature::ConvertLevels2DToData is - supported and equivalently for the 1D and 3D case. -- The function @ref doConvertToData(const CompressedImageView2D&) is called - only if @ref ImageConverterFeature::ConvertCompressed2DToData is supported - and equivalently for the 1D and 3D case. -- The function @ref doConvertToData(Containers::ArrayView) - is called only if @ref ImageConverterFeature::ConvertCompressedLevels2DToData - is supported and equivalently for the 1D and 3D case. -- The function @ref doConvertToFile(const ImageView2D&, Containers::StringView) - is called only if @ref ImageConverterFeature::Convert2DToFile is supported - and equivalently for the 1D and 3D case. -- The function @ref doConvertToFile(Containers::ArrayView, Containers::StringView) - is called only if @ref ImageConverterFeature::ConvertLevels2DToFile is - supported and equivalently for the 1D and 3D case. -- The function @ref doConvertToFile(const CompressedImageView2D&, Containers::StringView) - is called only if @ref ImageConverterFeature::ConvertCompressed2DToFile is - supported and equivalently for the 1D and 3D case. -- The function @ref doConvertToFile(Containers::ArrayView, Containers::StringView) - is called only if @ref ImageConverterFeature::ConvertCompressedLevels2DToFile - is supported and equivalently for the 1D and 3D case. + supported; equivalently for the 1D and 3D case. +- The @ref doConvertToData(const CompressedImageView2D&) function is called + only if @ref ImageConverterFeature::ConvertCompressed2DToData is supported; + equivalently for the 1D and 3D case. +- The @ref doConvertToData(Containers::ArrayView) + function is called only if + @ref ImageConverterFeature::ConvertCompressedLevels2DToData is supported; + equivalently for the 1D and 3D case. +- The @ref doConvertToFile(const ImageView2D&, Containers::StringView) + function is called only if @ref ImageConverterFeature::Convert2DToFile is + supported; equivalently for the 1D and 3D case. +- The @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + function is called only if @ref ImageConverterFeature::ConvertLevels2DToFile + is supported; equivalently for the 1D and 3D case. +- The @ref doConvertToFile(const CompressedImageView2D&, Containers::StringView) + function is called only if + @ref ImageConverterFeature::ConvertCompressed2DToFile is supported; + equivalently for the 1D and 3D case. +- The @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + function is called only if + @ref ImageConverterFeature::ConvertCompressedLevels2DToFile is supported; + equivalently for the 1D and 3D case. - All @ref doConvertToData() and @ref doConvertToFile() functions taking a single (compressed) image are called only if the image has a non-zero size in all dimensions and the view is not @cpp nullptr @ce. Note that this does From 5795c1505bf8af0b6d822846b0b627d37775d36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 20 Aug 2022 12:46:45 +0200 Subject: [PATCH 20/93] Tga{Importer,ImageConverter}: minor cleanup. --- .../TgaImageConverter/TgaImageConverter.h | 4 ++-- src/MagnumPlugins/TgaImporter/TgaImporter.h | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.h b/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.h index 43767e24d..2be10c928 100644 --- a/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.h +++ b/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.h @@ -107,8 +107,8 @@ class MAGNUM_TGAIMAGECONVERTER_EXPORT TgaImageConverter: public AbstractImageCon explicit TgaImageConverter(PluginManager::AbstractManager& manager, const Containers::StringView& plugin); private: - ImageConverterFeatures MAGNUM_TGAIMAGECONVERTER_LOCAL doFeatures() const override; - Containers::Optional> MAGNUM_TGAIMAGECONVERTER_LOCAL doConvertToData(const ImageView2D& image) override; + MAGNUM_TGAIMAGECONVERTER_LOCAL ImageConverterFeatures doFeatures() const override; + MAGNUM_TGAIMAGECONVERTER_LOCAL Containers::Optional> doConvertToData(const ImageView2D& image) override; }; }} diff --git a/src/MagnumPlugins/TgaImporter/TgaImporter.h b/src/MagnumPlugins/TgaImporter/TgaImporter.h index faf2b586c..8cbfbbf68 100644 --- a/src/MagnumPlugins/TgaImporter/TgaImporter.h +++ b/src/MagnumPlugins/TgaImporter/TgaImporter.h @@ -110,12 +110,12 @@ class MAGNUM_TGAIMPORTER_EXPORT TgaImporter: public AbstractImporter { ~TgaImporter(); private: - ImporterFeatures MAGNUM_TGAIMPORTER_LOCAL doFeatures() const override; - bool MAGNUM_TGAIMPORTER_LOCAL doIsOpened() const override; - void MAGNUM_TGAIMPORTER_LOCAL doOpenData(Containers::Array&& data, DataFlags dataFlags) override; - void MAGNUM_TGAIMPORTER_LOCAL doClose() override; - UnsignedInt MAGNUM_TGAIMPORTER_LOCAL doImage2DCount() const override; - Containers::Optional MAGNUM_TGAIMPORTER_LOCAL doImage2D(UnsignedInt id, UnsignedInt level) override; + MAGNUM_TGAIMPORTER_LOCAL ImporterFeatures doFeatures() const override; + MAGNUM_TGAIMPORTER_LOCAL bool doIsOpened() const override; + MAGNUM_TGAIMPORTER_LOCAL void doOpenData(Containers::Array&& data, DataFlags dataFlags) override; + MAGNUM_TGAIMPORTER_LOCAL void doClose() override; + MAGNUM_TGAIMPORTER_LOCAL UnsignedInt doImage2DCount() const override; + MAGNUM_TGAIMPORTER_LOCAL Containers::Optional doImage2D(UnsignedInt id, UnsignedInt level) override; Containers::Array _in; }; From 3892ddc4dcf60d6ed43d4ca6e38a6ff963667f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 20 Aug 2022 13:17:08 +0200 Subject: [PATCH 21/93] Trade: add AbstractImageConverter::extension() and mimeType(). --- doc/changelog.dox | 4 ++ src/Magnum/Trade/AbstractImageConverter.cpp | 36 ++++++++++- src/Magnum/Trade/AbstractImageConverter.h | 52 ++++++++++++++++ .../Trade/Test/AbstractImageConverterTest.cpp | 60 +++++++++++++++++++ 4 files changed, 151 insertions(+), 1 deletion(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 4364d67d2..b40b2014e 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -527,6 +527,10 @@ See also: @relativeref{Trade::AbstractImageConverter,doConvertToData()}, for example when the implementation only neeeds to do a format detection based on file extension +- New @ref Trade::AbstractImageConverter::extension() and + @relativeref{Trade::AbstractImageConverter,mimeType()} interfaces to get + a file extension and MIME type corresponding to a file format produced by + a particular plugin. - Recognizing BMP and TIFF file header magic in @relativeref{Trade,AnyImageImporter} - Recognizing ASTC and WebP files and data in @relativeref{Trade,AnyImageImporter} diff --git a/src/Magnum/Trade/AbstractImageConverter.cpp b/src/Magnum/Trade/AbstractImageConverter.cpp index 9c51de7c9..29f12b250 100644 --- a/src/Magnum/Trade/AbstractImageConverter.cpp +++ b/src/Magnum/Trade/AbstractImageConverter.cpp @@ -57,7 +57,7 @@ using namespace Containers::Literals; Containers::StringView AbstractImageConverter::pluginInterface() { return /* [interface] */ -"cz.mosra.magnum.Trade.AbstractImageConverter/0.3.2"_s +"cz.mosra.magnum.Trade.AbstractImageConverter/0.3.3"_s /* [interface] */ ; } @@ -106,6 +106,40 @@ void AbstractImageConverter::clearFlags(ImageConverterFlags flags) { setFlags(_flags & ~flags); } +Containers::String AbstractImageConverter::extension() const { + CORRADE_ASSERT(features() & (ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::ConvertCompressed3DToFile), + "Trade::AbstractImageConverter::extension(): file conversion not supported", {}); + + Containers::String out = doExtension(); + CORRADE_ASSERT(out.isSmall() || !out.deleter(), + "Trade::AbstractImageConverter::extension(): implementation is not allowed to use a custom String deleter", {}); + return out; +} + +Containers::String AbstractImageConverter::doExtension() const { return {}; } + +Containers::String AbstractImageConverter::mimeType() const { + CORRADE_ASSERT(features() & (ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::ConvertCompressed3DToFile), + "Trade::AbstractImageConverter::mimeType(): file conversion not supported", {}); + + Containers::String out = doMimeType(); + CORRADE_ASSERT(out.isSmall() || !out.deleter(), + "Trade::AbstractImageConverter::mimeType(): implementation is not allowed to use a custom String deleter", {}); + return out; +} + +Containers::String AbstractImageConverter::doMimeType() const { return {}; } + Containers::Optional AbstractImageConverter::convert(const ImageView1D& image) { CORRADE_ASSERT(features() & ImageConverterFeature::Convert1D, "Trade::AbstractImageConverter::convert(): 1D image conversion not supported", {}); diff --git a/src/Magnum/Trade/AbstractImageConverter.h b/src/Magnum/Trade/AbstractImageConverter.h index 11e0e7bc5..ae2f0a01e 100644 --- a/src/Magnum/Trade/AbstractImageConverter.h +++ b/src/Magnum/Trade/AbstractImageConverter.h @@ -572,6 +572,10 @@ based on what features are supported. You don't need to do most of the redundant sanity checks, these things are checked by the implementation: +- The @ref doExtension() and @ref doMimeType() functions are called only + if @ref ImageConverterFeature::Convert2DToData "ImageConverterFeature::Convert*ToData" + or @ref ImageConverterFeature::Convert2DToFile "Convert*ToFile" is + supported - The @ref doConvert(const ImageView2D&) function is called only if @ref ImageConverterFeature::Convert2D is supported; equivalently for the 1D and 3D case. @@ -724,6 +728,38 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract */ void clearFlags(ImageConverterFlags flags); + /** + * @brief File extension + * @m_since_latest + * + * Available only if @ref ImageConverterFeature::Convert2DToFile "ImageConverterFeature::Convert*ToFile" + * or @ref ImageConverterFeature::Convert2DToData "ImageConverterFeature::Convert*ToData" + * is supported. Returns a standardized file extension corresponding + * to the file format used, such as @cpp "png" @ce for PNG files. If + * the file format doesn't have a standardized extension, empty string + * is returned. + * + * The returned value may depend on flags or configuration options and + * can change during plugin lifetime. + */ + Containers::String extension() const; + + /** + * @brief File MIME type + * @m_since_latest + * + * Available only if @ref ImageConverterFeature::Convert2DToFile "ImageConverterFeature::Convert*ToFile" + * or @ref ImageConverterFeature::Convert2DToData "ImageConverterFeature::Convert*ToData" + * is supported. Returns a standardized [MIME type](https://en.wikipedia.org/wiki/Media_type) + * corresponding to the file format used, such as @cpp "image/png" @ce + * for PNG files. If the file format doesn't have a standardized MIME + * type, empty string is returned. + * + * The returned value may depend on flags or configuration options and + * can change during plugin lifetime. + */ + Containers::String mimeType() const; + /** * @brief Convert a 1D image * @m_since_latest @@ -1777,6 +1813,22 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract */ virtual void doSetFlags(ImageConverterFlags flags); + /** + * @brief Implementation for @ref extension() + * @m_since_latest + * + * Default implementation returns an empty string. + */ + virtual Containers::String doExtension() const; + + /** + * @brief Implementation for @ref mimeType() + * @m_since_latest + * + * Default implementation returns an empty string. + */ + virtual Containers::String doMimeType() const; + /** * @brief Implementation for @ref convert(const ImageView1D&) * @m_since_latest diff --git a/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp b/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp index 71af13a11..1c6354b46 100644 --- a/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp @@ -56,6 +56,10 @@ struct AbstractImageConverterTest: TestSuite::Tester { void thingNotSupported(); + void extensionMimeType(); + void extensionMimeTypeNotImplemented(); + void extensionMimeTypeCustomDeleter(); + void convert1D(); void convert2D(); void convert3D(); @@ -307,6 +311,10 @@ AbstractImageConverterTest::AbstractImageConverterTest() { &AbstractImageConverterTest::thingNotSupported, + &AbstractImageConverterTest::extensionMimeType, + &AbstractImageConverterTest::extensionMimeTypeNotImplemented, + &AbstractImageConverterTest::extensionMimeTypeCustomDeleter, + &AbstractImageConverterTest::convert1D, &AbstractImageConverterTest::convert2D, &AbstractImageConverterTest::convert3D, @@ -599,6 +607,8 @@ void AbstractImageConverterTest::thingNotSupported() { std::ostringstream out; Error redirectError{&out}; + converter.extension(); + converter.mimeType(); converter.convert(ImageView1D{PixelFormat::R8Unorm, 0, nullptr}); converter.convert(ImageView2D{PixelFormat::R8Unorm, {}, nullptr}); converter.convert(ImageView3D{PixelFormat::R8Unorm, {}, nullptr}); @@ -630,6 +640,8 @@ void AbstractImageConverterTest::thingNotSupported() { converter.convertToFile({CompressedImageView2D{CompressedPixelFormat::Bc1RGBAUnorm, {}, nullptr}}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "image.out")); converter.convertToFile({CompressedImageView3D{CompressedPixelFormat::Bc1RGBAUnorm, {}, nullptr}}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "image.out")); CORRADE_COMPARE(out.str(), + "Trade::AbstractImageConverter::extension(): file conversion not supported\n" + "Trade::AbstractImageConverter::mimeType(): file conversion not supported\n" "Trade::AbstractImageConverter::convert(): 1D image conversion not supported\n" "Trade::AbstractImageConverter::convert(): 2D image conversion not supported\n" "Trade::AbstractImageConverter::convert(): 3D image conversion not supported\n" @@ -662,6 +674,54 @@ void AbstractImageConverterTest::thingNotSupported() { "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 3D image conversion not supported\n"); } +void AbstractImageConverterTest::extensionMimeType() { + struct: AbstractImageConverter { + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressedLevels3DToData; + } + Containers::String doExtension() const override { return "yello"; } + Containers::String doMimeType() const override { return "yel/low"; } + } converter; + + CORRADE_COMPARE(converter.extension(), "yello"); + CORRADE_COMPARE(converter.mimeType(), "yel/low"); +} + +void AbstractImageConverterTest::extensionMimeTypeNotImplemented() { + struct: AbstractImageConverter { + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToFile; + } + } converter; + + CORRADE_COMPARE(converter.extension(), ""); + CORRADE_COMPARE(converter.mimeType(), ""); +} + +void AbstractImageConverterTest::extensionMimeTypeCustomDeleter() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractImageConverter { + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData; + } + Containers::String doExtension() const override { + return Containers::String{"yello", 5, [](char*, std::size_t) {}}; + } + Containers::String doMimeType() const override { + return Containers::String{"yel/low", 7, [](char*, std::size_t) {}}; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.extension(); + converter.mimeType(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImageConverter::extension(): implementation is not allowed to use a custom String deleter\n" + "Trade::AbstractImageConverter::mimeType(): implementation is not allowed to use a custom String deleter\n"); +} + void AbstractImageConverterTest::convert1D() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::Convert1D; } From f5f96513b513e3dd091ae21adb1b08c14049c479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 20 Aug 2022 13:17:30 +0200 Subject: [PATCH 22/93] TgaImageConverter: expose file extension and MIME type interfaces. --- .../Test/TgaImageConverterTest.cpp | 3 +++ .../TgaImageConverter/TgaImageConverter.cpp | 14 +++++++++++++- .../TgaImageConverter/TgaImageConverter.h | 6 ++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/MagnumPlugins/TgaImageConverter/Test/TgaImageConverterTest.cpp b/src/MagnumPlugins/TgaImageConverter/Test/TgaImageConverterTest.cpp index 327f887b1..8e2f50abc 100644 --- a/src/MagnumPlugins/TgaImageConverter/Test/TgaImageConverterTest.cpp +++ b/src/MagnumPlugins/TgaImageConverter/Test/TgaImageConverterTest.cpp @@ -142,6 +142,9 @@ void TgaImageConverterTest::rgb() { setTestCaseDescription(data.name); Containers::Pointer converter = _converterManager.instantiate("TgaImageConverter"); + CORRADE_COMPARE(converter->extension(), "tga"); + CORRADE_COMPARE(converter->mimeType(), "image/x-tga"); + converter->setFlags(data.flags); std::ostringstream out; diff --git a/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.cpp b/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.cpp index e487fc91c..821a26e3f 100644 --- a/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.cpp +++ b/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -40,12 +41,23 @@ namespace Magnum { namespace Trade { +using namespace Containers::Literals; + TgaImageConverter::TgaImageConverter() = default; TgaImageConverter::TgaImageConverter(PluginManager::AbstractManager& manager, const Containers::StringView& plugin): AbstractImageConverter{manager, plugin} {} ImageConverterFeatures TgaImageConverter::doFeatures() const { return ImageConverterFeature::Convert2DToData; } +Containers::String TgaImageConverter::doExtension() const { return "tga"_s; } + +Containers::String TgaImageConverter::doMimeType() const { + /* https://en.wikipedia.org/wiki/Truevision_TGA says there's no registered + MIME type. It probably never will be. Using `file --mime-type` on a TGA + file returns image/x-tga, so using that here as well. */ + return "image/x-tga"_s; +} + Containers::Optional> TgaImageConverter::doConvertToData(const ImageView2D& image) { /* Warn about lost metadata */ if(image.flags() & ImageFlag2D::Array) { @@ -98,4 +110,4 @@ Containers::Optional> TgaImageConverter::doConvertToData }} CORRADE_PLUGIN_REGISTER(TgaImageConverter, Magnum::Trade::TgaImageConverter, - "cz.mosra.magnum.Trade.AbstractImageConverter/0.3.2") + "cz.mosra.magnum.Trade.AbstractImageConverter/0.3.3") diff --git a/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.h b/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.h index 2be10c928..1eccd9b35 100644 --- a/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.h +++ b/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.h @@ -97,6 +97,10 @@ and have the files smaller, use the @ref StbImageConverter plugin instead. The TGA file format doesn't have a way to distinguish between 2D and 1D array images. If an image has @ref ImageFlag2D::Array set, a warning is printed and the file is saved as a regular 2D image. + +While TGA files can have several extensions, @ref extension() always returns +@cpp "tga" @ce as that's the most common one. As TGA doesn't have a registered +MIME type, @ref mimeType() returns @cpp "image/x-tga" @ce. */ class MAGNUM_TGAIMAGECONVERTER_EXPORT TgaImageConverter: public AbstractImageConverter { public: @@ -108,6 +112,8 @@ class MAGNUM_TGAIMAGECONVERTER_EXPORT TgaImageConverter: public AbstractImageCon private: MAGNUM_TGAIMAGECONVERTER_LOCAL ImageConverterFeatures doFeatures() const override; + MAGNUM_TGAIMAGECONVERTER_LOCAL Containers::String doExtension() const override; + MAGNUM_TGAIMAGECONVERTER_LOCAL Containers::String doMimeType() const override; MAGNUM_TGAIMAGECONVERTER_LOCAL Containers::Optional> doConvertToData(const ImageView2D& image) override; }; From 278431dd8fb96461aedb58a60972948103b53199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 20 Aug 2022 13:41:00 +0200 Subject: [PATCH 23/93] AnyImageConverter: bump plugin interface. Can't really implement extension() and mimeType() here. --- src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp | 2 +- src/MagnumPlugins/AnyImageConverter/AnyImageConverter.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp index 97efdc571..9e50dbd45 100644 --- a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp +++ b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp @@ -626,4 +626,4 @@ bool AnyImageConverter::doConvertToFile(const Containers::ArrayView Date: Sat, 20 Aug 2022 19:00:16 +0200 Subject: [PATCH 24/93] TgaImageConverter: fix a non-deprecated build. I should really clean up the compatibility includes, this is getting unbearable. Can't even push straight to master without thinking. --- .../TgaImageConverter/Test/TgaImageConverterTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MagnumPlugins/TgaImageConverter/Test/TgaImageConverterTest.cpp b/src/MagnumPlugins/TgaImageConverter/Test/TgaImageConverterTest.cpp index 8e2f50abc..69380cc1e 100644 --- a/src/MagnumPlugins/TgaImageConverter/Test/TgaImageConverterTest.cpp +++ b/src/MagnumPlugins/TgaImageConverter/Test/TgaImageConverterTest.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include /** @todo remove once Debug is stream-free */ From 54d0b999b15f1ce8d0623e48c2aec11a08f25852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 22 Aug 2022 14:17:10 +0200 Subject: [PATCH 25/93] Trade: un-combinatorially-explode ImageConverterFeatures. Unlike most other options, where a plugin can support 3D but not 2D or compressed but not uncompressed data, the "levels" option is orthogonal to the rest -- if a format supports multi-level data, it obviously also supports a case where there is just a single level, and if it supports multi-level images, then it likely supports them for 1D, 2D and 3D, not just some. I used the same reasoning for the new SceneConverterFeatures interfaces already, just "backporting" it here as well. The bit pattern actually followed this already, but for some reason I still went ahead and stamped out all possible combinations. Because of that, nothing really changes on the ABI side either and the deprecated aliases are now exactly as they were before, so no need to bump the plugin interface version. --- src/Magnum/Trade/AbstractImageConverter.cpp | 119 ++-- src/Magnum/Trade/AbstractImageConverter.h | 525 ++++++++---------- .../Trade/Test/AbstractImageConverterTest.cpp | 488 ++++++++++++---- src/Magnum/Trade/imageconverter.cpp | 6 +- .../AnyImageConverter/AnyImageConverter.cpp | 7 +- 5 files changed, 672 insertions(+), 473 deletions(-) diff --git a/src/Magnum/Trade/AbstractImageConverter.cpp b/src/Magnum/Trade/AbstractImageConverter.cpp index 29f12b250..56068481c 100644 --- a/src/Magnum/Trade/AbstractImageConverter.cpp +++ b/src/Magnum/Trade/AbstractImageConverter.cpp @@ -343,7 +343,7 @@ AbstractImageConverter::convertToData(const ImageView1D& image) { } Containers::Optional> AbstractImageConverter::doConvertToData(const ImageView1D& image) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels1DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): 1D image conversion advertised but not implemented", {}); return doConvertToData(Containers::arrayView({image})); @@ -376,7 +376,7 @@ AbstractImageConverter::convertToData(const ImageView2D& image) { } Containers::Optional> AbstractImageConverter::doConvertToData(const ImageView2D& image) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels2DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): 2D image conversion advertised but not implemented", {}); return doConvertToData(Containers::arrayView({image})); @@ -416,7 +416,7 @@ AbstractImageConverter::convertToData(const ImageView3D& image) { } Containers::Optional> AbstractImageConverter::doConvertToData(const ImageView3D& image) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels3DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): 3D image conversion advertised but not implemented", {}); return doConvertToData(Containers::arrayView({image})); @@ -449,7 +449,7 @@ AbstractImageConverter::convertToData(const CompressedImageView1D& image) { } Containers::Optional> AbstractImageConverter::doConvertToData(const CompressedImageView1D& image) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels1DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): compressed 1D image conversion advertised but not implemented", {}); return doConvertToData(Containers::arrayView({image})); @@ -482,7 +482,7 @@ AbstractImageConverter::convertToData(const CompressedImageView2D& image) { } Containers::Optional> AbstractImageConverter::doConvertToData(const CompressedImageView2D& image) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels2DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): compressed 2D image conversion advertised but not implemented", {}); return doConvertToData(Containers::arrayView({image})); @@ -522,7 +522,7 @@ AbstractImageConverter::convertToData(const CompressedImageView3D& image) { } Containers::Optional> AbstractImageConverter::doConvertToData(const CompressedImageView3D& image) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels3DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): compressed 3D image conversion advertised but not implemented", {}); return doConvertToData(Containers::arrayView({image})); @@ -623,7 +623,7 @@ Containers::Optional> Implementation::ImageConverterOptionalButAlsoArray #endif AbstractImageConverter::convertToData(const Containers::ArrayView imageLevels) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels1DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): multi-level 1D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -662,7 +662,7 @@ Containers::Optional> Implementation::ImageConverterOptionalButAlsoArray #endif AbstractImageConverter::convertToData(const Containers::ArrayView imageLevels) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels2DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): multi-level 2D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -701,7 +701,7 @@ Containers::Optional> Implementation::ImageConverterOptionalButAlsoArray #endif AbstractImageConverter::convertToData(const Containers::ArrayView imageLevels) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels3DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): multi-level 3D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -740,7 +740,7 @@ Containers::Optional> Implementation::ImageConverterOptionalButAlsoArray #endif AbstractImageConverter::convertToData(const Containers::ArrayView imageLevels) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels1DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): multi-level compressed 1D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -779,7 +779,7 @@ Containers::Optional> Implementation::ImageConverterOptionalButAlsoArray #endif AbstractImageConverter::convertToData(const Containers::ArrayView imageLevels) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels2DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): multi-level compressed 2D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -818,7 +818,7 @@ Containers::Optional> Implementation::ImageConverterOptionalButAlsoArray #endif AbstractImageConverter::convertToData(const Containers::ArrayView imageLevels) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels3DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): multi-level compressed 3D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -868,7 +868,7 @@ bool AbstractImageConverter::doConvertToFile(const ImageView1D& image, const Con /* Prefer to go through the ToFile variant instead of ToData assuming that it could have a more memory-efficient implementation than having to materialize the whole output in memory first */ - if(features() >= ImageConverterFeature::ConvertLevels1DToFile) + if(features() >= ImageConverterFeature::Levels) return doConvertToFile(Containers::arrayView({image}), filename); CORRADE_ASSERT(features() >= ImageConverterFeature::Convert1DToData, "Trade::AbstractImageConverter::convertToFile(): 1D image conversion advertised but not implemented", false); @@ -902,7 +902,7 @@ bool AbstractImageConverter::doConvertToFile(const ImageView2D& image, const Con /* Prefer to go through the ToFile variant instead of ToData assuming that it could have a more memory-efficient implementation than having to materialize the whole output in memory first */ - if(features() >= ImageConverterFeature::ConvertLevels2DToFile) + if(features() >= ImageConverterFeature::Levels) return doConvertToFile(Containers::arrayView({image}), filename); CORRADE_ASSERT(features() >= ImageConverterFeature::Convert2DToData, "Trade::AbstractImageConverter::convertToFile(): 2D image conversion advertised but not implemented", false); @@ -942,7 +942,7 @@ bool AbstractImageConverter::doConvertToFile(const ImageView3D& image, const Con /* Prefer to go through the ToFile variant instead of ToData assuming that it could have a more memory-efficient implementation than having to materialize the whole output in memory first */ - if(features() >= ImageConverterFeature::ConvertLevels3DToFile) + if(features() >= ImageConverterFeature::Levels) return doConvertToFile(Containers::arrayView({image}), filename); CORRADE_ASSERT(features() >= ImageConverterFeature::Convert3DToData, "Trade::AbstractImageConverter::convertToFile(): 3D image conversion advertised but not implemented", false); @@ -976,7 +976,7 @@ bool AbstractImageConverter::doConvertToFile(const CompressedImageView1D& image, /* Prefer to go through the ToFile variant instead of ToData assuming that it could have a more memory-efficient implementation than having to materialize the whole output in memory first */ - if(features() >= ImageConverterFeature::ConvertCompressedLevels1DToFile) + if(features() >= ImageConverterFeature::Levels) return doConvertToFile(Containers::arrayView({image}), filename); CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressed1DToData, "Trade::AbstractImageConverter::convertToFile(): compressed 1D image conversion advertised but not implemented", false); @@ -1010,7 +1010,7 @@ bool AbstractImageConverter::doConvertToFile(const CompressedImageView2D& image, /* Prefer to go through the ToFile variant instead of ToData assuming that it could have a more memory-efficient implementation than having to materialize the whole output in memory first */ - if(features() >= ImageConverterFeature::ConvertCompressedLevels2DToFile) + if(features() >= ImageConverterFeature::Levels) return doConvertToFile(Containers::arrayView({image}), filename); CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressed2DToData, "Trade::AbstractImageConverter::convertToFile(): compressed 2D image conversion advertised but not implemented", false); @@ -1050,7 +1050,7 @@ bool AbstractImageConverter::doConvertToFile(const CompressedImageView3D& image, /* Prefer to go through the ToFile variant instead of ToData assuming that it could have a more memory-efficient implementation than having to materialize the whole output in memory first */ - if(features() >= ImageConverterFeature::ConvertCompressedLevels3DToFile) + if(features() >= ImageConverterFeature::Levels) return doConvertToFile(Containers::arrayView({image}), filename); CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressed3DToData, "Trade::AbstractImageConverter::convertToFile(): compressed 3D image conversion advertised but not implemented", false); @@ -1086,7 +1086,7 @@ bool AbstractImageConverter::convertToFile(const ImageData3D& image, const Conta } bool AbstractImageConverter::convertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() & ImageConverterFeature::ConvertLevels1DToFile, + CORRADE_ASSERT(features() >= (ImageConverterFeature::Convert1DToFile|ImageConverterFeature::Levels), "Trade::AbstractImageConverter::convertToFile(): multi-level 1D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -1103,7 +1103,7 @@ bool AbstractImageConverter::convertToFile(const std::initializer_list imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels1DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level 1D image conversion advertised but not implemented", false); + CORRADE_ASSERT(features() >= ImageConverterFeature::Convert1DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level 1D image conversion advertised but not implemented", false); const Containers::Optional> data = doConvertToData(imageLevels); /* No deleter checks as it doesn't matter here */ @@ -1118,7 +1118,7 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() & ImageConverterFeature::ConvertLevels2DToFile, + CORRADE_ASSERT(features() >= (ImageConverterFeature::Convert2DToFile|ImageConverterFeature::Levels), "Trade::AbstractImageConverter::convertToFile(): multi-level 2D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -1135,7 +1135,7 @@ bool AbstractImageConverter::convertToFile(const std::initializer_list imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels2DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level 2D image conversion advertised but not implemented", false); + CORRADE_ASSERT(features() >= ImageConverterFeature::Convert2DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level 2D image conversion advertised but not implemented", false); const Containers::Optional> data = doConvertToData(imageLevels); /* No deleter checks as it doesn't matter here */ @@ -1150,7 +1150,7 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() & ImageConverterFeature::ConvertLevels3DToFile, + CORRADE_ASSERT(features() >= (ImageConverterFeature::Convert3DToFile|ImageConverterFeature::Levels), "Trade::AbstractImageConverter::convertToFile(): multi-level 3D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -1167,7 +1167,7 @@ bool AbstractImageConverter::convertToFile(const std::initializer_list imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels3DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level 3D image conversion advertised but not implemented", false); + CORRADE_ASSERT(features() >= ImageConverterFeature::Convert3DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level 3D image conversion advertised but not implemented", false); const Containers::Optional> data = doConvertToData(imageLevels); /* No deleter checks as it doesn't matter here */ @@ -1182,7 +1182,7 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() & ImageConverterFeature::ConvertCompressedLevels1DToFile, + CORRADE_ASSERT(features() >= (ImageConverterFeature::ConvertCompressed1DToFile|ImageConverterFeature::Levels), "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 1D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -1199,7 +1199,7 @@ bool AbstractImageConverter::convertToFile(const std::initializer_list imageLevels, Containers::StringView filename) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels1DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 1D image conversion advertised but not implemented", false); + CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressed1DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 1D image conversion advertised but not implemented", false); const Containers::Optional> data = doConvertToData(imageLevels); /* No deleter checks as it doesn't matter here */ @@ -1214,7 +1214,7 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() & ImageConverterFeature::ConvertCompressedLevels2DToFile, + CORRADE_ASSERT(features() >= (ImageConverterFeature::ConvertCompressed2DToFile|ImageConverterFeature::Levels), "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 2D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -1231,7 +1231,7 @@ bool AbstractImageConverter::convertToFile(const std::initializer_list imageLevels, Containers::StringView filename) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels2DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 2D image conversion advertised but not implemented", false); + CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressed2DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 2D image conversion advertised but not implemented", false); const Containers::Optional> data = doConvertToData(imageLevels); /* No deleter checks as it doesn't matter here */ @@ -1246,7 +1246,7 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() & ImageConverterFeature::ConvertCompressedLevels3DToFile, + CORRADE_ASSERT(features() >= (ImageConverterFeature::ConvertCompressed3DToFile|ImageConverterFeature::Levels), "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 3D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -1263,7 +1263,7 @@ bool AbstractImageConverter::convertToFile(const std::initializer_list imageLevels, Containers::StringView filename) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels3DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 3D image conversion advertised but not implemented", false); + CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressed3DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 3D image conversion advertised but not implemented", false); const Containers::Optional> data = doConvertToData(imageLevels); /* No deleter checks as it doesn't matter here */ @@ -1278,6 +1278,13 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView(UnsignedInt(value)) << Debug::nospace << ")"; @@ -1328,22 +1344,6 @@ Debug& operator<<(Debug& debug, const ImageConverterFeatures value) { ImageConverterFeature::ConvertCompressed1D, ImageConverterFeature::ConvertCompressed2D, ImageConverterFeature::ConvertCompressed3D, - ImageConverterFeature::ConvertLevels1DToData, - ImageConverterFeature::ConvertLevels2DToData, - ImageConverterFeature::ConvertLevels3DToData, - ImageConverterFeature::ConvertCompressedLevels1DToData, - ImageConverterFeature::ConvertCompressedLevels2DToData, - ImageConverterFeature::ConvertCompressedLevels3DToData, - /* These 6 are implied by Convert[Compressed]LevelsToData, so have to - be after */ - ImageConverterFeature::ConvertLevels1DToFile, - ImageConverterFeature::ConvertLevels2DToFile, - ImageConverterFeature::ConvertLevels3DToFile, - ImageConverterFeature::ConvertCompressedLevels1DToFile, - ImageConverterFeature::ConvertCompressedLevels2DToFile, - ImageConverterFeature::ConvertCompressedLevels3DToFile, - /* These 12 are implied by Convert[Compressed]LevelsTo{File,Data}, so - have to be after */ ImageConverterFeature::Convert1DToData, ImageConverterFeature::Convert2DToData, ImageConverterFeature::Convert3DToData, @@ -1357,7 +1357,8 @@ Debug& operator<<(Debug& debug, const ImageConverterFeatures value) { ImageConverterFeature::Convert3DToFile, ImageConverterFeature::ConvertCompressed1DToFile, ImageConverterFeature::ConvertCompressed2DToFile, - ImageConverterFeature::ConvertCompressed3DToFile}); + ImageConverterFeature::ConvertCompressed3DToFile, + ImageConverterFeature::Levels}); } Debug& operator<<(Debug& debug, const ImageConverterFlag value) { diff --git a/src/Magnum/Trade/AbstractImageConverter.h b/src/Magnum/Trade/AbstractImageConverter.h index ae2f0a01e..fc819399a 100644 --- a/src/Magnum/Trade/AbstractImageConverter.h +++ b/src/Magnum/Trade/AbstractImageConverter.h @@ -242,112 +242,126 @@ enum class ImageConverterFeature: UnsignedInt { ConvertCompressed3DToData = ConvertCompressed3DToFile|(1 << 13), /** - * Convert a set of 1D image levels to a file with - * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView). - * Implies @ref ImageConverterFeature::Convert1DToFile. + * Convert multiple image levels with + * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView) / + * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView) + * if @ref ImageConverterFeature::Convert1DToFile / + * @relativeref{ImageConverterFeature,ConvertCompressed1DToFile} is also + * supported; with + * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView) / + * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView) + * if @ref ImageConverterFeature::Convert2DToFile / + * @relativeref{ImageConverterFeature,ConvertCompressed2DToFile} is also + * supported; with + * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView) / + * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView) + * if @ref ImageConverterFeature::Convert3DToFile / + * @relativeref{ImageConverterFeature,ConvertCompressed3DToFile} is also + * supported; with + * @ref AbstractImageConverter::convertToData(Containers::ArrayView) / + * @ref AbstractImageConverter::convertToData(Containers::ArrayView) + * if @ref ImageConverterFeature::Convert1DToData / + * @relativeref{ImageConverterFeature,ConvertCompressed1DToData} is also + * supported; with + * @ref AbstractImageConverter::convertToData(Containers::ArrayView) / + * @ref AbstractImageConverter::convertToData(Containers::ArrayView) + * if @ref ImageConverterFeature::Convert2DToData / + * @relativeref{ImageConverterFeature,ConvertCompressed2DToData} is also + * supported; with + * @ref AbstractImageConverter::convertToData(Containers::ArrayView) / + * @ref AbstractImageConverter::convertToData(Containers::ArrayView) + * if @ref ImageConverterFeature::Convert3DToData / + * @relativeref{ImageConverterFeature,ConvertCompressed3DToData} is also + * supported. * @m_since_latest */ - ConvertLevels1DToFile = Convert1DToFile|(1 << 14), + Levels = 1 << 14, + #ifdef MAGNUM_BUILD_DEPRECATED /** - * Convert a set of 2D image levels to a file with - * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView). - * Implies @ref ImageConverterFeature::Convert2DToFile. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::Convert1DToFile together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertLevels2DToFile = Convert2DToFile|(1 << 14), + ConvertLevels1DToFile CORRADE_DEPRECATED_ENUM("use Convert1DToFile together with Levels instead") = Convert1DToFile|Levels, /** - * Convert a set of 3D image levels to a file with - * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView). - * Implies @ref ImageConverterFeature::Convert3DToFile. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::Convert2DToFile together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertLevels3DToFile = Convert3DToFile|(1 << 14), + ConvertLevels2DToFile CORRADE_DEPRECATED_ENUM("use Convert2DToFile together with Levels instead") = Convert2DToFile|Levels, /** - * Convert a set of compressed 1D image levels to a file with - * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView). - * Implies @ref ImageConverterFeature::ConvertCompressed1DToFile. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::Convert3DToFile together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertCompressedLevels1DToFile = ConvertCompressed1DToFile|(1 << 14), + ConvertLevels3DToFile CORRADE_DEPRECATED_ENUM("use Convert3DToFile together with Levels instead") = Convert3DToFile|Levels, /** - * Convert a set of compressed 2D image levels to a file with - * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView). - * Implies @ref ImageConverterFeature::ConvertCompressed2DToFile. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::ConvertCompressed1DToFile together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertCompressedLevels2DToFile = ConvertCompressed2DToFile|(1 << 14), + ConvertCompressedLevels1DToFile CORRADE_DEPRECATED_ENUM("use ConvertCompressed1DToFile together with Levels instead") = ConvertCompressed1DToFile|Levels, /** - * Convert a set of compressed 3D image levels to a file with - * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView). - * Implies @ref ImageConverterFeature::ConvertCompressed3DToFile. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::ConvertCompressed2DToFile together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertCompressedLevels3DToFile = ConvertCompressed3DToFile|(1 << 14), + ConvertCompressedLevels2DToFile CORRADE_DEPRECATED_ENUM("use ConvertCompressed2DToFile together with Levels instead") = ConvertCompressed2DToFile|Levels, /** - * Convert a set of 1D image levels to raw data with - * @ref AbstractImageConverter::convertToData(Containers::ArrayView). - * Implies @ref ImageConverterFeature::ConvertLevels1DToFile and - * @relativeref{ImageConverterFeature,Convert1DToData}, which implies also - * @relativeref{ImageConverterFeature,Convert1DToFile}. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::ConvertCompressed3DToFile together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertLevels1DToData = ConvertLevels1DToFile|Convert1DToData|(1 << 14), + ConvertCompressedLevels3DToFile CORRADE_DEPRECATED_ENUM("use ConvertCompressed3DToFile together with Levels instead") = ConvertCompressed3DToFile|Levels, /** - * Convert a set of 2D image levels to raw data with - * @ref AbstractImageConverter::convertToData(Containers::ArrayView). - * Implies @ref ImageConverterFeature::ConvertLevels2DToFile and - * @relativeref{ImageConverterFeature,Convert2DToData}, which implies also - * @relativeref{ImageConverterFeature,Convert2DToFile}. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::Convert1DToData together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertLevels2DToData = ConvertLevels2DToFile|Convert2DToData|(1 << 14), + ConvertLevels1DToData CORRADE_DEPRECATED_ENUM("use Convert1DToData together with Levels instead") = Convert1DToData|Levels, /** - * Convert a set of 3D image levels to raw data with - * @ref AbstractImageConverter::convertToData(Containers::ArrayView). - * Implies @ref ImageConverterFeature::ConvertLevels3DToFile and - * @relativeref{ImageConverterFeature,Convert3DToData}, which implies also - * @relativeref{ImageConverterFeature,Convert3DToFile}. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::Convert2DToData together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertLevels3DToData = ConvertLevels3DToFile|Convert3DToData|(1 << 14), + ConvertLevels2DToData CORRADE_DEPRECATED_ENUM("use Convert2DToData together with Levels instead") = Convert2DToData|Levels, /** - * Convert a set of compressed 1D image levels to raw data with - * @ref AbstractImageConverter::convertToData(Containers::ArrayView). - * Implies @ref ImageConverterFeature::ConvertCompressedLevels1DToFile and - * @relativeref{ImageConverterFeature,ConvertCompressed1DToData}, which - * implies also @relativeref{ImageConverterFeature,ConvertCompressed1DToFile}. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::Convert3DToData together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertCompressedLevels1DToData = ConvertCompressedLevels1DToFile|ConvertCompressed1DToData|(1 << 14), + ConvertLevels3DToData CORRADE_DEPRECATED_ENUM("use Convert3DToData together with Levels instead") = Convert3DToData|Levels, /** - * Convert a set of compressed 2D image levels to raw data with - * @ref AbstractImageConverter::convertToData(Containers::ArrayView). - * Implies @ref ImageConverterFeature::ConvertCompressedLevels2DToFile and - * @relativeref{ImageConverterFeature,ConvertCompressed2DToData}, which - * implies also @relativeref{ImageConverterFeature,ConvertCompressed2DToFile}. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::ConvertCompressed1DToData together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertCompressedLevels2DToData = ConvertCompressedLevels2DToFile|ConvertCompressed2DToData|(1 << 14), + ConvertCompressedLevels1DToData CORRADE_DEPRECATED_ENUM("use ConvertCompressed1DToData together with Levels instead") = ConvertCompressed1DToData|Levels, /** - * Convert a set of compressed 3D image levels to raw data with - * @ref AbstractImageConverter::convertToData(Containers::ArrayView). - * Implies @ref ImageConverterFeature::ConvertCompressedLevels3DToFile and - * @relativeref{ImageConverterFeature,ConvertCompressed3DToData}, which - * implies also @relativeref{ImageConverterFeature,ConvertCompressed3DToFile}. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::ConvertCompressed2DToData together with + * @relativeref{ImageConverterFeature,Levels} instead. + */ + ConvertCompressedLevels2DToData CORRADE_DEPRECATED_ENUM("use ConvertCompressed2DToData together with Levels instead") = ConvertCompressed2DToData|Levels, + + /** + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::ConvertCompressed3DToData together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertCompressedLevels3DToData = ConvertCompressedLevels3DToFile|ConvertCompressed3DToData|(1 << 14) + ConvertCompressedLevels3DToData CORRADE_DEPRECATED_ENUM("use ConvertCompressed3DToData together with Levels instead") = ConvertCompressed3DToData|Levels + #endif }; /** @@ -454,20 +468,9 @@ commonly advertising support for a subset of them via @ref features(): - Saving a set of (compressed) 1D/2D/3D image levels to a file / data using @ref convertToFile() / @ref convertToData(). Common use case is to save already pregenerated levels instead of having to create them during load. - Advertised with @ref ImageConverterFeature::ConvertLevels1DToFile / - @relativeref{ImageConverterFeature,ConvertLevels2DToFile} / - @relativeref{ImageConverterFeature,ConvertLevels3DToFile} or - @ref ImageConverterFeature::ConvertLevels1DToData / - @relativeref{ImageConverterFeature,ConvertLevels2DToData} / - @relativeref{ImageConverterFeature,ConvertLevels3DToData} and - @ref ImageConverterFeature::ConvertCompressedLevels1DToFile / - @relativeref{ImageConverterFeature,ConvertCompressedLevels2DToFile} / - @relativeref{ImageConverterFeature,ConvertCompressedLevels3DToFile} or - @ref ImageConverterFeature::ConvertCompressedLevels1DToData - @relativeref{ImageConverterFeature,ConvertCompressedLevels2DToData} / - @relativeref{ImageConverterFeature,ConvertCompressedLevels3DToData} for - compressed input images. Note that if a plugin advertises those, it's also - capable of saving single images --- in that case the single-image + Advertised with @ref ImageConverterFeature::Levels in addition to the + single-image feature. Note that if a plugin advertises multi-level support, + it's also capable of saving single images --- in that case the single-image @ref convertToFile() / @ref convertToData() delegates to the multi-level variant with just a single image. - Performing an operation on the image data itself using @ref convert(), from @@ -576,39 +579,9 @@ checked by the implementation: if @ref ImageConverterFeature::Convert2DToData "ImageConverterFeature::Convert*ToData" or @ref ImageConverterFeature::Convert2DToFile "Convert*ToFile" is supported -- The @ref doConvert(const ImageView2D&) function is called only if - @ref ImageConverterFeature::Convert2D is supported; equivalently for the - 1D and 3D case. -- The @ref doConvert(const CompressedImageView2D&) function is called only if - @ref ImageConverterFeature::ConvertCompressed2D is supported; equivalently - for the 1D and 3D case. -- The @ref doConvertToData(const ImageView2D&) function is called only if - @ref ImageConverterFeature::Convert2DToData is supported; equivalently - for the 1D and 3D case. -- The @ref doConvertToData(Containers::ArrayView) function - is called only if @ref ImageConverterFeature::ConvertLevels2DToData is - supported; equivalently for the 1D and 3D case. -- The @ref doConvertToData(const CompressedImageView2D&) function is called - only if @ref ImageConverterFeature::ConvertCompressed2DToData is supported; - equivalently for the 1D and 3D case. -- The @ref doConvertToData(Containers::ArrayView) - function is called only if - @ref ImageConverterFeature::ConvertCompressedLevels2DToData is supported; - equivalently for the 1D and 3D case. -- The @ref doConvertToFile(const ImageView2D&, Containers::StringView) - function is called only if @ref ImageConverterFeature::Convert2DToFile is - supported; equivalently for the 1D and 3D case. -- The @ref doConvertToFile(Containers::ArrayView, Containers::StringView) - function is called only if @ref ImageConverterFeature::ConvertLevels2DToFile - is supported; equivalently for the 1D and 3D case. -- The @ref doConvertToFile(const CompressedImageView2D&, Containers::StringView) - function is called only if - @ref ImageConverterFeature::ConvertCompressed2DToFile is supported; - equivalently for the 1D and 3D case. -- The @ref doConvertToFile(Containers::ArrayView, Containers::StringView) - function is called only if - @ref ImageConverterFeature::ConvertCompressedLevels2DToFile is supported; - equivalently for the 1D and 3D case. +- The @ref doConvert(), @ref doConvertToData() and @ref doConvertToFile() + functions are called only if a corresponding @ref ImageConverterFeature is + supported - All @ref doConvertToData() and @ref doConvertToFile() functions taking a single (compressed) image are called only if the image has a non-zero size in all dimensions and the view is not @cpp nullptr @ce. Note that this does @@ -918,10 +891,9 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a 1D image to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::Convert1DToData or - * @ref ImageConverterFeature::ConvertLevels1DToData is supported. The - * image view is expected to not be @cpp nullptr @ce and to have a - * non-zero size. On failure prints a message to + * Available only if @ref ImageConverterFeature::Convert1DToData is + * supported. The image view is expected to not be @cpp nullptr @ce and + * to have a non-zero size. On failure prints a message to * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(const CompressedImageView1D&), * @ref convertToData(const ImageData1D&), @ref convert(), @@ -938,11 +910,11 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a 2D image to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::Convert2DToData or - * @ref ImageConverterFeature::ConvertLevels2DToData is supported. The - * image view is expected to not be @cpp nullptr @ce and to have a - * non-zero size in all dimensions. On failure prints a message to - * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. + * Available only if @ref ImageConverterFeature::Convert2DToData is + * supported. The image view is expected to not be @cpp nullptr @ce and + * to have a non-zero size in all dimensions. On failure prints a + * message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(const CompressedImageView2D&), * @ref convertToData(const ImageData2D&), @ref convert(), * @ref convertToFile() @@ -967,11 +939,11 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a 3D image to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::Convert3DToData or - * @ref ImageConverterFeature::ConvertLevels3DToData is supported. The - * image view is expected to not be @cpp nullptr @ce and to have a - * non-zero size in all dimensions. On failure prints a message to - * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. + * Available only if @ref ImageConverterFeature::Convert3DToData is + * supported. The image view is expected to not be @cpp nullptr @ce and + * to have a non-zero size in all dimensions. On failure prints a + * message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(const CompressedImageView3D&), * @ref convertToData(const ImageData3D&), @ref convert(), * @ref convertToFile() @@ -988,9 +960,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @m_since_latest * * Available only if @ref ImageConverterFeature::ConvertCompressed1DToData - * or @ref ImageConverterFeature::ConvertCompressedLevels1DToData is - * supported. The image view is expected to not be @cpp nullptr @ce and - * to have a non-zero size. On failure prints a message to + * is supported. The image view is expected to not be @cpp nullptr @ce + * and to have a non-zero size. On failure prints a message to * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(const ImageView1D&), * @ref convertToData(const ImageData1D&), @ref convert(), @@ -1008,9 +979,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @m_since_latest * * Available only if @ref ImageConverterFeature::ConvertCompressed2DToData - * or @ref ImageConverterFeature::ConvertCompressedLevels2DToData is - * supported. The image view is expected to not be @cpp nullptr @ce and - * to have a non-zero size in all dimensions. On failure prints a + * is supported. The image view is expected to not be @cpp nullptr @ce + * and to have a non-zero size in all dimensions. On failure prints a * message to @relativeref{Magnum,Error} and returns * @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(const ImageView2D&), @@ -1038,9 +1008,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @m_since_latest * * Available only if @ref ImageConverterFeature::ConvertCompressed3DToData - * or @ref ImageConverterFeature::ConvertCompressedLevels3DToData is - * supported. The image view is expected to not be @cpp nullptr @ce and - * to have a non-zero size in all dimensions. On failure prints a + * is supported. The image view is expected to not be @cpp nullptr @ce + * and to have a non-zero size in all dimensions. On failure prints a * message to @relativeref{Magnum,Error} and returns * @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(const ImageView3D&), @@ -1118,14 +1087,15 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of 1D image levels to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertLevels1DToData - * is supported. The function expects at least one image to be passed, - * with each view expected to not be @cpp nullptr @ce, to have a - * non-zero size, and all of them sharing the same pixel format and - * layout flags. Note that certain converters may impose additional - * size and order restrictions on the images, see documentation of a - * particular plugin for more information. On failure prints a message - * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,Convert1DToData} is supported. + * The function expects at least one image to be passed, with each view + * expected to not be @cpp nullptr @ce, to have a non-zero size, and + * all of them sharing the same pixel format and layout flags. Note + * that certain converters may impose additional size and order + * restrictions on the images, see documentation of a particular plugin + * for more information. On failure prints a message to + * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(Containers::ArrayView), * @ref convert(), @ref convertToFile() */ @@ -1150,15 +1120,15 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of 2D image levels to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertLevels2DToData - * is supported. The function expects at least one image to be passed, - * with each view expected to not be @cpp nullptr @ce, to have a - * non-zero size in all dimensions, and all views sharing the same - * pixel format and layout flags. Note that certain converters may - * impose additional size and order restrictions on the images, see - * documentation of a particular plugin for more information. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @ref Containers::NullOpt. + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,Convert2DToData} is supported. + * The function expects at least one image to be passed, with each view + * expected to not be @cpp nullptr @ce, to have a non-zero size in all + * dimensions, and all views sharing the same pixel format and layout + * flags. Note that certain converters may impose additional size and + * order restrictions on the images, see documentation of a particular + * plugin for more information. On failure prints a message to + * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(Containers::ArrayView), * @ref convert(), @ref convertToFile() */ @@ -1183,15 +1153,15 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of 3D image levels to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertLevels3DToData - * is supported. The function expects at least one image to be passed, - * with each view expected to not be @cpp nullptr @ce, to have a - * non-zero size in all dimensions, and all views sharing the same - * pixel format and layout flags. Note that certain converters may - * impose additional size and order restrictions on the images, see - * documentation of a particular plugin for more information. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @ref Containers::NullOpt. + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,Convert3DToData} is supported. + * The function expects at least one image to be passed, with each view + * expected to not be @cpp nullptr @ce, to have a non-zero size in all + * dimensions, and all views sharing the same pixel format and layout + * flags. Note that certain converters may impose additional size and + * order restrictions on the images, see documentation of a particular + * plugin for more information. On failure prints a message to + * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(Containers::ArrayView), * @ref convert(), @ref convertToFile() */ @@ -1216,8 +1186,9 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of compressed 1D image levels to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertCompressedLevels1DToData - * is supported. The function expects at least one image to be passed, + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,ConvertCompressed1DToData} is + * supported. The function expects at least one image to be passed, * with each view expected to not be @cpp nullptr @ce, to have a * non-zero size, and all views sharing the same pixel format and * layout flags. Note that certain converters may impose additional @@ -1248,8 +1219,9 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of compressed 2D image levels to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertCompressedLevels2DToData - * is supported. The function expects at least one image to be passed, + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,ConvertCompressed2DToData} is + * supported. The function expects at least one image to be passed, * with each view expected to not be @cpp nullptr @ce, to have a * non-zero size in all dimensions, and all views sharing the same * pixel format and layout flags. Note that certain converters may @@ -1281,8 +1253,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of compressed 3D image levels to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertCompressedLevels3DToData - * is supported. The function expects at least one image to be passed, + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,ConvertCompressed3DToData} is supported. The function expects at least one image to be passed, * with each view expected to not be @cpp nullptr @ce, to have a * non-zero size in all dimensions, and all views sharing the same * pixel format and layout flags. Note that certain converters may @@ -1314,11 +1286,10 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a 1D image to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::Convert1DToFile or - * @ref ImageConverterFeature::Convert1DToData is supported. The image - * view is expected to not be @cpp nullptr @ce and to have a non-zero - * size. On failure prints a message to @relativeref{Magnum,Error} and - * returns @cpp false @ce. + * Available only if @ref ImageConverterFeature::Convert1DToFile is + * supported. The image view is expected to not be @cpp nullptr @ce and + * to have a non-zero size. On failure prints a message to + * @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(const CompressedImageView1D&, Containers::StringView), * @ref convertToFile(const ImageData1D&, Containers::StringView), * @ref convert(), @ref convertToData() @@ -1329,11 +1300,10 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a 2D image to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::Convert2DToFile or - * @ref ImageConverterFeature::Convert2DToData is supported. The image - * view is expected to not be @cpp nullptr @ce and to have a non-zero - * size in all dimensions. Returns @cpp true @ce on success, - * @cpp false @ce otherwise. + * Available only if @ref ImageConverterFeature::Convert2DToFile is + * supported. The image view is expected to not be @cpp nullptr @ce and + * to have a non-zero size in all dimensions. Returns @cpp true @ce on + * success, @cpp false @ce otherwise. * @see @ref features(), @ref convertToFile(const CompressedImageView2D&, Containers::StringView), * @ref convertToFile(const ImageData2D&, Containers::StringView), * @ref convert(), @ref convertToData() @@ -1354,11 +1324,10 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a 3D image to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::Convert3DToFile or - * @ref ImageConverterFeature::Convert3DToData is supported. The image - * view is expected to not be @cpp nullptr @ce and to have a non-zero - * size. On failure prints a message to @relativeref{Magnum,Error} and - * returns @cpp false @ce. + * Available only if @ref ImageConverterFeature::Convert3DToFile is + * supported. The image view is expected to not be @cpp nullptr @ce and + * to have a non-zero size. On failure prints a message to + * @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(const CompressedImageView3D&, Containers::StringView), * @ref convertToFile(const ImageData3D&, Containers::StringView), * @ref convert(), @ref convertToData() @@ -1370,9 +1339,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @m_since_latest * * Available only if @ref ImageConverterFeature::ConvertCompressed1DToFile - * or @ref ImageConverterFeature::ConvertCompressed1DToData is - * supported. The image view is expected to not be @cpp nullptr @ce and - * to have a non-zero size in all dimensions. On failure prints a + * is supported. The image view is expected to not be @cpp nullptr @ce + * and to have a non-zero size in all dimensions. On failure prints a * message to @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(const ImageView1D&, Containers::StringView), * @ref convertToFile(const ImageData1D&, Containers::StringView), @@ -1385,9 +1353,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @m_since_latest * * Available only if @ref ImageConverterFeature::ConvertCompressed2DToFile - * or @ref ImageConverterFeature::ConvertCompressed2DToData is - * supported. The image view is expected to not be @cpp nullptr @ce and - * to have a non-zero size in all dimensions. On failure prints a + * is supported. The image view is expected to not be @cpp nullptr @ce + * and to have a non-zero size in all dimensions. On failure prints a * message to @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(const ImageView2D&, Containers::StringView), * @ref convertToFile(const ImageData2D&, Containers::StringView), @@ -1410,9 +1377,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @m_since_latest * * Available only if @ref ImageConverterFeature::ConvertCompressed3DToFile - * or @ref ImageConverterFeature::ConvertCompressed3DToData is - * supported. The image view is expected to not be @cpp nullptr @ce and - * to have a non-zero size in all dimensions. On failure prints a + * is supported. The image view is expected to not be @cpp nullptr @ce + * and to have a non-zero size in all dimensions. On failure prints a * message to @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(const ImageView3D&, Containers::StringView), * @ref convertToFile(const ImageData3D&, Containers::StringView), @@ -1482,13 +1448,14 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of 1D image levels to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertLevels1DToFile - * is supported. The function expects at least one image to be passed, - * with each view expected to not be @cpp nullptr @ce, to have a - * non-zero size, and all views sharing the same pixel format. Note - * that certain converters may impose additional size and order - * restrictions on the images, see documentation of a particular plugin - * for more information. On failure prints a message to + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,Convert1DToFile} is supported. + * The function expects at least one image to be passed, with each view + * expected to not be @cpp nullptr @ce, to have a non-zero size, and + * all views sharing the same pixel format and layout flags. Note that + * certain converters may impose additional size and order restrictions + * on the images, see documentation of a particular plugin for more + * information. On failure prints a message to * @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(Containers::ArrayView, Containers::StringView), * @ref convert(), @ref convertToData() @@ -1504,15 +1471,15 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of 2D image levels to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertLevels2DToFile - * is supported. The function expects at least one image to be passed, - * with each view expected to not be @cpp nullptr @ce, to have a - * non-zero size in all dimensions, and all views sharing the same - * pixel format and layout flags. Note that certain converters may - * impose additional size and order restrictions on the images, see - * documentation of a particular plugin for more information. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @cpp false @ce. + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,Convert2DToFile} is supported. + * The function expects at least one image to be passed, with each view + * expected to not be @cpp nullptr @ce, to have a non-zero size in all + * dimensions, and all views sharing the same pixel format and layout + * flags. Note that certain converters may impose additional size and + * order restrictions on the images, see documentation of a particular + * plugin for more information. On failure prints a message to + * @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(Containers::ArrayView, Containers::StringView), * @ref convert(), @ref convertToData() */ @@ -1527,15 +1494,15 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of 3D image levels to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertLevels3DToFile - * is supported. The function expects at least one image to be passed, - * with each view expected to not be @cpp nullptr @ce, to have a - * non-zero size in all dimensions, and all views sharing the same - * pixel format and layout flags. Note that certain converters may - * impose additional size and order restrictions on the images, see - * documentation of a particular plugin for more information. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @cpp false @ce. + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,Convert3DToFile} is supported. + * The function expects at least one image to be passed, with each view + * expected to not be @cpp nullptr @ce, to have a non-zero size in all + * dimensions, and all views sharing the same pixel format and layout + * flags. Note that certain converters may impose additional size and + * order restrictions on the images, see documentation of a particular + * plugin for more information. On failure prints a message to + * @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(Containers::ArrayView, Containers::StringView), * @ref convert(), @ref convertToData() */ @@ -1550,8 +1517,9 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of compressed 1D image levels to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertCompressedLevels1DToFile - * is supported. The function expects at least one image to be passed, + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,ConvertCompressed1DToFile} is + * supported. The function expects at least one image to be passed, * with each view expected to not be @cpp nullptr @ce, to have a * non-zero size, and all views sharing the same pixel format and * layout flags. Note that certain converters may impose additional @@ -1572,8 +1540,9 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of compressed 2D image levels to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertCompressedLevels2DToFile - * is supported. The function expects at least one image to be passed, + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,ConvertCompressed2DToFile} is + * supported. The function expects at least one image to be passed, * with each view expected to not be @cpp nullptr @ce, to have a * non-zero size in all dimensions, and all views sharing the same * pixel format and layout flags. Note that certain converters may @@ -1595,8 +1564,9 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of compressed 3D image levels to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertCompressedLevels3DToFile - * is supported. The function expects at least one image to be passed, + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,ConvertCompressed3DToFile} is + * supported. The function expects at least one image to be passed, * with each view expected to not be @cpp nullptr @ce, to have a * non-zero size in all dimensions, and all views sharing the same * pixel format and layout flags. Note that certain converters may @@ -1619,9 +1589,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(const ImageView1D&, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels1DToFile is supported, - * default implementation calls - * @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToFile(Containers::ArrayView, Containers::StringView) * with just the single @p image. Otherwise, if * @ref ImageConverterFeature::Convert1DToData is supported, default * implementation calls @ref doConvertToData(const ImageView1D&) and @@ -1635,9 +1604,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(const ImageView2D&, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels2DToFile is supported, - * default implementation calls - * @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToFile(Containers::ArrayView, Containers::StringView) * with just the single @p image. Otherwise, if * @ref ImageConverterFeature::Convert2DToData is supported, default * implementation calls @ref doConvertToData(const ImageView2D&) and @@ -1651,9 +1619,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(const ImageView3D&, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels3DToFile is supported, - * default implementation calls - * @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToFile(Containers::ArrayView, Containers::StringView) * with just the single @p image. Otherwise, if * @ref ImageConverterFeature::Convert3DToData is supported, default * implementation calls @ref doConvertToData(const ImageView3D&) and @@ -1667,9 +1634,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(const CompressedImageView1D&, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels1DToFile is - * supported, default implementation calls - * @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToFile(Containers::ArrayView, Containers::StringView) * with just the single @p image. Otherwise, if * @ref ImageConverterFeature::ConvertCompressed1DToData is supported, * default implementation calls @ref doConvertToData(const CompressedImageView1D&) @@ -1684,9 +1650,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(const CompressedImageView2D&, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels2DToFile is - * supported, default implementation calls - * @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToFile(Containers::ArrayView, Containers::StringView) * with just the single @p image. Otherwise, if * @ref ImageConverterFeature::ConvertCompressed2DToData is supported, * default implementation calls @ref doConvertToData(const CompressedImageView2D&) @@ -1701,9 +1666,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(const CompressedImageView3D&, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels3DToFile is - * supported, default implementation calls - * @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToFile(Containers::ArrayView, Containers::StringView) * with just the single @p image. Otherwise, if * @ref ImageConverterFeature::ConvertCompressed3DToData is supported, * default implementation calls @ref doConvertToData(const CompressedImageView3D&) @@ -1718,12 +1682,12 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(Containers::ArrayView, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels1DToData is supported, - * default implementation calls - * @ref doConvertToData(Containers::ArrayView) and - * saves the result to given file. It is allowed to call this function - * from your @ref doConvertToFile() implementation, for example when - * you only need to do format detection based on file extension. + * If @ref ImageConverterFeature::Convert1DToData is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) + * and saves the result to given file. It is allowed to call this + * function from your @ref doConvertToFile() implementation, for + * example when you only need to do format detection based on file + * extension. */ virtual bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename); @@ -1731,12 +1695,12 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(Containers::ArrayView, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels1DToData is supported, - * default implementation calls - * @ref doConvertToData(Containers::ArrayView) and - * saves the result to given file. It is allowed to call this function - * from your @ref doConvertToFile() implementation, for example when - * you only need to do format detection based on file extension. + * If @ref ImageConverterFeature::Convert2DToData is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) + * and saves the result to given file. It is allowed to call this + * function from your @ref doConvertToFile() implementation, for + * example when you only need to do format detection based on file + * extension. */ virtual bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename); @@ -1744,12 +1708,12 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(Containers::ArrayView, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels1DToData is supported, - * default implementation calls - * @ref doConvertToData(Containers::ArrayView) and - * saves the result to given file. It is allowed to call this function - * from your @ref doConvertToFile() implementation, for example when - * you only need to do format detection based on file extension. + * If @ref ImageConverterFeature::Convert3DToData is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) + * and saves the result to given file. It is allowed to call this + * function from your @ref doConvertToFile() implementation, for + * example when you only need to do format detection based on file + * extension. */ virtual bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename); @@ -1757,7 +1721,7 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(Containers::ArrayView, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels1DToData is + * If @ref ImageConverterFeature::ConvertCompressed1DToData is * supported, default implementation calls * @ref doConvertToData(Containers::ArrayView) * and saves the result to given file. It is allowed to call this @@ -1771,7 +1735,7 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(Containers::ArrayView, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels2DToData is + * If @ref ImageConverterFeature::ConvertCompressed2DToData is * supported, default implementation calls @ref doConvertToData(Containers::ArrayView) * and saves the result to given file. It is allowed to call this * function from your @ref doConvertToFile() implementation, for @@ -1784,7 +1748,7 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(Containers::ArrayView, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels3DToData is + * If @ref ImageConverterFeature::ConvertCompressed3DToData is * supported, default implementation calls @ref doConvertToData(Containers::ArrayView) * and saves the result to given file. It is allowed to call this * function from your @ref doConvertToFile() implementation, for @@ -1869,8 +1833,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToData(const ImageView1D&) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels1DToData is supported, - * default implementation calls @ref doConvertToData(Containers::ArrayView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) * with just the single @p image and propagates the result back. */ virtual Containers::Optional> doConvertToData(const ImageView1D& image); @@ -1879,8 +1843,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToData(const ImageView2D&) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels2DToData is supported, - * default implementation calls @ref doConvertToData(Containers::ArrayView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) * with just the single @p image and propagates the result back. */ virtual Containers::Optional> doConvertToData(const ImageView2D& image); @@ -1889,8 +1853,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToData(const ImageView3D&) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels3DToData is supported, - * default implementation calls @ref doConvertToData(Containers::ArrayView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) * with just the single @p image and propagates the result back. */ virtual Containers::Optional> doConvertToData(const ImageView3D& image); @@ -1899,9 +1863,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToData(const CompressedImageView1D&) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels1DToData is - * supported, default implementation calls - * @ref doConvertToData(Containers::ArrayView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) * with just the single @p image and propagates the result back. */ virtual Containers::Optional> doConvertToData(const CompressedImageView1D& image); @@ -1910,9 +1873,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToData(const CompressedImageView2D&) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels2DToData is - * supported, default implementation calls - * @ref doConvertToData(Containers::ArrayView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) * with just the single @p image and propagates the result back. */ virtual Containers::Optional> doConvertToData(const CompressedImageView2D& image); @@ -1921,9 +1883,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToData(const CompressedImageView3D&) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels3DToData is - * supported, default implementation calls - * @ref doConvertToData(Containers::ArrayView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) * with just the single @p image and propagates the result back. */ virtual Containers::Optional> doConvertToData(const CompressedImageView3D& image); diff --git a/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp b/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp index 1c6354b46..6b7b07739 100644 --- a/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp @@ -296,6 +296,9 @@ struct AbstractImageConverterTest: TestSuite::Tester { void convertCompressed3DToFileThroughLevels(); void debugFeature(); + #ifdef MAGNUM_BUILD_DEPRECATED + void debugFeatureDeprecated(); + #endif void debugFeatures(); void debugFeaturesSupersets(); void debugFlag(); @@ -532,6 +535,9 @@ AbstractImageConverterTest::AbstractImageConverterTest() { &AbstractImageConverterTest::convertCompressed3DToFileThroughLevels, &AbstractImageConverterTest::debugFeature, + #ifdef MAGNUM_BUILD_DEPRECATED + &AbstractImageConverterTest::debugFeatureDeprecated, + #endif &AbstractImageConverterTest::debugFeatures, &AbstractImageConverterTest::debugFeaturesSupersets, &AbstractImageConverterTest::debugFlag, @@ -677,7 +683,7 @@ void AbstractImageConverterTest::thingNotSupported() { void AbstractImageConverterTest::extensionMimeType() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels3DToData; + return ImageConverterFeature::ConvertCompressed3DToData; } Containers::String doExtension() const override { return "yello"; } Containers::String doMimeType() const override { return "yel/low"; } @@ -1766,7 +1772,10 @@ void AbstractImageConverterTest::convertImageData3DToData() { void AbstractImageConverterTest::convertLevels1DToData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -1785,7 +1794,10 @@ void AbstractImageConverterTest::convertLevels1DToData() { void AbstractImageConverterTest::convertLevels2DToData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -1804,7 +1816,10 @@ void AbstractImageConverterTest::convertLevels2DToData() { void AbstractImageConverterTest::convertLevels3DToData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -1824,7 +1839,8 @@ void AbstractImageConverterTest::convertLevels3DToData() { void AbstractImageConverterTest::convertLevels1DToDataFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertLevels1DToData; + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -1843,7 +1859,8 @@ void AbstractImageConverterTest::convertLevels1DToDataFailed() { void AbstractImageConverterTest::convertLevels2DToDataFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertLevels2DToData; + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -1862,7 +1879,8 @@ void AbstractImageConverterTest::convertLevels2DToDataFailed() { void AbstractImageConverterTest::convertLevels3DToDataFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertLevels3DToData; + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -1882,7 +1900,10 @@ void AbstractImageConverterTest::convertLevels1DToDataInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -1895,7 +1916,10 @@ void AbstractImageConverterTest::convertLevels2DToDataNoLevels() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -1908,7 +1932,10 @@ void AbstractImageConverterTest::convertLevels2DToDataZeroSize() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -1925,7 +1952,10 @@ void AbstractImageConverterTest::convertLevels2DToDataNullptr() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -1942,7 +1972,10 @@ void AbstractImageConverterTest::convertLevels2DToDataInconsistentFormat() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -1960,7 +1993,10 @@ void AbstractImageConverterTest::convertLevels2DToDataInconsistentFormatExtra() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -1978,7 +2014,10 @@ void AbstractImageConverterTest::convertLevels2DToDataInconsistentFlags() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -1996,7 +2035,10 @@ void AbstractImageConverterTest::convertLevels3DToDataInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -2009,7 +2051,10 @@ void AbstractImageConverterTest::convertLevels1DToDataNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } } converter; const char data[4]{}; @@ -2023,7 +2068,10 @@ void AbstractImageConverterTest::convertLevels2DToDataNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[4]{}; @@ -2037,7 +2085,10 @@ void AbstractImageConverterTest::convertLevels3DToDataNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } } converter; const char data[4]{}; @@ -2051,7 +2102,10 @@ void AbstractImageConverterTest::convertLevels1DToDataCustomDeleter() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::Array{nullptr, 0, [](char*, std::size_t) {}}; } @@ -2068,7 +2122,10 @@ void AbstractImageConverterTest::convertLevels2DToDataCustomDeleter() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::Array{nullptr, 0, [](char*, std::size_t) {}}; } @@ -2085,7 +2142,10 @@ void AbstractImageConverterTest::convertLevels3DToDataCustomDeleter() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::Array{nullptr, 0, [](char*, std::size_t) {}}; } @@ -2100,7 +2160,10 @@ void AbstractImageConverterTest::convertLevels3DToDataCustomDeleter() { void AbstractImageConverterTest::convertCompressedLevels1DToData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2119,7 +2182,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToData() { void AbstractImageConverterTest::convertCompressedLevels2DToData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2138,7 +2204,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToData() { void AbstractImageConverterTest::convertCompressedLevels3DToData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2158,7 +2227,8 @@ void AbstractImageConverterTest::convertCompressedLevels3DToData() { void AbstractImageConverterTest::convertCompressedLevels1DToDataFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels1DToData; + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -2177,7 +2247,8 @@ void AbstractImageConverterTest::convertCompressedLevels1DToDataFailed() { void AbstractImageConverterTest::convertCompressedLevels2DToDataFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels2DToData; + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -2196,7 +2267,8 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataFailed() { void AbstractImageConverterTest::convertCompressedLevels3DToDataFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels3DToData; + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -2216,7 +2288,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToDataInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -2229,7 +2304,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataNoLevels() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -2242,7 +2320,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataZeroSize() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -2259,7 +2340,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataNullptr() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -2276,7 +2360,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataInconsistentForm CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -2294,7 +2381,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataInconsistentFlag CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -2312,7 +2402,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToDataInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -2325,7 +2418,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToDataNotImplemented() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } } converter; const char data[8]{}; @@ -2339,7 +2435,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataNotImplemented() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[8]{}; @@ -2353,7 +2452,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToDataNotImplemented() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } } converter; const char data[8]{}; @@ -2367,7 +2469,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToDataCustomDeleter() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::Array{nullptr, 0, [](char*, std::size_t) {}}; } @@ -2384,7 +2489,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataCustomDeleter() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::Array{nullptr, 0, [](char*, std::size_t) {}}; } @@ -2401,7 +2509,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToDataCustomDeleter() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::Array{nullptr, 0, [](char*, std::size_t) {}}; } @@ -2416,7 +2527,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToDataCustomDeleter() void AbstractImageConverterTest::convert1DToDataThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2430,7 +2544,10 @@ void AbstractImageConverterTest::convert1DToDataThroughLevels() { void AbstractImageConverterTest::convert2DToDataThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2444,7 +2561,10 @@ void AbstractImageConverterTest::convert2DToDataThroughLevels() { void AbstractImageConverterTest::convert3DToDataThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2458,7 +2578,10 @@ void AbstractImageConverterTest::convert3DToDataThroughLevels() { void AbstractImageConverterTest::convertCompressed1DToDataThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2472,7 +2595,10 @@ void AbstractImageConverterTest::convertCompressed1DToDataThroughLevels() { void AbstractImageConverterTest::convertCompressed2DToDataThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2486,7 +2612,10 @@ void AbstractImageConverterTest::convertCompressed2DToDataThroughLevels() { void AbstractImageConverterTest::convertCompressed3DToDataThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -3310,7 +3439,10 @@ void AbstractImageConverterTest::convertImageData3DToFile() { void AbstractImageConverterTest::convertLevels1DToFile() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size()[0]), char(imageLevels.size())})); @@ -3334,7 +3466,10 @@ void AbstractImageConverterTest::convertLevels1DToFile() { void AbstractImageConverterTest::convertLevels2DToFile() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels.size())})); @@ -3358,7 +3493,10 @@ void AbstractImageConverterTest::convertLevels2DToFile() { void AbstractImageConverterTest::convertLevels3DToFile() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels[0].size().z()), char(imageLevels.size())})); @@ -3383,7 +3521,8 @@ void AbstractImageConverterTest::convertLevels3DToFile() { void AbstractImageConverterTest::convertLevels1DToFileFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertLevels1DToFile; + return ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Levels; } bool doConvertToFile(Containers::ArrayView, Containers::StringView) override { return {}; @@ -3402,7 +3541,8 @@ void AbstractImageConverterTest::convertLevels1DToFileFailed() { void AbstractImageConverterTest::convertLevels2DToFileFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertLevels2DToFile; + return ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Levels; } bool doConvertToFile(Containers::ArrayView, Containers::StringView) override { return {}; @@ -3421,7 +3561,8 @@ void AbstractImageConverterTest::convertLevels2DToFileFailed() { void AbstractImageConverterTest::convertLevels3DToFileFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertLevels3DToFile; + return ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::Levels; } bool doConvertToFile(Containers::ArrayView, Containers::StringView) override { return {}; @@ -3439,7 +3580,10 @@ void AbstractImageConverterTest::convertLevels3DToFileFailed() { void AbstractImageConverterTest::convertLevels1DToFileThroughData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::array({char(imageLevels[0].size()[0]), char(imageLevels.size())}); @@ -3465,7 +3609,10 @@ void AbstractImageConverterTest::convertLevels1DToFileThroughData() { void AbstractImageConverterTest::convertLevels2DToFileThroughData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::array({char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels.size())}); @@ -3490,7 +3637,10 @@ void AbstractImageConverterTest::convertLevels2DToFileThroughData() { void AbstractImageConverterTest::convertLevels3DToFileThroughData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::array({char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels[0].size().z()), char(imageLevels.size())}); @@ -3515,7 +3665,10 @@ void AbstractImageConverterTest::convertLevels3DToFileThroughData() { void AbstractImageConverterTest::convertLevels1DToFileThroughDataFailed() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -3540,7 +3693,10 @@ void AbstractImageConverterTest::convertLevels1DToFileThroughDataFailed() { void AbstractImageConverterTest::convertLevels2DToFileThroughDataFailed() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -3565,7 +3721,10 @@ void AbstractImageConverterTest::convertLevels2DToFileThroughDataFailed() { void AbstractImageConverterTest::convertLevels3DToFileThroughDataFailed() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -3590,7 +3749,10 @@ void AbstractImageConverterTest::convertLevels3DToFileThroughDataFailed() { void AbstractImageConverterTest::convertLevels1DToFileThroughDataNotWritable() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::array({'\x00'}); }; @@ -3607,7 +3769,10 @@ void AbstractImageConverterTest::convertLevels1DToFileThroughDataNotWritable() { void AbstractImageConverterTest::convertLevels2DToFileThroughDataNotWritable() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::array({'\x00'}); @@ -3625,7 +3790,10 @@ void AbstractImageConverterTest::convertLevels2DToFileThroughDataNotWritable() { void AbstractImageConverterTest::convertLevels3DToFileThroughDataNotWritable() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::array({'\x00'}); @@ -3645,7 +3813,10 @@ void AbstractImageConverterTest::convertLevels1DToFileInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -3658,7 +3829,10 @@ void AbstractImageConverterTest::convertLevels2DToFileInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -3671,7 +3845,10 @@ void AbstractImageConverterTest::convertLevels3DToFileInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -3684,7 +3861,10 @@ void AbstractImageConverterTest::convertLevels1DToFileNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Levels; + } } converter; const char data[4]{}; @@ -3698,7 +3878,10 @@ void AbstractImageConverterTest::convertLevels2DToFileNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Levels; + } } converter; const char data[4]{}; @@ -3712,7 +3895,10 @@ void AbstractImageConverterTest::convertLevels3DToFileNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::Levels; + } } converter; const char data[4]{}; @@ -3724,7 +3910,10 @@ void AbstractImageConverterTest::convertLevels3DToFileNotImplemented() { void AbstractImageConverterTest::convertCompressedLevels1DToFile() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size()[0]), char(imageLevels.size())})); @@ -3747,7 +3936,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFile() { void AbstractImageConverterTest::convertCompressedLevels2DToFile() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels.size())})); @@ -3770,7 +3962,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFile() { void AbstractImageConverterTest::convertCompressedLevels3DToFile() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels[0].size().z()), char(imageLevels.size())})); @@ -3794,7 +3989,8 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFile() { void AbstractImageConverterTest::convertCompressedLevels1DToFileFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels1DToFile; + return ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::Levels; } bool doConvertToFile(Containers::ArrayView, Containers::StringView) override { return {}; @@ -3813,7 +4009,8 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFileFailed() { void AbstractImageConverterTest::convertCompressedLevels2DToFileFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels2DToFile; + return ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::Levels; } bool doConvertToFile(Containers::ArrayView, Containers::StringView) override { return {}; @@ -3832,7 +4029,8 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFileFailed() { void AbstractImageConverterTest::convertCompressedLevels3DToFileFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels3DToFile; + return ImageConverterFeature::ConvertCompressed3DToFile| + ImageConverterFeature::Levels; } bool doConvertToFile(Containers::ArrayView, Containers::StringView) override { return {}; @@ -3850,7 +4048,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFileFailed() { void AbstractImageConverterTest::convertCompressedLevels1DToFileThroughData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::array({char(imageLevels[0].size()[0]), char(imageLevels.size())}); @@ -3875,7 +4076,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFileThroughData() { void AbstractImageConverterTest::convertCompressedLevels2DToFileThroughData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::array({char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels.size())}); @@ -3900,7 +4104,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFileThroughData() { void AbstractImageConverterTest::convertCompressedLevels3DToFileThroughData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::array({char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels[0].size().z()), char(imageLevels.size())}); @@ -3925,7 +4132,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFileThroughData() { void AbstractImageConverterTest::convertCompressedLevels1DToFileThroughDataFailed() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -3950,7 +4160,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFileThroughDataFaile void AbstractImageConverterTest::convertCompressedLevels2DToFileThroughDataFailed() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -3975,7 +4188,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFileThroughDataFaile void AbstractImageConverterTest::convertCompressedLevels3DToFileThroughDataFailed() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -4000,7 +4216,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFileThroughDataFaile void AbstractImageConverterTest::convertCompressedLevels1DToFileThroughDataNotWritable() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::array({'\x00'}); @@ -4018,7 +4237,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFileThroughDataNotWr void AbstractImageConverterTest::convertCompressedLevels2DToFileThroughDataNotWritable() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::array({'\x00'}); @@ -4036,7 +4258,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFileThroughDataNotWr void AbstractImageConverterTest::convertCompressedLevels3DToFileThroughDataNotWritable() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::array({'\x00'}); @@ -4056,7 +4281,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFileInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -4069,7 +4297,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFileInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -4082,7 +4313,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFileInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToFile| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -4095,7 +4329,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFileNotImplemented() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::Levels; + } } converter; const char data[8]{}; @@ -4109,7 +4346,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFileNotImplemented() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::Levels; + } } converter; const char data[8]{}; @@ -4123,7 +4363,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFileNotImplemented() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToFile| + ImageConverterFeature::Levels; + } } converter; const char data[8]{}; @@ -4135,7 +4378,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFileNotImplemented() void AbstractImageConverterTest::convert1DToFileThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size()[0]), char(imageLevels.size())})); @@ -4154,7 +4400,10 @@ void AbstractImageConverterTest::convert1DToFileThroughLevels() { void AbstractImageConverterTest::convert2DToFileThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels.size())})); @@ -4173,7 +4422,10 @@ void AbstractImageConverterTest::convert2DToFileThroughLevels() { void AbstractImageConverterTest::convert3DToFileThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels[0].size().z()), char(imageLevels.size())})); @@ -4192,7 +4444,10 @@ void AbstractImageConverterTest::convert3DToFileThroughLevels() { void AbstractImageConverterTest::convertCompressed1DToFileThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size()[0]), char(imageLevels.size())})); @@ -4211,7 +4466,10 @@ void AbstractImageConverterTest::convertCompressed1DToFileThroughLevels() { void AbstractImageConverterTest::convertCompressed2DToFileThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels.size())})); @@ -4230,7 +4488,10 @@ void AbstractImageConverterTest::convertCompressed2DToFileThroughLevels() { void AbstractImageConverterTest::convertCompressed3DToFileThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels[0].size().z()), char(imageLevels.size())})); @@ -4254,6 +4515,17 @@ void AbstractImageConverterTest::debugFeature() { CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertCompressed2D Trade::ImageConverterFeature(0xdeadbeef)\n"); } +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractImageConverterTest::debugFeatureDeprecated() { + std::ostringstream out; + + CORRADE_IGNORE_DEPRECATED_PUSH + Debug{&out} << ImageConverterFeature::ConvertCompressedLevels1DToData << ImageConverterFeature::ConvertLevels3DToFile; + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertCompressed1DToData|Trade::ImageConverterFeature::Levels Trade::ImageConverterFeature::Convert3DToFile|Trade::ImageConverterFeature::Levels\n"); +} +#endif + void AbstractImageConverterTest::debugFeatures() { std::ostringstream out; @@ -4275,34 +4547,6 @@ void AbstractImageConverterTest::debugFeaturesSupersets() { std::ostringstream out; Debug{&out} << (ImageConverterFeature::ConvertCompressed1DToData|ImageConverterFeature::ConvertCompressed1DToFile); CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertCompressed1DToData\n"); - - /* ConvertLevels*DToData is a superset of ConvertLevels*DToFile, so only - one should be printed */ - } { - std::ostringstream out; - Debug{&out} << (ImageConverterFeature::ConvertLevels2DToData|ImageConverterFeature::ConvertLevels2DToFile); - CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertLevels2DToData\n"); - - /* ConvertLevels*DToData is *also* a superset of Convert*DToData, so only - one should be printed */ - } { - std::ostringstream out; - Debug{&out} << (ImageConverterFeature::ConvertLevels3DToData|ImageConverterFeature::Convert3DToData); - CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertLevels3DToData\n"); - - /* ConvertCompressedLevels*DToData is a superset of - ConvertCompressedLevels*DToFile, so only one should be printed */ - } { - std::ostringstream out; - Debug{&out} << (ImageConverterFeature::ConvertCompressedLevels1DToData|ImageConverterFeature::ConvertCompressedLevels1DToFile); - CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertCompressedLevels1DToData\n"); - - /* ConvertCompressedLevels*DToData is *also* a superset of - ConvertCompressed*DToData, so only one should be printed */ - } { - std::ostringstream out; - Debug{&out} << (ImageConverterFeature::ConvertCompressedLevels3DToData|ImageConverterFeature::ConvertCompressed3DToData); - CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertCompressedLevels3DToData\n"); } } diff --git a/src/Magnum/Trade/imageconverter.cpp b/src/Magnum/Trade/imageconverter.cpp index c46e78843..2b5e8a2c8 100644 --- a/src/Magnum/Trade/imageconverter.cpp +++ b/src/Magnum/Trade/imageconverter.cpp @@ -1021,10 +1021,8 @@ no -C / --converter is specified, AnyImageConverter is used.)") Trade::ImageConverterFeature::ConvertCompressed3DToFile : Trade::ImageConverterFeature::Convert3DToFile; } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); - /** @todo use a sane flag once the feature enum is ... sane */ - constexpr Trade::ImageConverterFeatures ImageConverterFeatureLevels = - Trade::ImageConverterFeature::ConvertLevels1DToFile & ~Trade::ImageConverterFeature::Convert1DToFile; - if(outputIsMultiLevel) expectedFeatures |= ImageConverterFeatureLevels; + if(outputIsMultiLevel) + expectedFeatures |= Trade::ImageConverterFeature::Levels; if(!(converter->features() >= expectedFeatures)) { Error err; err << converterName << "doesn't support"; diff --git a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp index 9e50dbd45..a27e5ce14 100644 --- a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp +++ b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp @@ -54,12 +54,7 @@ ImageConverterFeatures AnyImageConverter::doFeatures() const { ImageConverterFeature::ConvertCompressed1DToFile| ImageConverterFeature::ConvertCompressed2DToFile| ImageConverterFeature::ConvertCompressed3DToFile| - ImageConverterFeature::ConvertLevels1DToFile| - ImageConverterFeature::ConvertLevels2DToFile| - ImageConverterFeature::ConvertLevels3DToFile| - ImageConverterFeature::ConvertCompressedLevels1DToFile| - ImageConverterFeature::ConvertCompressedLevels2DToFile| - ImageConverterFeature::ConvertCompressedLevels3DToFile; + ImageConverterFeature::Levels; } bool AnyImageConverter::doConvertToFile(const ImageView1D& image, const Containers::StringView filename) { From 75d927272b501b8caedf35b0643227fa944344e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 22 Aug 2022 14:21:21 +0200 Subject: [PATCH 26/93] Trade: doc++ --- src/Magnum/Trade/AbstractSceneConverter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magnum/Trade/AbstractSceneConverter.h b/src/Magnum/Trade/AbstractSceneConverter.h index 40973ca0a..984cc1262 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.h +++ b/src/Magnum/Trade/AbstractSceneConverter.h @@ -517,7 +517,7 @@ checked by the implementation: (if any) was aborted with @ref doAbort(). - The @ref doAdd() and various `doSet*()` functions are called only if a corresponding @ref SceneConverterFeature is supported. -- The @ref doAdd((UnsignedInt, Containers::Iterable, Containers::StringView) +- The @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) function is called only if the list has at least one mesh - All @ref doAdd() functions taking a single image are called only if the image has a non-zero size in all dimensions and the data is not From 569ae5a195f768ecaec4b827f5f66be28e6ee899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 22 Aug 2022 14:56:48 +0200 Subject: [PATCH 27/93] AnyImageImporter: fall back to BasisImporter if KtxImporter isn't found. Final piece of the KTX2/Basis delegation chain that I somehow forgot to implement. --- .../AnyImageImporter/AnyImageImporter.cpp | 39 +++++- .../AnyImageImporter/AnyImageImporter.h | 3 +- .../Test/AnyImageImporterTest.cpp | 112 ++++++++++++++++++ .../AnyImageImporter/Test/CMakeLists.txt | 2 + .../AnyImageImporter/Test/basis.ktx2 | Bin 0 -> 492 bytes 5 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 src/MagnumPlugins/AnyImageImporter/Test/basis.ktx2 diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp index 2c555b3d0..66e09ea32 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp @@ -94,9 +94,31 @@ void AnyImageImporter::doOpenFile(const Containers::StringView filename) { plugin = "JpegImporter"_s; else if(normalizedExtension == ".jp2"_s) plugin = "Jpeg2000Importer"_s; - else if(normalizedExtension == ".ktx2"_s) + else if(normalizedExtension == ".ktx2"_s) { plugin = "KtxImporter"_s; - else if(normalizedExtension == ".mng"_s) + + /* KtxImporter delegates to BasisImporter in case the file is + Basis-compressed, so that's a good default choice. However, if it + isn't available, we should try delegating to BasisImporter instead, + so people that have just Basis-compressed KTX files don't need to + have KtxImporter as well. + + BasisImporter unfortunately can't handle non-Basis-compressed KTX + files, so in case people have just BasisImporter and not + KtxImporter, it'll fail, but with a clear message suggesting to use + KtxImporter. If neither BasisImporter would be available, it'd fail + too (complaining that KtxImporter isn't available), so the behavior + is roughly the same. + + Further discussion and reasoning here: + https://github.com/mosra/magnum-plugins/pull/112#discussion_r734976174 */ + if(manager()->loadState("KtxImporter"_s) == PluginManager::LoadState::NotFound && + manager()->loadState("BasisImporter"_s) != PluginManager::LoadState::NotFound) { + if(flags() & ImporterFlag::Verbose) + Debug{} << "Trade::AnyImageImporter::openFile(): KtxImporter not found, trying a fallback"; + plugin = "BasisImporter"_s; + } + } else if(normalizedExtension == ".mng"_s) plugin = "MngImporter"_s; else if(normalizedExtension == ".pbm"_s) plugin = "PbmImporter"_s; @@ -200,10 +222,19 @@ void AnyImageImporter::doOpenData(Containers::Array&& data, DataFlags) { else if(dataString.hasPrefix("\xff\xd8\xff"_s)) plugin = "JpegImporter"_s; /* https://github.khronos.org/KTX-Specification/#_identifier */ - else if(dataString.hasPrefix("\xabKTX 20\xbb\r\n\x1a\n"_s)) + else if(dataString.hasPrefix("\xabKTX 20\xbb\r\n\x1a\n"_s)) { plugin = "KtxImporter"_s; + + /* Same logic as in doOpenFile() for *.ktx2 files, see above for more + information */ + if(manager()->loadState("KtxImporter"_s) == PluginManager::LoadState::NotFound && + manager()->loadState("BasisImporter"_s) != PluginManager::LoadState::NotFound) { + if(flags() & ImporterFlag::Verbose) + Debug{} << "Trade::AnyImageImporter::openData(): KtxImporter not found, trying a fallback"; + plugin = "BasisImporter"_s; + } /* https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header */ - else if(dataString.hasPrefix("\x89PNG\x0d\x0a\x1a\x0a"_s)) + } else if(dataString.hasPrefix("\x89PNG\x0d\x0a\x1a\x0a"_s)) plugin = "PngImporter"_s; /* http://paulbourke.net/dataformats/tiff/, http://paulbourke.net/dataformats/tiff/tiff_summary.pdf */ diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h index 7917facc4..4d5f67cbe 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h @@ -79,7 +79,8 @@ Supported formats: - JPEG 2000 (`*.jp2`), loaded with any plugin that provides `Jpeg2000Importer` - KTX2 (`*.ktx2` or data with corresponding signature), loaded with - @ref KtxImporter or any other plugin that provides it + @ref KtxImporter or any other plugin that provides it. If not found, + @ref BasisImporter is tried as a fallback. - Multiple-image Network Graphics (`*.mng`), loaded with any plugin that provides `MngImporter` - Portable Bitmap (`*.pbm`), loaded with any plugin that provides `PbmImporter` diff --git a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp index 112e583ee..9409d6faa 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp +++ b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,9 @@ struct AnyImageImporterTest: TestSuite::Tester { void load3D(); void detect(); + void ktxBasisFallbackFile(); + void ktxBasisFallbackData(); + void unknownExtension(); void unknownSignature(); void emptyData(); @@ -129,6 +133,31 @@ constexpr struct { /* Not testing everything, just the most important ones */ }; +const struct { + const char* name; + bool ktxImporterPresent; + bool basisImporterPresent; + bool verbose; + const char* expectedMessage; +} KtxBasisFallbackData[]{ + {"both KtxImporter and BasisImporter present", true, true, true, + "Trade::AnyImageImporter::{}(): using KtxImporter\n"}, + {"only KtxImporter present", true, false, true, + "Trade::AnyImageImporter::{}(): using KtxImporter\n"}, + {"only BasisImporter present", false, true, true, + "Trade::AnyImageImporter::{0}(): KtxImporter not found, trying a fallback\n" + "Trade::AnyImageImporter::{0}(): using BasisImporter\n"}, + {"only BasisImporter present, verbose output disabled", false, true, false, + nullptr}, + {"neither present", false, false, true, + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + "PluginManager::Manager::load(): plugin KtxImporter is not static and was not found in nonexistent\n" + #else + "PluginManager::Manager::load(): plugin KtxImporter was not found\n" + #endif + "Trade::AnyImageImporter::{}(): cannot load the KtxImporter plugin"} +}; + using namespace Containers::Literals; const struct { @@ -173,6 +202,10 @@ AnyImageImporterTest::AnyImageImporterTest() { addInstancedTests({&AnyImageImporterTest::detect}, Containers::arraySize(DetectData)); + addInstancedTests({&AnyImageImporterTest::ktxBasisFallbackFile, + &AnyImageImporterTest::ktxBasisFallbackData}, + Containers::arraySize(KtxBasisFallbackData)); + addTests({&AnyImageImporterTest::unknownExtension}); addInstancedTests({&AnyImageImporterTest::unknownSignature}, @@ -310,6 +343,85 @@ void AnyImageImporterTest::detect() { #endif } +void AnyImageImporterTest::ktxBasisFallbackFile() { + auto&& data = KtxBasisFallbackData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* Catch also ABI and interface mismatch errors */ + if(data.ktxImporterPresent && !(manager.load("KtxImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("KtxImporter plugin can't be loaded."); + if(data.basisImporterPresent && !(manager.load("BasisImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("BasisImporter plugin can't be loaded."); + + /* Set invalid plugin directory to ensure the remaining plugins don't get + loaded after this point */ + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + manager.setPluginDirectory("nonexistent"); + #endif + + Containers::Pointer importer = manager.instantiate("AnyImageImporter"); + if(data.verbose) + importer->setFlags(ImporterFlag::Verbose); + + std::ostringstream out; + Debug redirectOutput{&out}; + Error redirectError{&out}; + /* We don't care if the file opens (it won't if BasisImporter isn't + present), just verifying if correct plugin got picked by checking the + message. */ + importer->openFile(Utility::Path::join(ANYIMAGEIMPORTER_TEST_DIR, "basis.ktx2")); + if(data.expectedMessage) CORRADE_COMPARE_AS(out.str(), + Utility::formatString(data.expectedMessage, "openFile"), + TestSuite::Compare::StringHasPrefix); + else CORRADE_COMPARE(out.str(), ""); +} + +void AnyImageImporterTest::ktxBasisFallbackData() { + auto&& data = KtxBasisFallbackData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* Catch also ABI and interface mismatch errors */ + if(data.ktxImporterPresent && !(manager.load("KtxImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("KtxImporter plugin can't be loaded."); + if(data.basisImporterPresent && !(manager.load("BasisImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("BasisImporter plugin can't be loaded."); + + /* Set invalid plugin directory to ensure the remaining plugins don't get + loaded after this point */ + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + manager.setPluginDirectory("nonexistent"); + #endif + + Containers::Pointer importer = manager.instantiate("AnyImageImporter"); + if(data.verbose) + importer->setFlags(ImporterFlag::Verbose); + + Containers::Optional> read = Utility::Path::read(Utility::Path::join(ANYIMAGEIMPORTER_TEST_DIR, "basis.ktx2")); + CORRADE_VERIFY(read); + + std::ostringstream out; + Debug redirectOutput{&out}; + Error redirectError{&out}; + /* We don't care if the file opens (it won't if BasisImporter isn't + present), just verifying if correct plugin got picked by checking the + message. */ + importer->openData(*read); + if(data.expectedMessage) CORRADE_COMPARE_AS(out.str(), + Utility::formatString(data.expectedMessage, "openData"), + TestSuite::Compare::StringHasPrefix); + else CORRADE_COMPARE(out.str(), ""); +} + void AnyImageImporterTest::unknownExtension() { Containers::Pointer importer = _manager.instantiate("AnyImageImporter"); diff --git a/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt b/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt index f6599c548..a5aa3a79e 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt @@ -56,6 +56,8 @@ corrade_add_test(AnyImageImporterTest AnyImageImporterTest.cpp # Generated by AnyImageConverterTest::convert{1D,3D}() 1d.ktx2 3d.ktx2 + # rgb.ktx2 from BasisImporter test data (in magnum-plugins), renamed + basis.ktx2 gray.jpg image.exr image.tiff diff --git a/src/MagnumPlugins/AnyImageImporter/Test/basis.ktx2 b/src/MagnumPlugins/AnyImageImporter/Test/basis.ktx2 new file mode 100644 index 0000000000000000000000000000000000000000..41cc6ed214dfa21a1e33a85ecac4c0b89f03ebd8 GIT binary patch literal 492 zcmZ4O9TK5nWU!l;ONxsD2pECb9*Ctu94G*Z!EgqUtpmhUfLH~HcK|U6FoF~S!A>Y` 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 From 9941d83e1d5880a355452f630a1da4c291bc568b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 22 Aug 2022 18:35:43 +0200 Subject: [PATCH 28/93] Trade: rename internal MaterialData helpers for consistency. This is how SceneData names it, a prep for introducing findAttributeId() and findLayerId(). --- src/Magnum/Trade/MaterialData.cpp | 64 +++++++++++++++---------------- src/Magnum/Trade/MaterialData.h | 28 +++++++------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/Magnum/Trade/MaterialData.cpp b/src/Magnum/Trade/MaterialData.cpp index b8b12099f..debe4ecce 100644 --- a/src/Magnum/Trade/MaterialData.cpp +++ b/src/Magnum/Trade/MaterialData.cpp @@ -314,7 +314,7 @@ Containers::StringView MaterialData::attributeString(const MaterialAttribute nam return AttributeMap[UnsignedInt(name) - 1].name; } -UnsignedInt MaterialData::layerFor(const Containers::StringView layer) const { +UnsignedInt MaterialData::findLayerIdInternal(const Containers::StringView layer) const { for(std::size_t i = 1; i < _layerOffsets.size(); ++i) { if(_layerOffsets[i] > _layerOffsets[i - 1] && _data[_layerOffsets[i - 1]].name() == " LayerName"_s && @@ -325,7 +325,7 @@ UnsignedInt MaterialData::layerFor(const Containers::StringView layer) const { } bool MaterialData::hasLayer(const Containers::StringView layer) const { - return layerFor(layer) != ~UnsignedInt{}; + return findLayerIdInternal(layer) != ~UnsignedInt{}; } bool MaterialData::hasLayer(const MaterialLayer layer) const { @@ -335,7 +335,7 @@ bool MaterialData::hasLayer(const MaterialLayer layer) const { } UnsignedInt MaterialData::layerId(const Containers::StringView layer) const { - const UnsignedInt id = layerFor(layer); + const UnsignedInt id = findLayerIdInternal(layer); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::layerId(): layer" << layer << "not found", {}); return id; @@ -363,7 +363,7 @@ Float MaterialData::layerFactor(const UnsignedInt layer) const { } Float MaterialData::layerFactor(const Containers::StringView layer) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::layerFactor(): layer" << layer << "not found", {}); return layerFactor(layerId); @@ -383,7 +383,7 @@ UnsignedInt MaterialData::layerFactorTexture(const UnsignedInt layer) const { UnsignedInt MaterialData::layerFactorTexture(const Containers::StringView layer) const { #ifndef CORRADE_NO_ASSERT - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); #endif CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::layerFactorTexture(): layer" << layer << "not found", {}); @@ -408,7 +408,7 @@ MaterialTextureSwizzle MaterialData::layerFactorTextureSwizzle(const UnsignedInt MaterialTextureSwizzle MaterialData::layerFactorTextureSwizzle(const Containers::StringView layer) const { #ifndef CORRADE_NO_ASSERT - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); #endif CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::layerFactorTextureSwizzle(): layer" << layer << "not found", {}); @@ -438,7 +438,7 @@ Matrix3 MaterialData::layerFactorTextureMatrix(const UnsignedInt layer) const { } Matrix3 MaterialData::layerFactorTextureMatrix(const Containers::StringView layer) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::layerFactorTextureMatrix(): layer" << layer << "not found", {}); CORRADE_ASSERT(hasAttribute(layerId, MaterialAttribute::LayerFactorTexture), @@ -471,7 +471,7 @@ UnsignedInt MaterialData::layerFactorTextureCoordinates(const UnsignedInt layer) } UnsignedInt MaterialData::layerFactorTextureCoordinates(const Containers::StringView layer) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::layerFactorTextureCoordinates(): layer" << layer << "not found", {}); CORRADE_ASSERT(hasAttribute(layerId, MaterialAttribute::LayerFactorTexture), @@ -504,7 +504,7 @@ UnsignedInt MaterialData::layerFactorTextureLayer(const UnsignedInt layer) const } UnsignedInt MaterialData::layerFactorTextureLayer(const Containers::StringView layer) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::layerFactorTextureLayer(): layer" << layer << "not found", {}); CORRADE_ASSERT(hasAttribute(layerId, MaterialAttribute::LayerFactorTexture), @@ -533,7 +533,7 @@ UnsignedInt MaterialData::attributeCount(const UnsignedInt layer) const { } UnsignedInt MaterialData::attributeCount(const Containers::StringView layer) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attributeCount(): layer" << layer << "not found", {}); return attributeCount(layerId); @@ -545,7 +545,7 @@ UnsignedInt MaterialData::attributeCount(const MaterialLayer layer) const { return attributeCount(string); } -UnsignedInt MaterialData::attributeFor(const UnsignedInt layer, const Containers::StringView name) const { +UnsignedInt MaterialData::findAttributeIdInternal(const UnsignedInt layer, const Containers::StringView name) const { const MaterialAttributeData* begin = _data.begin() + (layer && _layerOffsets ? _layerOffsets[layer - 1] : 0); const MaterialAttributeData* end = @@ -560,7 +560,7 @@ UnsignedInt MaterialData::attributeFor(const UnsignedInt layer, const Containers bool MaterialData::hasAttribute(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::hasAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); - return attributeFor(layer, name) != ~UnsignedInt{}; + return findAttributeIdInternal(layer, name) != ~UnsignedInt{}; } bool MaterialData::hasAttribute(const UnsignedInt layer, const MaterialAttribute name) const { @@ -570,7 +570,7 @@ bool MaterialData::hasAttribute(const UnsignedInt layer, const MaterialAttribute } bool MaterialData::hasAttribute(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::hasAttribute(): layer" << layer << "not found", {}); return hasAttribute(layerId, name); @@ -597,7 +597,7 @@ bool MaterialData::hasAttribute(const MaterialLayer layer, const MaterialAttribu UnsignedInt MaterialData::attributeId(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::attributeId(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attributeId(): attribute" << name << "not found in layer" << layer, {}); return id; @@ -610,10 +610,10 @@ UnsignedInt MaterialData::attributeId(const UnsignedInt layer, const MaterialAtt } UnsignedInt MaterialData::attributeId(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attributeId(): layer" << layer << "not found", {}); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attributeId(): attribute" << name << "not found in layer" << layer, {}); return id; @@ -646,7 +646,7 @@ Containers::StringView MaterialData::attributeName(const UnsignedInt layer, cons } Containers::StringView MaterialData::attributeName(const Containers::StringView layer, const UnsignedInt id) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attributeName(): layer" << layer << "not found", {}); CORRADE_ASSERT(id < attributeCount(layer), @@ -671,7 +671,7 @@ MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::attributeType(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attributeType(): attribute" << name << "not found in layer" << layer, {}); return _data[layerOffset(layer) + id]._data.type; @@ -684,7 +684,7 @@ MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const } MaterialAttributeType MaterialData::attributeType(const Containers::StringView layer, const UnsignedInt id) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attributeType(): layer" << layer << "not found", {}); CORRADE_ASSERT(id < attributeCount(layer), @@ -693,10 +693,10 @@ MaterialAttributeType MaterialData::attributeType(const Containers::StringView l } MaterialAttributeType MaterialData::attributeType(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attributeType(): layer" << layer << "not found", {}); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attributeType(): attribute" << name << "not found in layer" << layer, {}); return _data[layerOffset(layerId) + id]._data.type; @@ -747,7 +747,7 @@ void* MaterialData::mutableAttribute(const UnsignedInt layer, const UnsignedInt const void* MaterialData::attribute(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::attribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); return _data[layerOffset(layer) + id].value(); @@ -758,7 +758,7 @@ void* MaterialData::mutableAttribute(const UnsignedInt layer, const Containers:: "Trade::MaterialData::mutableAttribute(): attribute data not mutable", {}); CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::mutableAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): attribute" << name << "not found in layer" << layer, {}); return const_cast(_data[layerOffset(layer) + id].value()); @@ -777,7 +777,7 @@ void* MaterialData::mutableAttribute(const UnsignedInt layer, const MaterialAttr } const void* MaterialData::attribute(const Containers::StringView layer, const UnsignedInt id) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); CORRADE_ASSERT(id < attributeCount(layer), @@ -788,7 +788,7 @@ const void* MaterialData::attribute(const Containers::StringView layer, const Un void* MaterialData::mutableAttribute(const Containers::StringView layer, const UnsignedInt id) { CORRADE_ASSERT(_attributeDataFlags & DataFlag::Mutable, "Trade::MaterialData::mutableAttribute(): attribute data not mutable", {}); - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): layer" << layer << "not found", {}); CORRADE_ASSERT(id < attributeCount(layer), @@ -797,10 +797,10 @@ void* MaterialData::mutableAttribute(const Containers::StringView layer, const U } const void* MaterialData::attribute(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); return _data[layerOffset(layerId) + id].value(); @@ -809,10 +809,10 @@ const void* MaterialData::attribute(const Containers::StringView layer, const Co void* MaterialData::mutableAttribute(const Containers::StringView layer, const Containers::StringView name) { CORRADE_ASSERT(_attributeDataFlags & DataFlag::Mutable, "Trade::MaterialData::mutableAttribute(): attribute data not mutable", {}); - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): layer" << layer << "not found", {}); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): attribute" << name << "not found in layer" << layer, {}); return const_cast(_data[layerOffset(layerId) + id].value()); @@ -901,7 +901,7 @@ template<> MAGNUM_TRADE_EXPORT Containers::MutableStringView MaterialData::mutab const void* MaterialData::tryAttribute(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::tryAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); if(id == ~UnsignedInt{}) return nullptr; return _data[layerOffset(layer) + id].value(); } @@ -913,10 +913,10 @@ const void* MaterialData::tryAttribute(const UnsignedInt layer, const MaterialAt } const void* MaterialData::tryAttribute(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::tryAttribute(): layer" << layer << "not found", {}); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); if(id == ~UnsignedInt{}) return nullptr; return _data[layerOffset(layerId) + id].value(); } diff --git a/src/Magnum/Trade/MaterialData.h b/src/Magnum/Trade/MaterialData.h index ca3a9de7f..e4b96bbd9 100644 --- a/src/Magnum/Trade/MaterialData.h +++ b/src/Magnum/Trade/MaterialData.h @@ -2962,11 +2962,11 @@ class MAGNUM_TRADE_EXPORT MaterialData { static Containers::StringView layerString(MaterialLayer name); static Containers::StringView attributeString(MaterialAttribute name); /* Internal helpers that don't assert, unlike layerId() / attributeId() */ - UnsignedInt layerFor(Containers::StringView layer) const; + UnsignedInt findLayerIdInternal(Containers::StringView layer) const; UnsignedInt layerOffset(UnsignedInt layer) const { return layer && _layerOffsets ? _layerOffsets[layer - 1] : 0; } - UnsignedInt attributeFor(UnsignedInt layer, Containers::StringView name) const; + UnsignedInt findAttributeIdInternal(UnsignedInt layer, Containers::StringView name) const; Containers::Array _data; Containers::Array _layerOffsets; @@ -3141,7 +3141,7 @@ template<> Containers::MutableStringView MaterialData::mutableAttribute T MaterialData::attribute(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::attribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); return attribute(layer, id); @@ -3150,7 +3150,7 @@ template T MaterialData::attribute(const UnsignedInt layer, const Conta template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const UnsignedInt layer, const Containers::StringView name) { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::mutableAttribute(): index" << layer << "out of range for" << layerCount() << "layers", *reinterpret_cast(this)); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): attribute" << name << "not found in layer" << layer, *reinterpret_cast(this)); return mutableAttribute(layer, id); @@ -3169,7 +3169,7 @@ template typename std::conditional T MaterialData::attribute(const Containers::StringView layer, const UnsignedInt id) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); CORRADE_ASSERT(id < attributeCount(layer), @@ -3178,7 +3178,7 @@ template T MaterialData::attribute(const Containers::StringView layer, } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const Containers::StringView layer, const UnsignedInt id) { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): layer" << layer << "not found", *reinterpret_cast(this)); CORRADE_ASSERT(id < attributeCount(layer), @@ -3187,20 +3187,20 @@ template typename std::conditional T MaterialData::attribute(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); return attribute(layerId, id); } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const Containers::StringView layer, const Containers::StringView name) { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): layer" << layer << "not found", *reinterpret_cast(this)); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): attribute" << name << "not found in layer" << layer, *reinterpret_cast(this)); return mutableAttribute(layerId, id); @@ -3257,7 +3257,7 @@ template typename std::conditional Containers::Optional MaterialData::tryAttribute(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::tryAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); if(id == ~UnsignedInt{}) return {}; return attribute(layer, id); } @@ -3269,7 +3269,7 @@ template Containers::Optional MaterialData::tryAttribute(const Unsig } template Containers::Optional MaterialData::tryAttribute(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::tryAttribute(): layer" << layer << "not found", {}); return tryAttribute(layerId, name); @@ -3296,7 +3296,7 @@ template Containers::Optional MaterialData::tryAttribute(const Mater template T MaterialData::attributeOr(const UnsignedInt layer, const Containers::StringView name, const T& defaultValue) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::attributeOr(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); if(id == ~UnsignedInt{}) return defaultValue; return attribute(layer, id); } @@ -3308,7 +3308,7 @@ template T MaterialData::attributeOr(const UnsignedInt layer, const Mat } template T MaterialData::attributeOr(const Containers::StringView layer, const Containers::StringView name, const T& defaultValue) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attributeOr(): layer" << layer << "not found", {}); return attributeOr(layerId, name, defaultValue); From a9a4caf740f0ae0683f3d4362ebef43e21621824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 22 Aug 2022 18:42:27 +0200 Subject: [PATCH 29/93] Trade: add MaterialData::findAttributeId() and findLayerId(). Similar to SceneData::findFieldId() and MeshData::findAttributeId(), useful to avoid a double lookup with hasAttribute() + attribute(). --- src/Magnum/Trade/MaterialData.cpp | 58 ++++++++++++++ src/Magnum/Trade/MaterialData.h | 54 +++++++++++-- src/Magnum/Trade/MaterialLayerData.h | 34 ++++++++ src/Magnum/Trade/Test/MaterialDataTest.cpp | 93 +++++++++++++++++++++- 4 files changed, 230 insertions(+), 9 deletions(-) diff --git a/src/Magnum/Trade/MaterialData.cpp b/src/Magnum/Trade/MaterialData.cpp index debe4ecce..555984052 100644 --- a/src/Magnum/Trade/MaterialData.cpp +++ b/src/Magnum/Trade/MaterialData.cpp @@ -334,6 +334,17 @@ bool MaterialData::hasLayer(const MaterialLayer layer) const { return hasLayer(string); } +Containers::Optional MaterialData::findLayerId(const Containers::StringView layer) const { + const UnsignedInt id = findLayerIdInternal(layer); + return id == ~UnsignedInt{} ? Containers::Optional{} : id; +} + +Containers::Optional MaterialData::findLayerId(const MaterialLayer layer) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string, "Trade::MaterialData::findLayerId(): invalid name" << layer, {}); + return findLayerId(string); +} + UnsignedInt MaterialData::layerId(const Containers::StringView layer) const { const UnsignedInt id = findLayerIdInternal(layer); CORRADE_ASSERT(id != ~UnsignedInt{}, @@ -594,6 +605,53 @@ bool MaterialData::hasAttribute(const MaterialLayer layer, const MaterialAttribu return hasAttribute(string, name); } +Containers::Optional MaterialData::findAttributeId(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::findAttributeId(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = findAttributeIdInternal(layer, name); + return id == ~UnsignedInt{} ? Containers::Optional{} : id; +} + +Containers::Optional MaterialData::findAttributeId(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string, "Trade::MaterialData::findAttributeId(): invalid name" << name, {}); + return findAttributeId(layer, string); +} + +Containers::Optional MaterialData::findAttributeId(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = findLayerIdInternal(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::findAttributeId(): layer" << layer << "not found", {}); + const UnsignedInt id = findAttributeIdInternal(layerId, name); + return id == ~UnsignedInt{} ? Containers::Optional{} : id; +} + +Containers::Optional MaterialData::findAttributeId(const Containers::StringView layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string, "Trade::MaterialData::findAttributeId(): invalid name" << name, {}); + return findAttributeId(layer, string); +} + +Containers::Optional MaterialData::findAttributeId(const MaterialLayer layer, const Containers::StringView name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string, "Trade::MaterialData::findAttributeId(): invalid name" << layer, {}); + return findAttributeId(string, name); +} + +Containers::Optional MaterialData::findAttributeId(const MaterialLayer layer, const MaterialAttribute name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string, "Trade::MaterialData::findAttributeId(): invalid name" << layer, {}); + return findAttributeId(string, name); +} + +Containers::Optional MaterialData::findAttributeId(const Containers::StringView name) const { + return findAttributeId(0, name); +} + +Containers::Optional MaterialData::findAttributeId(const MaterialAttribute name) const { + return findAttributeId(0, name); +} + UnsignedInt MaterialData::attributeId(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::attributeId(): index" << layer << "out of range for" << layerCount() << "layers", {}); diff --git a/src/Magnum/Trade/MaterialData.h b/src/Magnum/Trade/MaterialData.h index e4b96bbd9..8c76236c6 100644 --- a/src/Magnum/Trade/MaterialData.h +++ b/src/Magnum/Trade/MaterialData.h @@ -2043,10 +2043,19 @@ class MAGNUM_TRADE_EXPORT MaterialData { bool hasLayer(Containers::StringView layer) const; bool hasLayer(MaterialLayer layer) const; /**< @overload */ + /** + * @brief Find ID of a named layer + * + * The @p layer doesn't exist, returns @ref Containers::NullOpt. + * @see @ref hasLayer() + */ + Containers::Optional findLayerId(Containers::StringView layer) const; + Containers::Optional findLayerId(MaterialLayer layer) const; /**< @overload */ + /** * @brief ID of a named layer * - * The @p layer is expected to exist. + * Like @ref findLayerId(), but the @p layer is expected to exist. * @see @ref hasLayer() */ UnsignedInt layerId(Containers::StringView layer) const; @@ -2280,12 +2289,45 @@ class MAGNUM_TRADE_EXPORT MaterialData { return hasAttribute(0, name); } /**< @overload */ + /** + * @brief Find ID of a named attribute in given material layer + * + * If @p name doesn't exist, returns @ref Containers::NullOpt. The + * @p layer is expected to be smaller than @ref layerCount() const. + * @see @ref hasAttribute(), @ref attributeId() + */ + Containers::Optional findAttributeId(UnsignedInt layer, Containers::StringView name) const; + Containers::Optional findAttributeId(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Find ID of a named attribute in a named material layer + * + * If @p name doesn't exist, returns @ref Containers::NullOpt. The + * @p layer is expected to exist. + * @see @ref hasLayer(), @ref hasAttribute(), @ref attributeId(), + * @ref findLayerId() + */ + Containers::Optional findAttributeId(Containers::StringView layer, Containers::StringView name) const; + Containers::Optional findAttributeId(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + Containers::Optional findAttributeId(MaterialLayer layer, Containers::StringView name) const; /**< @overload */ + Containers::Optional findAttributeId(MaterialLayer layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Find ID of a named attribute in the base material + * + * Equivalent to calling @ref findAttributeId(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + Containers::Optional findAttributeId(Containers::StringView name) const; + Containers::Optional findAttributeId(MaterialAttribute name) const; /**< @overload */ + /** * @brief ID of a named attribute in given material layer * - * The @p layer is expected to be smaller than @ref layerCount() const - * and @p name is expected to exist in that layer. - * @see @ref hasAttribute() + * Like @ref findAttributeId(UnsignedInt, Containers::StringView) const, + * but the @p name is expected to exist. + * @see @ref hasAttribute(), + * @ref attributeName(UnsignedInt, UnsignedInt) const */ UnsignedInt attributeId(UnsignedInt layer, Containers::StringView name) const; UnsignedInt attributeId(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ @@ -2293,8 +2335,8 @@ class MAGNUM_TRADE_EXPORT MaterialData { /** * @brief ID of a named attribute in a named material layer * - * The @p layer is expected to exist and @p name is expected to exist - * in that layer. + * Like @ref findAttributeId(Containers::StringView, Containers::StringView) const, + * but the @p name is expected to exist. * @see @ref hasLayer(), @ref hasAttribute() */ UnsignedInt attributeId(Containers::StringView layer, Containers::StringView name) const; diff --git a/src/Magnum/Trade/MaterialLayerData.h b/src/Magnum/Trade/MaterialLayerData.h index 7ca98eff1..6af51c24c 100644 --- a/src/Magnum/Trade/MaterialLayerData.h +++ b/src/Magnum/Trade/MaterialLayerData.h @@ -30,6 +30,8 @@ * @m_since_latest */ +#include + #include "Magnum/Math/Matrix3.h" #include "Magnum/Trade/MaterialData.h" @@ -141,6 +143,18 @@ template class MaterialLayerData: public MaterialData { return MaterialData::hasAttribute(layer, name); } /**< @overload */ + /** + * @brief Find ID of a named attribute in this layer + * + * Same as calling @ref MaterialData::findAttributeId() with @p layer. + */ + Containers::Optional findAttributeId(Containers::StringView name) const { + return MaterialData::findAttributeId(layer, name); + } + Containers::Optional findAttributeId(MaterialAttribute name) const { + return MaterialData::findAttributeId(layer, name); + } /**< @overload */ + /** * @brief ID of a named attribute in this layer * @@ -294,6 +308,7 @@ template class MaterialLayerData: public MaterialData { #if !defined(CORRADE_TARGET_MSVC) || defined(CORRADE_TARGET_CLANG_CL) using MaterialData::attributeCount; using MaterialData::hasAttribute; + using MaterialData::findAttributeId; using MaterialData::attributeId; using MaterialData::attributeName; using MaterialData::attributeType; @@ -331,6 +346,25 @@ template class MaterialLayerData: public MaterialData { return MaterialData::hasAttribute(layer_, name); } + Containers::Optional findAttributeId(UnsignedInt layer_, Containers::StringView name) const { + return MaterialData::findAttributeId(layer_, name); + } + Containers::Optional findAttributeId(UnsignedInt layer_, MaterialAttribute name) const { + return MaterialData::findAttributeId(layer_, name); + } + Containers::Optional findAttributeId(Containers::StringView layer_, Containers::StringView name) const { + return MaterialData::findAttributeId(layer_, name); + } + Containers::Optional findAttributeId(Containers::StringView layer_, MaterialAttribute name) const { + return MaterialData::findAttributeId(layer_, name); + } + Containers::Optional findAttributeId(MaterialLayer layer_, Containers::StringView name) const { + return MaterialData::findAttributeId(layer_, name); + } + Containers::Optional findAttributeId(MaterialLayer layer_, MaterialAttribute name) const { + return MaterialData::findAttributeId(layer_, name); + } + UnsignedInt attributeId(UnsignedInt layer_, Containers::StringView name) const { return MaterialData::attributeId(layer_, name); } diff --git a/src/Magnum/Trade/Test/MaterialDataTest.cpp b/src/Magnum/Trade/Test/MaterialDataTest.cpp index 1cbd5854c..07133c214 100644 --- a/src/Magnum/Trade/Test/MaterialDataTest.cpp +++ b/src/Magnum/Trade/Test/MaterialDataTest.cpp @@ -885,8 +885,14 @@ void MaterialDataTest::construct() { /* Access by name */ CORRADE_VERIFY(data.hasAttribute(MaterialAttribute::DoubleSided)); CORRADE_VERIFY(data.hasAttribute(MaterialAttribute::AmbientTextureMatrix)); + CORRADE_VERIFY(data.hasAttribute(MaterialAttribute::DiffuseTextureCoordinates)); CORRADE_VERIFY(!data.hasAttribute(MaterialAttribute::TextureMatrix)); + CORRADE_COMPARE(data.findAttributeId(MaterialAttribute::DoubleSided), 2); + CORRADE_COMPARE(data.findAttributeId(MaterialAttribute::AmbientTextureMatrix), 0); + CORRADE_COMPARE(data.findAttributeId(MaterialAttribute::DiffuseTextureCoordinates), 1); + CORRADE_VERIFY(!data.findAttributeId(MaterialAttribute::TextureMatrix)); + CORRADE_COMPARE(data.attributeId(MaterialAttribute::DoubleSided), 2); CORRADE_COMPARE(data.attributeId(MaterialAttribute::AmbientTextureMatrix), 0); CORRADE_COMPARE(data.attributeId(MaterialAttribute::DiffuseTextureCoordinates), 1); @@ -912,8 +918,16 @@ void MaterialDataTest::construct() { /* Access by string */ CORRADE_VERIFY(data.hasAttribute("DoubleSided")); CORRADE_VERIFY(data.hasAttribute("highlightColor")); + CORRADE_VERIFY(data.hasAttribute("DiffuseTextureCoordinates")); + CORRADE_VERIFY(data.hasAttribute("highlightColor")); CORRADE_VERIFY(!data.hasAttribute("TextureMatrix")); + CORRADE_COMPARE(data.findAttributeId("DoubleSided"), 2); + CORRADE_COMPARE(data.findAttributeId("AmbientTextureMatrix"), 0); + CORRADE_COMPARE(data.findAttributeId("DiffuseTextureCoordinates"), 1); + CORRADE_COMPARE(data.findAttributeId("highlightColor"), 3); + CORRADE_VERIFY(!data.findAttributeId("TextureMatrix")); + CORRADE_COMPARE(data.attributeId("DoubleSided"), 2); CORRADE_COMPARE(data.attributeId("AmbientTextureMatrix"), 0); CORRADE_COMPARE(data.attributeId("DiffuseTextureCoordinates"), 1); @@ -1044,6 +1058,13 @@ void MaterialDataTest::constructLayers() { CORRADE_VERIFY(data.hasLayer(MaterialLayer::ClearCoat)); CORRADE_VERIFY(!data.hasLayer("")); CORRADE_VERIFY(!data.hasLayer("DoubleSided")); + /** @todo test hasLayer(MaterialLayer) once there's more than ClearCoat */ + + CORRADE_COMPARE(data.findLayerId("ClearCoat"), 1); + CORRADE_COMPARE(data.findLayerId(MaterialLayer::ClearCoat), 1); + CORRADE_VERIFY(!data.findLayerId("")); + CORRADE_VERIFY(!data.findLayerId("DoubleSided")); + /** @todo test findLayerId(MaterialLayer) once there's more than ClearCoat */ CORRADE_COMPARE(data.layerId("ClearCoat"), 1); CORRADE_COMPARE(data.layerId(MaterialLayer::ClearCoat), 1); @@ -1087,6 +1108,14 @@ void MaterialDataTest::constructLayers() { CORRADE_VERIFY(!data.hasAttribute(2, MaterialAttribute::NormalTexture)); CORRADE_VERIFY(data.hasAttribute(3, MaterialAttribute::NormalTexture)); + CORRADE_COMPARE(data.findAttributeId(0, MaterialAttribute::DiffuseTextureCoordinates), 0); + CORRADE_VERIFY(!data.findAttributeId(0, MaterialAttribute::AlphaBlend)); + CORRADE_COMPARE(data.findAttributeId(1, MaterialAttribute::AlphaBlend), 1); + CORRADE_COMPARE(data.findAttributeId(1, MaterialAttribute::LayerName), 0); + CORRADE_VERIFY(!data.findAttributeId(2, MaterialAttribute::LayerName)); + CORRADE_VERIFY(!data.findAttributeId(2, MaterialAttribute::NormalTexture)); + CORRADE_COMPARE(data.findAttributeId(3, MaterialAttribute::NormalTexture), 0); + CORRADE_COMPARE(data.attributeId(0, MaterialAttribute::DiffuseTextureCoordinates), 0); CORRADE_COMPARE(data.attributeId(1, MaterialAttribute::AlphaBlend), 1); CORRADE_COMPARE(data.attributeId(1, MaterialAttribute::LayerName), 0); @@ -1124,6 +1153,14 @@ void MaterialDataTest::constructLayers() { CORRADE_VERIFY(!data.hasAttribute(2, "NormalTexture")); CORRADE_VERIFY(data.hasAttribute(3, "NormalTexture")); + CORRADE_COMPARE(data.findAttributeId(0, "DoubleSided"), 1); + CORRADE_VERIFY(!data.findAttributeId(0, "highlightColor")); + CORRADE_COMPARE(data.findAttributeId(1, "highlightColor"), 2); + CORRADE_COMPARE(data.findAttributeId(1, " LayerName"), 0); + CORRADE_VERIFY(!data.findAttributeId(2, " LayerName")); + CORRADE_VERIFY(!data.findAttributeId(2, " NormalTexture")); + CORRADE_COMPARE(data.findAttributeId(3, "NormalTexture"), 0); + CORRADE_COMPARE(data.attributeId(0, "DoubleSided"), 1); CORRADE_COMPARE(data.attributeId(1, "highlightColor"), 2); CORRADE_COMPARE(data.attributeId(1, " LayerName"), 0); @@ -1172,6 +1209,11 @@ void MaterialDataTest::constructLayers() { /* Access by layer name and attribute name */ CORRADE_VERIFY(data.hasAttribute(MaterialLayer::ClearCoat, MaterialAttribute::AlphaBlend)); CORRADE_VERIFY(data.hasAttribute(MaterialLayer::ClearCoat, MaterialAttribute::LayerName)); + CORRADE_VERIFY(!data.hasAttribute(MaterialLayer::ClearCoat, MaterialAttribute::BaseColor)); + + CORRADE_COMPARE(data.findAttributeId(MaterialLayer::ClearCoat, MaterialAttribute::AlphaBlend), 1); + CORRADE_COMPARE(data.findAttributeId(MaterialLayer::ClearCoat, MaterialAttribute::LayerName), 0); + CORRADE_VERIFY(!data.findAttributeId(MaterialLayer::ClearCoat, MaterialAttribute::BaseColor)); CORRADE_COMPARE(data.attributeId(MaterialLayer::ClearCoat, MaterialAttribute::AlphaBlend), 1); CORRADE_COMPARE(data.attributeId(MaterialLayer::ClearCoat, MaterialAttribute::LayerName), 0); @@ -1192,6 +1234,11 @@ void MaterialDataTest::constructLayers() { /* Access by layer name and attribute string */ CORRADE_VERIFY(data.hasAttribute(MaterialLayer::ClearCoat, "highlightColor")); CORRADE_VERIFY(data.hasAttribute(MaterialLayer::ClearCoat, " LayerName")); + CORRADE_VERIFY(!data.hasAttribute(MaterialLayer::ClearCoat, "BaseColor")); + + CORRADE_COMPARE(data.findAttributeId(MaterialLayer::ClearCoat, "highlightColor"), 2); + CORRADE_COMPARE(data.findAttributeId(MaterialLayer::ClearCoat, " LayerName"), 0); + CORRADE_VERIFY(!data.findAttributeId(MaterialLayer::ClearCoat, "BaseColor")); CORRADE_COMPARE(data.attributeId(MaterialLayer::ClearCoat, "highlightColor"), 2); CORRADE_COMPARE(data.attributeId(MaterialLayer::ClearCoat, " LayerName"), 0); @@ -1229,6 +1276,11 @@ void MaterialDataTest::constructLayers() { /* Access by layer string and attribute name */ CORRADE_VERIFY(data.hasAttribute("ClearCoat", MaterialAttribute::AlphaBlend)); CORRADE_VERIFY(data.hasAttribute("ClearCoat", MaterialAttribute::LayerName)); + CORRADE_VERIFY(!data.hasAttribute("ClearCoat", MaterialAttribute::BaseColor)); + + CORRADE_COMPARE(data.findAttributeId("ClearCoat", MaterialAttribute::AlphaBlend), 1); + CORRADE_COMPARE(data.findAttributeId("ClearCoat", MaterialAttribute::LayerName), 0); + CORRADE_VERIFY(!data.findAttributeId("ClearCoat", MaterialAttribute::BaseColor)); CORRADE_COMPARE(data.attributeId("ClearCoat", MaterialAttribute::AlphaBlend), 1); CORRADE_COMPARE(data.attributeId("ClearCoat", MaterialAttribute::LayerName), 0); @@ -1249,6 +1301,11 @@ void MaterialDataTest::constructLayers() { /* Access by layer string and attribute string */ CORRADE_VERIFY(data.hasAttribute("ClearCoat", "highlightColor")); CORRADE_VERIFY(data.hasAttribute("ClearCoat", " LayerName")); + CORRADE_VERIFY(!data.hasAttribute("ClearCoat", "BaseColor")); + + CORRADE_COMPARE(data.findAttributeId("ClearCoat", "highlightColor"), 2); + CORRADE_COMPARE(data.findAttributeId("ClearCoat", " LayerName"), 0); + CORRADE_VERIFY(!data.findAttributeId("ClearCoat", "BaseColor")); CORRADE_COMPARE(data.attributeId("ClearCoat", "highlightColor"), 2); CORRADE_COMPARE(data.attributeId("ClearCoat", " LayerName"), 0); @@ -1763,7 +1820,9 @@ void MaterialDataTest::accessNotFound() { {"DiffuseColor", 0xff3366aa_rgbaf} }}; + /* These are fine */ CORRADE_VERIFY(!data.hasAttribute("DiffuseColour")); + CORRADE_VERIFY(!data.findAttributeId("DiffuseColour")); std::ostringstream out; Error redirectError{&out}; @@ -2325,6 +2384,8 @@ void MaterialDataTest::accessLayerOutOfBounds() { data.attributeCount(2); data.hasAttribute(2, "AlphaMask"); data.hasAttribute(2, MaterialAttribute::AlphaMask); + data.findAttributeId(2, "AlphaMask"); + data.findAttributeId(2, MaterialAttribute::AlphaMask); data.attributeId(2, "AlphaMask"); data.attributeId(2, MaterialAttribute::AlphaMask); data.attributeName(2, 0); @@ -2362,6 +2423,8 @@ void MaterialDataTest::accessLayerOutOfBounds() { "Trade::MaterialData::attributeCount(): index 2 out of range for 2 layers\n" "Trade::MaterialData::hasAttribute(): index 2 out of range for 2 layers\n" "Trade::MaterialData::hasAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::findAttributeId(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::findAttributeId(): index 2 out of range for 2 layers\n" "Trade::MaterialData::attributeId(): index 2 out of range for 2 layers\n" "Trade::MaterialData::attributeId(): index 2 out of range for 2 layers\n" "Trade::MaterialData::attributeName(): index 2 out of range for 2 layers\n" @@ -2398,6 +2461,9 @@ void MaterialDataTest::accessLayerNotFound() { {MaterialAttribute::AlphaMask, 0.5f}, }, {0, 2}}; + /* This is fine */ + CORRADE_VERIFY(!data.findLayerId("ClearCoat")); + std::ostringstream out; Error redirectError{&out}; data.layerId("ClearCoat"); @@ -2410,6 +2476,8 @@ void MaterialDataTest::accessLayerNotFound() { data.attributeCount("ClearCoat"); data.hasAttribute("ClearCoat", "AlphaMask"); data.hasAttribute("ClearCoat", MaterialAttribute::AlphaMask); + data.findAttributeId("ClearCoat", "AlphaMask"); + data.findAttributeId("ClearCoat", MaterialAttribute::AlphaMask); data.attributeId("ClearCoat", "AlphaMask"); data.attributeId("ClearCoat", MaterialAttribute::AlphaMask); data.attributeName("ClearCoat", 0); @@ -2447,6 +2515,8 @@ void MaterialDataTest::accessLayerNotFound() { "Trade::MaterialData::attributeCount(): layer ClearCoat not found\n" "Trade::MaterialData::hasAttribute(): layer ClearCoat not found\n" "Trade::MaterialData::hasAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::findAttributeId(): layer ClearCoat not found\n" + "Trade::MaterialData::findAttributeId(): layer ClearCoat not found\n" "Trade::MaterialData::attributeId(): layer ClearCoat not found\n" "Trade::MaterialData::attributeId(): layer ClearCoat not found\n" "Trade::MaterialData::attributeName(): layer ClearCoat not found\n" @@ -2482,7 +2552,8 @@ void MaterialDataTest::accessInvalidLayerName() { std::ostringstream out; Error redirectError{&out}; - data.layerId(MaterialLayer(0x0)); + data.findLayerId(MaterialLayer(0x0)); + data.findLayerId(MaterialLayer(0xfefe)); data.layerId(MaterialLayer(0xfefe)); data.layerFactor(MaterialLayer(0xfefe)); data.layerFactorTexture(MaterialLayer(0xfefe)); @@ -2493,6 +2564,8 @@ void MaterialDataTest::accessInvalidLayerName() { data.attributeCount(MaterialLayer(0xfefe)); data.hasAttribute(MaterialLayer(0xfefe), "AlphaMask"); data.hasAttribute(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); + data.findAttributeId(MaterialLayer(0xfefe), "AlphaMask"); + data.findAttributeId(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); data.attributeId(MaterialLayer(0xfefe), "AlphaMask"); data.attributeId(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); data.attributeName(MaterialLayer(0xfefe), 0); @@ -2520,7 +2593,8 @@ void MaterialDataTest::accessInvalidLayerName() { data.attributeOr(MaterialLayer(0xfefe), "AlphaMask", false); data.attributeOr(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask, false); CORRADE_COMPARE(out.str(), - "Trade::MaterialData::layerId(): invalid name Trade::MaterialLayer(0x0)\n" + "Trade::MaterialData::findLayerId(): invalid name Trade::MaterialLayer(0x0)\n" + "Trade::MaterialData::findLayerId(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::layerId(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::layerFactor(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::layerFactorTexture(): invalid name Trade::MaterialLayer(0xfefe)\n" @@ -2531,6 +2605,8 @@ void MaterialDataTest::accessInvalidLayerName() { "Trade::MaterialData::attributeCount(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::findAttributeId(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::findAttributeId(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::attributeId(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::attributeId(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::attributeName(): invalid name Trade::MaterialLayer(0xfefe)\n" @@ -2624,7 +2700,9 @@ void MaterialDataTest::accessNotFoundInLayerIndex() { {"DiffuseColor", 0xff3366aa_rgbaf} }, {0, 1}}; + /* These are fine */ CORRADE_VERIFY(!data.hasAttribute(1, "DiffuseColour")); + CORRADE_VERIFY(!data.findAttributeId(1, "DiffuseColour")); std::ostringstream out; Error redirectError{&out}; @@ -2651,7 +2729,9 @@ void MaterialDataTest::accessNotFoundInLayerString() { {"DiffuseColor", 0xff3366aa_rgbaf} }, {0, 1}}; - CORRADE_VERIFY(!data.hasAttribute(1, "DiffuseColour")); + /* These are fine */ + CORRADE_VERIFY(!data.hasAttribute("ClearCoat", "DiffuseColour")); + CORRADE_VERIFY(!data.findAttributeId("ClearCoat", "DiffuseColour")); std::ostringstream out; Error redirectError{&out}; @@ -2682,6 +2762,8 @@ void MaterialDataTest::accessInvalidAttributeName() { Error redirectError{&out}; data.hasAttribute(0, MaterialAttribute(0x0)); data.hasAttribute("Layer", MaterialAttribute(0xfefe)); + data.findAttributeId(0, MaterialAttribute(0x0)); + data.findAttributeId("Layer", MaterialAttribute(0xfefe)); data.attributeId(0, MaterialAttribute(0x0)); data.attributeId("Layer", MaterialAttribute(0xfefe)); data.attributeType(0, MaterialAttribute(0x0)); @@ -2703,6 +2785,8 @@ void MaterialDataTest::accessInvalidAttributeName() { CORRADE_COMPARE(out.str(), "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::findAttributeId(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::findAttributeId(): invalid name Trade::MaterialAttribute(0xfefe)\n" "Trade::MaterialData::attributeId(): invalid name Trade::MaterialAttribute(0x0)\n" "Trade::MaterialData::attributeId(): invalid name Trade::MaterialAttribute(0xfefe)\n" "Trade::MaterialData::attributeType(): invalid name Trade::MaterialAttribute(0x0)\n" @@ -2881,6 +2965,9 @@ void MaterialDataTest::templateLayerAccess() { CORRADE_VERIFY(data.hasAttribute(0, MaterialAttribute::BaseColor)); CORRADE_VERIFY(data.hasAttribute(0, "BaseColor")); + CORRADE_COMPARE(data.findAttributeId(MaterialAttribute::LayerFactorTexture), 2); + CORRADE_COMPARE(data.findAttributeId("LayerFactorTexture"), 2); + CORRADE_COMPARE(data.attributeId(MaterialAttribute::LayerFactorTexture), 2); CORRADE_COMPARE(data.attributeId("LayerFactorTexture"), 2); From 3cb7fe5993c80f5e2e91f5cc6f86892a8345901e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 22 Aug 2022 18:50:03 +0200 Subject: [PATCH 30/93] Trade: doc++ The MeshData and SceneData find APIs had the complexity documented, do the same for MaterialData too. Plus cross-link this API from hasAttribute() / hasField() so it's easier to find. --- src/Magnum/Trade/MaterialData.h | 22 +++++++++++++++------- src/Magnum/Trade/MeshData.h | 3 ++- src/Magnum/Trade/SceneData.h | 2 +- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Magnum/Trade/MaterialData.h b/src/Magnum/Trade/MaterialData.h index 8c76236c6..44e2ca559 100644 --- a/src/Magnum/Trade/MaterialData.h +++ b/src/Magnum/Trade/MaterialData.h @@ -2038,7 +2038,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @cpp 0 @ce is skipped as well) to avoid confusing base material with * a layer. If you want to create a material consisting of just a * layer, use @cpp 0 @ce for the first layer offset in the constructor. - * @see @ref hasAttribute() + * @see @ref hasAttribute(), @ref findLayerId() */ bool hasLayer(Containers::StringView layer) const; bool hasLayer(MaterialLayer layer) const; /**< @overload */ @@ -2046,7 +2046,9 @@ class MAGNUM_TRADE_EXPORT MaterialData { /** * @brief Find ID of a named layer * - * The @p layer doesn't exist, returns @ref Containers::NullOpt. + * The @p layer doesn't exist, returns @ref Containers::NullOpt. The + * lookup is done in an @f$ \mathcal{O}(n) @f$ complexity with + * @f$ n @f$ being the layer count. * @see @ref hasLayer() */ Containers::Optional findLayerId(Containers::StringView layer) const; @@ -2259,7 +2261,8 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @brief Whether a material layer has given attribute * * The @p layer is expected to be smaller than @ref layerCount() const. - * @see @ref tryAttribute(), @ref attributeOr(), @ref hasLayer() + * @see @ref tryAttribute(), @ref attributeOr(), @ref hasLayer(), + * @ref findAttributeId() */ bool hasAttribute(UnsignedInt layer, Containers::StringView name) const; bool hasAttribute(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ @@ -2268,7 +2271,8 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @brief Whether a named material layer has given attribute * * The @p layer is expected to exist. - * @see @ref tryAttribute(), @ref attributeOr(), @ref hasLayer() + * @see @ref tryAttribute(), @ref attributeOr(), @ref hasLayer(), + * @ref findAttributeId() */ bool hasAttribute(Containers::StringView layer, Containers::StringView name) const; bool hasAttribute(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ @@ -2280,7 +2284,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * * Equivalent to calling @ref hasAttribute(UnsignedInt, Containers::StringView) const * with @p layer set to @cpp 0 @ce. - * @see @ref tryAttribute(), @ref attributeOr() + * @see @ref tryAttribute(), @ref attributeOr(), @ref findAttributeId() */ bool hasAttribute(Containers::StringView name) const { return hasAttribute(0, name); @@ -2293,7 +2297,9 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @brief Find ID of a named attribute in given material layer * * If @p name doesn't exist, returns @ref Containers::NullOpt. The - * @p layer is expected to be smaller than @ref layerCount() const. + * @p layer is expected to be smaller than @ref layerCount() const. The + * lookup is done in an @f$ \mathcal{O}(\log n) @f$ complexity with + * @f$ n @f$ being attribute count in given @p layer. * @see @ref hasAttribute(), @ref attributeId() */ Containers::Optional findAttributeId(UnsignedInt layer, Containers::StringView name) const; @@ -2303,7 +2309,9 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @brief Find ID of a named attribute in a named material layer * * If @p name doesn't exist, returns @ref Containers::NullOpt. The - * @p layer is expected to exist. + * @p layer is expected to exist. The lookup is done in an + * @f$ \mathcal{O}(m + \log n) @f$ complexity with @f$ m @f$ being + * layer count and @f$ n @f$ being attribute count in given @p layer. * @see @ref hasLayer(), @ref hasAttribute(), @ref attributeId(), * @ref findLayerId() */ diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index ed83af1e9..e45e32cb2 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -1396,7 +1396,8 @@ class MAGNUM_TRADE_EXPORT MeshData { /** * @brief Whether the mesh has given attribute * - * @see @ref attributeCount(MeshAttribute) const + * @see @ref attributeCount(MeshAttribute) const, + * @ref findAttributeId() */ bool hasAttribute(MeshAttribute name) const { return attributeCount(name); diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index 6f7aa2c7f..9a5364d66 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -1488,7 +1488,7 @@ class MAGNUM_TRADE_EXPORT SceneData { * @brief Whether the scene has given field * @m_since_latest * - * @see @ref is2D(), @ref is3D() + * @see @ref is2D(), @ref is3D(), @ref findFieldId() */ bool hasField(SceneField name) const; From 16703f31378f934f7d7405b45b76d040b143e7d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 24 Aug 2022 12:36:37 +0200 Subject: [PATCH 31/93] Trade: doc++ --- src/Magnum/Trade/MaterialData.h | 40 +++++++++++++++++---------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/Magnum/Trade/MaterialData.h b/src/Magnum/Trade/MaterialData.h index 44e2ca559..f6d79db0a 100644 --- a/src/Magnum/Trade/MaterialData.h +++ b/src/Magnum/Trade/MaterialData.h @@ -1184,8 +1184,8 @@ enum class MaterialAttributeType: UnsignedByte { /** * Null-terminated string. Can be stored using any type convertible to - * @ref Corrade::Containers::StringView, retrieval has to be done using - * @ref Corrade::Containers::StringView. + * @relativeref{Corrade,Containers::StringView}, retrieval has to be done + * using @relativeref{Corrade,Containers::StringView}. */ String, @@ -1355,7 +1355,7 @@ class MAGNUM_TRADE_EXPORT MaterialAttributeData { * @brief Attribute name * * The returned view always has - * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + * @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} set. */ Containers::StringView name() const { return _data.data + 1; } @@ -1697,7 +1697,7 @@ existing attributes. @section Trade-MaterialData-populating Populating an instance A @ref MaterialData instance by default takes over ownership of an -@ref Corrade::Containers::Array containing @ref MaterialAttributeData +@relativeref{Corrade,Containers::Array} containing @ref MaterialAttributeData instances, together with @ref MaterialTypes suggesting available material types (or an empty set, in case of a fully custom material). Attribute values can be in one of the types from @ref MaterialAttributeType, and the type is in most @@ -1727,8 +1727,8 @@ already sorted by name. @par Additionally, as shown above, in order to create a @cpp constexpr @ce @ref MaterialAttributeData array, you need to use - @ref Corrade::Containers::StringView literals instead of plain C strings - or the @ref MaterialAttribute enum, and be sure to call only + @relativeref{Corrade,Containers::StringView} literals instead of plain C + strings or the @ref MaterialAttribute enum, and be sure to call only @cpp constexpr @ce-enabled constructors of stored data types. @subsection Trade-MaterialData-populating-custom Custom material attributes @@ -2371,7 +2371,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * The @p layer is expected to be smaller than @ref layerCount() const * and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const * in that layer. The returned view always has - * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + * @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} set. * @see @ref attributeType() */ Containers::StringView attributeName(UnsignedInt layer, UnsignedInt id) const; @@ -2643,7 +2643,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @ref attributeCount(UnsignedInt) const in that layer. Expects that * @p T corresponds to @ref attributeType(UnsignedInt, UnsignedInt) const * for given @p layer and @p id. In case of a string, the returned view - * always has @ref Corrade::Containers::StringViewFlag::NullTerminated + * always has @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} * set. */ template T attribute(UnsignedInt layer, UnsignedInt id) const; @@ -2669,8 +2669,8 @@ class MAGNUM_TRADE_EXPORT MaterialData { * and @p name is expected to exist in that layer. Expects that @p T * corresponds to @ref attributeType(UnsignedInt, Containers::StringView) const * for given @p layer and @p name. In case of a string, the returned - * view always has - * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + * view always has @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} + * set. * @see @ref hasLayer(), @ref hasAttribute() */ template T attribute(UnsignedInt layer, Containers::StringView name) const; @@ -2699,7 +2699,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * Expects that @p T corresponds to * @ref attributeType(Containers::StringView, UnsignedInt) const * for given @p layer and @p id. In case of a string, the returned view - * always has @ref Corrade::Containers::StringViewFlag::NullTerminated + * always has @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} * set. * @see @ref hasLayer() */ @@ -2728,8 +2728,8 @@ class MAGNUM_TRADE_EXPORT MaterialData { * in that layer. Expects that @p T corresponds to * @ref attributeType(Containers::StringView, Containers::StringView) const * for given @p layer and @p name. In case of a string, the returned - * view always has - * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + * view always has @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} + * set. * @see @ref hasLayer(), @ref hasAttribute() */ template T attribute(Containers::StringView layer, Containers::StringView name) const; @@ -2830,9 +2830,10 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @brief Value of a named attribute in given material layer, if exists * * Compared to @ref attribute(UnsignedInt, Containers::StringView name) const, - * if @p name doesn't exist, returns @ref Corrade::Containers::NullOpt - * instead of asserting. Expects that @p layer is smaller than - * @ref layerCount() const and that @p T corresponds to + * if @p name doesn't exist, returns + * @relativeref{Corrade,Containers::NullOpt} instead of asserting. + * Expects that @p layer is smaller than @ref layerCount() const and + * that @p T corresponds to * @ref attributeType(UnsignedInt, Containers::StringView) const for * given @p layer and @p name. */ @@ -2843,9 +2844,10 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @brief Value of a named attribute in a named material layer, if exists * * Compared to @ref attribute(Containers::StringView, Containers::StringView name) const, - * if @p name doesn't exist, returns @ref Corrade::Containers::NullOpt - * instead of asserting. Expects that @p layer exists and that @p T - * corresponds to @ref attributeType(Containers::StringView, Containers::StringView) const + * if @p name doesn't exist, returns + * @relativeref{Corrade,Containers::NullOpt} instead of asserting. + * Expects that @p layer exists and that @p T corresponds to + * @ref attributeType(Containers::StringView, Containers::StringView) const * for given @p layer and @p name. * @see @ref hasLayer() */ From e1619e5aabd8ce811f1684c074ed0b93cdffdae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 24 Aug 2022 12:57:17 +0200 Subject: [PATCH 32/93] Trade: expose materialLayerName() and materialAttributeName(). The enum-to-string conversion was just a private API and we need to use it in various material filtering code. It was also a private class member for some reason, even though it has no relation to / dependency on the MaterialData class. So it's made a free function instead. --- src/Magnum/Trade/MaterialData.cpp | 133 ++++++++++++--------- src/Magnum/Trade/MaterialData.h | 81 +++++++++---- src/Magnum/Trade/Test/MaterialDataTest.cpp | 44 ++++++- 3 files changed, 175 insertions(+), 83 deletions(-) diff --git a/src/Magnum/Trade/MaterialData.cpp b/src/Magnum/Trade/MaterialData.cpp index 555984052..d9f2eecab 100644 --- a/src/Magnum/Trade/MaterialData.cpp +++ b/src/Magnum/Trade/MaterialData.cpp @@ -65,6 +65,39 @@ constexpr struct { } +namespace Implementation { + +Containers::StringView materialLayerNameInternal(const MaterialLayer layer) { + #ifndef CORRADE_NO_ASSERT + if(UnsignedInt(layer) - 1 >= Containers::arraySize(LayerMap)) + return nullptr; + #endif + return LayerMap[UnsignedInt(layer) - 1]; +} + +Containers::StringView materialAttributeNameInternal(const MaterialAttribute attribute) { + #ifndef CORRADE_NO_ASSERT + if(UnsignedInt(attribute) - 1 >= Containers::arraySize(AttributeMap)) + return nullptr; + #endif + return AttributeMap[UnsignedInt(attribute) - 1].name; +} + +} + +Containers::StringView materialLayerName(const MaterialLayer layer) { + CORRADE_ASSERT(UnsignedInt(layer) - 1 < Containers::arraySize(LayerMap), + "Trade::materialLayerName(): invalid layer" << layer, {}); + return LayerMap[UnsignedInt(layer) - 1]; +} + +Containers::StringView materialAttributeName(const MaterialAttribute attribute) { + CORRADE_ASSERT(UnsignedInt(attribute) - 1 < Containers::arraySize(AttributeMap), + "Trade::materialAttributeName(): invalid attribute" << attribute, {}); + return AttributeMap[UnsignedInt(attribute) - 1].name; +} + + UnsignedInt materialTextureSwizzleComponentCount(const MaterialTextureSwizzle swizzle) { return (UnsignedInt(swizzle) & 0xff000000u ? 1 : 0) + (UnsignedInt(swizzle) & 0x00ff0000u ? 1 : 0) + @@ -298,22 +331,6 @@ MaterialData::~MaterialData() = default; MaterialData& MaterialData::operator=(MaterialData&&) noexcept = default; -Containers::StringView MaterialData::layerString(const MaterialLayer name) { - #ifndef CORRADE_NO_ASSERT - if(UnsignedInt(name) - 1 >= Containers::arraySize(LayerMap)) - return nullptr; - #endif - return LayerMap[UnsignedInt(name) - 1]; -} - -Containers::StringView MaterialData::attributeString(const MaterialAttribute name) { - #ifndef CORRADE_NO_ASSERT - if(UnsignedInt(name) - 1 >= Containers::arraySize(AttributeMap)) - return nullptr; - #endif - return AttributeMap[UnsignedInt(name) - 1].name; -} - UnsignedInt MaterialData::findLayerIdInternal(const Containers::StringView layer) const { for(std::size_t i = 1; i < _layerOffsets.size(); ++i) { if(_layerOffsets[i] > _layerOffsets[i - 1] && @@ -329,7 +346,7 @@ bool MaterialData::hasLayer(const Containers::StringView layer) const { } bool MaterialData::hasLayer(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::hasLayer(): invalid name" << layer, {}); return hasLayer(string); } @@ -340,7 +357,7 @@ Containers::Optional MaterialData::findLayerId(const Containers::St } Containers::Optional MaterialData::findLayerId(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::findLayerId(): invalid name" << layer, {}); return findLayerId(string); } @@ -353,7 +370,7 @@ UnsignedInt MaterialData::layerId(const Containers::StringView layer) const { } UnsignedInt MaterialData::layerId(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerId(): invalid name" << layer, {}); return layerId(string); } @@ -381,7 +398,7 @@ Float MaterialData::layerFactor(const Containers::StringView layer) const { } Float MaterialData::layerFactor(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerFactor(): invalid name" << layer, {}); return layerFactor(string); } @@ -404,7 +421,7 @@ UnsignedInt MaterialData::layerFactorTexture(const Containers::StringView layer) } UnsignedInt MaterialData::layerFactorTexture(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerFactorTexture(): invalid name" << layer, {}); return layerFactorTexture(string); } @@ -431,7 +448,7 @@ MaterialTextureSwizzle MaterialData::layerFactorTextureSwizzle(const Containers: } MaterialTextureSwizzle MaterialData::layerFactorTextureSwizzle(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerFactorTextureSwizzle(): invalid name" << layer, {}); return layerFactorTextureSwizzle(string); } @@ -464,7 +481,7 @@ Matrix3 MaterialData::layerFactorTextureMatrix(const Containers::StringView laye } Matrix3 MaterialData::layerFactorTextureMatrix(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerFactorTextureMatrix(): invalid name" << layer, {}); return layerFactorTextureMatrix(string); } @@ -497,7 +514,7 @@ UnsignedInt MaterialData::layerFactorTextureCoordinates(const Containers::String } UnsignedInt MaterialData::layerFactorTextureCoordinates(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerFactorTextureCoordinates(): invalid name" << layer, {}); return layerFactorTextureCoordinates(string); } @@ -530,7 +547,7 @@ UnsignedInt MaterialData::layerFactorTextureLayer(const Containers::StringView l } UnsignedInt MaterialData::layerFactorTextureLayer(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerFactorTextureLayer(): invalid name" << layer, {}); return layerFactorTextureLayer(string); } @@ -551,7 +568,7 @@ UnsignedInt MaterialData::attributeCount(const Containers::StringView layer) con } UnsignedInt MaterialData::attributeCount(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeCount(): invalid name" << layer, {}); return attributeCount(string); } @@ -575,7 +592,7 @@ bool MaterialData::hasAttribute(const UnsignedInt layer, const Containers::Strin } bool MaterialData::hasAttribute(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::hasAttribute(): invalid name" << name, {}); return hasAttribute(layer, string); } @@ -588,19 +605,19 @@ bool MaterialData::hasAttribute(const Containers::StringView layer, const Contai } bool MaterialData::hasAttribute(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::hasAttribute(): invalid name" << name, {}); return hasAttribute(layer, string); } bool MaterialData::hasAttribute(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::hasAttribute(): invalid name" << layer, {}); return hasAttribute(string, name); } bool MaterialData::hasAttribute(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::hasAttribute(): invalid name" << layer, {}); return hasAttribute(string, name); } @@ -613,7 +630,7 @@ Containers::Optional MaterialData::findAttributeId(const UnsignedIn } Containers::Optional MaterialData::findAttributeId(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::findAttributeId(): invalid name" << name, {}); return findAttributeId(layer, string); } @@ -627,19 +644,19 @@ Containers::Optional MaterialData::findAttributeId(const Containers } Containers::Optional MaterialData::findAttributeId(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::findAttributeId(): invalid name" << name, {}); return findAttributeId(layer, string); } Containers::Optional MaterialData::findAttributeId(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::findAttributeId(): invalid name" << layer, {}); return findAttributeId(string, name); } Containers::Optional MaterialData::findAttributeId(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::findAttributeId(): invalid name" << layer, {}); return findAttributeId(string, name); } @@ -662,7 +679,7 @@ UnsignedInt MaterialData::attributeId(const UnsignedInt layer, const Containers: } UnsignedInt MaterialData::attributeId(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::attributeId(): invalid name" << name, {}); return attributeId(layer, string); } @@ -678,19 +695,19 @@ UnsignedInt MaterialData::attributeId(const Containers::StringView layer, const } UnsignedInt MaterialData::attributeId(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::attributeId(): invalid name" << name, {}); return attributeId(layer, string); } UnsignedInt MaterialData::attributeId(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeId(): invalid name" << layer, {}); return attributeId(string, name); } UnsignedInt MaterialData::attributeId(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeId(): invalid name" << layer, {}); return attributeId(string, name); } @@ -713,7 +730,7 @@ Containers::StringView MaterialData::attributeName(const Containers::StringView } Containers::StringView MaterialData::attributeName(const MaterialLayer layer, const UnsignedInt id) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeName(): invalid name" << layer, {}); return attributeName(string, id); } @@ -736,7 +753,7 @@ MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const } MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::attributeType(): invalid name" << name, {}); return attributeType(layer, string); } @@ -761,25 +778,25 @@ MaterialAttributeType MaterialData::attributeType(const Containers::StringView l } MaterialAttributeType MaterialData::attributeType(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::attributeType(): invalid name" << name, {}); return attributeType(layer, string); } MaterialAttributeType MaterialData::attributeType(const MaterialLayer layer, const UnsignedInt id) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeType(): invalid name" << layer, {}); return attributeType(string, id); } MaterialAttributeType MaterialData::attributeType(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeType(): invalid name" << layer, {}); return attributeType(string, name); } MaterialAttributeType MaterialData::attributeType(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeType(): invalid name" << layer, {}); return attributeType(string, name); } @@ -823,13 +840,13 @@ void* MaterialData::mutableAttribute(const UnsignedInt layer, const Containers:: } const void* MaterialData::attribute(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::attribute(): invalid name" << name, {}); return attribute(layer, string); } void* MaterialData::mutableAttribute(const UnsignedInt layer, const MaterialAttribute name) { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::mutableAttribute(): invalid name" << name, {}); return mutableAttribute(layer, string); } @@ -877,49 +894,49 @@ void* MaterialData::mutableAttribute(const Containers::StringView layer, const C } const void* MaterialData::attribute(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::attribute(): invalid name" << name, {}); return attribute(layer, string); } void* MaterialData::mutableAttribute(const Containers::StringView layer, const MaterialAttribute name) { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::mutableAttribute(): invalid name" << name, {}); return mutableAttribute(layer, string); } const void* MaterialData::attribute(const MaterialLayer layer, const UnsignedInt id) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attribute(): invalid name" << layer, {}); return attribute(string, id); } void* MaterialData::mutableAttribute(const MaterialLayer layer, const UnsignedInt id) { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::mutableAttribute(): invalid name" << layer, {}); return mutableAttribute(string, id); } const void* MaterialData::attribute(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attribute(): invalid name" << layer, {}); return attribute(string, name); } void* MaterialData::mutableAttribute(const MaterialLayer layer, const Containers::StringView name) { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::mutableAttribute(): invalid name" << layer, {}); return mutableAttribute(string, name); } const void* MaterialData::attribute(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attribute(): invalid name" << layer, {}); return attribute(string, name); } void* MaterialData::mutableAttribute(const MaterialLayer layer, const MaterialAttribute name) { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::mutableAttribute(): invalid name" << layer, {}); return mutableAttribute(string, name); } @@ -965,7 +982,7 @@ const void* MaterialData::tryAttribute(const UnsignedInt layer, const Containers } const void* MaterialData::tryAttribute(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); return tryAttribute(layer, string); } @@ -980,19 +997,19 @@ const void* MaterialData::tryAttribute(const Containers::StringView layer, const } const void* MaterialData::tryAttribute(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); return tryAttribute(layer, string); } const void* MaterialData::tryAttribute(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::tryAttribute(): invalid name" << layer, {}); return tryAttribute(string, name); } const void* MaterialData::tryAttribute(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::tryAttribute(): invalid name" << layer, {}); return tryAttribute(string, name); } diff --git a/src/Magnum/Trade/MaterialData.h b/src/Magnum/Trade/MaterialData.h index f6d79db0a..d1c00f021 100644 --- a/src/Magnum/Trade/MaterialData.h +++ b/src/Magnum/Trade/MaterialData.h @@ -57,7 +57,8 @@ for @cpp "ClearCoat" @ce. Each layer is expected to contain (a subset of) the @ref MaterialAttribute::LayerFactorTextureMatrix, @ref MaterialAttribute::LayerFactorTextureCoordinates attributes in addition to what's specified for a particular named layer. -@see @ref MaterialData, @ref MaterialData::layerName(), @ref MaterialLayerData +@see @ref MaterialData, @ref MaterialData::layerName(), @ref MaterialLayerData, + @ref materialLayerName() */ enum class MaterialLayer: UnsignedInt { /* Zero used for an invalid value */ @@ -80,6 +81,21 @@ enum class MaterialLayer: UnsignedInt { ClearCoat = 1, }; +namespace Implementation { + /* Compared to materialLayerName() below returns an empty string for + invalid layers, used internally to provide better assertion messages */ + MAGNUM_TRADE_EXPORT Containers::StringView materialLayerNameInternal(MaterialLayer layer); +} + +/** +@brief Material layer name as a string + +Expects that @p layer is a valid @ref MaterialLayer value. The returned view +has both @relativeref{Corrade,Containers::StringViewFlag::Global} and +@relativeref{Corrade::Containers::StringViewFlag,NullTerminated} set. +*/ +MAGNUM_TRADE_EXPORT Containers::StringView materialLayerName(MaterialLayer layer); + /** @debugoperatorenum{MaterialLayer} @m_since_latest @@ -99,7 +115,8 @@ only exception is @ref MaterialAttribute::LayerName which is When this enum is used in @ref MaterialAttributeData constructors, the data are additionally checked for type compatibility. Other than that, there is no difference to the string variants. -@see @ref MaterialAttributeData, @ref MaterialData +@see @ref MaterialAttributeData, @ref MaterialData, + @ref materialAttributeName() */ enum class MaterialAttribute: UnsignedInt { /* Zero used for an invalid value */ @@ -1052,6 +1069,22 @@ enum class MaterialAttribute: UnsignedInt { TextureLayer, }; +namespace Implementation { + /* Compared to materialLayerName() below returns an empty string for + invalid layers, used internally to provide better assertion messages */ + MAGNUM_TRADE_EXPORT Containers::StringView materialAttributeNameInternal(MaterialAttribute attribute); +} + +/** +@brief Material layer name as a string +@m_since_latest + +Expects that @p attribute is a valid @ref MaterialAttribute value. The returned +view has both @relativeref{Corrade,Containers::StringViewFlag::Global} and +@relativeref{Corrade::Containers::StringViewFlag,NullTerminated} set. +*/ +MAGNUM_TRADE_EXPORT Containers::StringView materialAttributeName(MaterialAttribute attribute); + /** @debugoperatorenum{MaterialAttribute} @m_since_latest @@ -2075,6 +2108,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @cpp 0 @ce) to avoid confsing base material with a layer. If you * want to create a material consisting of just a layer, use @cpp 0 @ce * for the first layer offset in the constructor. + * @see @ref materialLayerName() */ Containers::StringView layerName(UnsignedInt layer) const; @@ -2372,7 +2406,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const * in that layer. The returned view always has * @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} set. - * @see @ref attributeType() + * @see @ref attributeType(), @ref materialAttributeName() */ Containers::StringView attributeName(UnsignedInt layer, UnsignedInt id) const; @@ -2381,7 +2415,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * * The @p layer is expected to exist and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const * in that layer. - * @see @ref hasLayer() + * @see @ref hasLayer(), @ref materialAttributeName() */ Containers::StringView attributeName(Containers::StringView layer, UnsignedInt id) const; Containers::StringView attributeName(MaterialLayer layer, UnsignedInt id) const; /**< @overload */ @@ -2391,6 +2425,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * * Equivalent to calling @ref attributeName(UnsignedInt, UnsignedInt) const * with @p layer set to @cpp 0 @ce. + * @see @ref materialAttributeName() */ Containers::StringView attributeName(UnsignedInt id) const { return attributeName(0, id); @@ -3011,8 +3046,6 @@ class MAGNUM_TRADE_EXPORT MaterialData { implementations. */ friend AbstractImporter; - static Containers::StringView layerString(MaterialLayer name); - static Containers::StringView attributeString(MaterialAttribute name); /* Internal helpers that don't assert, unlike layerId() / attributeId() */ UnsignedInt findLayerIdInternal(Containers::StringView layer) const; UnsignedInt layerOffset(UnsignedInt layer) const { @@ -3209,13 +3242,13 @@ template typename std::conditional T MaterialData::attribute(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); return attribute(layer, string); } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const UnsignedInt layer, const MaterialAttribute name) { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::mutableAttribute(): invalid name" << name, *reinterpret_cast(this)); return mutableAttribute(layer, string); } @@ -3259,49 +3292,49 @@ template typename std::conditional T MaterialData::attribute(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); return attribute(layer, string); } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const Containers::StringView layer, const MaterialAttribute name) { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::mutableAttribute(): invalid name" << name, *reinterpret_cast(this)); return mutableAttribute(layer, string); } template T MaterialData::attribute(const MaterialLayer layer, const UnsignedInt id) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << layer, {}); return attribute(string, id); } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const MaterialLayer layer, const UnsignedInt id) { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::mutableAttribute(): invalid name" << layer, *reinterpret_cast(this)); return mutableAttribute(string, id); } template T MaterialData::attribute(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << layer, {}); return attribute(string, name); } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const MaterialLayer layer, const Containers::StringView name) { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::mutableAttribute(): invalid name" << layer, *reinterpret_cast(this)); return mutableAttribute(string, name); } template T MaterialData::attribute(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << layer, {}); return attribute(string, name); } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const MaterialLayer layer, const MaterialAttribute name) { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::mutableAttribute(): invalid name" << layer, *reinterpret_cast(this)); return mutableAttribute(string, name); } @@ -3315,7 +3348,7 @@ template Containers::Optional MaterialData::tryAttribute(const Unsig } template Containers::Optional MaterialData::tryAttribute(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); return tryAttribute(layer, string); } @@ -3328,19 +3361,19 @@ template Containers::Optional MaterialData::tryAttribute(const Conta } template Containers::Optional MaterialData::tryAttribute(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); return tryAttribute(layer, string); } template Containers::Optional MaterialData::tryAttribute(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << layer, {}); return tryAttribute(string, name); } template Containers::Optional MaterialData::tryAttribute(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << layer, {}); return tryAttribute(string, name); } @@ -3354,7 +3387,7 @@ template T MaterialData::attributeOr(const UnsignedInt layer, const Con } template T MaterialData::attributeOr(const UnsignedInt layer, const MaterialAttribute name, const T& defaultValue) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << name, {}); return attributeOr(layer, string, defaultValue); } @@ -3367,19 +3400,19 @@ template T MaterialData::attributeOr(const Containers::StringView layer } template T MaterialData::attributeOr(const Containers::StringView layer, const MaterialAttribute name, const T& defaultValue) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << name, {}); return attributeOr(layer, string, defaultValue); } template T MaterialData::attributeOr(const MaterialLayer layer, const Containers::StringView name, const T& defaultValue) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << layer, {}); return attributeOr(string, name, defaultValue); } template T MaterialData::attributeOr(const MaterialLayer layer, const MaterialAttribute name, const T& defaultValue) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << layer, {}); return attributeOr(string, name, defaultValue); } diff --git a/src/Magnum/Trade/Test/MaterialDataTest.cpp b/src/Magnum/Trade/Test/MaterialDataTest.cpp index 07133c214..d071911ef 100644 --- a/src/Magnum/Trade/Test/MaterialDataTest.cpp +++ b/src/Magnum/Trade/Test/MaterialDataTest.cpp @@ -46,6 +46,10 @@ namespace Magnum { namespace Trade { namespace Test { namespace { struct MaterialDataTest: TestSuite::Tester { explicit MaterialDataTest(); + void layerName(); + void layerNameInvalid(); + void attributeName(); + void attributeNameInvalid(); void textureSwizzleComponentCount(); void attributeTypeSize(); void attributeTypeSizeInvalid(); @@ -172,10 +176,16 @@ struct MaterialDataTest: TestSuite::Tester { }; MaterialDataTest::MaterialDataTest() { - addTests({&MaterialDataTest::textureSwizzleComponentCount, + addTests({&MaterialDataTest::layerName, + &MaterialDataTest::layerNameInvalid, + &MaterialDataTest::attributeName, + &MaterialDataTest::attributeNameInvalid, + + &MaterialDataTest::textureSwizzleComponentCount, &MaterialDataTest::attributeTypeSize, &MaterialDataTest::attributeTypeSizeInvalid, + &MaterialDataTest::attributeMap, &MaterialDataTest::layerMap, @@ -325,6 +335,38 @@ MaterialDataTest::MaterialDataTest() { using namespace Containers::Literals; using namespace Math::Literals; +void MaterialDataTest::layerName() { + CORRADE_COMPARE(materialLayerName(MaterialLayer::ClearCoat), "ClearCoat"); +} + +void MaterialDataTest::layerNameInvalid() { + CORRADE_SKIP_IF_NO_ASSERT(); + + std::ostringstream out; + Error redirectError{&out}; + materialLayerName(MaterialLayer(0x0)); + materialLayerName(MaterialLayer(0xdeadbeef)); + CORRADE_COMPARE(out.str(), + "Trade::materialLayerName(): invalid layer Trade::MaterialLayer(0x0)\n" + "Trade::materialLayerName(): invalid layer Trade::MaterialLayer(0xdeadbeef)\n"); +} + +void MaterialDataTest::attributeName() { + CORRADE_COMPARE(materialAttributeName(MaterialAttribute::BaseColorTextureMatrix), "BaseColorTextureMatrix"); +} + +void MaterialDataTest::attributeNameInvalid() { + CORRADE_SKIP_IF_NO_ASSERT(); + + std::ostringstream out; + Error redirectError{&out}; + materialAttributeName(MaterialAttribute(0x0)); + materialAttributeName(MaterialAttribute(0xdeadbeef)); + CORRADE_COMPARE(out.str(), + "Trade::materialAttributeName(): invalid attribute Trade::MaterialAttribute(0x0)\n" + "Trade::materialAttributeName(): invalid attribute Trade::MaterialAttribute(0xdeadbeef)\n"); +} + void MaterialDataTest::textureSwizzleComponentCount() { CORRADE_COMPARE(materialTextureSwizzleComponentCount(MaterialTextureSwizzle::B), 1); CORRADE_COMPARE(materialTextureSwizzleComponentCount(MaterialTextureSwizzle::RG), 2); From dfc7c3fe2ce087d25defd82bb3e0fdc471d1b850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 24 Aug 2022 12:59:26 +0200 Subject: [PATCH 33/93] DebugTools: no need to set hidden visibility for a nested class. It makes no difference in this case, the State struct is not exported even without the attribute. OTOH, it would need to have an EXPORT attribute if it was desired to be exported, for example like done with nested classes in GL::Framebuffer. --- src/Magnum/DebugTools/CompareImage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magnum/DebugTools/CompareImage.h b/src/Magnum/DebugTools/CompareImage.h index e66ec4402..b2d1fefb2 100644 --- a/src/Magnum/DebugTools/CompareImage.h +++ b/src/Magnum/DebugTools/CompareImage.h @@ -92,7 +92,7 @@ class MAGNUM_DEBUGTOOLS_EXPORT ImageComparatorBase { void saveDiagnostic(TestSuite::ComparisonStatusFlags flags, Utility::Debug& out, Containers::StringView path); private: - class MAGNUM_DEBUGTOOLS_LOCAL State; + class State; Containers::Pointer _state; }; From 72a51c359477972588832de549894eaffc8bafae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 24 Aug 2022 13:02:08 +0200 Subject: [PATCH 34/93] DebugTools: doc++ Just make the title consistent for all CompareImage variants. --- src/Magnum/DebugTools/CompareImage.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Magnum/DebugTools/CompareImage.h b/src/Magnum/DebugTools/CompareImage.h index b2d1fefb2..5567e2ed0 100644 --- a/src/Magnum/DebugTools/CompareImage.h +++ b/src/Magnum/DebugTools/CompareImage.h @@ -359,7 +359,7 @@ class CompareImage { }; /** -@brief Image file comparator +@brief Image file comparator for @ref Corrade::TestSuite Similar to @ref CompareImage, but comparing images loaded from files. Example usage: @@ -465,7 +465,7 @@ class CompareImageFile { }; /** -@brief Image-to-file comparator +@brief Image-to-file comparator for @ref Corrade::TestSuite A combination of @ref CompareImage and @ref CompareImageFile, which allows to compare an in-memory image to a image file. See their documentation for more @@ -528,7 +528,7 @@ class CompareImageToFile { }; /** -@brief File-to-image comparator +@brief File-to-image comparator for @ref Corrade::TestSuite A combination of @ref CompareImage and @ref CompareImageFile, which allows to compare an image file to an in-memory image. See their documentation for more From 7c1ae161b9d608c859b95a07ce3db80efe1e49d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 25 Aug 2022 13:01:31 +0200 Subject: [PATCH 35/93] {image,scene}converter: don't use a white terminal color in --info. It's invisible on white backgrounds. Use the default color instead, which is white on black backgrounds and black on white backgrounds. --- src/Magnum/SceneTools/sceneconverter.cpp | 46 ++++++++++++------------ src/Magnum/Trade/imageconverter.cpp | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Magnum/SceneTools/sceneconverter.cpp b/src/Magnum/SceneTools/sceneconverter.cpp index 88afda92f..f8ae8dcef 100644 --- a/src/Magnum/SceneTools/sceneconverter.cpp +++ b/src/Magnum/SceneTools/sceneconverter.cpp @@ -823,7 +823,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") std::size_t totalSceneDataSize = 0; for(const SceneInfo& info: sceneInfos) { Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Scene" << info.scene << Debug::nospace << ":" << Debug::resetColor; + d << Debug::boldColor(Debug::Color::Default) << "Scene" << info.scene << Debug::nospace << ":" << Debug::resetColor; if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; d << Debug::newline; d << " Bound:" << info.mappingBound << "objects" @@ -840,7 +840,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") d << Debug::newline << " Fields:"; for(const SceneFieldInfo& field: info.fields) { d << Debug::newline << " " - << Debug::boldColor(Debug::Color::White); + << Debug::boldColor(Debug::Color::Default); if(Trade::isSceneFieldCustom(field.name)) { d << "Custom(" << Debug::nospace << Trade::sceneFieldCustom(field.name) @@ -848,7 +848,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") << Debug::color(Debug::Color::Yellow) << sceneFieldNames[sceneFieldCustom(field.name)] << Debug::nospace - << Debug::boldColor(Debug::Color::White) << ")"; + << Debug::boldColor(Debug::Color::Default) << ")"; } else d << Debug::packed << field.name; d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << field.type; @@ -872,7 +872,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") if(!info.name && !info.scenes) continue; Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Object" << info.object << Debug::resetColor; + d << Debug::boldColor(Debug::Color::Default) << "Object" << info.object << Debug::resetColor; if(sceneInfos) { const UnsignedInt count = Math::popcount(info.scenes); @@ -881,7 +881,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") if(!count) d << Debug::resetColor; } - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" << Debug::resetColor; if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; @@ -911,7 +911,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") std::size_t totalAnimationDataSize = 0; for(const AnimationInfo& info: animationInfos) { Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Animation" << info.animation << Debug::nospace << ":" << Debug::resetColor; + d << Debug::boldColor(Debug::Color::Default) << "Animation" << info.animation << Debug::nospace << ":" << Debug::resetColor; if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; d << Debug::newline << " Duration:" << info.data.duration() @@ -924,7 +924,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") for(UnsignedInt i = 0; i != info.data.trackCount(); ++i) { d << Debug::newline << " Track" << i << Debug::nospace << ":" - << Debug::packed << Debug::boldColor(Debug::Color::White) + << Debug::packed << Debug::boldColor(Debug::Color::Default) << info.data.trackTargetType(i) << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) @@ -958,7 +958,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") for(const SkinInfo& info: skinInfos) { Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Skin" << info.skin + d << Debug::boldColor(Debug::Color::Default) << "Skin" << info.skin << Debug::resetColor; /* Print reference count only if there actually are scenes and they @@ -970,7 +970,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") if(!count) d << Debug::resetColor; } - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" << Debug::resetColor; if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; @@ -980,7 +980,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") for(const LightInfo& info: lightInfos) { Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Light" << info.light << Debug::resetColor; + d << Debug::boldColor(Debug::Color::Default) << "Light" << info.light << Debug::resetColor; /* Print reference count only if there actually are scenes and they were parsed, otherwise this information is useless */ @@ -991,7 +991,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") if(!count) d << Debug::resetColor; } - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" << Debug::resetColor; if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; @@ -1019,7 +1019,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") for(const MaterialInfo& info: materialInfos) { Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Material" << info.material << Debug::resetColor; + d << Debug::boldColor(Debug::Color::Default) << "Material" << info.material << Debug::resetColor; /* Print reference count only if there actually are scenes and they were parsed, otherwise this information is useless */ @@ -1030,7 +1030,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") if(!count) d << Debug::resetColor; } - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" << Debug::resetColor; if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; @@ -1043,7 +1043,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") d << Debug::newline << " Layer" << i << Debug::nospace << ":"; if(!info.data.layerName(i).isEmpty()) { if(std::isupper(info.data.layerName(i)[0])) - d << Debug::boldColor(Debug::Color::White); + d << Debug::boldColor(Debug::Color::Default); else d << Debug::color(Debug::Color::Yellow); d << info.data.layerName(i) << Debug::resetColor; @@ -1063,7 +1063,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") d << Debug::newline << indent; if(std::isupper(info.data.attributeName(i, j)[0])) - d << Debug::boldColor(Debug::Color::White); + d << Debug::boldColor(Debug::Color::Default); else d << Debug::color(Debug::Color::Yellow); d << info.data.attributeName(i, j) << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) @@ -1136,7 +1136,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") for(const MeshInfo& info: meshInfos) { Debug d{useColor}; if(info.level == 0) { - d << Debug::boldColor(Debug::Color::White) << "Mesh" << info.mesh << Debug::resetColor; + d << Debug::boldColor(Debug::Color::Default) << "Mesh" << info.mesh << Debug::resetColor; /* Print reference count only if there actually are scenes and they were parsed, otherwise this information is useless */ @@ -1147,7 +1147,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") if(!count) d << Debug::resetColor; } - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" << Debug::resetColor; if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; d << Debug::newline; @@ -1164,14 +1164,14 @@ is specified as well, the IDs reference attributes of the first mesh.)") for(const MeshAttributeInfo& attribute: info.attributes) { d << Debug::newline << " " - << Debug::boldColor(Debug::Color::White); + << Debug::boldColor(Debug::Color::Default); if(Trade::isMeshAttributeCustom(attribute.name)) { d << "Custom(" << Debug::nospace << Trade::meshAttributeCustom(attribute.name) << Debug::nospace << ":" << Debug::nospace << Debug::color(Debug::Color::Yellow) << attribute.customName << Debug::nospace - << Debug::boldColor(Debug::Color::White) << ")"; + << Debug::boldColor(Debug::Color::Default) << ")"; } else d << Debug::packed << attribute.name; d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << attribute.format; @@ -1205,7 +1205,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") for(const TextureInfo& info: textureInfos) { Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Texture" << info.texture << Debug::resetColor; + d << Debug::boldColor(Debug::Color::Default) << "Texture" << info.texture << Debug::resetColor; /* Print reference count only if there actually are materials and they were parsed, otherwise this information is useless */ @@ -1216,7 +1216,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") if(!count) d << Debug::resetColor; } - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" << Debug::resetColor; if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; @@ -1247,7 +1247,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") for(const Trade::Implementation::ImageInfo& info: imageInfos) { Debug d{useColor}; if(info.level == 0) { - d << Debug::boldColor(Debug::Color::White); + d << Debug::boldColor(Debug::Color::Default); if(info.size.z()) d << "3D image"; else if(info.size.y()) d << "2D image"; else d << "1D image"; @@ -1270,7 +1270,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") if(!*count) d << Debug::resetColor; } - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" << Debug::resetColor; if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; diff --git a/src/Magnum/Trade/imageconverter.cpp b/src/Magnum/Trade/imageconverter.cpp index 2b5e8a2c8..a2e45731a 100644 --- a/src/Magnum/Trade/imageconverter.cpp +++ b/src/Magnum/Trade/imageconverter.cpp @@ -599,7 +599,7 @@ no -C / --converter is specified, AnyImageConverter is used.)") for(const Trade::Implementation::ImageInfo& info: infos) { Debug d{useColor}; if(info.level == 0) { - d << Debug::boldColor(Debug::Color::White); + d << Debug::boldColor(Debug::Color::Default); if(info.size.z()) d << "3D image"; else if(info.size.y()) d << "2D image"; else d << "1D image"; From 9b610ee600eb4e7e72c4c145ef3c915a01a263f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 30 Aug 2022 18:46:36 +0200 Subject: [PATCH 36/93] Audio: actually test ImporterFeatures debug printing. And not just a single ImporterFeature. --- src/Magnum/Audio/Test/AbstractImporterTest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Magnum/Audio/Test/AbstractImporterTest.cpp b/src/Magnum/Audio/Test/AbstractImporterTest.cpp index 650244065..5767bc7bf 100644 --- a/src/Magnum/Audio/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Audio/Test/AbstractImporterTest.cpp @@ -391,8 +391,8 @@ void AbstractImporterTest::debugFeature() { void AbstractImporterTest::debugFeatures() { std::ostringstream out; - Debug{&out} << ImporterFeature::OpenData << ImporterFeatures{}; - CORRADE_COMPARE(out.str(), "Audio::ImporterFeature::OpenData Audio::ImporterFeatures{}\n"); + Debug{&out} << (ImporterFeature::OpenData|ImporterFeature(0xf0)) << ImporterFeatures{}; + CORRADE_COMPARE(out.str(), "Audio::ImporterFeature::OpenData|Audio::ImporterFeature(0xf0) Audio::ImporterFeatures{}\n"); } }}}} From f0ad33f850b62f01bad3865822ae751e06d84006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 30 Aug 2022 18:51:21 +0200 Subject: [PATCH 37/93] Audio,ShaderTools,Text,Trade: packed debug printing for all Feature enums. --- src/Magnum/Audio/AbstractImporter.cpp | 11 ++++--- .../Audio/Test/AbstractImporterTest.cpp | 20 ++++++++++++- src/Magnum/ShaderTools/AbstractConverter.cpp | 11 ++++--- .../Test/AbstractConverterTest.cpp | 18 +++++++++++ src/Magnum/Text/AbstractFont.cpp | 11 ++++--- src/Magnum/Text/AbstractFontConverter.cpp | 11 ++++--- .../Text/Test/AbstractFontConverterTest.cpp | 20 ++++++++++++- src/Magnum/Text/Test/AbstractFontTest.cpp | 20 ++++++++++++- src/Magnum/Trade/AbstractImageConverter.cpp | 13 ++++---- src/Magnum/Trade/AbstractImporter.cpp | 11 ++++--- src/Magnum/Trade/AbstractSceneConverter.cpp | 11 ++++--- .../Trade/Test/AbstractImageConverterTest.cpp | 30 +++++++++++++++++++ .../Trade/Test/AbstractImporterTest.cpp | 18 +++++++++++ .../Trade/Test/AbstractSceneConverterTest.cpp | 18 +++++++++++ 14 files changed, 191 insertions(+), 32 deletions(-) diff --git a/src/Magnum/Audio/AbstractImporter.cpp b/src/Magnum/Audio/AbstractImporter.cpp index 040c9c2cb..ab7ffd87a 100644 --- a/src/Magnum/Audio/AbstractImporter.cpp +++ b/src/Magnum/Audio/AbstractImporter.cpp @@ -145,21 +145,24 @@ Containers::Array AbstractImporter::data() { } Debug& operator<<(Debug& debug, const ImporterFeature value) { - debug << "Audio::ImporterFeature" << Debug::nospace; + const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; + + if(!packed) + debug << "Audio::ImporterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case ImporterFeature::v: return debug << "::" #v; + #define _c(v) case ImporterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(OpenData) #undef _c /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const ImporterFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "Audio::ImporterFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Audio::ImporterFeatures{}", { ImporterFeature::OpenData}); } diff --git a/src/Magnum/Audio/Test/AbstractImporterTest.cpp b/src/Magnum/Audio/Test/AbstractImporterTest.cpp index 5767bc7bf..c40300f00 100644 --- a/src/Magnum/Audio/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Audio/Test/AbstractImporterTest.cpp @@ -67,7 +67,9 @@ struct AbstractImporterTest: TestSuite::Tester { void dataCustomDeleter(); void debugFeature(); + void debugFeaturePacked(); void debugFeatures(); + void debugFeaturesPacked(); }; AbstractImporterTest::AbstractImporterTest() { @@ -92,7 +94,9 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::dataCustomDeleter, &AbstractImporterTest::debugFeature, - &AbstractImporterTest::debugFeatures}); + &AbstractImporterTest::debugFeaturePacked, + &AbstractImporterTest::debugFeatures, + &AbstractImporterTest::debugFeaturesPacked}); } void AbstractImporterTest::construct() { @@ -388,6 +392,13 @@ void AbstractImporterTest::debugFeature() { CORRADE_COMPARE(out.str(), "Audio::ImporterFeature::OpenData Audio::ImporterFeature(0xf0)\n"); } +void AbstractImporterTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << ImporterFeature::OpenData << Debug::packed << ImporterFeature(0xf0) << ImporterFeature::OpenData; + CORRADE_COMPARE(out.str(), "OpenData 0xf0 Audio::ImporterFeature::OpenData\n"); +} + void AbstractImporterTest::debugFeatures() { std::ostringstream out; @@ -395,6 +406,13 @@ void AbstractImporterTest::debugFeatures() { CORRADE_COMPARE(out.str(), "Audio::ImporterFeature::OpenData|Audio::ImporterFeature(0xf0) Audio::ImporterFeatures{}\n"); } +void AbstractImporterTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (ImporterFeature::OpenData|ImporterFeature(0xf0)) << Debug::packed << ImporterFeatures{} << ImporterFeature::OpenData; + CORRADE_COMPARE(out.str(), "OpenData|0xf0 {} Audio::ImporterFeature::OpenData\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Audio::Test::AbstractImporterTest) diff --git a/src/Magnum/ShaderTools/AbstractConverter.cpp b/src/Magnum/ShaderTools/AbstractConverter.cpp index 4d3a9596e..dec8ac9fd 100644 --- a/src/Magnum/ShaderTools/AbstractConverter.cpp +++ b/src/Magnum/ShaderTools/AbstractConverter.cpp @@ -733,11 +733,14 @@ Containers::Optional> AbstractConverter::doLinkFilesToDa } Debug& operator<<(Debug& debug, const ConverterFeature value) { - debug << "ShaderTools::ConverterFeature" << Debug::nospace; + const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; + + if(!packed) + debug << "ShaderTools::ConverterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case ConverterFeature::v: return debug << "::" #v; + #define _c(v) case ConverterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(ValidateData) _c(ValidateFile) _c(ConvertData) @@ -752,11 +755,11 @@ Debug& operator<<(Debug& debug, const ConverterFeature value) { /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const ConverterFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "ShaderTools::ConverterFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "ShaderTools::ConverterFeatures{}", { ConverterFeature::ValidateData, /* Implied by ValidateData, has to be after */ ConverterFeature::ValidateFile, diff --git a/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp b/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp index 1ce7185ac..949e917e2 100644 --- a/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp +++ b/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp @@ -189,7 +189,9 @@ struct AbstractConverterTest: TestSuite::Tester { void setInputFileCallbackLinkFilesToDataAsDataFailed(); void debugFeature(); + void debugFeaturePacked(); void debugFeatures(); + void debugFeaturesPacked(); void debugFeaturesSupersets(); void debugFlag(); void debugFlags(); @@ -339,7 +341,9 @@ AbstractConverterTest::AbstractConverterTest() { &AbstractConverterTest::setInputFileCallbackLinkFilesToDataAsDataFailed, &AbstractConverterTest::debugFeature, + &AbstractConverterTest::debugFeaturePacked, &AbstractConverterTest::debugFeatures, + &AbstractConverterTest::debugFeaturesPacked, &AbstractConverterTest::debugFeaturesSupersets, &AbstractConverterTest::debugFlag, &AbstractConverterTest::debugFlags, @@ -3533,6 +3537,13 @@ void AbstractConverterTest::debugFeature() { CORRADE_COMPARE(out.str(), "ShaderTools::ConverterFeature::ConvertData ShaderTools::ConverterFeature(0xf0)\n"); } +void AbstractConverterTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << ConverterFeature::ConvertData << Debug::packed << ConverterFeature(0xf0) << ConverterFeature::ValidateFile; + CORRADE_COMPARE(out.str(), "ConvertData 0xf0 ShaderTools::ConverterFeature::ValidateFile\n"); +} + void AbstractConverterTest::debugFeatures() { std::ostringstream out; @@ -3540,6 +3551,13 @@ void AbstractConverterTest::debugFeatures() { CORRADE_COMPARE(out.str(), "ShaderTools::ConverterFeature::ValidateData|ShaderTools::ConverterFeature::ConvertFile ShaderTools::ConverterFeatures{}\n"); } +void AbstractConverterTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (ConverterFeature::ValidateData|ConverterFeature::ConvertFile) << Debug::packed << ConverterFeatures{} << ConverterFeature::InputFileCallback; + CORRADE_COMPARE(out.str(), "ValidateData|ConvertFile {} ShaderTools::ConverterFeature::InputFileCallback\n"); +} + void AbstractConverterTest::debugFeaturesSupersets() { /* ValidateData is a superset of ValidateFile, so only one should be printed */ diff --git a/src/Magnum/Text/AbstractFont.cpp b/src/Magnum/Text/AbstractFont.cpp index b88755173..a926aed6e 100644 --- a/src/Magnum/Text/AbstractFont.cpp +++ b/src/Magnum/Text/AbstractFont.cpp @@ -283,11 +283,14 @@ Containers::Pointer AbstractFont::layout(const AbstractGlyphCa } Debug& operator<<(Debug& debug, const FontFeature value) { - debug << "Text::FontFeature" << Debug::nospace; + const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; + + if(!packed) + debug << "Text::FontFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case FontFeature::v: return debug << "::" #v; + #define _c(v) case FontFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(OpenData) _c(FileCallback) _c(PreparedGlyphCache) @@ -295,11 +298,11 @@ Debug& operator<<(Debug& debug, const FontFeature value) { /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const FontFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "Text::FontFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Text::FontFeatures{}", { FontFeature::OpenData, FontFeature::FileCallback, FontFeature::PreparedGlyphCache}); diff --git a/src/Magnum/Text/AbstractFontConverter.cpp b/src/Magnum/Text/AbstractFontConverter.cpp index 02058ee2c..89a8f30ce 100644 --- a/src/Magnum/Text/AbstractFontConverter.cpp +++ b/src/Magnum/Text/AbstractFontConverter.cpp @@ -263,11 +263,14 @@ Containers::Pointer AbstractFontConverter::doImportGlyphCach } Debug& operator<<(Debug& debug, const FontConverterFeature value) { - debug << "Text::FontConverterFeature" << Debug::nospace; + const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; + + if(!packed) + debug << "Text::FontConverterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case FontConverterFeature::v: return debug << "::" #v; + #define _c(v) case FontConverterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(ExportFont) _c(ExportGlyphCache) _c(ImportGlyphCache) @@ -277,11 +280,11 @@ Debug& operator<<(Debug& debug, const FontConverterFeature value) { /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(Containers::enumCastUnderlyingType(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(Containers::enumCastUnderlyingType(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const FontConverterFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "Text::FontConverterFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Text::FontConverterFeatures{}", { FontConverterFeature::ExportFont, FontConverterFeature::ExportGlyphCache, FontConverterFeature::ImportGlyphCache, diff --git a/src/Magnum/Text/Test/AbstractFontConverterTest.cpp b/src/Magnum/Text/Test/AbstractFontConverterTest.cpp index a30d36da4..4e7cff7a7 100644 --- a/src/Magnum/Text/Test/AbstractFontConverterTest.cpp +++ b/src/Magnum/Text/Test/AbstractFontConverterTest.cpp @@ -95,7 +95,9 @@ struct AbstractFontConverterTest: TestSuite::Tester { void importGlyphCacheFromFileAsSingleDataNotFound(); void debugFeature(); + void debugFeaturePacked(); void debugFeatures(); + void debugFeaturesPacked(); }; AbstractFontConverterTest::AbstractFontConverterTest() { @@ -149,7 +151,9 @@ AbstractFontConverterTest::AbstractFontConverterTest() { &AbstractFontConverterTest::importGlyphCacheFromFileAsSingleDataNotFound, &AbstractFontConverterTest::debugFeature, - &AbstractFontConverterTest::debugFeatures}); + &AbstractFontConverterTest::debugFeaturePacked, + &AbstractFontConverterTest::debugFeatures, + &AbstractFontConverterTest::debugFeaturesPacked}); /* Create testing dir */ Utility::Path::make(TEXT_TEST_OUTPUT_DIR); @@ -1019,6 +1023,13 @@ void AbstractFontConverterTest::debugFeature() { CORRADE_COMPARE(out.str(), "Text::FontConverterFeature::ExportFont Text::FontConverterFeature(0xf0)\n"); } +void AbstractFontConverterTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << FontConverterFeature::ExportFont << Debug::packed << FontConverterFeature(0xf0) << FontConverterFeature::ImportGlyphCache; + CORRADE_COMPARE(out.str(), "ExportFont 0xf0 Text::FontConverterFeature::ImportGlyphCache\n"); +} + void AbstractFontConverterTest::debugFeatures() { std::ostringstream out; @@ -1026,6 +1037,13 @@ void AbstractFontConverterTest::debugFeatures() { CORRADE_COMPARE(out.str(), "Text::FontConverterFeature::ExportFont|Text::FontConverterFeature::ImportGlyphCache Text::FontConverterFeatures{}\n"); } +void AbstractFontConverterTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (FontConverterFeature::ExportFont|FontConverterFeature::ImportGlyphCache) << Debug::packed << FontConverterFeatures{} << FontConverterFeature::ExportGlyphCache; + CORRADE_COMPARE(out.str(), "ExportFont|ImportGlyphCache {} Text::FontConverterFeature::ExportGlyphCache\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Text::Test::AbstractFontConverterTest) diff --git a/src/Magnum/Text/Test/AbstractFontTest.cpp b/src/Magnum/Text/Test/AbstractFontTest.cpp index f0df222a6..e11f2dd55 100644 --- a/src/Magnum/Text/Test/AbstractFontTest.cpp +++ b/src/Magnum/Text/Test/AbstractFontTest.cpp @@ -95,7 +95,9 @@ struct AbstractFontTest: TestSuite::Tester { void createGlyphCacheNoFont(); void debugFeature(); + void debugFeaturePacked(); void debugFeatures(); + void debugFeaturesPacked(); }; AbstractFontTest::AbstractFontTest() { @@ -150,7 +152,9 @@ AbstractFontTest::AbstractFontTest() { &AbstractFontTest::createGlyphCacheNoFont, &AbstractFontTest::debugFeature, - &AbstractFontTest::debugFeatures}); + &AbstractFontTest::debugFeaturePacked, + &AbstractFontTest::debugFeatures, + &AbstractFontTest::debugFeaturesPacked}); } void AbstractFontTest::construct() { @@ -1154,6 +1158,13 @@ void AbstractFontTest::debugFeature() { CORRADE_COMPARE(out.str(), "Text::FontFeature::OpenData Text::FontFeature(0xf0)\n"); } +void AbstractFontTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << FontFeature::OpenData << Debug::packed << FontFeature(0xf0) << FontFeature::FileCallback; + CORRADE_COMPARE(out.str(), "OpenData 0xf0 Text::FontFeature::FileCallback\n"); +} + void AbstractFontTest::debugFeatures() { std::ostringstream out; @@ -1161,6 +1172,13 @@ void AbstractFontTest::debugFeatures() { CORRADE_COMPARE(out.str(), "Text::FontFeature::OpenData|Text::FontFeature::PreparedGlyphCache Text::FontFeatures{}\n"); } +void AbstractFontTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (FontFeature::OpenData|FontFeature::PreparedGlyphCache) << Debug::packed << FontFeatures{} << FontFeature::FileCallback; + CORRADE_COMPARE(out.str(), "OpenData|PreparedGlyphCache {} Text::FontFeature::FileCallback\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Text::Test::AbstractFontTest) diff --git a/src/Magnum/Trade/AbstractImageConverter.cpp b/src/Magnum/Trade/AbstractImageConverter.cpp index 56068481c..fc7bba2c0 100644 --- a/src/Magnum/Trade/AbstractImageConverter.cpp +++ b/src/Magnum/Trade/AbstractImageConverter.cpp @@ -1278,18 +1278,21 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView= Debug::Flag::Packed; + #ifdef MAGNUM_BUILD_DEPRECATED /* If printing a deprecated flag combination, make it look like the enum set */ if((value & ImageConverterFeature::Levels) && (value & ~ImageConverterFeature::Levels)) - return debug << (value & ~ImageConverterFeature::Levels) << Debug::nospace << "|Trade::ImageConverterFeature::Levels"; + return debug << (value & ~ImageConverterFeature::Levels) << Debug::nospace << (packed ? "|Levels" : "|Trade::ImageConverterFeature::Levels"); #endif - debug << "Trade::ImageConverterFeature" << Debug::nospace; + if(!packed) + debug << "Trade::ImageConverterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case ImageConverterFeature::v: return debug << "::" #v; + #define _c(v) case ImageConverterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(Convert1D) _c(Convert2D) _c(Convert3D) @@ -1333,11 +1336,11 @@ Debug& operator<<(Debug& debug, const ImageConverterFeature value) { #endif } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const ImageConverterFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "Trade::ImageConverterFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Trade::ImageConverterFeatures{}", { ImageConverterFeature::Convert1D, ImageConverterFeature::Convert2D, ImageConverterFeature::Convert3D, diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index f5d012d3c..f363ae97c 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -1598,11 +1598,14 @@ const void* AbstractImporter::importerState() const { const void* AbstractImporter::doImporterState() const { return nullptr; } Debug& operator<<(Debug& debug, const ImporterFeature value) { - debug << "Trade::ImporterFeature" << Debug::nospace; + const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; + + if(!packed) + debug << "Trade::ImporterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case ImporterFeature::v: return debug << "::" #v; + #define _c(v) case ImporterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(OpenData) _c(OpenState) _c(FileCallback) @@ -1610,11 +1613,11 @@ Debug& operator<<(Debug& debug, const ImporterFeature value) { /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const ImporterFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "Trade::ImporterFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Trade::ImporterFeatures{}", { ImporterFeature::OpenData, ImporterFeature::OpenState, ImporterFeature::FileCallback}); diff --git a/src/Magnum/Trade/AbstractSceneConverter.cpp b/src/Magnum/Trade/AbstractSceneConverter.cpp index d0c676c28..9b40f8009 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.cpp +++ b/src/Magnum/Trade/AbstractSceneConverter.cpp @@ -1187,11 +1187,14 @@ Containers::Optional AbstractSceneConverter::add(const Containers:: } Debug& operator<<(Debug& debug, const SceneConverterFeature value) { - debug << "Trade::SceneConverterFeature" << Debug::nospace; + const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; + + if(!packed) + debug << "Trade::SceneConverterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case SceneConverterFeature::v: return debug << "::" #v; + #define _c(v) case SceneConverterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(ConvertMesh) _c(ConvertMeshInPlace) _c(ConvertMeshToData) @@ -1220,11 +1223,11 @@ Debug& operator<<(Debug& debug, const SceneConverterFeature value) { /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const SceneConverterFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "Trade::SceneConverterFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Trade::SceneConverterFeatures{}", { SceneConverterFeature::ConvertMesh, SceneConverterFeature::ConvertMeshInPlace, SceneConverterFeature::ConvertMeshToData, diff --git a/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp b/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp index 6b7b07739..078ee968f 100644 --- a/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp @@ -296,10 +296,13 @@ struct AbstractImageConverterTest: TestSuite::Tester { void convertCompressed3DToFileThroughLevels(); void debugFeature(); + void debugFeaturePacked(); #ifdef MAGNUM_BUILD_DEPRECATED void debugFeatureDeprecated(); + void debugFeatureDeprecatedPacked(); #endif void debugFeatures(); + void debugFeaturesPacked(); void debugFeaturesSupersets(); void debugFlag(); void debugFlags(); @@ -535,10 +538,13 @@ AbstractImageConverterTest::AbstractImageConverterTest() { &AbstractImageConverterTest::convertCompressed3DToFileThroughLevels, &AbstractImageConverterTest::debugFeature, + &AbstractImageConverterTest::debugFeaturePacked, #ifdef MAGNUM_BUILD_DEPRECATED &AbstractImageConverterTest::debugFeatureDeprecated, + &AbstractImageConverterTest::debugFeatureDeprecatedPacked, #endif &AbstractImageConverterTest::debugFeatures, + &AbstractImageConverterTest::debugFeaturesPacked, &AbstractImageConverterTest::debugFeaturesSupersets, &AbstractImageConverterTest::debugFlag, &AbstractImageConverterTest::debugFlags}); @@ -4515,6 +4521,13 @@ void AbstractImageConverterTest::debugFeature() { CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertCompressed2D Trade::ImageConverterFeature(0xdeadbeef)\n"); } +void AbstractImageConverterTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << ImageConverterFeature::ConvertCompressed2D << Debug::packed << ImageConverterFeature(0xdeadbeef) << ImageConverterFeature::Convert3D; + CORRADE_COMPARE(out.str(), "ConvertCompressed2D 0xdeadbeef Trade::ImageConverterFeature::Convert3D\n"); +} + #ifdef MAGNUM_BUILD_DEPRECATED void AbstractImageConverterTest::debugFeatureDeprecated() { std::ostringstream out; @@ -4524,6 +4537,16 @@ void AbstractImageConverterTest::debugFeatureDeprecated() { CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertCompressed1DToData|Trade::ImageConverterFeature::Levels Trade::ImageConverterFeature::Convert3DToFile|Trade::ImageConverterFeature::Levels\n"); } + +void AbstractImageConverterTest::debugFeatureDeprecatedPacked() { + std::ostringstream out; + + CORRADE_IGNORE_DEPRECATED_PUSH + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << ImageConverterFeature::ConvertCompressedLevels1DToData << Debug::packed << ImageConverterFeature::ConvertLevels3DToFile << ImageConverterFeature::Convert1D; + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), "ConvertCompressed1DToData|Levels Convert3DToFile|Levels Trade::ImageConverterFeature::Convert1D\n"); +} #endif void AbstractImageConverterTest::debugFeatures() { @@ -4533,6 +4556,13 @@ void AbstractImageConverterTest::debugFeatures() { CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::Convert2DToData|Trade::ImageConverterFeature::ConvertCompressed2DToFile Trade::ImageConverterFeatures{}\n"); } +void AbstractImageConverterTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (ImageConverterFeature::Convert2DToData|ImageConverterFeature::ConvertCompressed2DToFile) << Debug::packed << ImageConverterFeatures{} << ImageConverterFeature::Convert1D; + CORRADE_COMPARE(out.str(), "Convert2DToData|ConvertCompressed2DToFile {} Trade::ImageConverterFeature::Convert1D\n"); +} + void AbstractImageConverterTest::debugFeaturesSupersets() { /* Convert*DToData is a superset of Convert*DToFile, so only one should be printed */ diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index c8464df4f..23dd7fc25 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -373,7 +373,9 @@ struct AbstractImporterTest: TestSuite::Tester { void importerStateNoFile(); void debugFeature(); + void debugFeaturePacked(); void debugFeatures(); + void debugFeaturesPacked(); void debugFlag(); void debugFlags(); }; @@ -699,7 +701,9 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::importerStateNoFile, &AbstractImporterTest::debugFeature, + &AbstractImporterTest::debugFeaturePacked, &AbstractImporterTest::debugFeatures, + &AbstractImporterTest::debugFeaturesPacked, &AbstractImporterTest::debugFlag, &AbstractImporterTest::debugFlags}); } @@ -7695,6 +7699,13 @@ void AbstractImporterTest::debugFeature() { CORRADE_COMPARE(out.str(), "Trade::ImporterFeature::OpenData Trade::ImporterFeature(0xf0)\n"); } +void AbstractImporterTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << ImporterFeature::OpenData << Debug::packed << ImporterFeature(0xf0) << ImporterFeature::FileCallback; + CORRADE_COMPARE(out.str(), "OpenData 0xf0 Trade::ImporterFeature::FileCallback\n"); +} + void AbstractImporterTest::debugFeatures() { std::ostringstream out; @@ -7702,6 +7713,13 @@ void AbstractImporterTest::debugFeatures() { CORRADE_COMPARE(out.str(), "Trade::ImporterFeature::OpenData|Trade::ImporterFeature::OpenState Trade::ImporterFeatures{}\n"); } +void AbstractImporterTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (ImporterFeature::OpenData|ImporterFeature::OpenState) << Debug::packed << ImporterFeatures{} << ImporterFeature::FileCallback; + CORRADE_COMPARE(out.str(), "OpenData|OpenState {} Trade::ImporterFeature::FileCallback\n"); +} + void AbstractImporterTest::debugFlag() { std::ostringstream out; diff --git a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp index 4b171c762..1398f24e0 100644 --- a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp @@ -275,7 +275,9 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void addImage3DThroughLevels(); void debugFeature(); + void debugFeaturePacked(); void debugFeatures(); + void debugFeaturesPacked(); void debugFeaturesSupersets(); void debugFlag(); void debugFlags(); @@ -488,7 +490,9 @@ AbstractSceneConverterTest::AbstractSceneConverterTest() { &AbstractSceneConverterTest::addImage3DThroughLevels, &AbstractSceneConverterTest::debugFeature, + &AbstractSceneConverterTest::debugFeaturePacked, &AbstractSceneConverterTest::debugFeatures, + &AbstractSceneConverterTest::debugFeaturesPacked, &AbstractSceneConverterTest::debugFeaturesSupersets, &AbstractSceneConverterTest::debugFlag, &AbstractSceneConverterTest::debugFlags}); @@ -5358,6 +5362,13 @@ void AbstractSceneConverterTest::debugFeature() { CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshInPlace Trade::SceneConverterFeature(0xdeaddead)\n"); } +void AbstractSceneConverterTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << SceneConverterFeature::ConvertMeshInPlace << Debug::packed << SceneConverterFeature(0xdeaddead) << SceneConverterFeature::AddCameras; + CORRADE_COMPARE(out.str(), "ConvertMeshInPlace 0xdeaddead Trade::SceneConverterFeature::AddCameras\n"); +} + void AbstractSceneConverterTest::debugFeatures() { std::ostringstream out; @@ -5365,6 +5376,13 @@ void AbstractSceneConverterTest::debugFeatures() { CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMesh|Trade::SceneConverterFeature::ConvertMeshToFile Trade::SceneConverterFeatures{}\n"); } +void AbstractSceneConverterTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (SceneConverterFeature::ConvertMesh|SceneConverterFeature::ConvertMeshToFile) << Debug::packed << SceneConverterFeatures{} << SceneConverterFeature::AddLights; + CORRADE_COMPARE(out.str(), "ConvertMesh|ConvertMeshToFile {} Trade::SceneConverterFeature::AddLights\n"); +} + void AbstractSceneConverterTest::debugFeaturesSupersets() { /* ConvertMeshToData is a superset of ConvertMeshToFile, so only one should be printed */ From 66a57088b1eeb4b4be59835d41aa1f30aa573df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 30 Aug 2022 19:16:40 +0200 Subject: [PATCH 38/93] Trade: add SceneData::fieldSizeBound(). Like mappingBound(), but instead of giving an upper bound for object count, gives an upper bound for field size. --- src/Magnum/Trade/SceneData.cpp | 7 +++++++ src/Magnum/Trade/SceneData.h | 17 +++++++++++++++++ src/Magnum/Trade/Test/SceneDataTest.cpp | 3 +++ 3 files changed, 27 insertions(+) diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 4f88b3809..61d9b8387 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -851,6 +851,13 @@ Containers::StridedArrayView1D SceneData::fieldDataFieldViewInternal return fieldDataFieldViewInternal(field, 0, field._size); } +std::size_t SceneData::fieldSizeBound() const { + std::size_t out = 0; + for(const SceneFieldData& i: _fields) + out = Math::max(out, std::size_t(i._size)); + return out; +} + SceneFieldData SceneData::fieldData(const UnsignedInt id) const { CORRADE_ASSERT(id < _fields.size(), "Trade::SceneData::fieldData(): index" << id << "out of range for" << _fields.size() << "fields", SceneFieldData{}); diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index 9a5364d66..bc9243148 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -1304,6 +1304,11 @@ class MAGNUM_TRADE_EXPORT SceneData { * @m_since_latest * * Upper bound on object mapping indices of all fields in the scene. + * Note that an object can have a certain field associated with + * it multiple times with different values (for example an object + * having multiple meshes), and thus a field size can be larger than + * @ref mappingBound() --- see @ref fieldSizeBound() for an upper + * bound for all field sizes. * @see @ref fieldCount(), @ref fieldSize() */ UnsignedLong mappingBound() const { return _mappingBound; } @@ -1320,6 +1325,18 @@ class MAGNUM_TRADE_EXPORT SceneData { */ UnsignedInt fieldCount() const { return _fields.size(); } + /** + * @brief Field size bound + * @m_since_latest + * + * A maximum of all @ref fieldSize() or @cpp 0 @ce for a scene with no + * fields. Note that an object can have a certain field associated with + * it multiple times with different values (for example an object + * having multiple meshes), and thus a field size can be larger than + * @ref mappingBound(). + */ + std::size_t fieldSizeBound() const; + /** * @brief Raw field metadata * @m_since_latest diff --git a/src/Magnum/Trade/Test/SceneDataTest.cpp b/src/Magnum/Trade/Test/SceneDataTest.cpp index ecd20e495..bf760404b 100644 --- a/src/Magnum/Trade/Test/SceneDataTest.cpp +++ b/src/Magnum/Trade/Test/SceneDataTest.cpp @@ -1367,6 +1367,7 @@ void SceneDataTest::construct() { CORRADE_COMPARE(scene.mappingBound(), 8); CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedShort); CORRADE_COMPARE(scene.fieldCount(), 4); + CORRADE_COMPARE(scene.fieldSizeBound(), 5); CORRADE_COMPARE(scene.importerState(), &importerState); /* is2D() / is3D() exhaustively tested in transformations*DAsArray[TRS]() @@ -1608,6 +1609,7 @@ void SceneDataTest::constructZeroFields() { CORRADE_COMPARE(scene.mappingBound(), 37563); CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedShort); CORRADE_COMPARE(scene.fieldCount(), 0); + CORRADE_COMPARE(scene.fieldSizeBound(), 0); CORRADE_VERIFY(!scene.is2D()); CORRADE_VERIFY(!scene.is3D()); } @@ -1626,6 +1628,7 @@ void SceneDataTest::constructZeroObjects() { CORRADE_COMPARE(scene.mappingBound(), 0); CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedInt); CORRADE_COMPARE(scene.fieldCount(), 2); + CORRADE_COMPARE(scene.fieldSizeBound(), 0); /* Field property access by name */ CORRADE_COMPARE(scene.fieldType(SceneField::Mesh), SceneFieldType::UnsignedShort); From c6c35035f817323dacf0946965a4664e7eabd165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 30 Aug 2022 20:40:38 +0200 Subject: [PATCH 39/93] package/ci: further reduce ES3 build parallelism. ARGH, I really need to start wrapping up the release so I can get rid of all the compatibility STL includes. --- package/ci/circleci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/ci/circleci.yml b/package/ci/circleci.yml index 939233540..319ab6f86 100644 --- a/package/ci/circleci.yml +++ b/package/ci/circleci.yml @@ -357,11 +357,11 @@ jobs: steps: - install-base-linux: extra: libsdl2-dev libglfw3-dev wget unzip - # In this case it gets stuck even with 24 jobs. Only on GCC, usually when + # In this case it gets stuck even with 20 jobs. Only on GCC, usually when # the huge TradeAbstractImporterTest / TradeMaterialDataTest get involved. # TODO: revisit when we get rid of more STL / deprecated includes - cap-ninja-jobs: - count: 20 + count: 16 - install-gcc-4_8 - install-cmake: version: "3.4.3" From 84b27e14d019b91f64912174bcb1400b9a7e25e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 31 Aug 2022 00:16:51 +0200 Subject: [PATCH 40/93] package/ci: add a TODO for Ninja job capping. Ah, so that's why it reports insane values! --- package/ci/circleci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package/ci/circleci.yml b/package/ci/circleci.yml index 319ab6f86..3eb08bacf 100644 --- a/package/ci/circleci.yml +++ b/package/ci/circleci.yml @@ -67,6 +67,10 @@ commands: if [[ "$CMAKE_CXX_FLAGS" == *"--coverage"* ]]; then export LCOV_PACKAGES="lcov curl"; fi sudo apt install -y ninja-build gcc cmake $LCOV_PACKAGES << parameters.extra >> + # TODO this might get resolved with 1.11.1: + # https://github.com/ninja-build/ninja/pull/1827 + # https://github.com/ninja-build/ninja/pull/2174 + # But wouldn't it build too slow then? Heh cap-ninja-jobs: parameters: count: From 6aaf3711f2ff88450650513c9d59a3514ddeb0ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 1 Sep 2022 12:35:19 +0200 Subject: [PATCH 41/93] Math: doc++ --- src/Magnum/Math/Complex.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magnum/Math/Complex.h b/src/Magnum/Math/Complex.h index 382186861..38d706991 100644 --- a/src/Magnum/Math/Complex.h +++ b/src/Magnum/Math/Complex.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Class @ref Magnum::Math::Complex, function @ref Magnum::Math::dot(), @ref Magnum::math::angle() + * @brief Class @ref Magnum::Math::Complex, function @ref Magnum::Math::dot(), @ref Magnum::Math::angle() */ #include From d344eca5153630657a57ddfbf7ac61b0df1235e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 1 Sep 2022 12:42:30 +0200 Subject: [PATCH 42/93] Trade: properly propagate image view flags in AbstractSceneConverter. This happened because I made an early implementation of the batch AbstractSceneConverter APIs, stashed it away, then implemented image flags and then continued working on the stash, unaware that it's outdated. --- src/Magnum/Trade/AbstractSceneConverter.cpp | 24 +++++------ .../Trade/Test/AbstractSceneConverterTest.cpp | 40 +++++++++++++------ 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/Magnum/Trade/AbstractSceneConverter.cpp b/src/Magnum/Trade/AbstractSceneConverter.cpp index 9b40f8009..a9bae93eb 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.cpp +++ b/src/Magnum/Trade/AbstractSceneConverter.cpp @@ -915,7 +915,7 @@ bool AbstractSceneConverter::doAdd(const UnsignedInt id, const ImageData1D& imag } Containers::Optional AbstractSceneConverter::add(const ImageView1D& image, const Containers::StringView name) { - return add(ImageData1D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}, name); + return add(ImageData1D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data(), image.flags()}, name); } Containers::Optional AbstractSceneConverter::add(const ImageView1D& image) { @@ -923,7 +923,7 @@ Containers::Optional AbstractSceneConverter::add(const ImageView1D& } Containers::Optional AbstractSceneConverter::add(const CompressedImageView1D& image, const Containers::StringView name) { - return add(ImageData1D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}, name); + return add(ImageData1D{image.storage(), image.format(), image.size(), DataFlags{}, image.data(), image.flags()}, name); } Containers::Optional AbstractSceneConverter::add(const CompressedImageView1D& image) { @@ -960,7 +960,7 @@ Containers::Optional AbstractSceneConverter::add(const Containers:: Containers::Array data{NoInit, imageLevels.size()}; for(std::size_t i = 0; i != imageLevels.size(); ++i) { const ImageView1D& image = imageLevels[i]; - new(&data[i]) ImageData1D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}; + new(&data[i]) ImageData1D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data(), image.flags()}; } return add(data, name); @@ -974,7 +974,7 @@ Containers::Optional AbstractSceneConverter::add(const Containers:: Containers::Array data{NoInit, imageLevels.size()}; for(std::size_t i = 0; i != imageLevels.size(); ++i) { const CompressedImageView1D& image = imageLevels[i]; - new(&data[i]) ImageData1D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}; + new(&data[i]) ImageData1D{image.storage(), image.format(), image.size(), DataFlags{}, image.data(), image.flags()}; } return add(data, name); @@ -1016,7 +1016,7 @@ bool AbstractSceneConverter::doAdd(const UnsignedInt id, const ImageData2D& imag } Containers::Optional AbstractSceneConverter::add(const ImageView2D& image, const Containers::StringView name) { - return add(ImageData2D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}, name); + return add(ImageData2D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data(), image.flags()}, name); } Containers::Optional AbstractSceneConverter::add(const ImageView2D& image) { @@ -1024,7 +1024,7 @@ Containers::Optional AbstractSceneConverter::add(const ImageView2D& } Containers::Optional AbstractSceneConverter::add(const CompressedImageView2D& image, const Containers::StringView name) { - return add(ImageData2D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}, name); + return add(ImageData2D{image.storage(), image.format(), image.size(), DataFlags{}, image.data(), image.flags()}, name); } Containers::Optional AbstractSceneConverter::add(const CompressedImageView2D& image) { @@ -1061,7 +1061,7 @@ Containers::Optional AbstractSceneConverter::add(const Containers:: Containers::Array data{NoInit, imageLevels.size()}; for(std::size_t i = 0; i != imageLevels.size(); ++i) { const ImageView2D& image = imageLevels[i]; - new(&data[i]) ImageData2D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}; + new(&data[i]) ImageData2D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data(), image.flags()}; } return add(data, name); @@ -1075,7 +1075,7 @@ Containers::Optional AbstractSceneConverter::add(const Containers:: Containers::Array data{NoInit, imageLevels.size()}; for(std::size_t i = 0; i != imageLevels.size(); ++i) { const CompressedImageView2D& image = imageLevels[i]; - new(&data[i]) ImageData2D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}; + new(&data[i]) ImageData2D{image.storage(), image.format(), image.size(), DataFlags{}, image.data(), image.flags()}; } return add(data, name); @@ -1117,7 +1117,7 @@ bool AbstractSceneConverter::doAdd(const UnsignedInt id, const ImageData3D& imag } Containers::Optional AbstractSceneConverter::add(const ImageView3D& image, const Containers::StringView name) { - return add(ImageData3D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}, name); + return add(ImageData3D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data(), image.flags()}, name); } Containers::Optional AbstractSceneConverter::add(const ImageView3D& image) { @@ -1125,7 +1125,7 @@ Containers::Optional AbstractSceneConverter::add(const ImageView3D& } Containers::Optional AbstractSceneConverter::add(const CompressedImageView3D& image, const Containers::StringView name) { - return add(ImageData3D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}, name); + return add(ImageData3D{image.storage(), image.format(), image.size(), DataFlags{}, image.data(), image.flags()}, name); } Containers::Optional AbstractSceneConverter::add(const CompressedImageView3D& image) { @@ -1162,7 +1162,7 @@ Containers::Optional AbstractSceneConverter::add(const Containers:: Containers::Array data{NoInit, imageLevels.size()}; for(std::size_t i = 0; i != imageLevels.size(); ++i) { const ImageView3D& image = imageLevels[i]; - new(&data[i]) ImageData3D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}; + new(&data[i]) ImageData3D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data(), image.flags()}; } return add(data, name); @@ -1176,7 +1176,7 @@ Containers::Optional AbstractSceneConverter::add(const Containers:: Containers::Array data{NoInit, imageLevels.size()}; for(std::size_t i = 0; i != imageLevels.size(); ++i) { const CompressedImageView3D& image = imageLevels[i]; - new(&data[i]) ImageData3D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}; + new(&data[i]) ImageData3D{image.storage(), image.format(), image.size(), DataFlags{}, image.data(), image.flags()}; } return add(data, name); diff --git a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp index 1398f24e0..dc0519ea9 100644 --- a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp @@ -3904,6 +3904,7 @@ void AbstractSceneConverterTest::addImage1DView() { CORRADE_COMPARE(image.storage().alignment(), 2); CORRADE_COMPARE(image.format(), PixelFormat::RG8Snorm); CORRADE_COMPARE(image.size(), 3); + CORRADE_COMPARE(image.flags(), ImageFlags1D{}); CORRADE_VERIFY(image.data()); addCalled = true; @@ -3939,6 +3940,7 @@ void AbstractSceneConverterTest::addImage1DCompressedView() { CORRADE_COMPARE(image.compressedStorage().compressedBlockSize(), Vector3i{3}); CORRADE_COMPARE(image.compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); CORRADE_COMPARE(image.size(), 3); + CORRADE_COMPARE(image.flags(), ImageFlags1D{}); CORRADE_VERIFY(image.data()); addCalled = true; @@ -4088,6 +4090,7 @@ void AbstractSceneConverterTest::addImage2DView() { CORRADE_COMPARE(image.storage().alignment(), 2); CORRADE_COMPARE(image.format(), PixelFormat::RG8Snorm); CORRADE_COMPARE(image.size(), (Vector2i{3, 1})); + CORRADE_COMPARE(image.flags(), ImageFlag2D::Array); CORRADE_VERIFY(image.data()); addCalled = true; @@ -4103,7 +4106,8 @@ void AbstractSceneConverterTest::addImage2DView() { CORRADE_COMPARE(converter.image2DCount(), 0); CORRADE_COMPARE(converter.add(ImageView2D{ PixelStorage{}.setAlignment(2), - PixelFormat::RG8Snorm, {3, 1}, imageData}, "hello"), 0); + PixelFormat::RG8Snorm, {3, 1}, imageData, ImageFlag2D::Array}, + "hello"), 0); CORRADE_VERIFY(converter.addCalled); CORRADE_COMPARE(converter.image2DCount(), 1); } @@ -4123,6 +4127,7 @@ void AbstractSceneConverterTest::addImage2DCompressedView() { CORRADE_COMPARE(image.compressedStorage().compressedBlockSize(), Vector3i{3}); CORRADE_COMPARE(image.compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); CORRADE_COMPARE(image.size(), (Vector2i{3, 2})); + CORRADE_COMPARE(image.flags(), ImageFlag2D::Array); CORRADE_VERIFY(image.data()); addCalled = true; @@ -4138,7 +4143,8 @@ void AbstractSceneConverterTest::addImage2DCompressedView() { CORRADE_COMPARE(converter.image2DCount(), 0); CORRADE_COMPARE(converter.add(CompressedImageView2D{ CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), - CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 2}, imageData}, "hello"), 0); + CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 2}, imageData, + ImageFlag2D::Array}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); CORRADE_COMPARE(converter.image2DCount(), 1); } @@ -4294,6 +4300,7 @@ void AbstractSceneConverterTest::addImage3DView() { CORRADE_COMPARE(image.storage().alignment(), 2); CORRADE_COMPARE(image.format(), PixelFormat::RG8Snorm); CORRADE_COMPARE(image.size(), (Vector3i{1, 3, 1})); + CORRADE_COMPARE(image.flags(), ImageFlag3D::Array); CORRADE_VERIFY(image.data()); addCalled = true; @@ -4309,7 +4316,8 @@ void AbstractSceneConverterTest::addImage3DView() { CORRADE_COMPARE(converter.image3DCount(), 0); CORRADE_COMPARE(converter.add(ImageView3D{ PixelStorage{}.setAlignment(2), - PixelFormat::RG8Snorm, {1, 3, 1}, imageData}, "hello"), 0); + PixelFormat::RG8Snorm, {1, 3, 1}, imageData, ImageFlag3D::Array}, + "hello"), 0); CORRADE_VERIFY(converter.addCalled); CORRADE_COMPARE(converter.image3DCount(), 1); } @@ -4329,6 +4337,7 @@ void AbstractSceneConverterTest::addImage3DCompressedView() { CORRADE_COMPARE(image.compressedStorage().compressedBlockSize(), Vector3i{3}); CORRADE_COMPARE(image.compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); CORRADE_COMPARE(image.size(), (Vector3i{1, 3, 1})); + CORRADE_COMPARE(image.flags(), ImageFlag3D::Array); CORRADE_VERIFY(image.data()); addCalled = true; @@ -4344,7 +4353,8 @@ void AbstractSceneConverterTest::addImage3DCompressedView() { CORRADE_COMPARE(converter.image3DCount(), 0); CORRADE_COMPARE(converter.add(CompressedImageView3D{ CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), - CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 3, 1}, imageData}, "hello"), 0); + CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 3, 1}, imageData, + ImageFlag3D::Array}, "hello"), 0); CORRADE_VERIFY(converter.addCalled); CORRADE_COMPARE(converter.image3DCount(), 1); } @@ -4491,6 +4501,7 @@ void AbstractSceneConverterTest::addImageLevels1DView() { CORRADE_COMPARE(imageLevels[1].storage().alignment(), 2); CORRADE_COMPARE(imageLevels[1].format(), PixelFormat::RG8Snorm); CORRADE_COMPARE(imageLevels[1].size(), 3); + CORRADE_COMPARE(imageLevels[1].flags(), ImageFlags1D{}); CORRADE_VERIFY(imageLevels[1].data()); addCalled = true; @@ -4529,6 +4540,7 @@ void AbstractSceneConverterTest::addImageLevels1DCompressedView() { CORRADE_COMPARE(imageLevels[1].compressedStorage().compressedBlockSize(), Vector3i{3}); CORRADE_COMPARE(imageLevels[1].compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); CORRADE_COMPARE(imageLevels[1].size(), 3); + CORRADE_COMPARE(imageLevels[1].flags(), ImageFlags1D{}); CORRADE_VERIFY(imageLevels[1].data()); addCalled = true; @@ -4707,6 +4719,7 @@ void AbstractSceneConverterTest::addImageLevels2DView() { CORRADE_COMPARE(imageLevels[1].storage().alignment(), 2); CORRADE_COMPARE(imageLevels[1].format(), PixelFormat::RG8Snorm); CORRADE_COMPARE(imageLevels[1].size(), (Vector2i{1, 3})); + CORRADE_COMPARE(imageLevels[1].flags(), ImageFlag2D::Array); CORRADE_VERIFY(imageLevels[1].data()); addCalled = true; @@ -4721,8 +4734,8 @@ void AbstractSceneConverterTest::addImageLevels2DView() { CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image2DCount(), 0); CORRADE_COMPARE(converter.add({ - ImageView2D{PixelFormat::RG8Snorm, {1, 1}, imageData}, - ImageView2D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, {1, 3}, imageData} + ImageView2D{PixelFormat::RG8Snorm, {1, 1}, imageData, ImageFlag2D::Array}, + ImageView2D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, {1, 3}, imageData, ImageFlag2D::Array} }, "hello"), 0); CORRADE_VERIFY(converter.addCalled); CORRADE_COMPARE(converter.image2DCount(), 1); @@ -4745,6 +4758,7 @@ void AbstractSceneConverterTest::addImageLevels2DCompressedView() { CORRADE_COMPARE(imageLevels[1].compressedStorage().compressedBlockSize(), Vector3i{3}); CORRADE_COMPARE(imageLevels[1].compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); CORRADE_COMPARE(imageLevels[1].size(), (Vector2i{3, 1})); + CORRADE_COMPARE(imageLevels[1].flags(), ImageFlag2D::Array); CORRADE_VERIFY(imageLevels[1].data()); addCalled = true; @@ -4759,8 +4773,8 @@ void AbstractSceneConverterTest::addImageLevels2DCompressedView() { CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image2DCount(), 0); CORRADE_COMPARE(converter.add({ - CompressedImageView2D{CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 1}, imageData}, - CompressedImageView2D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 1}, imageData} + CompressedImageView2D{CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 1}, imageData, ImageFlag2D::Array}, + CompressedImageView2D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 1}, imageData, ImageFlag2D::Array} }, "hello"), 0); CORRADE_VERIFY(converter.addCalled); CORRADE_COMPARE(converter.image2DCount(), 1); @@ -5112,6 +5126,7 @@ void AbstractSceneConverterTest::addImageLevels3DView() { CORRADE_COMPARE(imageLevels[1].storage().alignment(), 2); CORRADE_COMPARE(imageLevels[1].format(), PixelFormat::RG8Snorm); CORRADE_COMPARE(imageLevels[1].size(), (Vector3i{1, 3, 1})); + CORRADE_COMPARE(imageLevels[1].flags(), ImageFlag3D::Array); CORRADE_VERIFY(imageLevels[1].data()); addCalled = true; @@ -5126,8 +5141,8 @@ void AbstractSceneConverterTest::addImageLevels3DView() { CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image3DCount(), 0); CORRADE_COMPARE(converter.add({ - ImageView3D{PixelFormat::RG8Snorm, {1, 1, 1}, imageData}, - ImageView3D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, {1, 3, 1}, imageData} + ImageView3D{PixelFormat::RG8Snorm, {1, 1, 1}, imageData, ImageFlag3D::Array}, + ImageView3D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, {1, 3, 1}, imageData, ImageFlag3D::Array} }, "hello"), 0); CORRADE_VERIFY(converter.addCalled); CORRADE_COMPARE(converter.image3DCount(), 1); @@ -5150,6 +5165,7 @@ void AbstractSceneConverterTest::addImageLevels3DCompressedView() { CORRADE_COMPARE(imageLevels[1].compressedStorage().compressedBlockSize(), Vector3i{3}); CORRADE_COMPARE(imageLevels[1].compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); CORRADE_COMPARE(imageLevels[1].size(), (Vector3i{3, 1, 1})); + CORRADE_COMPARE(imageLevels[1].flags(), ImageFlag3D::Array); CORRADE_VERIFY(imageLevels[1].data()); addCalled = true; @@ -5164,8 +5180,8 @@ void AbstractSceneConverterTest::addImageLevels3DCompressedView() { CORRADE_VERIFY(converter.begin()); CORRADE_COMPARE(converter.image3DCount(), 0); CORRADE_COMPARE(converter.add({ - CompressedImageView3D{CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 1, 1}, imageData}, - CompressedImageView3D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 1, 1}, imageData} + CompressedImageView3D{CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 1, 1}, imageData, ImageFlag3D::Array}, + CompressedImageView3D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 1, 1}, imageData, ImageFlag3D::Array} }, "hello"), 0); CORRADE_VERIFY(converter.addCalled); CORRADE_COMPARE(converter.image3DCount(), 1); From ffba3e26431e9ded02e9f573a6ec83100d13acce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 1 Sep 2022 12:45:44 +0200 Subject: [PATCH 43/93] AnySceneImporter: explicitly verify delegating *.glb files as well. The case is so common that it's worth to have it checked. --- src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp index 7091c7edb..56d79fe6a 100644 --- a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp +++ b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp @@ -91,6 +91,7 @@ constexpr struct { {"3MF", "print.3mf", "3mfImporter"}, {"FBX", "autodesk.fbx", "FbxImporter"}, {"glTF", "khronos.gltf", "GltfImporter"}, + {"glTF binary", "khronos.glb", "GltfImporter"}, {"OpenGEX", "eric.ogex", "OpenGexImporter"}, {"Stanford PLY", "bunny.ply", "StanfordImporter"}, {"Stanford PLY uppercase", "ARMADI~1.PLY", "StanfordImporter"}, From 213e80426d1a13c7d6f378943b48fbaf0a494ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 1 Sep 2022 12:46:41 +0200 Subject: [PATCH 44/93] AnySceneConverter: recognize glTF files. --- doc/changelog.dox | 1 + .../AnySceneConverter/AnySceneConverter.cpp | 10 ++++++++-- .../AnySceneConverter/AnySceneConverter.h | 2 ++ .../AnySceneConverter/Test/AnySceneConverterTest.cpp | 4 ++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index b40b2014e..15ab0f554 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -539,6 +539,7 @@ See also: [mosra/magnum#529](https://github.com/mosra/magnum/pull/529)) - Recognizing KTX2 for (compressed) 1D/2D/3D and multi-level 1D/2D/3D images in @relativeref{Trade,AnyImageConverter} +- Recognizing glTF files in @relativeref{Trade,AnySceneConverter} - Recognizing 3MF files in @relativeref{Trade,AnySceneImporter} - Recognizing OpenVBD files in @relativeref{Trade,AnyImageImporter} and @relativeref{Trade,AnyImageConverter} diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp index 747350b79..84c5d126e 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp @@ -83,7 +83,10 @@ bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers:: /* Detect the plugin from extension */ Containers::StringView plugin; - if(normalizedExtension == ".ply"_s) + if(normalizedExtension == ".gltf"_s || + normalizedExtension == ".glb"_s) + plugin = "GltfSceneConverter"_s; + else if(normalizedExtension == ".ply"_s) plugin = "StanfordSceneConverter"_s; else { Error{} << "Trade::AnySceneConverter::convertToFile(): cannot determine the format of" << filename; @@ -132,7 +135,10 @@ bool AnySceneConverter::doBeginFile(const Containers::StringView filename) { /* Detect the plugin from extension */ Containers::StringView plugin; - if(normalizedExtension == ".ply"_s) + if(normalizedExtension == ".gltf"_s || + normalizedExtension == ".glb"_s) + plugin = "GltfSceneConverter"_s; + else if(normalizedExtension == ".ply"_s) plugin = "StanfordSceneConverter"_s; else { Error{} << "Trade::AnySceneConverter::beginFile(): cannot determine the format of" << filename; diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h index d11296579..8fa18d800 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h @@ -60,6 +60,8 @@ namespace Magnum { namespace Trade { Detects file type based on file extension, loads corresponding plugin and then tries to convert the file with it. Supported formats: +- glTF (`*.gltf`, `*.glb`), converted with @ref GltfSceneConverter or any + other plugin that provides it - Stanford (`*.ply`), converted with @ref StanfordSceneConverter or any other plugin that provides it diff --git a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp index 1b65dda77..7f2526e30 100644 --- a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp +++ b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp @@ -73,6 +73,8 @@ constexpr struct { const char* filename; const char* plugin; } DetectConvertData[]{ + {"glTF", "khronos.gltf", "GltfSceneConverter"}, + {"glTF binary", "khronos.glb", "GltfSceneConverter"}, {"Stanford PLY", "bunny.ply", "StanfordSceneConverter"}, /* Have at least one test case with uppercase */ {"Stanford PLY uppercase", "ARMADI~1.PLY", "StanfordSceneConverter"} @@ -83,6 +85,8 @@ constexpr struct { const char* filename; const char* plugin; } DetectBeginEndData[]{ + {"glTF", "khronos.gltf", "GltfSceneConverter"}, + {"glTF binary", "khronos.glb", "GltfSceneConverter"}, {"Stanford PLY", "bunny.ply", "StanfordSceneConverter"}, /* Have at least one test case with uppercase */ {"Stanford PLY uppercase", "ARMADI~1.PLY", "StanfordSceneConverter"} From 65f3d81d103f0785c890a1799ad18abdef76e29b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 1 Sep 2022 12:47:46 +0200 Subject: [PATCH 45/93] doc: list GltfSceneConverter in the file format tables. Marking it as having "some" caveats right now, will change it to none once the remaining features like camera & light export are added and once the mesh buffer views get more compact. --- doc/file-formats.dox | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/file-formats.dox b/doc/file-formats.dox index 1fabe99f0..192547e23 100644 --- a/doc/file-formats.dox +++ b/doc/file-formats.dox @@ -583,6 +583,16 @@ Derived from @ref Trade::AbstractSceneConverter. @m_span{m-text m-dim} none @m_endspan + + + +glTF (`*.gltf`, `*.glb`) +`GltfSceneConverter` +@relativeref{Trade,GltfSceneConverter} +@ref Trade-GltfSceneConverter-behavior "some" +@m_span{m-text m-dim} none @m_endspan + + @endparblock From 4d8fe7780933b19d3ad7555b74e25070865700df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 5 Sep 2022 19:01:59 +0200 Subject: [PATCH 46/93] Use a more concrete message prefix in MAGNUM_ASSERT_*_SUPPORTED() macros. --- src/Magnum/Audio/Context.h | 2 +- src/Magnum/GL/Context.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Magnum/Audio/Context.h b/src/Magnum/Audio/Context.h index 5458aeacc..c449b7bb6 100644 --- a/src/Magnum/Audio/Context.h +++ b/src/Magnum/Audio/Context.h @@ -565,7 +565,7 @@ Example usage: #define MAGNUM_ASSERT_AUDIO_EXTENSION_SUPPORTED(extension) \ do { \ if(!Magnum::Audio::Context::current().isExtensionSupported()) { \ - Corrade::Utility::Error() << "Magnum: required OpenAL extension" << extension::string() << "is not supported"; \ + Corrade::Utility::Error{} << "Magnum::Audio: required OpenAL extension" << extension::string() << "is not supported"; \ std::abort(); \ } \ } while(0) diff --git a/src/Magnum/GL/Context.h b/src/Magnum/GL/Context.h index 2ba235f44..862f771df 100644 --- a/src/Magnum/GL/Context.h +++ b/src/Magnum/GL/Context.h @@ -1192,7 +1192,7 @@ Example usage: #define MAGNUM_ASSERT_GL_VERSION_SUPPORTED(version) \ do { \ if(!Magnum::GL::Context::current().isVersionSupported(version)) { \ - Corrade::Utility::Error() << "Magnum: required version" << version << "is not supported"; \ + Corrade::Utility::Error{} << "Magnum::GL: required version" << version << "is not supported"; \ std::abort(); \ } \ } while(0) @@ -1221,7 +1221,7 @@ Example usage: #define MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(extension) \ do { \ if(!Magnum::GL::Context::current().isExtensionSupported()) { \ - Corrade::Utility::Error() << "Magnum: required extension" << extension::string() << "is not supported"; \ + Corrade::Utility::Error{} << "Magnum::GL: required extension" << extension::string() << "is not supported"; \ std::abort(); \ } \ } while(0) From d605b06b91d5c411c9ae0863b2560bd1ae4dd9ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 5 Sep 2022 19:02:55 +0200 Subject: [PATCH 47/93] Shaders,Text: fix some Engrish. --- src/Magnum/Shaders/DistanceFieldVectorGL.h | 2 +- src/Magnum/Shaders/VectorGL.h | 2 +- src/Magnum/Text/AbstractFont.h | 10 +++++----- src/Magnum/Text/DistanceFieldGlyphCache.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.h b/src/Magnum/Shaders/DistanceFieldVectorGL.h index 1be8f0465..42f21524f 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.h +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.h @@ -554,7 +554,7 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector */ /** - * @brief Bind vector texture + * @brief Bind a vector texture * @return Reference to self (for method chaining) * * @see @ref DistanceFieldVectorGL::Flag::TextureTransformation, diff --git a/src/Magnum/Shaders/VectorGL.h b/src/Magnum/Shaders/VectorGL.h index e1157ba9d..3b8519fa3 100644 --- a/src/Magnum/Shaders/VectorGL.h +++ b/src/Magnum/Shaders/VectorGL.h @@ -509,7 +509,7 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL */ /** - * @brief Bind vector texture + * @brief Bind a vector texture * @return Reference to self (for method chaining) * * @see @ref Flag::TextureTransformation, @ref setTextureMatrix()s diff --git a/src/Magnum/Text/AbstractFont.h b/src/Magnum/Text/AbstractFont.h index 5d7b34f18..90dadf437 100644 --- a/src/Magnum/Text/AbstractFont.h +++ b/src/Magnum/Text/AbstractFont.h @@ -312,7 +312,7 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { bool isOpened() const { return doIsOpened(); } /** - * @brief Open font from raw data + * @brief Open raw data * @param data File data * @param size Font size * @@ -339,7 +339,7 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { #endif /** - * @brief Open font from file + * @brief Open a file * @param filename Font file * @param size Font size * @@ -349,7 +349,7 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { */ bool openFile(const std::string& filename, Float size); - /** @brief Close font */ + /** @brief Close currently opened file */ void close(); /** @@ -592,11 +592,11 @@ class MAGNUM_TEXT_EXPORT AbstractLayouter { /** @brief Moving is not allowed */ AbstractLayouter& operator=(const AbstractLayouter&&) = delete; - /** @brief Count of glyphs in laid out text */ + /** @brief Count of glyphs in the laid out text */ UnsignedInt glyphCount() const { return _glyphCount; } /** - * @brief Render glyph + * @brief Render a glyph * @param i Glyph index * @param cursorPosition Cursor position * @param rectangle Bounding rectangle diff --git a/src/Magnum/Text/DistanceFieldGlyphCache.h b/src/Magnum/Text/DistanceFieldGlyphCache.h index 430269708..944d54655 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCache.h +++ b/src/Magnum/Text/DistanceFieldGlyphCache.h @@ -77,7 +77,7 @@ class MAGNUM_TEXT_EXPORT DistanceFieldGlyphCache: public GlyphCache { explicit DistanceFieldGlyphCache(const Vector2i& originalSize, const Vector2i& size, UnsignedInt radius); /** - * @brief Set distance field cache image + * @brief Set a distance field cache image * * Uploads already computed distance field image to given offset in * distance field texture. From 4b2de39396c864a897bb5185acdd319bc60e9cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 5 Sep 2022 19:24:51 +0200 Subject: [PATCH 48/93] singles: update MagnumMath docs with what CorradeCpu has already. In particular: - listing the *very important* stuff -- such as what macro to define to make the library usable at all -- in the header - mentioning what to do if cross-SO/DLL visibility is desired The actual generated single-header file will be updated once I find more time. Not now. --- doc/namespaces.dox | 6 ++++-- src/singles/MagnumMath.hpp | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/doc/namespaces.dox b/doc/namespaces.dox index a3927ea5a..361294b9d 100644 --- a/doc/namespaces.dox +++ b/doc/namespaces.dox @@ -109,8 +109,10 @@ more information. #include @endcode @par - In addition, contents of the @ref GlmIntegration and @ref EigenIntegration - libraries are included as well --- opt-in by specifying either + If you need the deinlined symbols to be exported from a shared library, + @cpp #define MAGNUM_EXPORT @ce as appropriate. In addition, contents of the + @ref GlmIntegration and @ref EigenIntegration libraries are included as + well --- opt-in by specifying either @cpp #define MAGNUM_MATH_GLM_INTEGRATION @ce or @cpp #define MAGNUM_MATH_EIGEN_INTEGRATION @ce before including the file. Including it multiple times with different macros defined works as well. diff --git a/src/singles/MagnumMath.hpp b/src/singles/MagnumMath.hpp index 3ab46ddc8..fab3cb5f5 100644 --- a/src/singles/MagnumMath.hpp +++ b/src/singles/MagnumMath.hpp @@ -15,6 +15,19 @@ - GitHub project page — https://github.com/mosra/magnum - GitHub Singles repository — https://github.com/mosra/magnum-singles + The library has a separate non-inline implementation part, enable it *just + once* like this: + + #define MAGNUM_MATH_IMPLEMENTATION + #include + + If you need the deinlined symbols to be exported from a shared library, + `#define MAGNUM_EXPORT` as appropriate. In addition, contents of the + GlmIntegration and EigenIntegration libraries are included as well --- + opt-in by specifying either `#define MAGNUM_MATH_GLM_INTEGRATION` or + `#define MAGNUM_MATH_EIGEN_INTEGRATION` before including the file. + Including it multiple times with different macros defined works as well. + v2020.06-0-gfac6f4da2 (2020-06-27) - Various fixes for Clang-CL compatibility - Expanding the APIs to work with Half and long double types From 96f97d4e7a28700b595067820a60b9c3e93281e8 Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Fri, 8 Jul 2022 22:01:09 +0300 Subject: [PATCH 49/93] GL: implement APIs for async shader compilation & linking. --- src/Magnum/GL/AbstractShaderProgram.cpp | 88 +++++++------ src/Magnum/GL/AbstractShaderProgram.h | 53 ++++++-- .../GL/Implementation/ShaderProgramState.cpp | 7 + .../GL/Implementation/ShaderProgramState.h | 3 + src/Magnum/GL/Implementation/ShaderState.cpp | 12 +- src/Magnum/GL/Implementation/ShaderState.h | 3 + src/Magnum/GL/Shader.cpp | 113 ++++++++-------- src/Magnum/GL/Shader.h | 42 +++++- .../GL/Test/AbstractShaderProgramGLTest.cpp | 123 ++++++++++++++++++ src/Magnum/GL/Test/ShaderGLTest.cpp | 88 ++++++++++++- 10 files changed, 410 insertions(+), 122 deletions(-) diff --git a/src/Magnum/GL/AbstractShaderProgram.cpp b/src/Magnum/GL/AbstractShaderProgram.cpp index aab585448..c551d876b 100644 --- a/src/Magnum/GL/AbstractShaderProgram.cpp +++ b/src/Magnum/GL/AbstractShaderProgram.cpp @@ -585,53 +585,55 @@ void AbstractShaderProgram::transformFeedbackVaryingsImplementationDanglingWorka bool AbstractShaderProgram::link() { return link({*this}); } -bool AbstractShaderProgram::link(std::initializer_list> shaders) { - bool allSuccess = true; +void AbstractShaderProgram::submitLink() { + glLinkProgram(_id); +} + +bool AbstractShaderProgram::checkLink() { + GLint success, logLength; + glGetProgramiv(_id, GL_LINK_STATUS, &success); + glGetProgramiv(_id, GL_INFO_LOG_LENGTH, &logLength); - /* Invoke (possibly parallel) linking on all shaders */ - for(AbstractShaderProgram& shader: shaders) glLinkProgram(shader._id); - - /* After linking phase, check status of all shaders */ - Int i = 1; - for(AbstractShaderProgram& shader: shaders) { - GLint success, logLength; - glGetProgramiv(shader._id, GL_LINK_STATUS, &success); - glGetProgramiv(shader._id, GL_INFO_LOG_LENGTH, &logLength); - - /* Error or warning message. The string is returned null-terminated, - strip the \0 at the end afterwards. */ - std::string message(logLength, '\n'); - if(message.size() > 1) - glGetProgramInfoLog(shader._id, message.size(), nullptr, &message[0]); - message.resize(Math::max(logLength, 1)-1); - - /* Some drivers are chatty and can't keep shut when there's nothing to - be said, handle that as well. */ - Context::current().state().shaderProgram.cleanLogImplementation(message); - - /* Show error log */ - if(!success) { - Error out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::AbstractShaderProgram::link(): linking"; - if(shaders.size() != 1) out << "of shader" << i; - out << "failed with the following message:" << Debug::newline << message; - - /* Or just warnings, if any */ - } else if(!message.empty()) { - Warning out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::AbstractShaderProgram::link(): linking"; - if(shaders.size() != 1) out << "of shader" << i; - out << "succeeded with the following message:" << Debug::newline << message; - } - - /* Success of all depends on each of them */ - allSuccess = allSuccess && success; - ++i; + /* Error or warning message. The string is returned null-terminated, + strip the \0 at the end afterwards. */ + std::string message(logLength, '\n'); + if(message.size() > 1) + glGetProgramInfoLog(_id, message.size(), nullptr, &message[0]); + message.resize(Math::max(logLength, 1)-1); + + /* Some drivers are chatty and can't keep shut when there's nothing to + be said, handle that as well. */ + Context::current().state().shaderProgram.cleanLogImplementation(message); + + /* Show error log */ + if(!success) { + Error out{Debug::Flag::NoNewlineAtTheEnd}; + out << "GL::AbstractShaderProgram::link(): linking failed with the following message:" + << Debug::newline << message; + + /* Or just warnings, if any */ + } else if(!message.empty()) { + Warning out{Debug::Flag::NoNewlineAtTheEnd}; + out << "GL::AbstractShaderProgram::link(): linking succeeded with the following message:" + << Debug::newline << message; } + return success; +} + +bool AbstractShaderProgram::link(std::initializer_list> shaders) { + for(AbstractShaderProgram& shader: shaders) shader.submitLink(); + bool allSuccess = true; + for(AbstractShaderProgram& shader: shaders) allSuccess = allSuccess && shader.checkLink(); return allSuccess; } +bool AbstractShaderProgram::isLinkFinished() { + GLint success; + Context::current().state().shaderProgram.completionStatusImplementation(_id, GL_COMPLETION_STATUS_KHR, &success); + return success == GL_TRUE; +} + void AbstractShaderProgram::cleanLogImplementationNoOp(std::string&) {} #if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES) @@ -646,6 +648,10 @@ void AbstractShaderProgram::cleanLogImplementationAngle(std::string& message) { } #endif +void AbstractShaderProgram::completionStatusImplementationFallback(GLuint, GLenum, GLint* value) { + *value = GL_TRUE; +} + Int AbstractShaderProgram::uniformLocationInternal(const Containers::ArrayView name) { const GLint location = glGetUniformLocation(_id, name); if(location == -1) diff --git a/src/Magnum/GL/AbstractShaderProgram.h b/src/Magnum/GL/AbstractShaderProgram.h index 01b4429ab..a7ad8c5e1 100644 --- a/src/Magnum/GL/AbstractShaderProgram.h +++ b/src/Magnum/GL/AbstractShaderProgram.h @@ -1258,19 +1258,26 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { AbstractShaderProgram& dispatchCompute(const Vector3ui& workgroupCount); #endif - protected: /** - * @brief Link the shader + * @brief Non-blocking linking status check + * @return @cpp true @ce if linking finished, @cpp false @ce otherwise + * + * On some drivers this might return false even after + * @ref checkLink() reported successful linking. * + * @see @fn_gl_keyword{GetProgram} with + * @def_gl_extension{COMPLETION_STATUS,KHR,parallel_shader_compile} + */ + bool isLinkFinished(); + + protected: + /** @brief Link the shaders + * + * Calls @ref submitLink() on all shaders first, then @ref checkLink(). * Returns @cpp false @ce if linking of any shader failed, @cpp true @ce - * if everything succeeded. Linker message (if any) is printed to error - * output. All attached shaders must be compiled with - * @ref Shader::compile() before linking. The operation is batched in a + * if everything succeeded. The operation is batched in a * way that allows the driver to link multiple shaders simultaneously * (i.e. in multiple threads). - * @see @fn_gl_keyword{LinkProgram}, @fn_gl_keyword{GetProgram} with - * @def_gl{LINK_STATUS} and @def_gl{INFO_LOG_LENGTH}, - * @fn_gl_keyword{GetProgramInfoLog} */ static bool link(std::initializer_list> shaders); @@ -1446,13 +1453,37 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { /** * @brief Link the shader * - * Links single shader. If possible, prefer to link multiple shaders - * at once using @ref link(std::initializer_list>) + * Calls @ref submitLink(), then @ref checkLink(). + * If possible, prefer to link multiple shaders at once using + * @ref link(std::initializer_list>) * for improved performance, see its documentation for more * information. */ bool link(); + /** + * @brief Submit for linking + * + * The attached shaders must be compiled with @ref Shader::compile() + * or @ref Shader::submitCompile() before linking. + * + * @see @fn_gl_keyword{LinkProgram} + */ + void submitLink(); + + /** + * @brief Check link status and await completion + * + * Returns @cpp false @ce if linking failed, @cpp true @ce on success. + * Linker message (if any) is printed to error output. This function + * must be called only after @ref submitLink(). + * + * @see @fn_gl_keyword{GetProgram} with + * @def_gl{LINK_STATUS} and @def_gl{INFO_LOG_LENGTH}, + * @fn_gl_keyword{GetProgramInfoLog} + */ + bool checkLink(); + /** * @brief Get uniform location * @param name Uniform name @@ -1668,6 +1699,8 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { static MAGNUM_GL_LOCAL void cleanLogImplementationAngle(std::string& message); #endif + MAGNUM_GL_LOCAL static void APIENTRY completionStatusImplementationFallback(GLuint, GLenum, GLint*); + MAGNUM_GL_LOCAL static void use(GLuint id); void use(); diff --git a/src/Magnum/GL/Implementation/ShaderProgramState.cpp b/src/Magnum/GL/Implementation/ShaderProgramState.cpp index 1e3125560..9799fc087 100644 --- a/src/Magnum/GL/Implementation/ShaderProgramState.cpp +++ b/src/Magnum/GL/Implementation/ShaderProgramState.cpp @@ -78,6 +78,13 @@ ShaderProgramState::ShaderProgramState(Context& context, Containers::StaticArray cleanLogImplementation = &AbstractShaderProgram::cleanLogImplementationNoOp; } + if(context.isExtensionSupported()) { + extensions[Extensions::KHR::parallel_shader_compile::Index] = Extensions::KHR::parallel_shader_compile::string(); + completionStatusImplementation = glGetProgramiv; + } else { + completionStatusImplementation = &AbstractShaderProgram::completionStatusImplementationFallback; + } + #ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES diff --git a/src/Magnum/GL/Implementation/ShaderProgramState.h b/src/Magnum/GL/Implementation/ShaderProgramState.h index 01f131b8f..95001ea80 100644 --- a/src/Magnum/GL/Implementation/ShaderProgramState.h +++ b/src/Magnum/GL/Implementation/ShaderProgramState.h @@ -47,6 +47,9 @@ struct ShaderProgramState { void(AbstractShaderProgram::*transformFeedbackVaryingsImplementation)(Containers::ArrayView, AbstractShaderProgram::TransformFeedbackBufferMode); #endif void(*cleanLogImplementation)(std::string&); + /* This is a direct pointer to a GL function, so needs a __stdcall on + Windows to compile properly on 32 bits */ + void(APIENTRY *completionStatusImplementation)(GLuint, GLenum, GLint* value); #ifndef MAGNUM_TARGET_WEBGL void(APIENTRY *uniform1fvImplementation)(GLuint, GLint, GLsizei, const GLfloat*); diff --git a/src/Magnum/GL/Implementation/ShaderState.cpp b/src/Magnum/GL/Implementation/ShaderState.cpp index ffc0c2f1e..17758a801 100644 --- a/src/Magnum/GL/Implementation/ShaderState.cpp +++ b/src/Magnum/GL/Implementation/ShaderState.cpp @@ -31,12 +31,13 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Shader.h" +#include "Magnum/GL/Extensions.h" namespace Magnum { namespace GL { namespace Implementation { using namespace Containers::Literals; -ShaderState::ShaderState(Context& context, Containers::StaticArrayView): +ShaderState::ShaderState(Context& context, Containers::StaticArrayView extensions): maxVertexOutputComponents{}, maxFragmentInputComponents{}, #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) maxTessellationControlInputComponents{}, maxTessellationControlOutputComponents{}, maxTessellationControlTotalOutputComponents{}, maxTessellationEvaluationInputComponents{}, maxTessellationEvaluationOutputComponents{}, maxGeometryInputComponents{}, maxGeometryOutputComponents{}, maxGeometryTotalOutputComponents{}, maxAtomicCounterBuffers{}, maxCombinedAtomicCounterBuffers{}, maxAtomicCounters{}, maxCombinedAtomicCounters{}, maxImageUniforms{}, maxCombinedImageUniforms{}, maxShaderStorageBlocks{}, maxCombinedShaderStorageBlocks{}, @@ -68,9 +69,12 @@ ShaderState::ShaderState(Context& context, Containers::StaticArrayView(context); + if(context.isExtensionSupported()) { + extensions[Extensions::KHR::parallel_shader_compile::Index] = Extensions::KHR::parallel_shader_compile::string(); + completionStatusImplementation = glGetShaderiv; + } else { + completionStatusImplementation = &Shader::completionStatusImplementationFallback; + } } }}} diff --git a/src/Magnum/GL/Implementation/ShaderState.h b/src/Magnum/GL/Implementation/ShaderState.h index 3eef9f0b5..4d37c764d 100644 --- a/src/Magnum/GL/Implementation/ShaderState.h +++ b/src/Magnum/GL/Implementation/ShaderState.h @@ -53,6 +53,9 @@ struct ShaderState { void(Shader::*addSourceImplementation)(std::string); void(*cleanLogImplementation)(std::string&); + /* This is a direct pointer to a GL function, so needs a __stdcall on + Windows to compile properly on 32 bits */ + void(APIENTRY *completionStatusImplementation)(GLuint, GLenum, GLint* value); GLint maxVertexOutputComponents, maxFragmentInputComponents; diff --git a/src/Magnum/GL/Shader.cpp b/src/Magnum/GL/Shader.cpp index 8708fa4b9..8b7bbb98b 100644 --- a/src/Magnum/GL/Shader.cpp +++ b/src/Magnum/GL/Shader.cpp @@ -749,74 +749,69 @@ Shader& Shader::addFile(const std::string& filename) { bool Shader::compile() { return compile({*this}); } -bool Shader::compile(std::initializer_list> shaders) { - bool allSuccess = true; +void Shader::submitCompile() { + CORRADE_ASSERT(_sources.size() > 1, "GL::Shader::compile(): no files added", ); - /* Allocate large enough array for source pointers and sizes (to avoid - reallocating it for each of them) */ - std::size_t maxSourceCount = 0; - for(Shader& shader: shaders) { - CORRADE_ASSERT(shader._sources.size() > 1, "GL::Shader::compile(): no files added", false); - maxSourceCount = Math::max(shader._sources.size(), maxSourceCount); - } /** @todo ArrayTuple/VLAs */ - Containers::Array pointers(maxSourceCount); - Containers::Array sizes(maxSourceCount); + Containers::Array pointers(_sources.size()); + Containers::Array sizes(_sources.size()); /* Upload sources of all shaders */ - for(Shader& shader: shaders) { - for(std::size_t i = 0; i != shader._sources.size(); ++i) { - pointers[i] = static_cast(shader._sources[i].data()); - sizes[i] = shader._sources[i].size(); - } - - glShaderSource(shader._id, shader._sources.size(), pointers, sizes); + for(std::size_t i = 0; i != _sources.size(); ++i) { + pointers[i] = static_cast(_sources[i].data()); + sizes[i] = _sources[i].size(); } - /* Invoke (possibly parallel) compilation on all shaders */ - for(Shader& shader: shaders) glCompileShader(shader._id); - - /* After compilation phase, check status of all shaders */ - Int i = 1; - for(Shader& shader: shaders) { - GLint success, logLength; - glGetShaderiv(shader._id, GL_COMPILE_STATUS, &success); - glGetShaderiv(shader._id, GL_INFO_LOG_LENGTH, &logLength); - - /* Error or warning message. The string is returned null-terminated, - strip the \0 at the end afterwards. */ - std::string message(logLength, '\0'); - if(message.size() > 1) - glGetShaderInfoLog(shader._id, message.size(), nullptr, &message[0]); - message.resize(Math::max(logLength, 1)-1); - - /* Some drivers are chatty and can't keep shut when there's nothing to - be said, handle that as well. */ - Context::current().state().shader.cleanLogImplementation(message); - - /* Show error log */ - if(!success) { - Error out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::Shader::compile(): compilation of" << shaderName(shader._type) << "shader"; - if(shaders.size() != 1) out << i; - out << "failed with the following message:" << Debug::newline << message; - - /* Or just warnings, if any */ - } else if(!message.empty()) { - Warning out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::Shader::compile(): compilation of" << shaderName(shader._type) << "shader"; - if(shaders.size() != 1) out << i; - out << "succeeded with the following message:" << Debug::newline << message; - } - - /* Success of all depends on each of them */ - allSuccess = allSuccess && success; - ++i; + glShaderSource(_id, _sources.size(), pointers, sizes); + glCompileShader(_id); +} + +bool Shader::checkCompile() { /* After compilation phase, check status of all shaders */ + GLint success, logLength; + glGetShaderiv(_id, GL_COMPILE_STATUS, &success); + glGetShaderiv(_id, GL_INFO_LOG_LENGTH, &logLength); + + /* Error or warning message. The string is returned null-terminated, + strip the \0 at the end afterwards. */ + std::string message(logLength, '\0'); + if(message.size() > 1) + glGetShaderInfoLog(_id, message.size(), nullptr, &message[0]); + message.resize(Math::max(logLength, 1)-1); + + /* Some drivers are chatty and can't keep shut when there's nothing to + be said, handle that as well. */ + Context::current().state().shader.cleanLogImplementation(message); + + /* Show error log */ + if(!success) { + Error out{Debug::Flag::NoNewlineAtTheEnd}; + out << "GL::Shader::compile(): compilation of" << shaderName(_type) << "shader" + << "failed with the following message:" << Debug::newline << message; + + /* Or just warnings, if any */ + } else if(!message.empty()) { + Warning out{Debug::Flag::NoNewlineAtTheEnd}; + out << "GL::Shader::compile(): compilation of" << shaderName(_type) << "shader" + << "succeeded with the following message:" << Debug::newline << message; } + return success; +} + +bool Shader::compile(std::initializer_list> shaders) { + /* Invoke (possibly parallel) compilation on all shaders */ + for(Shader& shader: shaders) shader.submitCompile(); + bool allSuccess = true; + for(Shader& shader: shaders) allSuccess = allSuccess && shader.checkCompile(); return allSuccess; } +bool Shader::isCompileFinished() { + GLint success; + Context::current().state().shader.completionStatusImplementation(_id, GL_COMPLETION_STATUS_KHR, &success); + return success == GL_TRUE; +} + void Shader::cleanLogImplementationNoOp(std::string&) {} #if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES) @@ -825,6 +820,10 @@ void Shader::cleanLogImplementationIntelWindows(std::string& message) { } #endif +void Shader::completionStatusImplementationFallback(GLuint, GLenum, GLint* value) { + *value = GL_TRUE; +} + #ifndef DOXYGEN_GENERATING_OUTPUT Debug& operator<<(Debug& debug, const Shader::Type value) { debug << "GL::Shader::Type" << Debug::nospace; diff --git a/src/Magnum/GL/Shader.h b/src/Magnum/GL/Shader.h index 99984dc64..ee3ded533 100644 --- a/src/Magnum/GL/Shader.h +++ b/src/Magnum/GL/Shader.h @@ -510,14 +510,11 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { /** * @brief Compile multiple shaders simultaneously * + * Calls @ref submitCompile() on all shaders first, then @ref checkCompile(). * Returns @cpp false @ce if compilation of any shader failed, - * @cpp true @ce if everything succeeded. Compiler messages (if any) - * are printed to error output. The operation is batched in a way that + * @cpp true @ce if everything succeeded. The operation is batched in a way that * allows the driver to perform multiple compilations simultaneously * (i.e. in multiple threads). - * @see @fn_gl_keyword{ShaderSource}, @fn_gl_keyword{CompileShader}, - * @fn_gl_keyword{GetShader} with @def_gl{COMPILE_STATUS} and - * @def_gl{INFO_LOG_LENGTH}, @fn_gl_keyword{GetShaderInfoLog} */ static bool compile(std::initializer_list> shaders); @@ -636,13 +633,42 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { /** * @brief Compile shader * - * Compiles single shader. Prefer to compile multiple shaders at once - * using @ref compile(std::initializer_list>) + * Calls @ref submitCompile(), then @ref checkCompile(). + * Prefer to compile multiple shaders at once using + * @ref compile(std::initializer_list>) * for improved performance, see its documentation for more * information. */ bool compile(); + /** + * @brief Submit shader for compilation + * + * @see @fn_gl_keyword{ShaderSource}, @fn_gl_keyword{CompileShader} + */ + void submitCompile(); + + /** + * @brief Check compilation status and await completion + * + * Returns @cpp false @ce if compilation of failed, @cpp true @ce on success. + * This function must be called only after @ref submitCompile(). + * + * @see @fn_gl_keyword{GetShader} with @def_gl{COMPILE_STATUS} and + * @def_gl{INFO_LOG_LENGTH}, @fn_gl_keyword{GetShaderInfoLog} + */ + bool checkCompile(); + + /** + * @brief Non-blocking compilation status check + * @return @cpp true @ce if shader compilation finished, @cpp false @ce otherwise + * + * @see @fn_gl_keyword{GetProgram} with + * @def_gl_extension{COMPLETION_STATUS,KHR,parallel_shader_compile} + * + */ + bool isCompileFinished(); + private: void MAGNUM_GL_LOCAL addSourceImplementationDefault(std::string source); #if defined(CORRADE_TARGET_EMSCRIPTEN) && defined(__EMSCRIPTEN_PTHREADS__) @@ -654,6 +680,8 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { static MAGNUM_GL_LOCAL void cleanLogImplementationIntelWindows(std::string& message); #endif + MAGNUM_GL_LOCAL static void APIENTRY completionStatusImplementationFallback(GLuint, GLenum, GLint*); + Type _type; GLuint _id; diff --git a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp index 658fa112e..e378aca0e 100644 --- a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp +++ b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp @@ -27,8 +27,10 @@ #include #include /** @todo remove when Shader is -free */ #include +#include #include #include +#include #include "Magnum/Image.h" #include "Magnum/ImageView.h" @@ -68,8 +70,11 @@ struct AbstractShaderProgramGLTest: OpenGLTester { #ifndef MAGNUM_TARGET_GLES void createMultipleOutputsIndexed(); #endif + void createAsync(); void linkFailure(); + void linkFailureAsync(); + void uniformNotFound(); void uniform(); @@ -103,12 +108,14 @@ AbstractShaderProgramGLTest::AbstractShaderProgramGLTest() { #endif &AbstractShaderProgramGLTest::create, + &AbstractShaderProgramGLTest::createAsync, &AbstractShaderProgramGLTest::createMultipleOutputs, #ifndef MAGNUM_TARGET_GLES &AbstractShaderProgramGLTest::createMultipleOutputsIndexed, #endif &AbstractShaderProgramGLTest::linkFailure, + &AbstractShaderProgramGLTest::linkFailureAsync, &AbstractShaderProgramGLTest::uniformNotFound, &AbstractShaderProgramGLTest::uniform, @@ -205,6 +212,8 @@ struct MyPublicShader: AbstractShaderProgram { using AbstractShaderProgram::bindFragmentDataLocation; #endif using AbstractShaderProgram::link; + using AbstractShaderProgram::submitLink; + using AbstractShaderProgram::checkLink; using AbstractShaderProgram::uniformLocation; #ifndef MAGNUM_TARGET_GLES2 using AbstractShaderProgram::uniformBlockIndex; @@ -257,6 +266,80 @@ void AbstractShaderProgramGLTest::create() { MAGNUM_VERIFY_NO_GL_ERROR(); CORRADE_VERIFY(linked); + + // Some drivers need a bit of time to update this result + Utility::System::sleep(200); + CORRADE_VERIFY(program.isLinkFinished()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(valid); + } + + const Int matrixUniform = program.uniformLocation("matrix"); + const Int multiplierUniform = program.uniformLocation("multiplier"); + const Int colorUniform = program.uniformLocation("color"); + const Int additionsUniform = program.uniformLocation("additions"); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(matrixUniform >= 0); + CORRADE_VERIFY(multiplierUniform >= 0); + CORRADE_VERIFY(colorUniform >= 0); + CORRADE_VERIFY(additionsUniform >= 0); +} + +void AbstractShaderProgramGLTest::createAsync() { + Utility::Resource rs("AbstractShaderProgramGLTest"); + + Shader vert( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Vertex); + vert.addSource(rs.getString("MyShader.vert")); + const bool vertCompiled = vert.compile(); + + Shader frag( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Fragment); + frag.addSource(rs.getString("MyShader.frag")); + const bool fragCompiled = frag.compile(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(vertCompiled); + CORRADE_VERIFY(fragCompiled); + + MyPublicShader program; + program.attachShaders({vert, frag}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + program.bindAttributeLocation(0, "position"); + program.submitLink(); + + while(!program.isLinkFinished()) + Utility::System::sleep(100); + + CORRADE_VERIFY(program.checkLink()); + CORRADE_VERIFY(program.isLinkFinished()); + const bool valid = program.validate().first; + + MAGNUM_VERIFY_NO_GL_ERROR(); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); @@ -410,6 +493,46 @@ void AbstractShaderProgramGLTest::linkFailure() { MyPublicShader program; program.attachShaders({shader}); CORRADE_VERIFY(!program.link()); + + Utility::System::sleep(200); + CORRADE_VERIFY(program.isLinkFinished()); +} + +void AbstractShaderProgramGLTest::linkFailureAsync() { + Shader shader( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Fragment); + shader.addSource("[fu] bleh error #:! stuff\n"); + + { + Error redirectError{nullptr}; + CORRADE_VERIFY(!shader.compile()); + } + + MyPublicShader program; + program.attachShaders({shader}); + + std::ostringstream out; + Error redirectError{&out}; + program.submitLink(); + + while(!program.isLinkFinished()) + Utility::System::sleep(100); + + CORRADE_VERIFY(out.str().empty()); + + CORRADE_VERIFY(!program.checkLink()); + CORRADE_VERIFY(program.isLinkFinished()); + CORRADE_COMPARE_AS(out.str(), "GL::AbstractShaderProgram::link(): linking failed with the following message:", + TestSuite::Compare::StringHasPrefix); } void AbstractShaderProgramGLTest::uniformNotFound() { diff --git a/src/Magnum/GL/Test/ShaderGLTest.cpp b/src/Magnum/GL/Test/ShaderGLTest.cpp index 5c7978b77..49ef6cc66 100644 --- a/src/Magnum/GL/Test/ShaderGLTest.cpp +++ b/src/Magnum/GL/Test/ShaderGLTest.cpp @@ -23,8 +23,11 @@ DEALINGS IN THE SOFTWARE. */ +#include #include /** @todo remove once Shader is -free */ +#include #include +#include #include #include "Magnum/GL/Context.h" @@ -55,6 +58,9 @@ struct ShaderGLTest: OpenGLTester { void addSourceNoVersion(); void addFile(); void compile(); + void compileFailure(); + void compileAsync(); + void compileAsyncFailure(); void compileUtf8(); void compileNoVersion(); }; @@ -72,6 +78,9 @@ ShaderGLTest::ShaderGLTest() { &ShaderGLTest::addSourceNoVersion, &ShaderGLTest::addFile, &ShaderGLTest::compile, + &ShaderGLTest::compileFailure, + &ShaderGLTest::compileAsync, + &ShaderGLTest::compileAsyncFailure, &ShaderGLTest::compileUtf8, &ShaderGLTest::compileNoVersion}); } @@ -276,11 +285,84 @@ void ShaderGLTest::compile() { Shader shader(v, Shader::Type::Fragment); shader.addSource("void main() {}\n"); + CORRADE_VERIFY(shader.compile()); + CORRADE_VERIFY(shader.isCompileFinished()); +} + +void ShaderGLTest::compileFailure() { + #ifndef MAGNUM_TARGET_GLES + constexpr Version v = + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + ; + #else + constexpr Version v = Version::GLES200; + #endif + + Shader shader(v, Shader::Type::Fragment); + shader.addSource("[fu] bleh error #:! stuff\n"); + + CORRADE_VERIFY(!shader.compile()); + CORRADE_VERIFY(shader.isCompileFinished()); +} + +void ShaderGLTest::compileAsync() { + #ifndef MAGNUM_TARGET_GLES + constexpr Version v = + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + ; + #else + constexpr Version v = Version::GLES200; + #endif + + Shader shader(v, Shader::Type::Fragment); + shader.addSource("void main() {}\n"); + shader.submitCompile(); + + while(!shader.isCompileFinished()) + Utility::System::sleep(100); + + CORRADE_VERIFY(shader.checkCompile()); + CORRADE_VERIFY(shader.isCompileFinished()); +} + +void ShaderGLTest::compileAsyncFailure() { + #ifndef MAGNUM_TARGET_GLES + constexpr Version v = + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + ; + #else + constexpr Version v = Version::GLES200; + #endif + + Shader shader(v, Shader::Type::Fragment); + shader.addSource("[fu] bleh error #:! stuff\n"); + + std::ostringstream out; + Error redirectError{&out}; + shader.submitCompile(); + + while(!shader.isCompileFinished()) + Utility::System::sleep(100); + + CORRADE_VERIFY(out.str().empty()); - Shader shader2(v, Shader::Type::Fragment); - shader2.addSource("[fu] bleh error #:! stuff\n"); - CORRADE_VERIFY(!shader2.compile()); + CORRADE_VERIFY(!shader.checkCompile()); + CORRADE_VERIFY(shader.isCompileFinished()); + CORRADE_COMPARE_AS(out.str(), "GL::Shader::compile(): compilation of fragment shader failed with the following message:", + TestSuite::Compare::StringHasPrefix); } void ShaderGLTest::compileUtf8() { From eb17d775625439292eab029d71ce658b4d2ae143 Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Fri, 8 Jul 2022 22:01:09 +0300 Subject: [PATCH 50/93] Shaders: implement async shader compilation for all shaders. --- src/Magnum/Shaders/DistanceFieldVectorGL.cpp | 69 ++-- src/Magnum/Shaders/DistanceFieldVectorGL.h | 31 ++ src/Magnum/Shaders/FlatGL.cpp | 105 +++--- src/Magnum/Shaders/FlatGL.h | 31 ++ src/Magnum/Shaders/MeshVisualizerGL.cpp | 308 ++++++++++-------- src/Magnum/Shaders/MeshVisualizerGL.h | 79 ++++- src/Magnum/Shaders/PhongGL.cpp | 166 ++++++---- src/Magnum/Shaders/PhongGL.h | 30 ++ .../Test/DistanceFieldVectorGLTest.cpp | 67 ++++ src/Magnum/Shaders/Test/FlatGLTest.cpp | 65 ++++ .../Shaders/Test/MeshVisualizerGLTest.cpp | 116 +++++++ src/Magnum/Shaders/Test/PhongGLTest.cpp | 63 +++- src/Magnum/Shaders/Test/VectorGLTest.cpp | 67 ++++ src/Magnum/Shaders/Test/VertexColorGLTest.cpp | 63 +++- src/Magnum/Shaders/VectorGL.cpp | 69 ++-- src/Magnum/Shaders/VectorGL.h | 31 ++ src/Magnum/Shaders/VertexColorGL.cpp | 60 +++- src/Magnum/Shaders/VertexColorGL.h | 30 ++ 18 files changed, 1142 insertions(+), 308 deletions(-) diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp index a33a9386d..6c627cf98 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp @@ -31,7 +31,6 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" -#include "Magnum/GL/Shader.h" #include "Magnum/GL/Texture.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" @@ -63,22 +62,16 @@ namespace { #endif } -template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags +template typename DistanceFieldVectorGL::CompileState DistanceFieldVectorGL::compile(const Flags flags #ifndef MAGNUM_TARGET_GLES2 , const UnsignedInt materialCount, const UnsignedInt drawCount #endif -): - _flags{flags} - #ifndef MAGNUM_TARGET_GLES2 - , _materialCount{materialCount}, - _drawCount{drawCount} - #endif -{ +) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, - "Shaders::DistanceFieldVectorGL: material count can't be zero", ); + "Shaders::DistanceFieldVectorGL: material count can't be zero", CompileState{NoCreate}); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::DistanceFieldVectorGL: draw count can't be zero", ); + "Shaders::DistanceFieldVectorGL: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -142,9 +135,17 @@ template DistanceFieldVectorGL::DistanceFiel frag.addSource(rs.getString("generic.glsl")) .addSource(rs.getString("DistanceFieldVector.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); + + DistanceFieldVectorGL out{NoInit}; + out._flags = flags; + #ifndef MAGNUM_TARGET_GLES2 + out._materialCount = materialCount; + out._drawCount = drawCount; + #endif - attachShaders({vert, frag}); + out.attachShaders({vert, frag}); /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) @@ -152,25 +153,35 @@ template DistanceFieldVectorGL::DistanceFiel if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); - bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); + out.bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + return CompileState{std::move(out), std::move(vert), std::move(frag), version}; +} + +template DistanceFieldVectorGL::DistanceFieldVectorGL(CompileState&& cs): DistanceFieldVectorGL{static_cast(std::move(cs))} { + if (id() == 0) return; + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = cs._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else #endif { _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) _textureMatrixUniform = uniformLocation("textureMatrix"); _colorUniform = uniformLocation("color"); _outlineColorUniform = uniformLocation("outlineColor"); @@ -185,11 +196,11 @@ template DistanceFieldVectorGL::DistanceFiel { setUniform(uniformLocation("vectorTexture"), TextureUnit); #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); } #endif @@ -198,13 +209,13 @@ template DistanceFieldVectorGL::DistanceFiel /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { /* Draw offset is zero by default */ } else #endif { setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setTextureMatrix(Matrix3{Math::IdentityInit}); setColor(Color4{1.0f}); /* Outline color is zero by default */ @@ -212,10 +223,22 @@ template DistanceFieldVectorGL::DistanceFiel setSmoothness(0.04f); } #endif + + static_cast(context); + static_cast(version); } +template DistanceFieldVectorGL::DistanceFieldVectorGL(NoInitT) {} + +template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags): DistanceFieldVectorGL{compile(flags)} {} + #ifndef MAGNUM_TARGET_GLES2 -template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags): DistanceFieldVectorGL{flags, 1, 1} {} +template typename DistanceFieldVectorGL::CompileState DistanceFieldVectorGL::compile(const Flags flags) { + return compile(flags, 1, 1); +} + +template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags, UnsignedInt materialCount, UnsignedInt drawCount): + DistanceFieldVectorGL{compile(flags, materialCount, drawCount)} {} #endif template DistanceFieldVectorGL& DistanceFieldVectorGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.h b/src/Magnum/Shaders/DistanceFieldVectorGL.h index 42f21524f..f46773784 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.h +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.h @@ -32,6 +32,7 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" +#include "Magnum/GL/Shader.h" #include "Magnum/Shaders/GenericGL.h" #include "Magnum/Shaders/visibility.h" @@ -280,6 +281,20 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector */ explicit DistanceFieldVectorGL(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} + class CompileState; + + explicit DistanceFieldVectorGL(CompileState&& cs); + + static CompileState compile(Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , UnsignedInt materialCount, UnsignedInt drawCount + #endif + ); + + #ifndef MAGNUM_TARGET_GLES2 + static CompileState compile(Flags flags); + #endif + /** @brief Copying is not allowed */ DistanceFieldVectorGL(const DistanceFieldVectorGL&) = delete; @@ -602,6 +617,8 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector #endif private: + explicit DistanceFieldVectorGL(NoInitT); + /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES using GL::AbstractShaderProgram::drawTransformFeedback; @@ -627,6 +644,20 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector #endif }; + +template class DistanceFieldVectorGL::CompileState: public DistanceFieldVectorGL { +private: + friend class DistanceFieldVectorGL; + + explicit CompileState(NoCreateT): DistanceFieldVectorGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + CompileState(DistanceFieldVectorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): + DistanceFieldVectorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + + GL::Shader _vert, _frag; + GL::Version _version; +}; + /** @brief Two-dimensional distance field vector OpenGL shader @m_since_latest diff --git a/src/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index 86504acad..645d7eade 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -31,7 +31,6 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" -#include "Magnum/GL/Shader.h" #include "Magnum/GL/Texture.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" @@ -68,16 +67,11 @@ namespace { #endif } -template FlatGL::FlatGL(const Flags flags - #ifndef MAGNUM_TARGET_GLES2 - , const UnsignedInt materialCount, const UnsignedInt drawCount - #endif -): - _flags{flags} - #ifndef MAGNUM_TARGET_GLES2 - , _materialCount{materialCount}, _drawCount{drawCount} - #endif -{ +template typename FlatGL::CompileState FlatGL::compile(Flags flags +#ifndef MAGNUM_TARGET_GLES2 +, UnsignedInt materialCount, UnsignedInt drawCount +#endif +) { #ifndef CORRADE_NO_ASSERT { const bool textureTransformationNotEnabledOrTextured = !(flags & Flag::TextureTransformation) || flags & Flag::Textured @@ -86,22 +80,22 @@ template FlatGL::FlatGL(const Flags flags #endif ; CORRADE_ASSERT(textureTransformationNotEnabledOrTextured, - "Shaders::FlatGL: texture transformation enabled but the shader is not textured", ); + "Shaders::FlatGL: texture transformation enabled but the shader is not textured", CompileState{NoCreate}); } #endif #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, - "Shaders::FlatGL: material count can't be zero", ); + "Shaders::FlatGL: material count can't be zero", CompileState{NoCreate}); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::FlatGL: draw count can't be zero", ); + "Shaders::FlatGL: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags & Flag::TextureArrays) || flags & Flag::Textured || flags >= Flag::ObjectIdTexture, - "Shaders::FlatGL: texture arrays enabled but the shader is not textured", ); + "Shaders::FlatGL: texture arrays enabled but the shader is not textured", CompileState{NoCreate}); CORRADE_ASSERT(!(flags & Flag::UniformBuffers) || !(flags & Flag::TextureArrays) || flags >= (Flag::TextureArrays|Flag::TextureTransformation), - "Shaders::FlatGL: texture arrays require texture transformation enabled as well if uniform buffers are used", ); + "Shaders::FlatGL: texture arrays require texture transformation enabled as well if uniform buffers are used", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -195,9 +189,17 @@ template FlatGL::FlatGL(const Flags flags frag.addSource(rs.getString("generic.glsl")) .addSource(rs.getString("Flat.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); - attachShaders({vert, frag}); + FlatGL out{NoInit}; + out._flags = flags; + #ifndef MAGNUM_TARGET_GLES2 + out._materialCount = materialCount; + out._drawCount = drawCount; + #endif + + out.attachShaders({vert, frag}); /* ES3 has this done in the shader directly and doesn't even provide bindFragmentDataLocation() */ @@ -206,53 +208,64 @@ template FlatGL::FlatGL(const Flags flags if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(Position::Location, "position"); if(flags & Flag::Textured #ifndef MAGNUM_TARGET_GLES2 || flags >= Flag::ObjectIdTexture #endif ) - bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); + out.bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); if(flags & Flag::VertexColor) - bindAttributeLocation(Color3::Location, "vertexColor"); /* Color4 is the same */ + out.bindAttributeLocation(Color3::Location, "vertexColor"); /* Color4 is the same */ #ifndef MAGNUM_TARGET_GLES2 if(flags & Flag::ObjectId) { - bindFragmentDataLocation(ColorOutput, "color"); - bindFragmentDataLocation(ObjectIdOutput, "objectId"); + out.bindFragmentDataLocation(ColorOutput, "color"); + out.bindFragmentDataLocation(ObjectIdOutput, "objectId"); } if(flags >= Flag::InstancedObjectId) - bindAttributeLocation(ObjectId::Location, "instanceObjectId"); + out.bindAttributeLocation(ObjectId::Location, "instanceObjectId"); #endif if(flags & Flag::InstancedTransformation) - bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); + out.bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); if(flags >= Flag::InstancedTextureOffset) - bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); + out.bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag), version}; +} + +template FlatGL::FlatGL(CompileState&& cs): FlatGL{static_cast(std::move(cs))} { + if (id() == 0) return; + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = cs._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else #endif { _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) _textureMatrixUniform = uniformLocation("textureMatrix"); #ifndef MAGNUM_TARGET_GLES2 - if(flags & Flag::TextureArrays) + if(_flags & Flag::TextureArrays) _textureLayerUniform = uniformLocation("textureLayer"); #endif _colorUniform = uniformLocation("color"); - if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); + if(_flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); #ifndef MAGNUM_TARGET_GLES2 - if(flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); + if(_flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); #endif } } @@ -261,13 +274,13 @@ template FlatGL::FlatGL(const Flags flags if(!context.isExtensionSupported(version)) #endif { - if(flags & Flag::Textured) setUniform(uniformLocation("textureData"), TextureUnit); + if(_flags & Flag::Textured) setUniform(uniformLocation("textureData"), TextureUnit); #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::ObjectIdTexture) setUniform(uniformLocation("objectIdTextureData"), ObjectIdTextureUnit); - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::ObjectIdTexture) setUniform(uniformLocation("objectIdTextureData"), ObjectIdTextureUnit); + if(_flags >= Flag::UniformBuffers) { setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); } @@ -277,26 +290,38 @@ template FlatGL::FlatGL(const Flags flags /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { /* Draw offset is zero by default */ } else #endif { setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setTextureMatrix(Matrix3{Math::IdentityInit}); /* Texture layer is zero by default */ setColor(Magnum::Color4{1.0f}); - if(flags & Flag::AlphaMask) setAlphaMask(0.5f); + if(_flags & Flag::AlphaMask) setAlphaMask(0.5f); /* Object ID is zero by default */ } #endif + + static_cast(version); + static_cast(context); } +template FlatGL::FlatGL(Flags flags): FlatGL{compile(flags)} {} + #ifndef MAGNUM_TARGET_GLES2 -template FlatGL::FlatGL(const Flags flags): FlatGL{flags, 1, 1} {} +template typename FlatGL::CompileState FlatGL::compile(Flags flags) { + return compile(flags, 1, 1); +} + +template FlatGL::FlatGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount): + FlatGL{compile(flags, materialCount, drawCount)} {} #endif +template FlatGL::FlatGL(NoInitT) {} + template FlatGL& FlatGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), diff --git a/src/Magnum/Shaders/FlatGL.h b/src/Magnum/Shaders/FlatGL.h index bd76a49c8..354d43240 100644 --- a/src/Magnum/Shaders/FlatGL.h +++ b/src/Magnum/Shaders/FlatGL.h @@ -32,6 +32,7 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" +#include "Magnum/GL/Shader.h" #include "Magnum/Shaders/GenericGL.h" #include "Magnum/Shaders/visibility.h" @@ -602,6 +603,20 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: */ explicit FlatGL(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} + class CompileState; + + explicit FlatGL(CompileState&& cs); + + static CompileState compile(Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , UnsignedInt materialCount, UnsignedInt drawCount + #endif + ); + + #ifndef MAGNUM_TARGET_GLES2 + static CompileState compile(Flags flags); + #endif + /** @brief Copying is not allowed */ FlatGL(const FlatGL&) = delete; @@ -1011,6 +1026,9 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: #endif private: + /* Creates the GL shader program object but nothing else. Internal, used by compile(). */ + explicit FlatGL(NoInitT); + /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES using GL::AbstractShaderProgram::drawTransformFeedback; @@ -1038,6 +1056,19 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: #endif }; +template class FlatGL::CompileState: public FlatGL { +private: + friend class FlatGL; + + explicit CompileState(NoCreateT): FlatGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + CompileState(FlatGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): + FlatGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + + GL::Shader _vert, _frag; + GL::Version _version; +}; + /** @brief 2D flat OpenGL shader @m_since_latest diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index 615231aa5..6aaaac536 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -26,7 +26,6 @@ #include "MeshVisualizerGL.h" #include -#include #include #include #include @@ -72,17 +71,7 @@ namespace { namespace Implementation { -MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags - #ifndef MAGNUM_TARGET_GLES2 - , const UnsignedInt materialCount, const UnsignedInt drawCount - #endif -): - _flags{flags} - #ifndef MAGNUM_TARGET_GLES2 - , _materialCount{materialCount}, - _drawCount{drawCount} - #endif -{ +void MeshVisualizerGLBase::assertExtensions(const FlagsBase flags) { #ifndef MAGNUM_TARGET_GLES2 #ifndef CORRADE_NO_ASSERT Int countMutuallyExclusive = 0; @@ -120,7 +109,7 @@ MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags #endif #ifndef MAGNUM_TARGET_GLES2 - if(_flags & FlagBase::Wireframe && !(_flags & FlagBase::NoGeometryShader)) { + if(flags & FlagBase::Wireframe && !(flags & FlagBase::NoGeometryShader)) { #ifndef MAGNUM_TARGET_GLES MAGNUM_ASSERT_GL_VERSION_SUPPORTED(GL::Version::GL320); MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::geometry_shader4); @@ -129,12 +118,12 @@ MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags #endif } #else - if(_flags & FlagBase::Wireframe) + if(flags & FlagBase::Wireframe) MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::OES::standard_derivatives); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(_flags & FlagBase::PrimitiveId && !(_flags >= FlagBase::PrimitiveIdFromVertexId)) { + if(flags & FlagBase::PrimitiveId && !(flags >= FlagBase::PrimitiveIdFromVertexId)) { #ifndef MAGNUM_TARGET_GLES MAGNUM_ASSERT_GL_VERSION_SUPPORTED(GL::Version::GL320); #else @@ -150,18 +139,23 @@ MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags #endif } -GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs) const { +GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs, + const FlagsBase flags + #ifndef MAGNUM_TARGET_GLES2 + , UnsignedInt materialCount, UnsignedInt drawCount + #endif +) { GL::Context& context = GL::Context::current(); #ifndef MAGNUM_TARGET_GLES const GL::Version version = context.supportedVersion({GL::Version::GL320, GL::Version::GL310, GL::Version::GL300, GL::Version::GL210}); /* Extended in MeshVisualizerGL3D for TBN visualization */ - CORRADE_INTERNAL_ASSERT(!(_flags & FlagBase::Wireframe) || _flags & FlagBase::NoGeometryShader || version >= GL::Version::GL320); + CORRADE_INTERNAL_ASSERT(!(flags & FlagBase::Wireframe) || flags & FlagBase::NoGeometryShader || version >= GL::Version::GL320); #elif !defined(MAGNUM_TARGET_WEBGL) /* ES 3.2 needed for gl_PrimitiveID */ const GL::Version version = context.supportedVersion({GL::Version::GLES320, GL::Version::GLES310, GL::Version::GLES300, GL::Version::GLES200}); /* Extended in MeshVisualizerGL3D for TBN visualization */ - CORRADE_INTERNAL_ASSERT(!(_flags & FlagBase::Wireframe) || _flags & FlagBase::NoGeometryShader || version >= GL::Version::GLES310); + CORRADE_INTERNAL_ASSERT(!(flags & FlagBase::Wireframe) || flags & FlagBase::NoGeometryShader || version >= GL::Version::GLES310); #else const GL::Version version = context.supportedVersion({GL::Version::GLES300, GL::Version::GLES200}); #endif @@ -169,18 +163,18 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra vert = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Vertex); frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); - vert.addSource(_flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") + vert.addSource(flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") #ifndef MAGNUM_TARGET_GLES2 - .addSource(_flags >= FlagBase::ObjectIdTexture ? "#define TEXTURED\n" : "") - .addSource(_flags & FlagBase::TextureTransformation ? "#define TEXTURE_TRANSFORMATION\n" : "") - .addSource(_flags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") - .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(flags >= FlagBase::ObjectIdTexture ? "#define TEXTURED\n" : "") + .addSource(flags & FlagBase::TextureTransformation ? "#define TEXTURE_TRANSFORMATION\n" : "") + .addSource(flags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") + .addSource(flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") #endif - .addSource(_flags & FlagBase::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : "") + .addSource(flags & FlagBase::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : "") #ifndef MAGNUM_TARGET_GLES2 - .addSource(_flags >= FlagBase::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : "") - .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") - .addSource(_flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "") + .addSource(flags >= FlagBase::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : "") + .addSource(flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") + .addSource(flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "") #endif #ifdef MAGNUM_TARGET_WEBGL .addSource("#define SUBSCRIPTING_WORKAROUND\n") @@ -190,38 +184,38 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra #endif ; #ifndef MAGNUM_TARGET_GLES2 - if(_flags >= FlagBase::UniformBuffers) { + if(flags >= FlagBase::UniformBuffers) { vert.addSource(Utility::formatString( "#define UNIFORM_BUFFERS\n" "#define DRAW_COUNT {}\n" "#define MATERIAL_COUNT {}\n", - _drawCount, - _materialCount)); - vert.addSource(_flags >= FlagBase::MultiDraw ? "#define MULTI_DRAW\n" : ""); + drawCount, + materialCount)); + vert.addSource(flags >= FlagBase::MultiDraw ? "#define MULTI_DRAW\n" : ""); } #endif - frag.addSource(_flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") + frag.addSource(flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") #ifndef MAGNUM_TARGET_GLES2 - .addSource(_flags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") - .addSource(_flags >= FlagBase::ObjectIdTexture ? "#define OBJECT_ID_TEXTURE\n" : "") - .addSource(_flags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") - .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") - .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") - .addSource(_flags & FlagBase::PrimitiveId ? - (_flags >= FlagBase::PrimitiveIdFromVertexId ? + .addSource(flags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") + .addSource(flags >= FlagBase::ObjectIdTexture ? "#define OBJECT_ID_TEXTURE\n" : "") + .addSource(flags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") + .addSource(flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") + .addSource(flags & FlagBase::PrimitiveId ? + (flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "#define PRIMITIVE_ID\n") : "") #endif ; #ifndef MAGNUM_TARGET_GLES2 - if(_flags >= FlagBase::UniformBuffers) { + if(flags >= FlagBase::UniformBuffers) { frag.addSource(Utility::formatString( "#define UNIFORM_BUFFERS\n" "#define DRAW_COUNT {}\n" "#define MATERIAL_COUNT {}\n", - _drawCount, - _materialCount)); - frag.addSource(_flags >= FlagBase::MultiDraw ? "#define MULTI_DRAW\n" : ""); + drawCount, + materialCount)); + frag.addSource(flags >= FlagBase::MultiDraw ? "#define MULTI_DRAW\n" : ""); } #endif @@ -382,21 +376,20 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::bindObjectIdTexture(GL::Texture2DArr } -MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags +MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(Flags flags #ifndef MAGNUM_TARGET_GLES2 , const UnsignedInt materialCount, const UnsignedInt drawCount #endif -): Implementation::MeshVisualizerGLBase{Implementation::MeshVisualizerGLBase::FlagBase(UnsignedInt(flags)) - #ifndef MAGNUM_TARGET_GLES2 - , materialCount, drawCount - #endif -} { +) { + FlagsBase baseFlags = Implementation::MeshVisualizerGLBase::FlagBase(UnsignedInt(flags)); + assertExtensions(baseFlags); + #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), - "Shaders::MeshVisualizerGL2D: at least one visualization feature has to be enabled", ); + "Shaders::MeshVisualizerGL2D: at least one visualization feature has to be enabled", CompileState{NoCreate}); #else CORRADE_ASSERT(flags & (Flag::Wireframe & ~Flag::NoGeometryShader), - "Shaders::MeshVisualizerGL2D: at least Flag::Wireframe has to be enabled", ); + "Shaders::MeshVisualizerGL2D: at least Flag::Wireframe has to be enabled", CompileState{NoCreate}); #endif /* Has to be here and not in the base class in order to have it exit the @@ -404,9 +397,9 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags otherwise */ #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, - "Shaders::MeshVisualizerGL2D: material count can't be zero", ); + "Shaders::MeshVisualizerGL2D: material count can't be zero", CompileState{NoCreate}); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::MeshVisualizerGL2D: draw count can't be zero", ); + "Shaders::MeshVisualizerGL2D: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -416,7 +409,12 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags Utility::Resource rs{"MagnumShadersGL"}; GL::Shader vert{NoCreate}; GL::Shader frag{NoCreate}; - const GL::Version version = setupShaders(vert, frag, rs); + const GL::Version version = setupShaders(vert, frag, rs, baseFlags + #ifndef MAGNUM_TARGET_GLES2 + , materialCount, drawCount + #endif + ); + Containers::Optional geom; vert.addSource("#define TWO_DIMENSIONS\n") /* Pass NO_GEOMETRY_SHADER not only when NoGeometryShader but also when @@ -439,19 +437,19 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags frag.addSource(rs.getString("generic.glsl")) .addSource(rs.getString("MeshVisualizer.frag")); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - Containers::Optional geom; if(flags & Flag::Wireframe && !(flags & Flag::NoGeometryShader)) { geom = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Geometry); (*geom) .addSource("#define WIREFRAME_RENDERING\n#define MAX_VERTICES 3\n") - .addSource(_flags >= FlagBase::ObjectIdTexture ? "#define TEXTURED\n" : "") - .addSource(_flags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") - .addSource(_flags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") - .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") - .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") - .addSource(_flags & FlagBase::PrimitiveId ? - (_flags >= FlagBase::PrimitiveIdFromVertexId ? + .addSource(baseFlags >= FlagBase::ObjectIdTexture ? "#define TEXTURED\n" : "") + .addSource(baseFlags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") + .addSource(baseFlags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") + .addSource(baseFlags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(baseFlags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") + .addSource(baseFlags & FlagBase::PrimitiveId ? + (baseFlags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "#define PRIMITIVE_ID\n") : ""); #ifndef MAGNUM_TARGET_GLES2 @@ -461,8 +459,8 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags "#define UNIFORM_BUFFERS\n" "#define DRAW_COUNT {}\n" "#define MATERIAL_COUNT {}\n", - _drawCount, - _materialCount)); + drawCount, + materialCount)); geom->addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); } #endif @@ -472,48 +470,75 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags static_cast(version); #endif - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(geom) CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, *geom, frag})); - else - #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); + if (geom) geom->submitCompile(); - attachShaders({vert, frag}); - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(geom) attachShader(*geom); + MeshVisualizerGL2D out{NoInit}; + out._flags = baseFlags; + #ifndef MAGNUM_TARGET_GLES2 + out._materialCount = materialCount; + out._drawCount = drawCount; #endif + out.attachShaders({vert, frag}); + if (geom) out.attachShader(*geom); + /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(Position::Location, "position"); #ifndef MAGNUM_TARGET_GLES2 if(flags >= Flag::ObjectIdTexture) - bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); + out.bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); if(flags >= Flag::InstancedObjectId) - bindAttributeLocation(ObjectId::Location, "instanceObjectId"); + out.bindAttributeLocation(ObjectId::Location, "instanceObjectId"); #endif if(flags & Flag::InstancedTransformation) - bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); + out.bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); #ifndef MAGNUM_TARGET_GLES2 if(flags >= Flag::InstancedTextureOffset) - bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); + out.bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); #endif #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) #ifndef MAGNUM_TARGET_GLES if(!context.isVersionSupported(GL::Version::GL310)) #endif { - bindAttributeLocation(VertexIndex::Location, "vertexIndex"); + out.bindAttributeLocation(VertexIndex::Location, "vertexIndex"); } #endif } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag), std::move(geom), flags, version}; +} + +MeshVisualizerGL2D::MeshVisualizerGL2D(Flags flags) : MeshVisualizerGL2D{compile(flags)} {} + +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(Flags flags) { + return compile(flags, 1, 1); +} + +MeshVisualizerGL2D::MeshVisualizerGL2D(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount) + : MeshVisualizerGL2D{compile(flags, materialCount, drawCount)} {} +#endif + +MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& cs) +: MeshVisualizerGL2D{static_cast(std::move(cs))} { + if (id() == 0) return; + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = cs._version; + Flags flags = cs._flags; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) @@ -608,11 +633,10 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags #endif } #endif -} -#ifndef MAGNUM_TARGET_GLES2 -MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): MeshVisualizerGL2D{flags, 1, 1} {} -#endif + static_cast(context); + static_cast(version); +} MeshVisualizerGL2D& MeshVisualizerGL2D::setViewportSize(const Vector2& size) { /* Not asserting here, since the relation to wireframe is a bit vague. @@ -674,33 +698,32 @@ MeshVisualizerGL2D& MeshVisualizerGL2D::bindDrawBuffer(GL::Buffer& buffer, const } #endif -MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags +MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(Flags flags #ifndef MAGNUM_TARGET_GLES2 , const UnsignedInt materialCount, const UnsignedInt drawCount #endif -): Implementation::MeshVisualizerGLBase{Implementation::MeshVisualizerGLBase::FlagBase(UnsignedInt(flags)) - #ifndef MAGNUM_TARGET_GLES2 - , materialCount, drawCount - #endif -} { +) { + FlagsBase baseFlags = Implementation::MeshVisualizerGLBase::FlagBase(UnsignedInt(flags)); + assertExtensions(baseFlags); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection|Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), - "Shaders::MeshVisualizerGL3D: at least one visualization feature has to be enabled", ); + "Shaders::MeshVisualizerGL3D: at least one visualization feature has to be enabled", CompileState{NoCreate}); CORRADE_ASSERT(!(flags & Flag::NoGeometryShader && flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)), - "Shaders::MeshVisualizerGL3D: geometry shader has to be enabled when rendering TBN direction", ); + "Shaders::MeshVisualizerGL3D: geometry shader has to be enabled when rendering TBN direction", CompileState{NoCreate}); CORRADE_ASSERT(!(flags & Flag::BitangentDirection && flags & Flag::BitangentFromTangentDirection), - "Shaders::MeshVisualizerGL3D: Flag::BitangentDirection and Flag::BitangentFromTangentDirection are mutually exclusive", ); + "Shaders::MeshVisualizerGL3D: Flag::BitangentDirection and Flag::BitangentFromTangentDirection are mutually exclusive", CompileState{NoCreate}); #elif !defined(MAGNUM_TARGET_GLES2) CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), - "Shaders::MeshVisualizerGL3D: at least one visualization feature has to be enabled", ); + "Shaders::MeshVisualizerGL3D: at least one visualization feature has to be enabled", CompileState{NoCreate}); #else CORRADE_ASSERT(flags & (Flag::Wireframe & ~Flag::NoGeometryShader), - "Shaders::MeshVisualizerGL3D: at least Flag::Wireframe has to be enabled", ); + "Shaders::MeshVisualizerGL3D: at least Flag::Wireframe has to be enabled", CompileState{NoCreate}); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) CORRADE_ASSERT(!(flags >= Flag::InstancedObjectId) || !(flags & Flag::BitangentDirection), - "Shaders::MeshVisualizerGL3D: Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead", ); + "Shaders::MeshVisualizerGL3D: Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead", CompileState{NoCreate}); #endif /* Has to be here and not in the base class in order to have it exit the @@ -708,9 +731,9 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags otherwise */ #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, - "Shaders::MeshVisualizerGL3D: material count can't be zero", ); + "Shaders::MeshVisualizerGL3D: material count can't be zero", CompileState{NoCreate}); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::MeshVisualizerGL3D: draw count can't be zero", ); + "Shaders::MeshVisualizerGL3D: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -720,7 +743,12 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags Utility::Resource rs{"MagnumShadersGL"}; GL::Shader vert{NoCreate}; GL::Shader frag{NoCreate}; - const GL::Version version = setupShaders(vert, frag, rs); + const GL::Version version = setupShaders(vert, frag, rs, baseFlags + #ifndef MAGNUM_TARGET_GLES2 + , materialCount, drawCount + #endif + ); + Containers::Optional geom; /* Expands the check done for wireframe in MeshVisualizerBase with TBN */ #ifndef MAGNUM_TARGET_GLES @@ -768,7 +796,6 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags .addSource(rs.getString("MeshVisualizer.frag")); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - Containers::Optional geom; if(flags & (Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection) && !(flags & Flag::NoGeometryShader)) { Int maxVertices = 0; if(flags & Flag::Wireframe) maxVertices += 3; @@ -781,13 +808,13 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags (*geom) .addSource(Utility::formatString("#define MAX_VERTICES {}\n", maxVertices)) .addSource(flags & Flag::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") - .addSource(_flags >= FlagBase::ObjectIdTexture ? "#define TEXTURED\n" : "") - .addSource(_flags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") - .addSource(_flags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") - .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") - .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") - .addSource(_flags & FlagBase::PrimitiveId ? - (_flags >= FlagBase::PrimitiveIdFromVertexId ? + .addSource(baseFlags >= FlagBase::ObjectIdTexture ? "#define TEXTURED\n" : "") + .addSource(baseFlags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") + .addSource(baseFlags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") + .addSource(baseFlags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(baseFlags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") + .addSource(baseFlags & FlagBase::PrimitiveId ? + (baseFlags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "#define PRIMITIVE_ID\n") : "") .addSource(flags & Flag::TangentDirection ? "#define TANGENT_DIRECTION\n" : "") @@ -800,8 +827,8 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags "#define UNIFORM_BUFFERS\n" "#define DRAW_COUNT {}\n" "#define MATERIAL_COUNT {}\n", - _drawCount, - _materialCount)); + drawCount, + materialCount)); geom->addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); } #endif @@ -811,50 +838,53 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags static_cast(version); #endif - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(geom) CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, *geom, frag})); - else - #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); + if (geom) geom->submitCompile(); - attachShaders({vert, frag}); - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(geom) attachShader(*geom); + MeshVisualizerGL3D out{NoInit}; + out._flags = baseFlags; + #ifndef MAGNUM_TARGET_GLES2 + out._materialCount = materialCount; + out._drawCount = drawCount; #endif + out.attachShaders({vert, frag}); + if (geom) out.attachShader(*geom); + /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(Position::Location, "position"); #ifndef MAGNUM_TARGET_GLES2 if(flags >= Flag::ObjectIdTexture) - bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); + out.bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); if(flags >= Flag::InstancedObjectId) - bindAttributeLocation(ObjectId::Location, "instanceObjectId"); + out.bindAttributeLocation(ObjectId::Location, "instanceObjectId"); #endif if(flags & Flag::InstancedTransformation) { - bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); + out.bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) - bindAttributeLocation(NormalMatrix::Location, "instancedNormalMatrix"); + out.bindAttributeLocation(NormalMatrix::Location, "instancedNormalMatrix"); #endif } #ifndef MAGNUM_TARGET_GLES2 if(flags >= Flag::InstancedTextureOffset) - bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); + out.bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) if(flags & Flag::TangentDirection || flags & Flag::BitangentFromTangentDirection) - bindAttributeLocation(Tangent4::Location, "tangent"); + out.bindAttributeLocation(Tangent4::Location, "tangent"); if(flags & Flag::BitangentDirection) - bindAttributeLocation(Bitangent::Location, "bitangent"); + out.bindAttributeLocation(Bitangent::Location, "bitangent"); if(flags & Flag::NormalDirection || flags & Flag::BitangentFromTangentDirection) - bindAttributeLocation(Normal::Location, "normal"); + out.bindAttributeLocation(Normal::Location, "normal"); #endif #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) @@ -862,13 +892,25 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags if(!context.isVersionSupported(GL::Version::GL310)) #endif { - bindAttributeLocation(VertexIndex::Location, "vertexIndex"); + out.bindAttributeLocation(VertexIndex::Location, "vertexIndex"); } #endif } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag), std::move(geom), flags, version}; +} + +MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& cs): MeshVisualizerGL3D{static_cast(std::move(cs))} { + if (id() == 0) return; + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = cs._version; + Flags flags = cs._flags; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) @@ -996,10 +1038,20 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags #endif } #endif + + static_cast(context); + static_cast(version); } +MeshVisualizerGL3D::MeshVisualizerGL3D(Flags flags) : MeshVisualizerGL3D{compile(flags)} {} + #ifndef MAGNUM_TARGET_GLES2 -MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): MeshVisualizerGL3D{flags, 1, 1} {} +MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(Flags flags) { + return compile(flags, 1, 1); +} + +MeshVisualizerGL3D::MeshVisualizerGL3D(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount): + MeshVisualizerGL3D{compile(flags, materialCount, drawCount)} {} #endif MeshVisualizerGL3D& MeshVisualizerGL3D::setTransformationMatrix(const Matrix4& matrix) { diff --git a/src/Magnum/Shaders/MeshVisualizerGL.h b/src/Magnum/Shaders/MeshVisualizerGL.h index b217f1d39..aa58e7f45 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -32,8 +32,10 @@ #include +#include #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" +#include "Magnum/GL/Shader.h" #include "Magnum/Shaders/GenericGL.h" #include "Magnum/Shaders/visibility.h" @@ -69,14 +71,17 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr CORRADE_ENUMSET_FRIEND_OPERATORS(FlagsBase) - explicit MeshVisualizerGLBase(FlagsBase flags + explicit MeshVisualizerGLBase(NoInitT) {} + + explicit MeshVisualizerGLBase(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} + + static MAGNUM_SHADERS_LOCAL void assertExtensions(const FlagsBase flags); + static MAGNUM_SHADERS_LOCAL GL::Version setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs, + const FlagsBase flags #ifndef MAGNUM_TARGET_GLES2 , UnsignedInt materialCount, UnsignedInt drawCount #endif ); - explicit MeshVisualizerGLBase(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} - - MAGNUM_SHADERS_LOCAL GL::Version setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs) const; #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGLBase& setTextureMatrix(const Matrix3& matrix); @@ -505,6 +510,20 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua */ explicit MeshVisualizerGL2D(NoCreateT) noexcept: Implementation::MeshVisualizerGLBase{NoCreate} {} + class CompileState; + + explicit MeshVisualizerGL2D(CompileState&& cs); + + static CompileState compile(Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , UnsignedInt materialCount, UnsignedInt drawCount + #endif + ); + + #ifndef MAGNUM_TARGET_GLES2 + static CompileState compile(Flags flags); + #endif + /** @brief Copying is not allowed */ MeshVisualizerGL2D(const MeshVisualizerGL2D&) = delete; @@ -862,9 +881,26 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua #endif private: + explicit MeshVisualizerGL2D(NoInitT) : Implementation::MeshVisualizerGLBase{NoInit} {} + Int _transformationProjectionMatrixUniform{9}; }; +class MeshVisualizerGL2D::CompileState: public MeshVisualizerGL2D { +private: + friend class MeshVisualizerGL2D; + + explicit CompileState(NoCreateT): MeshVisualizerGL2D{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + CompileState(MeshVisualizerGL2D&& shader, GL::Shader&& vert, GL::Shader&& frag, Containers::Optional&& geom, Flags flags, GL::Version version): + MeshVisualizerGL2D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{std::move(geom)}, _flags{flags}, _version{version} {} + + GL::Shader _vert, _frag; + Containers::Optional _geom; + Flags _flags; + GL::Version _version; +}; + /** @brief 3D mesh visualization OpenGL shader @m_since_latest @@ -1639,7 +1675,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @m_deprecated_since{2020,06} Use @ref MeshVisualizerGL3D(Flags) * instead. */ - explicit CORRADE_DEPRECATED("use MeshVisualizerGL3D(Flags) instead") MeshVisualizerGL3D(): MeshVisualizerGL3D{{}} {} + explicit CORRADE_DEPRECATED("use MeshVisualizerGL3D(Flags) instead") MeshVisualizerGL3D(): MeshVisualizerGL3D{Flags{}} {} #endif #ifndef MAGNUM_TARGET_GLES2 @@ -1698,6 +1734,21 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua */ explicit MeshVisualizerGL3D(NoCreateT) noexcept: Implementation::MeshVisualizerGLBase{NoCreate} {} + class CompileState; + + explicit MeshVisualizerGL3D(CompileState&& cs); + + static CompileState compile(Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , UnsignedInt materialCount, UnsignedInt drawCount + #endif + ); + + #ifndef MAGNUM_TARGET_GLES2 + static CompileState compile(Flags flags); + #endif + + /** @brief Copying is not allowed */ MeshVisualizerGL3D(const MeshVisualizerGL3D&) = delete; @@ -2326,6 +2377,8 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua #endif private: + explicit MeshVisualizerGL3D(NoInitT) : Implementation::MeshVisualizerGLBase{NoInit} {} + Int _transformationMatrixUniform{9}, _projectionMatrixUniform{10}; #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) @@ -2335,6 +2388,22 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua #endif }; +class MeshVisualizerGL3D::CompileState : public MeshVisualizerGL3D { +private: + friend class MeshVisualizerGL3D; + + explicit CompileState(NoCreateT) : MeshVisualizerGL3D{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + CompileState(MeshVisualizerGL3D&& shader, GL::Shader&& vert, GL::Shader&& frag, Containers::Optional&& geom, Flags flags, GL::Version version) : + MeshVisualizerGL3D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{std::move(geom)}, _flags{flags}, _version{version} {} + + GL::Shader _vert, _frag; + Containers::Optional _geom; + Flags _flags; + GL::Version _version; +}; + + /** @debugoperatorclassenum{MeshVisualizerGL2D,MeshVisualizerGL2D::Flag} */ MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, MeshVisualizerGL2D::Flag value); diff --git a/src/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index a23561463..bd8bf9e65 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -37,7 +37,6 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" -#include "Magnum/GL/Shader.h" #include "Magnum/GL/Texture.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" @@ -74,21 +73,12 @@ namespace { #endif } -PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount +PhongGL::CompileState PhongGL::compile(const Flags flags, const UnsignedInt lightCount #ifndef MAGNUM_TARGET_GLES2 , const UnsignedInt materialCount, const UnsignedInt drawCount #endif -): - _flags{flags}, - _lightCount{lightCount}, - #ifndef MAGNUM_TARGET_GLES2 - _materialCount{materialCount}, - _drawCount{drawCount}, - #endif - _lightColorsUniform{_lightPositionsUniform + Int(lightCount)}, - _lightSpecularColorsUniform{_lightPositionsUniform + 2*Int(lightCount)}, - _lightRangesUniform{_lightPositionsUniform + 3*Int(lightCount)} -{ +) { + #ifndef CORRADE_NO_ASSERT { const bool textureTransformationNotEnabledOrTextured = !(flags & Flag::TextureTransformation) || (flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture|Flag::NormalTexture)) #ifndef MAGNUM_TARGET_GLES2 @@ -96,32 +86,33 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount #endif ; CORRADE_ASSERT(textureTransformationNotEnabledOrTextured, - "Shaders::PhongGL: texture transformation enabled but the shader is not textured", ); + "Shaders::PhongGL: texture transformation enabled but the shader is not textured", CompileState{NoCreate}); } + #endif #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags >= Flag::InstancedObjectId) || !(flags & Flag::Bitangent), - "Shaders::PhongGL: Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead", ); + "Shaders::PhongGL: Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, - "Shaders::PhongGL: material count can't be zero", ); + "Shaders::PhongGL: material count can't be zero", CompileState{NoCreate}); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::PhongGL: draw count can't be zero", ); + "Shaders::PhongGL: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags & Flag::TextureArrays) || (flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture|Flag::NormalTexture)) || flags >= Flag::ObjectIdTexture, - "Shaders::PhongGL: texture arrays enabled but the shader is not textured", ); + "Shaders::PhongGL: texture arrays enabled but the shader is not textured", CompileState{NoCreate}); CORRADE_ASSERT(!(flags & Flag::UniformBuffers) || !(flags & Flag::TextureArrays) || flags >= (Flag::TextureArrays|Flag::TextureTransformation), - "Shaders::PhongGL: texture arrays require texture transformation enabled as well if uniform buffers are used", ); + "Shaders::PhongGL: texture arrays require texture transformation enabled as well if uniform buffers are used", CompileState{NoCreate}); CORRADE_ASSERT(!(flags & Flag::LightCulling) || (flags & Flag::UniformBuffers), - "Shaders::PhongGL: light culling requires uniform buffers to be enabled", ); + "Shaders::PhongGL: light culling requires uniform buffers to be enabled", CompileState{NoCreate}); #endif CORRADE_ASSERT(!(flags & Flag::SpecularTexture) || !(flags & (Flag::NoSpecular)), - "Shaders::PhongGL: specular texture requires the shader to not have specular disabled", ); + "Shaders::PhongGL: specular texture requires the shader to not have specular disabled", CompileState{NoCreate}); #ifndef MAGNUM_TARGET_GLES if(flags >= Flag::UniformBuffers) @@ -154,7 +145,7 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount #ifndef MAGNUM_TARGET_GLES CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || context.isExtensionSupported(), - "Shaders::PhongGL: uniform buffers require" << GL::Extensions::ARB::uniform_buffer_object::string(), ); + "Shaders::PhongGL: uniform buffers require" << GL::Extensions::ARB::uniform_buffer_object::string(), CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -163,6 +154,17 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount const GL::Version version = context.supportedVersion({GL::Version::GLES300, GL::Version::GLES200}); #endif + PhongGL out{NoInit}; + out._flags = flags; + out._lightCount = lightCount; + out._lightColorsUniform = out._lightPositionsUniform + Int(lightCount); + out._lightSpecularColorsUniform = out._lightPositionsUniform + 2*Int(lightCount); + out._lightRangesUniform = out._lightPositionsUniform + 3*Int(lightCount); + #ifndef MAGNUM_TARGET_GLES2 + out._materialCount = materialCount; + out._drawCount = drawCount; + #endif + GL::Shader vert = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Vertex); GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); @@ -283,9 +285,9 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount "#define LIGHT_SPECULAR_COLORS_LOCATION {}\n" "#define LIGHT_RANGES_LOCATION {}\n", lightCount, - _lightPositionsUniform + lightCount, - _lightPositionsUniform + 2*lightCount, - _lightPositionsUniform + 3*lightCount)); + out._lightPositionsUniform + lightCount, + out._lightPositionsUniform + 2*lightCount, + out._lightPositionsUniform + 3*lightCount)); } #ifndef MAGNUM_TARGET_GLES if(!(flags >= Flag::UniformBuffers) && lightCount) @@ -294,9 +296,10 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount frag.addSource(rs.getString("generic.glsl")) .addSource(rs.getString("Phong.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); - attachShaders({vert, frag}); + out.attachShaders({vert, frag}); /* ES3 has this done in the shader directly and doesn't even provide bindFragmentDataLocation() */ @@ -305,103 +308,114 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(Position::Location, "position"); if(lightCount) - bindAttributeLocation(Normal::Location, "normal"); + out.bindAttributeLocation(Normal::Location, "normal"); if((flags & Flag::NormalTexture) && lightCount) { - bindAttributeLocation(Tangent::Location, "tangent"); + out.bindAttributeLocation(Tangent::Location, "tangent"); if(flags & Flag::Bitangent) - bindAttributeLocation(Bitangent::Location, "bitangent"); + out.bindAttributeLocation(Bitangent::Location, "bitangent"); } if(flags & Flag::VertexColor) - bindAttributeLocation(Color3::Location, "vertexColor"); /* Color4 is the same */ + out.bindAttributeLocation(Color3::Location, "vertexColor"); /* Color4 is the same */ if(flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture) #ifndef MAGNUM_TARGET_GLES2 || flags >= Flag::ObjectIdTexture #endif ) - bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); + out.bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); #ifndef MAGNUM_TARGET_GLES2 if(flags & Flag::ObjectId) { - bindFragmentDataLocation(ColorOutput, "color"); - bindFragmentDataLocation(ObjectIdOutput, "objectId"); + out.bindFragmentDataLocation(ColorOutput, "color"); + out.bindFragmentDataLocation(ObjectIdOutput, "objectId"); } if(flags >= Flag::InstancedObjectId) - bindAttributeLocation(ObjectId::Location, "instanceObjectId"); + out.bindAttributeLocation(ObjectId::Location, "instanceObjectId"); #endif if(flags & Flag::InstancedTransformation) { - bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); + out.bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); if(lightCount) - bindAttributeLocation(NormalMatrix::Location, "instancedNormalMatrix"); + out.bindAttributeLocation(NormalMatrix::Location, "instancedNormalMatrix"); } if(flags >= Flag::InstancedTextureOffset) - bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); + out.bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag), version}; +} + +PhongGL::PhongGL(CompileState&& cs): PhongGL{static_cast(std::move(cs))} { + if (id() == 0) return; + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = cs._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else #endif { _transformationMatrixUniform = uniformLocation("transformationMatrix"); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) _textureMatrixUniform = uniformLocation("textureMatrix"); #ifndef MAGNUM_TARGET_GLES2 - if(flags & Flag::TextureArrays) + if(_flags & Flag::TextureArrays) _textureLayerUniform = uniformLocation("textureLayer"); #endif _projectionMatrixUniform = uniformLocation("projectionMatrix"); _ambientColorUniform = uniformLocation("ambientColor"); - if(lightCount) { + if(_lightCount) { _normalMatrixUniform = uniformLocation("normalMatrix"); _diffuseColorUniform = uniformLocation("diffuseColor"); - if(!(flags & Flag::NoSpecular)) { + if(!(_flags & Flag::NoSpecular)) { _specularColorUniform = uniformLocation("specularColor"); _shininessUniform = uniformLocation("shininess"); } - if(flags & Flag::NormalTexture) + if(_flags & Flag::NormalTexture) _normalTextureScaleUniform = uniformLocation("normalTextureScale"); _lightPositionsUniform = uniformLocation("lightPositions"); _lightColorsUniform = uniformLocation("lightColors"); - if(!(flags & Flag::NoSpecular)) + if(!(_flags & Flag::NoSpecular)) _lightSpecularColorsUniform = uniformLocation("lightSpecularColors"); _lightRangesUniform = uniformLocation("lightRanges"); } - if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); + if(_flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); #ifndef MAGNUM_TARGET_GLES2 - if(flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); + if(_flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); #endif } } #ifndef MAGNUM_TARGET_GLES - if(flags && !context.isExtensionSupported(version)) + if(_flags && !context.isExtensionSupported(version)) #endif { - if(flags & Flag::AmbientTexture) setUniform(uniformLocation("ambientTexture"), AmbientTextureUnit); - if(lightCount) { - if(flags & Flag::DiffuseTexture) setUniform(uniformLocation("diffuseTexture"), DiffuseTextureUnit); - if(flags & Flag::SpecularTexture) setUniform(uniformLocation("specularTexture"), SpecularTextureUnit); - if(flags & Flag::NormalTexture) setUniform(uniformLocation("normalTexture"), NormalTextureUnit); + if(_flags & Flag::AmbientTexture) setUniform(uniformLocation("ambientTexture"), AmbientTextureUnit); + if(_lightCount) { + if(_flags & Flag::DiffuseTexture) setUniform(uniformLocation("diffuseTexture"), DiffuseTextureUnit); + if(_flags & Flag::SpecularTexture) setUniform(uniformLocation("specularTexture"), SpecularTextureUnit); + if(_flags & Flag::NormalTexture) setUniform(uniformLocation("normalTexture"), NormalTextureUnit); } #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::ObjectIdTexture) setUniform(uniformLocation("objectIdTextureData"), ObjectIdTextureUnit); - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::ObjectIdTexture) setUniform(uniformLocation("objectIdTextureData"), ObjectIdTextureUnit); + if(_flags >= Flag::UniformBuffers) { setUniformBlockBinding(uniformBlockIndex("Projection"), ProjectionBufferBinding); setUniformBlockBinding(uniformBlockIndex("Transformation"), TransformationBufferBinding); setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); - if(lightCount) + if(_lightCount) setUniformBlockBinding(uniformBlockIndex("Light"), LightBufferBinding); } #endif @@ -410,44 +424,54 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { /* Draw offset is zero by default */ } else #endif { /* Default to fully opaque white so we can see the textures */ - if(flags & Flag::AmbientTexture) setAmbientColor(Magnum::Color4{1.0f}); + if(_flags & Flag::AmbientTexture) setAmbientColor(Magnum::Color4{1.0f}); else setAmbientColor(Magnum::Color4{0.0f}); setTransformationMatrix(Matrix4{Math::IdentityInit}); setProjectionMatrix(Matrix4{Math::IdentityInit}); - if(lightCount) { + if(_lightCount) { setDiffuseColor(Magnum::Color4{1.0f}); - if(!(flags & Flag::NoSpecular)) { + if(!(_flags & Flag::NoSpecular)) { setSpecularColor(Magnum::Color4{1.0f, 0.0f}); setShininess(80.0f); } - if(flags & Flag::NormalTexture) + if(_flags & Flag::NormalTexture) setNormalTextureScale(1.0f); - setLightPositions(Containers::Array{DirectInit, lightCount, Vector4{0.0f, 0.0f, 1.0f, 0.0f}}); - Containers::Array colors{DirectInit, lightCount, Magnum::Color3{1.0f}}; + setLightPositions(Containers::Array{DirectInit, _lightCount, Vector4{0.0f, 0.0f, 1.0f, 0.0f}}); + Containers::Array colors{DirectInit, _lightCount, Magnum::Color3{1.0f}}; setLightColors(colors); - if(!(flags & Flag::NoSpecular)) + if(!(_flags & Flag::NoSpecular)) setLightSpecularColors(colors); - setLightRanges(Containers::Array{DirectInit, lightCount, Constants::inf()}); + setLightRanges(Containers::Array{DirectInit, _lightCount, Constants::inf()}); /* Light position is zero by default */ setNormalMatrix(Matrix3x3{Math::IdentityInit}); } - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setTextureMatrix(Matrix3{Math::IdentityInit}); /* Texture layer is zero by default */ - if(flags & Flag::AlphaMask) setAlphaMask(0.5f); + if(_flags & Flag::AlphaMask) setAlphaMask(0.5f); /* Object ID is zero by default */ } #endif + + static_cast(context); + static_cast(version); } +PhongGL::PhongGL(Flags flags, UnsignedInt lightCount): PhongGL{compile(flags, lightCount)} {} + #ifndef MAGNUM_TARGET_GLES2 -PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): PhongGL{flags, lightCount, 1, 1} {} +PhongGL::CompileState PhongGL::compile(Flags flags, UnsignedInt lightCount) { + return compile(flags, lightCount, 1, 1); +} + +PhongGL::PhongGL(Flags flags, UnsignedInt lightCount, UnsignedInt materialCount, UnsignedInt drawCount): + PhongGL{compile(flags, lightCount, materialCount, drawCount)} {} #endif PhongGL& PhongGL::setAmbientColor(const Magnum::Color4& color) { diff --git a/src/Magnum/Shaders/PhongGL.h b/src/Magnum/Shaders/PhongGL.h index 1a59c564b..8a06e2ccc 100644 --- a/src/Magnum/Shaders/PhongGL.h +++ b/src/Magnum/Shaders/PhongGL.h @@ -31,6 +31,7 @@ */ #include "Magnum/GL/AbstractShaderProgram.h" +#include "Magnum/GL/Shader.h" #include "Magnum/Shaders/GenericGL.h" #include "Magnum/Shaders/visibility.h" @@ -821,6 +822,20 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { */ explicit PhongGL(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} + class CompileState; + + explicit PhongGL(CompileState&& cs); + + static CompileState compile(Flags flags, UnsignedInt lightCount + #ifndef MAGNUM_TARGET_GLES2 + , UnsignedInt materialCount, UnsignedInt drawCount + #endif + ); + + #ifndef MAGNUM_TARGET_GLES2 + static CompileState compile(Flags flags, UnsignedInt lightCount); + #endif + /** @brief Copying is not allowed */ PhongGL(const PhongGL&) = delete; @@ -1744,6 +1759,8 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { #endif private: + explicit PhongGL(NoInitT) {} + /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES using GL::AbstractShaderProgram::drawTransformFeedback; @@ -1784,6 +1801,19 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { #endif }; +class PhongGL::CompileState: public PhongGL { +private: + friend class PhongGL; + + explicit CompileState(NoCreateT): PhongGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + CompileState(PhongGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): + PhongGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + + GL::Shader _vert, _frag; + GL::Version _version; +}; + /** @debugoperatorclassenum{PhongGL,PhongGL::Flag} */ MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, PhongGL::Flag value); diff --git a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp index d8bf027ac..e23db4a99 100644 --- a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #ifdef CORRADE_TARGET_APPLE @@ -84,8 +85,10 @@ struct DistanceFieldVectorGLTest: GL::OpenGLTester { explicit DistanceFieldVectorGLTest(); template void construct(); + template void constructAsync(); #ifndef MAGNUM_TARGET_GLES2 template void constructUniformBuffers(); + template void constructUniformBuffersAsync(); #endif template void constructMove(); @@ -251,11 +254,19 @@ DistanceFieldVectorGLTest::DistanceFieldVectorGLTest() { &DistanceFieldVectorGLTest::construct<3>}, Containers::arraySize(ConstructData)); + addTests({ + &DistanceFieldVectorGLTest::constructAsync<2>, + &DistanceFieldVectorGLTest::constructAsync<3>}); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({ &DistanceFieldVectorGLTest::constructUniformBuffers<2>, &DistanceFieldVectorGLTest::constructUniformBuffers<3>}, Containers::arraySize(ConstructUniformBuffersData)); + + addTests({ + &DistanceFieldVectorGLTest::constructUniformBuffersAsync<2>, + &DistanceFieldVectorGLTest::constructUniformBuffersAsync<3>}); #endif addTests({ @@ -374,6 +385,29 @@ template void DistanceFieldVectorGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +template void DistanceFieldVectorGLTest::constructAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + auto compileState = DistanceFieldVectorGL::compile(DistanceFieldVectorGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(compileState.flags(), DistanceFieldVectorGL2D::Flag::TextureTransformation); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + DistanceFieldVectorGL shader{std::move(compileState)}; + CORRADE_COMPARE(shader.flags(), DistanceFieldVectorGL2D::Flag::TextureTransformation); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + #ifndef MAGNUM_TARGET_GLES2 template void DistanceFieldVectorGLTest::constructUniformBuffers() { setTestCaseTemplateName(Utility::format("{}", dimensions)); @@ -413,6 +447,39 @@ template void DistanceFieldVectorGLTest::constructUnifor MAGNUM_VERIFY_NO_GL_ERROR(); } + +template void DistanceFieldVectorGLTest::constructUniformBuffersAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + auto compileState = DistanceFieldVectorGL::compile(DistanceFieldVectorGL2D::Flag::UniformBuffers, 16, 4); + CORRADE_COMPARE(compileState.flags(), DistanceFieldVectorGL2D::Flag::UniformBuffers); + CORRADE_COMPARE(compileState.materialCount(), 16); + CORRADE_COMPARE(compileState.drawCount(), 4); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + DistanceFieldVectorGL shader{std::move(compileState)}; + CORRADE_COMPARE(shader.flags(), DistanceFieldVectorGL2D::Flag::UniformBuffers); + CORRADE_COMPARE(shader.materialCount(), 16); + CORRADE_COMPARE(shader.drawCount(), 4); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} #endif template void DistanceFieldVectorGLTest::constructMove() { diff --git a/src/Magnum/Shaders/Test/FlatGLTest.cpp b/src/Magnum/Shaders/Test/FlatGLTest.cpp index 730bb0fad..192c5fca2 100644 --- a/src/Magnum/Shaders/Test/FlatGLTest.cpp +++ b/src/Magnum/Shaders/Test/FlatGLTest.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #ifdef CORRADE_TARGET_APPLE @@ -83,8 +84,10 @@ struct FlatGLTest: GL::OpenGLTester { explicit FlatGLTest(); template void construct(); + template void constructAsync(); #ifndef MAGNUM_TARGET_GLES2 template void constructUniformBuffers(); + template void constructUniformBuffersAsync(); #endif template void constructMove(); @@ -600,11 +603,19 @@ FlatGLTest::FlatGLTest() { &FlatGLTest::construct<3>}, Containers::arraySize(ConstructData)); + addTests({ + &FlatGLTest::constructAsync<2>, + &FlatGLTest::constructAsync<3>}); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({ &FlatGLTest::constructUniformBuffers<2>, &FlatGLTest::constructUniformBuffers<3>}, Containers::arraySize(ConstructUniformBuffersData)); + + addTests({ + &FlatGLTest::constructUniformBuffersAsync<2>, + &FlatGLTest::constructUniformBuffersAsync<3>}); #endif addTests({ @@ -853,6 +864,28 @@ template void FlatGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +template void FlatGLTest::constructAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + auto compileState = FlatGL::compile(FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(compileState.flags(), FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + FlatGL shader{std::move(compileState)}; + CORRADE_COMPARE(shader.flags(), FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation); + + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + #ifndef MAGNUM_TARGET_GLES2 template void FlatGLTest::constructUniformBuffers() { setTestCaseTemplateName(Utility::format("{}", dimensions)); @@ -896,6 +929,38 @@ template void FlatGLTest::constructUniformBuffers() { MAGNUM_VERIFY_NO_GL_ERROR(); } + +template void FlatGLTest::constructUniformBuffersAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + auto compileState = FlatGL::compile(FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask, 1, 1); + CORRADE_COMPARE(compileState.flags(), FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask); + CORRADE_COMPARE(compileState.materialCount(), 1); + CORRADE_COMPARE(compileState.drawCount(), 1); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + FlatGL shader{std::move(compileState)}; + CORRADE_COMPARE(shader.flags(), FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask); + CORRADE_COMPARE(shader.materialCount(), 1); + CORRADE_COMPARE(shader.drawCount(), 1); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + #endif template void FlatGLTest::constructMove() { diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index 7ac0ac612..fbe96056e 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #ifdef CORRADE_TARGET_APPLE @@ -88,12 +89,16 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { explicit MeshVisualizerGLTest(); void construct2D(); + void construct2DAsync(); #ifndef MAGNUM_TARGET_GLES2 void constructUniformBuffers2D(); + void constructUniformBuffers2DAsync(); #endif void construct3D(); + void construct3DAsync(); #ifndef MAGNUM_TARGET_GLES2 void constructUniformBuffers3D(); + void constructUniformBuffers3DAsync(); #endif void construct2DInvalid(); @@ -1055,17 +1060,23 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { addInstancedTests({&MeshVisualizerGLTest::construct2D}, Containers::arraySize(ConstructData2D)); + addTests({&MeshVisualizerGLTest::construct2DAsync}); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffers2D}, Containers::arraySize(ConstructUniformBuffersData2D)); + addTests({&MeshVisualizerGLTest::constructUniformBuffers2DAsync}); #endif addInstancedTests({&MeshVisualizerGLTest::construct3D}, Containers::arraySize(ConstructData3D)); + addTests({&MeshVisualizerGLTest::construct3DAsync}); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffers3D}, Containers::arraySize(ConstructUniformBuffersData3D)); + addTests({&MeshVisualizerGLTest::constructUniformBuffers3DAsync}); #endif addInstancedTests({&MeshVisualizerGLTest::construct2DInvalid}, @@ -1403,6 +1414,28 @@ void MeshVisualizerGLTest::construct2D() { MAGNUM_VERIFY_NO_GL_ERROR(); } + +void MeshVisualizerGLTest::construct2DAsync() { + auto compileState = MeshVisualizerGL2D::compile(MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(compileState.flags(), MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + MeshVisualizerGL2D shader{std::move(compileState)}; + CORRADE_COMPARE(shader.flags(), MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + #ifndef MAGNUM_TARGET_GLES2 void MeshVisualizerGLTest::constructUniformBuffers2D() { auto&& data = ConstructUniformBuffersData2D[testCaseInstanceId()]; @@ -1480,8 +1513,40 @@ void MeshVisualizerGLTest::constructUniformBuffers2D() { MAGNUM_VERIFY_NO_GL_ERROR(); } + + +void MeshVisualizerGLTest::constructUniformBuffers2DAsync() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + auto compileState = MeshVisualizerGL2D::compile( MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 8, 55); + CORRADE_COMPARE(compileState.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(compileState.materialCount(), 8); + CORRADE_COMPARE(compileState.drawCount(), 55); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + MeshVisualizerGL2D shader{std::move(compileState)}; + CORRADE_COMPARE(shader.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(shader.materialCount(), 8); + CORRADE_COMPARE(shader.drawCount(), 55); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} #endif + void MeshVisualizerGLTest::construct3D() { auto&& data = ConstructData3D[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -1541,6 +1606,27 @@ void MeshVisualizerGLTest::construct3D() { MAGNUM_VERIFY_NO_GL_ERROR(); } +void MeshVisualizerGLTest::construct3DAsync() { + auto compileState = MeshVisualizerGL3D::compile(MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(compileState.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + MeshVisualizerGL3D shader{std::move(compileState)}; + CORRADE_COMPARE(shader.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + #ifndef MAGNUM_TARGET_GLES2 void MeshVisualizerGLTest::constructUniformBuffers3D() { auto&& data = ConstructUniformBuffersData3D[testCaseInstanceId()]; @@ -1618,6 +1704,36 @@ void MeshVisualizerGLTest::constructUniformBuffers3D() { MAGNUM_VERIFY_NO_GL_ERROR(); } + +void MeshVisualizerGLTest::constructUniformBuffers3DAsync() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + auto compileState = MeshVisualizerGL3D::compile(MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 6, 28); + CORRADE_COMPARE(compileState.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(compileState.materialCount(), 6); + CORRADE_COMPARE(compileState.drawCount(), 28); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + MeshVisualizerGL3D shader{std::move(compileState)}; + CORRADE_COMPARE(shader.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(compileState.materialCount(), 6); + CORRADE_COMPARE(compileState.drawCount(), 28); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} #endif void MeshVisualizerGLTest::construct2DInvalid() { diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index e6414a2c2..d0df58ec4 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -31,12 +31,12 @@ #include #include #include +#include #include #include #ifdef CORRADE_TARGET_APPLE #include -#include /* isSandboxed() */ #endif #include "Magnum/Image.h" @@ -83,8 +83,10 @@ struct PhongGLTest: GL::OpenGLTester { explicit PhongGLTest(); void construct(); + void constructAsync(); #ifndef MAGNUM_TARGET_GLES2 void constructUniformBuffers(); + void constructUniformBuffersAsync(); #endif void constructMove(); @@ -927,9 +929,13 @@ PhongGLTest::PhongGLTest() { addInstancedTests({&PhongGLTest::construct}, Containers::arraySize(ConstructData)); + addTests({&PhongGLTest::constructAsync}); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({&PhongGLTest::constructUniformBuffers}, Containers::arraySize(ConstructUniformBuffersData)); + + addTests({&PhongGLTest::constructUniformBuffersAsync}); #endif addTests({ @@ -1192,6 +1198,29 @@ void PhongGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +void PhongGLTest::constructAsync() { + auto compileState = PhongGL::compile(PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset, 3); + CORRADE_COMPARE(compileState.flags(), PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset); + CORRADE_COMPARE(compileState.lightCount(), 3); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + PhongGL shader{std::move(compileState)}; + CORRADE_COMPARE(shader.flags(), PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset); + CORRADE_COMPARE(shader.lightCount(), 3); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + #ifndef MAGNUM_TARGET_GLES2 void PhongGLTest::constructUniformBuffers() { auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; @@ -1234,6 +1263,38 @@ void PhongGLTest::constructUniformBuffers() { MAGNUM_VERIFY_NO_GL_ERROR(); } + +void PhongGLTest::constructUniformBuffersAsync() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + auto compileState = PhongGL::compile(PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling, 8, 8, 24); + CORRADE_COMPARE(compileState.flags(), PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling); + CORRADE_COMPARE(compileState.lightCount(), 8); + CORRADE_COMPARE(compileState.materialCount(), 8); + CORRADE_COMPARE(compileState.drawCount(), 24); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + PhongGL shader{std::move(compileState)}; + CORRADE_COMPARE(shader.flags(), PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling); + CORRADE_COMPARE(shader.lightCount(), 8); + CORRADE_COMPARE(shader.materialCount(), 8); + CORRADE_COMPARE(shader.drawCount(), 24); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} #endif void PhongGLTest::constructMove() { diff --git a/src/Magnum/Shaders/Test/VectorGLTest.cpp b/src/Magnum/Shaders/Test/VectorGLTest.cpp index 670886fb7..715688e1e 100644 --- a/src/Magnum/Shaders/Test/VectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VectorGLTest.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #ifdef CORRADE_TARGET_APPLE @@ -83,8 +84,10 @@ struct VectorGLTest: GL::OpenGLTester { explicit VectorGLTest(); template void construct(); + template void constructAsync(); #ifndef MAGNUM_TARGET_GLES2 template void constructUniformBuffers(); + template void constructUniformBuffersAsync(); #endif template void constructMove(); @@ -247,11 +250,19 @@ VectorGLTest::VectorGLTest() { &VectorGLTest::construct<3>}, Containers::arraySize(ConstructData)); + addTests({ + &VectorGLTest::constructAsync<2>, + &VectorGLTest::constructAsync<3>}); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({ &VectorGLTest::constructUniformBuffers<2>, &VectorGLTest::constructUniformBuffers<3>}, Containers::arraySize(ConstructUniformBuffersData)); + + addTests({ + &VectorGLTest::constructUniformBuffersAsync<2>, + &VectorGLTest::constructUniformBuffersAsync<3>}); #endif addTests({ @@ -370,6 +381,30 @@ template void VectorGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +template void VectorGLTest::constructAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + auto compileState = VectorGL::compile(VectorGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(compileState.flags(), VectorGL2D::Flag::TextureTransformation); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + VectorGL shader{std::move(compileState)}; + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_COMPARE(shader.flags(), VectorGL2D::Flag::TextureTransformation); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + + #ifndef MAGNUM_TARGET_GLES2 template void VectorGLTest::constructUniformBuffers() { setTestCaseTemplateName(Utility::format("{}", dimensions)); @@ -408,6 +443,38 @@ template void VectorGLTest::constructUniformBuffers() { MAGNUM_VERIFY_NO_GL_ERROR(); } + +template void VectorGLTest::constructUniformBuffersAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + auto compileState = VectorGL::compile(VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation, 1, 1); + CORRADE_COMPARE(compileState.flags(), VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(compileState.materialCount(), 1); + CORRADE_COMPARE(compileState.drawCount(), 1); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + VectorGL shader{std::move(compileState)}; + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_COMPARE(shader.flags(), VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(shader.materialCount(), 1); + CORRADE_COMPARE(shader.drawCount(), 1); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} #endif template void VectorGLTest::constructMove() { diff --git a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp index abbd56bf4..451c7348f 100644 --- a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #ifdef CORRADE_TARGET_APPLE @@ -74,8 +75,10 @@ struct VertexColorGLTest: GL::OpenGLTester { explicit VertexColorGLTest(); template void construct(); + template void constructAsync(); #ifndef MAGNUM_TARGET_GLES2 template void constructUniformBuffers(); + template void constructUniformBuffersAsync(); #endif template void constructMove(); @@ -190,13 +193,19 @@ constexpr struct { VertexColorGLTest::VertexColorGLTest() { addTests({ &VertexColorGLTest::construct<2>, - &VertexColorGLTest::construct<3>}); + &VertexColorGLTest::construct<3>, + &VertexColorGLTest::constructAsync<2>, + &VertexColorGLTest::constructAsync<3>}); #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({ &VertexColorGLTest::constructUniformBuffers<2>, &VertexColorGLTest::constructUniformBuffers<3>}, Containers::arraySize(ConstructUniformBuffersData)); + + addTests({ + &VertexColorGLTest::constructUniformBuffersAsync<2>, + &VertexColorGLTest::constructUniformBuffersAsync<3>}); #endif addTests({ @@ -309,6 +318,28 @@ template void VertexColorGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +template void VertexColorGLTest::constructAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + auto compileState = VertexColorGL::compile({}); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + VertexColorGL shader{std::move(compileState)}; + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + + #ifndef MAGNUM_TARGET_GLES2 template void VertexColorGLTest::constructUniformBuffers() { setTestCaseTemplateName(Utility::format("{}", dimensions)); @@ -347,6 +378,36 @@ template void VertexColorGLTest::constructUniformBuffers MAGNUM_VERIFY_NO_GL_ERROR(); } + +template void VertexColorGLTest::constructUniformBuffersAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + auto compileState = VertexColorGL::compile(VertexColorGL2D::Flag::UniformBuffers, 63); + CORRADE_COMPARE(compileState.flags(), VertexColorGL2D::Flag::UniformBuffers); + CORRADE_COMPARE(compileState.drawCount(), 63); + + while(!compileState.isLinkFinished()) + Utility::System::sleep(100); + + VertexColorGL shader{std::move(compileState)}; + CORRADE_COMPARE(shader.flags(), VertexColorGL2D::Flag::UniformBuffers); + CORRADE_COMPARE(shader.drawCount(), 63); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} #endif template void VertexColorGLTest::constructMove() { diff --git a/src/Magnum/Shaders/VectorGL.cpp b/src/Magnum/Shaders/VectorGL.cpp index addcb7433..dd6ff2c07 100644 --- a/src/Magnum/Shaders/VectorGL.cpp +++ b/src/Magnum/Shaders/VectorGL.cpp @@ -63,21 +63,16 @@ namespace { #endif } -template VectorGL::VectorGL(const Flags flags +template typename VectorGL::CompileState VectorGL::compile(const Flags flags #ifndef MAGNUM_TARGET_GLES2 , const UnsignedInt materialCount, const UnsignedInt drawCount #endif -): - _flags{flags} - #ifndef MAGNUM_TARGET_GLES2 - , _materialCount{materialCount}, _drawCount{drawCount} - #endif -{ +) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, - "Shaders::VectorGL: material count can't be zero", ); + "Shaders::VectorGL: material count can't be zero", CompileState{NoCreate}); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::VectorGL: draw count can't be zero", ); + "Shaders::VectorGL: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -141,9 +136,17 @@ template VectorGL::VectorGL(const Flags flag frag.addSource(rs.getString("generic.glsl")) .addSource(rs.getString("Vector.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); + + VectorGL out{NoInit}; + out._flags = flags; + #ifndef MAGNUM_TARGET_GLES2 + out._materialCount = materialCount; + out._drawCount = drawCount; + #endif - attachShaders({vert, frag}); + out.attachShaders({vert, frag}); /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) @@ -151,25 +154,37 @@ template VectorGL::VectorGL(const Flags flag if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); - bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); + out.bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag), version}; +} + +template VectorGL::VectorGL(CompileState&& cs): VectorGL{static_cast(std::move(cs))} { + if (id() == 0) return; + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = cs._version; + #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else #endif { _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) _textureMatrixUniform = uniformLocation("textureMatrix"); _backgroundColorUniform = uniformLocation("backgroundColor"); _colorUniform = uniformLocation("color"); @@ -182,10 +197,10 @@ template VectorGL::VectorGL(const Flags flag { setUniform(uniformLocation("vectorTexture"), TextureUnit); #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); } @@ -195,24 +210,36 @@ template VectorGL::VectorGL(const Flags flag /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { /* Draw offset is zero by default */ } else #endif { setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setTextureMatrix(Matrix3{Math::IdentityInit}); /* Background color is zero by default */ setColor(Color4{1.0f}); } #endif + + static_cast(context); + static_cast(version); } +template VectorGL::VectorGL(Flags flags): VectorGL{compile(flags)} {} + #ifndef MAGNUM_TARGET_GLES2 -template VectorGL::VectorGL(const Flags flags): VectorGL{flags, 1, 1} {} +template typename VectorGL::CompileState VectorGL::compile(Flags flags) { + return compile(flags, 1, 1); +} + +template VectorGL::VectorGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount): + VectorGL{compile(flags, materialCount, drawCount)} {} #endif +template VectorGL::VectorGL(NoInitT) {} + template VectorGL& VectorGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), diff --git a/src/Magnum/Shaders/VectorGL.h b/src/Magnum/Shaders/VectorGL.h index 3b8519fa3..057dfc620 100644 --- a/src/Magnum/Shaders/VectorGL.h +++ b/src/Magnum/Shaders/VectorGL.h @@ -32,6 +32,7 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" +#include "Magnum/GL/Shader.h" #include "Magnum/Shaders/GenericGL.h" #include "Magnum/Shaders/visibility.h" @@ -273,6 +274,20 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL */ explicit VectorGL(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} + class CompileState; + + explicit VectorGL(CompileState&& cs); + + static CompileState compile(Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , UnsignedInt materialCount, UnsignedInt drawCount + #endif + ); + + #ifndef MAGNUM_TARGET_GLES2 + static CompileState compile(Flags flags); + #endif + /** @brief Copying is not allowed */ VectorGL(const VectorGL&) = delete; @@ -554,6 +569,9 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL #endif private: + /* Creates the GL shader program object but nothing else. Internal, used by compile(). */ + explicit VectorGL(NoInitT); + /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES using GL::AbstractShaderProgram::drawTransformFeedback; @@ -577,6 +595,19 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL #endif }; +template class VectorGL::CompileState: public VectorGL { +private: + friend class VectorGL; + + explicit CompileState(NoCreateT): VectorGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + CompileState(VectorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): + VectorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + + GL::Shader _vert, _frag; + GL::Version _version; +}; + /** @brief Two-dimensional vector OpenGL shader @m_since_latest diff --git a/src/Magnum/Shaders/VertexColorGL.cpp b/src/Magnum/Shaders/VertexColorGL.cpp index 31d6e4821..01af44cd1 100644 --- a/src/Magnum/Shaders/VertexColorGL.cpp +++ b/src/Magnum/Shaders/VertexColorGL.cpp @@ -31,7 +31,6 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" -#include "Magnum/GL/Shader.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" @@ -57,19 +56,14 @@ namespace { #endif } -template VertexColorGL::VertexColorGL(const Flags flags +template typename VertexColorGL::CompileState VertexColorGL::compile(const Flags flags #ifndef MAGNUM_TARGET_GLES2 , const UnsignedInt drawCount #endif -): - _flags{flags} - #ifndef MAGNUM_TARGET_GLES2 - , _drawCount{drawCount} - #endif -{ +) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::VertexColorGL: draw count can't be zero", ); + "Shaders::VertexColorGL: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -121,9 +115,16 @@ template VertexColorGL::VertexColorGL(const frag.addSource(rs.getString("generic.glsl")) .addSource(rs.getString("VertexColor.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); + + VertexColorGL out{NoInit}; + out._flags = flags; + #ifndef MAGNUM_TARGET_GLES2 + out._drawCount = drawCount; + #endif - attachShaders({vert, frag}); + out.attachShaders({vert, frag}); /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) @@ -131,19 +132,30 @@ template VertexColorGL::VertexColorGL(const if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); - bindAttributeLocation(Color3::Location, "color"); /* Color4 is the same */ + out.bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(Color3::Location, "color"); /* Color4 is the same */ } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag), version}; +} + +template VertexColorGL::VertexColorGL(CompileState&& cs): VertexColorGL{static_cast(std::move(cs))} { + if (id() == 0) return; + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = cs._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else #endif @@ -153,7 +165,7 @@ template VertexColorGL::VertexColorGL(const } #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers + if(_flags >= Flag::UniformBuffers #ifndef MAGNUM_TARGET_GLES && !context.isExtensionSupported(version) #endif @@ -165,7 +177,7 @@ template VertexColorGL::VertexColorGL(const /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { /* Draw offset is zero by default */ } else #endif @@ -173,12 +185,24 @@ template VertexColorGL::VertexColorGL(const setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); } #endif + + static_cast(context); + static_cast(version); } +template VertexColorGL::VertexColorGL(Flags flags): VertexColorGL{compile(flags)} {} + #ifndef MAGNUM_TARGET_GLES2 -template VertexColorGL::VertexColorGL(const Flags flags): VertexColorGL{flags, 1} {} +template typename VertexColorGL::CompileState VertexColorGL::compile(Flags flags) { + return compile(flags, 1); +} + +template VertexColorGL::VertexColorGL(Flags flags, UnsignedInt drawCount): + VertexColorGL{compile(flags, drawCount)} {} #endif +template VertexColorGL::VertexColorGL(NoInitT) {} + template VertexColorGL& VertexColorGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), diff --git a/src/Magnum/Shaders/VertexColorGL.h b/src/Magnum/Shaders/VertexColorGL.h index 3ecdd673f..988af7811 100644 --- a/src/Magnum/Shaders/VertexColorGL.h +++ b/src/Magnum/Shaders/VertexColorGL.h @@ -32,6 +32,7 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" +#include "Magnum/GL/Shader.h" #include "Magnum/Shaders/GenericGL.h" #include "Magnum/Shaders/visibility.h" @@ -264,6 +265,20 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ */ explicit VertexColorGL(NoCreateT) noexcept: AbstractShaderProgram{NoCreate} {} + class CompileState; + + explicit VertexColorGL(CompileState&& cs); + + static CompileState compile(Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , UnsignedInt drawCount + #endif + ); + + #ifndef MAGNUM_TARGET_GLES2 + static CompileState compile(Flags flags); + #endif + /** @brief Copying is not allowed */ VertexColorGL(const VertexColorGL&) = delete; @@ -406,6 +421,8 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ #endif private: + explicit VertexColorGL(NoInitT); + /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES using GL::AbstractShaderProgram::drawTransformFeedback; @@ -426,6 +443,19 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ #endif }; +template class VertexColorGL::CompileState: public VertexColorGL { +private: + friend class VertexColorGL; + + explicit CompileState(NoCreateT): VertexColorGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + CompileState(VertexColorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): + VertexColorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + + GL::Shader _vert, _frag; + GL::Version _version; +}; + /** @brief 2D vertex color OpenGL shader @m_since_latest From 1f3c250e8e4fd912176fbe699f6cb81fa89c08e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 5 Sep 2022 22:00:45 +0200 Subject: [PATCH 51/93] doc: move MyShader definition out of snippet main(). Doesn't make sense to have it there. I wanted to add another MyShader wrapped in a namespace and that was impossible to do next to this one. --- doc/snippets/MagnumGL.cpp | 318 +++++++++++++++++++------------------- 1 file changed, 159 insertions(+), 159 deletions(-) diff --git a/doc/snippets/MagnumGL.cpp b/doc/snippets/MagnumGL.cpp index 2425d0d9c..98b995c73 100644 --- a/doc/snippets/MagnumGL.cpp +++ b/doc/snippets/MagnumGL.cpp @@ -90,165 +90,6 @@ using namespace Magnum; using namespace Magnum::Math::Literals; -int main() { - -#ifndef MAGNUM_TARGET_GLES2 -{ -ImageView2D diffuse{PixelFormat::RGBA8Unorm, {}}; -ImageView2D specular{PixelFormat::RGBA8Unorm, {}}; -ImageView2D bump{PixelFormat::RGBA8Unorm, {}}; -/* [method-chaining-texture] */ -GL::Texture2D carDiffuseTexture, carSpecularTexture, carBumpTexture; - -carDiffuseTexture.setStorage(5, GL::TextureFormat::SRGB8, {256, 256}); -carSpecularTexture.setStorage(3, GL::TextureFormat::R8, {256, 256}); -carBumpTexture.setStorage(5, GL::TextureFormat::RGB8, {256, 256}); -carDiffuseTexture.setSubImage(0, {}, diffuse); -carSpecularTexture.setSubImage(0, {}, specular); -carBumpTexture.setSubImage(0, {}, bump); -carDiffuseTexture.generateMipmap(); -carSpecularTexture.generateMipmap(); -carBumpTexture.generateMipmap(); -/* [method-chaining-texture] */ - -/* [method-chaining-texture-chained] */ -carDiffuseTexture.setStorage(5, GL::TextureFormat::SRGB8, {256, 256}) - .setSubImage(0, {}, diffuse) - .generateMipmap(); -carSpecularTexture.setStorage(3, GL::TextureFormat::R8, {256, 256}) - .setSubImage(0, {}, diffuse) - .generateMipmap(); -carBumpTexture.setStorage(5, GL::TextureFormat::RGB8, {256, 256}) - .setSubImage(0, {}, bump) - .generateMipmap(); -/* [method-chaining-texture-chained] */ -} -#endif - -{ -struct Foo { - void setSomeBuffer(GLuint) {} - GLuint someBuffer() { return {}; } -} externalLib; -char someData[1]; -/* [opengl-wrapping-transfer] */ -/* Transferring the instance to external library */ -{ - GL::Buffer buffer; - buffer.setData(someData, GL::BufferUsage::StaticDraw); - GLuint id = buffer.release(); - externalLib.setSomeBuffer(id); /* The library is responsible for deletion */ -} - -/* Acquiring an instance from external library */ -{ - GLuint id = externalLib.someBuffer(); - GL::Buffer buffer = GL::Buffer::wrap(id, GL::ObjectFlag::DeleteOnDestruction); - /* The buffer instance now handles deletion */ -} -/* [opengl-wrapping-transfer] */ -} - -#ifndef MAGNUM_TARGET_GLES -{ -struct: GL::AbstractShaderProgram {} someShader; -/* [opengl-wrapping-state] */ -GL::Buffer buffer; -GL::Mesh mesh; -// ... -someShader.draw(mesh); - -{ - /* Entering a section with 3rd-party OpenGL code -- clean up all state that - could cause accidental modifications of our objects from outside */ - GL::Context::current().resetState(GL::Context::State::EnterExternal); - - /* Raw OpenGL calls */ - glBindBuffer(GL_ARRAY_BUFFER, buffer.id()); - glBufferStorage(GL_ARRAY_BUFFER, 32768, nullptr, GL_MAP_READ_BIT|GL_MAP_WRITE_BIT); - // ... - - /* Exiting a section with 3rd-party OpenGL code -- reset our state tracker */ - GL::Context::current().resetState(GL::Context::State::ExitExternal); -} - -/* Use the buffer through Magnum again */ -auto data = buffer.map(0, 32768, GL::Buffer::MapFlag::Read|GL::Buffer::MapFlag::Write); -// ... -/* [opengl-wrapping-state] */ -static_cast(data); -} -#endif - -#ifndef MAGNUM_TARGET_GLES -{ -/* [opengl-wrapping-extensions] */ -GL::TextureFormat format; -if(GL::Context::current().isExtensionSupported()) - format = GL::TextureFormat::DepthComponent32F; -else - format = GL::TextureFormat::DepthComponent24; -/* [opengl-wrapping-extensions] */ -static_cast(format); -} -#endif - -#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) -{ -/* [opengl-wrapping-dsa] */ -GL::Texture2D texture; - -/* - on OpenGL 4.5+/ARB_direct_state_access this calls glTextureStorage2D() - - on OpenGL 4.2+/ARB_texture_storage and OpenGL ES 3.0+ calls glTexStorage2D() - - on OpenGL ES 2.0 with EXT_texture_storage calls glTexStorage2DEXT() - - otherwise emulated using a sequence of four glTexImage2D() calls */ -texture.setStorage(4, GL::TextureFormat::RGBA8, {256, 256}); -/* [opengl-wrapping-dsa] */ -} -#endif - -{ -/* [portability-targets] */ -#ifndef MAGNUM_TARGET_GLES -GL::Renderer::setPolygonMode(GL::Renderer::PolygonMode::Line); -// draw mesh as wireframe... -#else -// use different mesh, as polygon mode is not supported in OpenGL ES... -#endif -/* [portability-targets] */ -} - -#ifndef MAGNUM_TARGET_GLES -{ -/* [portability-extensions] */ -if(GL::Context::current().isExtensionSupported()) { - // draw mesh with wireframe on top in one pass using geometry shader... -} else { - // draw underlying mesh... - GL::Renderer::setPolygonMode(GL::Renderer::PolygonMode::Line); - // draw mesh as wirefreame in second pass... -} -/* [portability-extensions] */ -} - -{ -/* [portability-extension-assert] */ -MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::geometry_shader4); -// just use geometry shader and don't care about old hardware -/* [portability-extension-assert] */ -} - -{ -/* [portability-shaders] */ -// MyShader.cpp -GL::Version version = GL::Context::current().supportedVersion({ - GL::Version::GL430, GL::Version::GL330, GL::Version::GL210}); -GL::Shader vert{version, GL::Shader::Type::Vertex}; -vert.addFile("MyShader.vert"); -/* [portability-shaders] */ -} -#endif - #ifndef MAGNUM_TARGET_GLES struct MyShader: GL::AbstractShaderProgram { /* [AbstractShaderProgram-input-attributes] */ @@ -416,6 +257,165 @@ setTransformFeedbackOutputs({ }; #endif +int main() { + +#ifndef MAGNUM_TARGET_GLES2 +{ +ImageView2D diffuse{PixelFormat::RGBA8Unorm, {}}; +ImageView2D specular{PixelFormat::RGBA8Unorm, {}}; +ImageView2D bump{PixelFormat::RGBA8Unorm, {}}; +/* [method-chaining-texture] */ +GL::Texture2D carDiffuseTexture, carSpecularTexture, carBumpTexture; + +carDiffuseTexture.setStorage(5, GL::TextureFormat::SRGB8, {256, 256}); +carSpecularTexture.setStorage(3, GL::TextureFormat::R8, {256, 256}); +carBumpTexture.setStorage(5, GL::TextureFormat::RGB8, {256, 256}); +carDiffuseTexture.setSubImage(0, {}, diffuse); +carSpecularTexture.setSubImage(0, {}, specular); +carBumpTexture.setSubImage(0, {}, bump); +carDiffuseTexture.generateMipmap(); +carSpecularTexture.generateMipmap(); +carBumpTexture.generateMipmap(); +/* [method-chaining-texture] */ + +/* [method-chaining-texture-chained] */ +carDiffuseTexture.setStorage(5, GL::TextureFormat::SRGB8, {256, 256}) + .setSubImage(0, {}, diffuse) + .generateMipmap(); +carSpecularTexture.setStorage(3, GL::TextureFormat::R8, {256, 256}) + .setSubImage(0, {}, diffuse) + .generateMipmap(); +carBumpTexture.setStorage(5, GL::TextureFormat::RGB8, {256, 256}) + .setSubImage(0, {}, bump) + .generateMipmap(); +/* [method-chaining-texture-chained] */ +} +#endif + +{ +struct Foo { + void setSomeBuffer(GLuint) {} + GLuint someBuffer() { return {}; } +} externalLib; +char someData[1]; +/* [opengl-wrapping-transfer] */ +/* Transferring the instance to external library */ +{ + GL::Buffer buffer; + buffer.setData(someData, GL::BufferUsage::StaticDraw); + GLuint id = buffer.release(); + externalLib.setSomeBuffer(id); /* The library is responsible for deletion */ +} + +/* Acquiring an instance from external library */ +{ + GLuint id = externalLib.someBuffer(); + GL::Buffer buffer = GL::Buffer::wrap(id, GL::ObjectFlag::DeleteOnDestruction); + /* The buffer instance now handles deletion */ +} +/* [opengl-wrapping-transfer] */ +} + +#ifndef MAGNUM_TARGET_GLES +{ +struct: GL::AbstractShaderProgram {} someShader; +/* [opengl-wrapping-state] */ +GL::Buffer buffer; +GL::Mesh mesh; +// ... +someShader.draw(mesh); + +{ + /* Entering a section with 3rd-party OpenGL code -- clean up all state that + could cause accidental modifications of our objects from outside */ + GL::Context::current().resetState(GL::Context::State::EnterExternal); + + /* Raw OpenGL calls */ + glBindBuffer(GL_ARRAY_BUFFER, buffer.id()); + glBufferStorage(GL_ARRAY_BUFFER, 32768, nullptr, GL_MAP_READ_BIT|GL_MAP_WRITE_BIT); + // ... + + /* Exiting a section with 3rd-party OpenGL code -- reset our state tracker */ + GL::Context::current().resetState(GL::Context::State::ExitExternal); +} + +/* Use the buffer through Magnum again */ +auto data = buffer.map(0, 32768, GL::Buffer::MapFlag::Read|GL::Buffer::MapFlag::Write); +// ... +/* [opengl-wrapping-state] */ +static_cast(data); +} +#endif + +#ifndef MAGNUM_TARGET_GLES +{ +/* [opengl-wrapping-extensions] */ +GL::TextureFormat format; +if(GL::Context::current().isExtensionSupported()) + format = GL::TextureFormat::DepthComponent32F; +else + format = GL::TextureFormat::DepthComponent24; +/* [opengl-wrapping-extensions] */ +static_cast(format); +} +#endif + +#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) +{ +/* [opengl-wrapping-dsa] */ +GL::Texture2D texture; + +/* - on OpenGL 4.5+/ARB_direct_state_access this calls glTextureStorage2D() + - on OpenGL 4.2+/ARB_texture_storage and OpenGL ES 3.0+ calls glTexStorage2D() + - on OpenGL ES 2.0 with EXT_texture_storage calls glTexStorage2DEXT() + - otherwise emulated using a sequence of four glTexImage2D() calls */ +texture.setStorage(4, GL::TextureFormat::RGBA8, {256, 256}); +/* [opengl-wrapping-dsa] */ +} +#endif + +{ +/* [portability-targets] */ +#ifndef MAGNUM_TARGET_GLES +GL::Renderer::setPolygonMode(GL::Renderer::PolygonMode::Line); +// draw mesh as wireframe... +#else +// use different mesh, as polygon mode is not supported in OpenGL ES... +#endif +/* [portability-targets] */ +} + +#ifndef MAGNUM_TARGET_GLES +{ +/* [portability-extensions] */ +if(GL::Context::current().isExtensionSupported()) { + // draw mesh with wireframe on top in one pass using geometry shader... +} else { + // draw underlying mesh... + GL::Renderer::setPolygonMode(GL::Renderer::PolygonMode::Line); + // draw mesh as wirefreame in second pass... +} +/* [portability-extensions] */ +} + +{ +/* [portability-extension-assert] */ +MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::geometry_shader4); +// just use geometry shader and don't care about old hardware +/* [portability-extension-assert] */ +} + +{ +/* [portability-shaders] */ +// MyShader.cpp +GL::Version version = GL::Context::current().supportedVersion({ + GL::Version::GL430, GL::Version::GL330, GL::Version::GL210}); +GL::Shader vert{version, GL::Shader::Type::Vertex}; +vert.addFile("MyShader.vert"); +/* [portability-shaders] */ +} +#endif + #ifndef MAGNUM_TARGET_GLES { MyShader shader; From 4580c30d1e4fbe1cef744d980a137c8fe0923344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 15:06:43 +0200 Subject: [PATCH 52/93] GL: document async shader compilation and linking. --- doc/shaders.dox | 28 ++++++ doc/snippets/MagnumGL.cpp | 69 +++++++++++++++ src/Magnum/GL/AbstractShaderProgram.h | 119 +++++++++++++++++++++----- src/Magnum/GL/Shader.cpp | 2 +- src/Magnum/GL/Shader.h | 57 ++++++++---- 5 files changed, 238 insertions(+), 37 deletions(-) diff --git a/doc/shaders.dox b/doc/shaders.dox index 36ab1fd2b..9489b5853 100644 --- a/doc/shaders.dox +++ b/doc/shaders.dox @@ -287,6 +287,34 @@ While the primary use case of texture arrays is with uniform buffers and multidraw, they work in the classic uniform workflow as well --- use @relativeref{Shaders::PhongGL,setTextureLayer()} there instead. +@section shaders-async Async shader compilation and linking + +By default, shaders are compiled and linked directly in their constructor. +While that's convenient and easy to use, applications using heavier shaders, +many shader combinations or running on platforms that translate GLSL to other +APIs such as HLSL or MSL, may spend a significant portion of their startup +time just on shader compilation and linking. + +To mitigate this problem, shaders can be compiled in an asynchronous way. +Depending on the driver and system, this can mean that for example eight +shaders get compiled at the same time in eight parallel threads, instead of +sequentially one after another. To achieve such parallelism, the construction +needs to be broken into two parts --- first submitting compilation of all +shaders using @ref Shaders::FlatGL::compile() "Shaders::*GL::compile()", +forming temporary @ref Shaders::FlatGL::CompileState "Shaders::*GL::CompileState" +instances, then possibly doing other work until it's completed, and finally +constructing final shader instances out of the temporary state: + +@snippet MagnumShaders-gl.cpp shaders-async + +The above code will work correctly also on drivers that implement async +compilation partially or not at all --- there +@ref GL::AbstractShaderProgram::isLinkFinished() will implicitly return +@cpp true @ce, and the final construction will stall if it happens before a +(potentially async) compilation is finished. See also the +@ref GL-AbstractShaderProgram-async "GL::AbstractShaderProgram documentation" +for more information. + @section shaders-generic Generic vertex attributes and framebuffer attachments Many shaders share the same vertex attribute definitions, such as positions, diff --git a/doc/snippets/MagnumGL.cpp b/doc/snippets/MagnumGL.cpp index 98b995c73..59d627381 100644 --- a/doc/snippets/MagnumGL.cpp +++ b/doc/snippets/MagnumGL.cpp @@ -257,6 +257,59 @@ setTransformFeedbackOutputs({ }; #endif +#ifndef MAGNUM_TARGET_GLES +namespace Foo { + +struct MyShader: GL::AbstractShaderProgram { + class CompileState; + + MyShader(NoInitT); + MyShader(CompileState&&); + MyShader(int); + + static CompileState compile(int); +}; + +/* [AbstractShaderProgram-async] */ +class MyShader::CompileState: public MyShader { + friend MyShader; + + explicit CompileState(MyShader&& shader, GL::Shader&& vert, GL::Shader&& frag): + MyShader{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)} {} + + GL::Shader _vert, _frag; +}; + +MyShader::CompileState MyShader::compile(DOXYGEN_ELLIPSIS(int)) { + GL::Shader vert{GL::Version::GL430, GL::Shader::Type::Vertex}; + GL::Shader frag{GL::Version::GL430, GL::Shader::Type::Fragment}; + DOXYGEN_ELLIPSIS() + vert.submitCompile(); + frag.submitCompile(); + + MyShader out{NoInit}; + DOXYGEN_ELLIPSIS() + out.attachShaders({vert, frag}); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag)}; +} + +MyShader::MyShader(NoInitT) {} + +MyShader::MyShader(CompileState&& state): + MyShader{static_cast(std::move(state))} +{ + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + DOXYGEN_ELLIPSIS() +} + +MyShader::MyShader(DOXYGEN_ELLIPSIS(int a)): MyShader{compile(DOXYGEN_ELLIPSIS(a))} {} +/* [AbstractShaderProgram-async] */ + +} +#endif + int main() { #ifndef MAGNUM_TARGET_GLES2 @@ -432,6 +485,22 @@ shader.setTransformationMatrix(transformation) } #endif +#ifndef MAGNUM_TARGET_GLES +{ +using Foo::MyShader; +/* [AbstractShaderProgram-async-usage] */ +MyShader::CompileState state = MyShader::compile(DOXYGEN_ELLIPSIS(0)); +// Other shaders to compile.... + +while(!state.isLinkFinished()) { + // Do other work... +} + +MyShader shader{std::move(state)}; +/* [AbstractShaderProgram-async-usage] */ +} +#endif + { GL::Framebuffer framebuffer{{}}; /* [AbstractFramebuffer-read1] */ diff --git a/src/Magnum/GL/AbstractShaderProgram.h b/src/Magnum/GL/AbstractShaderProgram.h index a7ad8c5e1..682f46c70 100644 --- a/src/Magnum/GL/AbstractShaderProgram.h +++ b/src/Magnum/GL/AbstractShaderProgram.h @@ -422,6 +422,74 @@ See also @ref Attribute::DataType enum for additional type options. @ref Magnum::Matrix4x2 "Matrix4x2", @ref Magnum::Matrix3x4 "Matrix3x4" and @ref Magnum::Matrix4x3 "Matrix4x3") are not available in WebGL 1.0. +@section GL-AbstractShaderProgram-async Asynchronous shader compilation and linking + +The workflow described @ref GL-AbstractShaderProgram-subclassing "at the very top" +compiles and links the shader directly in a constructor. While that's fine for +many use cases, with heavier shaders, many shader combinations or on +platforms that translate GLSL to other APIs such as HLSL or MSL, the +compilation and linking can take a significant portion of application startup +time. + +To mitigate this problem, nowadays drivers implement *asynchronous compilation* +--- when shader compilation or linking is requested, the driver offloads the +work to separate worker threads, and serializes it back to the application +thread only once the application wants to retrieve the result of the operation. +Which means, the ideal way to spread the operation over more CPU cores is to +first submit compilation & linking of several shaders at once and only then ask +for operation result. That allows the driver to perform compilation/linking of +multiple shaders at once. Furthermore, the +@gl_extension{KHR,parallel_shader_compile} extension adds a possibility to +query whether the operation was finished for a particular shader. That allows +the application to schedule other work in the meantime. + +Async compilation and linking can be implemented by using +@ref Shader::submitCompile() and @ref submitLink(), followed by +@ref checkLink() (and optionally @ref Shader::checkCompile()), instead of +@ref Shader::compile() and @ref link(). Calling the submit functions will +trigger a (potentially async) compilation and linking, calling the check +functions will check the operation result, potentially stalling if the async +operation isn't finished yet. + +The @ref Shader::isCompileFinished() and +@ref isLinkFinished() APIs then provide a way to query if the submitted +operation finished. If @gl_extension{KHR,parallel_shader_compile} is not +available, those two implicitly return @cpp true @ce, thus effectively causing +a stall if the operation isn't yet done at the time you call +@ref Shader::checkCompile() / @ref checkLink() --- but compared to the linear +workflow you still get the benefits from submitting multiple operations at +once. + +A common way to equip an @ref AbstractShaderProgram subclass with async +creation capability while keeping also the simple constructor is the following: + +1. An internal @ref NoInit constructor for the subclass is added, which only + creates the @ref AbstractShaderProgram base but does nothing else. +2. A @cpp CompileState @ce inner class is defined as a subclass of + @cpp MyShader @ce. Besides that it holds all temporary state needed to + finish the construction --- in particular all @ref Shader instances. +3. A @cpp static CompileState compile(…) @ce function does everything until + and including linking as the original constructor did, except that it calls + @ref Shader::submitCompile() and @ref submitLink() instead of + @ref Shader::compile() and @ref link(), and returns a populated + @cpp CompileState @ce instance. +4. A @cpp MyShader(CompileState&&) @ce constructor then takes over the base + of @cpp CompileState @ce by delegating it into the move constructor. Then + it calls @ref checkLink() (and if that fails also @ref Shader::checkCompile() + to provide more context) and then performs any remaining post-link steps + such as uniform setup. +5. The original @cpp MyShader(…) @ce constructor now only passes the result of + @cpp compile() @ce to @cpp MyShader(CompileState&&) @ce. + +@snippet MagnumGL.cpp AbstractShaderProgram-async + +Usage-wise, it can look for example like below, with the last line waiting for +linking to finish and making the shader ready to use. On drivers that don't +perform any async compilation this will behave the same as if the construction +was done the usual way. + +@snippet MagnumGL.cpp AbstractShaderProgram-async-usage + @section GL-AbstractShaderProgram-performance-optimization Performance optimizations The engine tracks currently used shader program to avoid unnecessary calls to @@ -1259,14 +1327,19 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { #endif /** - * @brief Non-blocking linking status check - * @return @cpp true @ce if linking finished, @cpp false @ce otherwise - * - * On some drivers this might return false even after - * @ref checkLink() reported successful linking. + * @brief Whether a @ref submitLink() operation has finished + * @m_since_latest * - * @see @fn_gl_keyword{GetProgram} with - * @def_gl_extension{COMPLETION_STATUS,KHR,parallel_shader_compile} + * Has to be called only if @ref submitLink() was called before, and + * before @ref checkLink(). If returns @cpp false @ce, a subsequent + * @ref checkLink() call will block until the linking is finished. If + * @gl_extension{KHR,parallel_shader_compile} is not available, the + * function always returns @cpp true @ce --- i.e., as if the linking + * was done synchronously. See @ref GL-AbstractShaderProgram-async for + * more information. + * @see @ref Shader::isCompileFinished(), + * @fn_gl_keyword{GetProgram} with + * @def_gl_extension{COMPLETION_STATUS,KHR,parallel_shader_compile} */ bool isLinkFinished(); @@ -1453,32 +1526,36 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { /** * @brief Link the shader * - * Calls @ref submitLink(), then @ref checkLink(). - * If possible, prefer to link multiple shaders at once using - * @ref link(std::initializer_list>) - * for improved performance, see its documentation for more - * information. + * Calls @ref submitLink(), immediately followed by @ref checkLink(), + * passing back its return value. See documentation of those two + * functions for details. + * @see @ref Shader::compile() */ bool link(); /** - * @brief Submit for linking - * - * The attached shaders must be compiled with @ref Shader::compile() - * or @ref Shader::submitCompile() before linking. + * @brief Submit the shader for linking + * @m_since_latest * + * The attached shaders must be at least submitted for compilation + * with @ref Shader::submitCompile() or @ref Shader::compile() before + * linking. Call @ref isLinkFinished() or @ref checkLink() after, see + * @ref GL-AbstractShaderProgram-async for more information. * @see @fn_gl_keyword{LinkProgram} */ void submitLink(); /** - * @brief Check link status and await completion + * @brief Check shader linking status and await completion + * @m_since_latest * + * Has to be called only if @ref submitLink() was called before. * Returns @cpp false @ce if linking failed, @cpp true @ce on success. - * Linker message (if any) is printed to error output. This function - * must be called only after @ref submitLink(). - * - * @see @fn_gl_keyword{GetProgram} with + * Linker message (if any) is printed to error output. The function + * will stall until a (potentially async) linking operation finishes, + * you can use @ref isLinkFinished() to check the status instead. See + * @ref GL-AbstractShaderProgram-async for more information. + * @see @ref Shader::checkCompile(), @fn_gl_keyword{GetProgram} with * @def_gl{LINK_STATUS} and @def_gl{INFO_LOG_LENGTH}, * @fn_gl_keyword{GetProgramInfoLog} */ diff --git a/src/Magnum/GL/Shader.cpp b/src/Magnum/GL/Shader.cpp index 8b7bbb98b..d45b304f7 100644 --- a/src/Magnum/GL/Shader.cpp +++ b/src/Magnum/GL/Shader.cpp @@ -766,7 +766,7 @@ void Shader::submitCompile() { glCompileShader(_id); } -bool Shader::checkCompile() { /* After compilation phase, check status of all shaders */ +bool Shader::checkCompile() { GLint success, logLength; glGetShaderiv(_id, GL_COMPILE_STATUS, &success); glGetShaderiv(_id, GL_INFO_LOG_LENGTH, &logLength); diff --git a/src/Magnum/GL/Shader.h b/src/Magnum/GL/Shader.h index ee3ded533..f42eee3c6 100644 --- a/src/Magnum/GL/Shader.h +++ b/src/Magnum/GL/Shader.h @@ -631,41 +631,68 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { Shader& addFile(const std::string& filename); /** - * @brief Compile shader + * @brief Compile the shader * - * Calls @ref submitCompile(), then @ref checkCompile(). - * Prefer to compile multiple shaders at once using - * @ref compile(std::initializer_list>) - * for improved performance, see its documentation for more - * information. + * Calls @ref submitCompile(), immediately followed by + * @ref checkCompile(), passing back its return value. See + * documentation of those two functions for details. */ bool compile(); /** - * @brief Submit shader for compilation + * @brief Submit the shader for compilation + * @m_since_latest * + * You can call @ref isCompileFinished() or @ref checkCompile() after, + * but in most cases it's enough to defer that to after + * @ref AbstractShaderProgram::attachShader() and + * @relativeref{AbstractShaderProgram,submitLink()} were called, and + * then continuing with @relativeref{AbstractShaderProgram,isLinkFinished()} + * or @relativeref{AbstractShaderProgram,checkLink()} on the final + * program --- if compilation would fail, subsequent linking will as + * well. See @ref GL-AbstractShaderProgram-async for more information. * @see @fn_gl_keyword{ShaderSource}, @fn_gl_keyword{CompileShader} */ void submitCompile(); /** - * @brief Check compilation status and await completion - * - * Returns @cpp false @ce if compilation of failed, @cpp true @ce on success. - * This function must be called only after @ref submitCompile(). + * @brief Check shader compilation status and await completion + * @m_since_latest * + * Has to be called only if @ref submitCompile() was called before. In + * most cases it's enough to defer this check to after + * @ref AbstractShaderProgram::attachShader() and + * @relativeref{AbstractShaderProgram,submitLink()} were called, and + * then continuing with @relativeref{AbstractShaderProgram,isLinkFinished()} + * or @relativeref{AbstractShaderProgram,checkLink()} on the final + * program --- if compilation would fail, subsequent linking will as + * well. See @ref GL-AbstractShaderProgram-async for more information. * @see @fn_gl_keyword{GetShader} with @def_gl{COMPILE_STATUS} and * @def_gl{INFO_LOG_LENGTH}, @fn_gl_keyword{GetShaderInfoLog} */ bool checkCompile(); /** - * @brief Non-blocking compilation status check - * @return @cpp true @ce if shader compilation finished, @cpp false @ce otherwise + * @brief Whether a @ref submitCompile() operation has finished + * @m_since_latest * - * @see @fn_gl_keyword{GetProgram} with - * @def_gl_extension{COMPLETION_STATUS,KHR,parallel_shader_compile} + * Has to be called only if @ref submitCompile() was called before, and + * before @ref checkCompile(). If returns @cpp false @ce, a subsequent + * @ref checkCompile() call will block until the compilation is + * finished. If @gl_extension{KHR,parallel_shader_compile} is not + * available, the function always returns @cpp true @ce --- i.e., as if + * the compilation was done synchronously. * + * In most cases it's enough to only wait for the final link to finish, + * and not for particular compilations --- i.e., right after + * @ref submitCompile() continue with + * @ref AbstractShaderProgram::attachShader() and + * @relativeref{AbstractShaderProgram,submitLink()}, and then check + * with @relativeref{AbstractShaderProgram,isLinkFinished()} on the + * final program. See @ref GL-AbstractShaderProgram-async for more + * information. + * @see @fn_gl_keyword{GetProgram} with + * @def_gl_extension{COMPLETION_STATUS,KHR,parallel_shader_compile} */ bool isCompileFinished(); From e9520c2a5aeab1e5be6b442136091b9040ef09c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 17:53:27 +0200 Subject: [PATCH 53/93] GL: show the actual variable declaration in a doc snippet. To be consistent with what's shown for the async compile. --- doc/snippets/MagnumGL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/snippets/MagnumGL.cpp b/doc/snippets/MagnumGL.cpp index 59d627381..06adbcf01 100644 --- a/doc/snippets/MagnumGL.cpp +++ b/doc/snippets/MagnumGL.cpp @@ -471,11 +471,11 @@ vert.addFile("MyShader.vert"); #ifndef MAGNUM_TARGET_GLES { -MyShader shader; GL::Mesh mesh; Matrix4 transformation, projection; GL::Texture2D diffuseTexture, specularTexture; /* [AbstractShaderProgram-rendering] */ +MyShader shader; shader.setTransformationMatrix(transformation) .setProjectionMatrix(projection) .bindDiffuseTexture(diffuseTexture) From 82a1f8e76787caa3e4e6a7fdc4519df2a698e69a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 17:52:49 +0200 Subject: [PATCH 54/93] Shaders: fix a typo in a doc snippet. --- doc/snippets/MagnumShaders-gl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/snippets/MagnumShaders-gl.cpp b/doc/snippets/MagnumShaders-gl.cpp index ad5ac5f93..c1af534c0 100644 --- a/doc/snippets/MagnumShaders-gl.cpp +++ b/doc/snippets/MagnumShaders-gl.cpp @@ -284,7 +284,7 @@ ImageView2D coneDiffuse{DOXYGEN_ELLIPSIS({}, {})}, cubeDiffuse{DOXYGEN_ELLIPSIS( GL::Texture2DArray diffuseTexture; diffuseTexture DOXYGEN_ELLIPSIS() - /* Assuming all iamges have the same format and size */ + /* Assuming all images have the same format and size */ .setStorage(1, GL::textureFormat(coneDiffuse.format()), {coneDiffuse.size(), 3}) .setSubImage(0, {}, coneDiffuse) From 48326ac418590fa89a4d5ee21231bb61ad0c865e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 15:19:30 +0200 Subject: [PATCH 55/93] Shaders: minor cleanup & document new async compilation APIs. Apart from docs mostly just reordering declaratios, removing usage of auto and providing default arguments where missing. --- doc/snippets/MagnumShaders-gl.cpp | 21 +++ src/Magnum/Shaders/DistanceFieldVectorGL.cpp | 6 +- src/Magnum/Shaders/DistanceFieldVectorGL.h | 68 ++++++-- src/Magnum/Shaders/FlatGL.cpp | 14 +- src/Magnum/Shaders/FlatGL.h | 70 ++++++-- src/Magnum/Shaders/MeshVisualizerGL.cpp | 60 ++++--- src/Magnum/Shaders/MeshVisualizerGL.h | 154 +++++++++++++----- src/Magnum/Shaders/PhongGL.cpp | 13 +- src/Magnum/Shaders/PhongGL.h | 69 ++++++-- .../Test/DistanceFieldVectorGLTest.cpp | 25 ++- src/Magnum/Shaders/Test/FlatGLTest.cpp | 27 +-- .../Shaders/Test/MeshVisualizerGLTest.cpp | 46 +++--- src/Magnum/Shaders/Test/PhongGLTest.cpp | 26 +-- src/Magnum/Shaders/Test/VectorGLTest.cpp | 26 +-- src/Magnum/Shaders/Test/VertexColorGLTest.cpp | 18 +- src/Magnum/Shaders/VectorGL.cpp | 14 +- src/Magnum/Shaders/VectorGL.h | 70 ++++++-- src/Magnum/Shaders/VertexColorGL.cpp | 13 +- src/Magnum/Shaders/VertexColorGL.h | 69 ++++++-- 19 files changed, 531 insertions(+), 278 deletions(-) diff --git a/doc/snippets/MagnumShaders-gl.cpp b/doc/snippets/MagnumShaders-gl.cpp index c1af534c0..5e5de8316 100644 --- a/doc/snippets/MagnumShaders-gl.cpp +++ b/doc/snippets/MagnumShaders-gl.cpp @@ -341,6 +341,27 @@ shader /* [shaders-meshvisualizer] */ } +{ +/* [shaders-async] */ +Shaders::FlatGL3D::CompileState flatState = + Shaders::FlatGL3D::compile(); +Shaders::FlatGL3D::CompileState flatTexturedState = + Shaders::FlatGL3D::compile(Shaders::FlatGL3D::Flag::Textured); +Shaders::MeshVisualizerGL3D::CompileState meshVisualizerState = + Shaders::MeshVisualizerGL3D::compile(DOXYGEN_ELLIPSIS(Shaders::MeshVisualizerGL3D::Flag::Wireframe)); + +while(!flatState.isLinkFinished() || + !flatTexturedState.isLinkFinished() || + !meshVisualizerState.isLinkFinished()) { + // Do other work ... +} + +Shaders::FlatGL3D flat{std::move(flatState)}; +Shaders::FlatGL3D flatTextured{std::move(flatTexturedState)}; +Shaders::MeshVisualizerGL3D meshVisualizer{std::move(meshVisualizerState)}; +/* [shaders-async] */ +} + /* internal compiler error: in gimplify_init_constructor, at gimplify.c:4271 on GCC 4.8 in the [60] array */ #if !defined(CORRADE_TARGET_GCC) || defined(CORRADE_TARGET_CLANG) || __GNUC__ >= 5 diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp index 6c627cf98..eb7b1f45e 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp @@ -162,13 +162,13 @@ template typename DistanceFieldVectorGL::Com return CompileState{std::move(out), std::move(vert), std::move(frag), version}; } -template DistanceFieldVectorGL::DistanceFieldVectorGL(CompileState&& cs): DistanceFieldVectorGL{static_cast(std::move(cs))} { - if (id() == 0) return; +template DistanceFieldVectorGL::DistanceFieldVectorGL(CompileState&& state): DistanceFieldVectorGL{static_cast(std::move(state))} { + if(!id()) return; CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); const GL::Context& context = GL::Context::current(); - const GL::Version version = cs._version; + const GL::Version version = state._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.h b/src/Magnum/Shaders/DistanceFieldVectorGL.h index f46773784..c4332e200 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.h +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.h @@ -121,6 +121,8 @@ example. */ template class MAGNUM_SHADERS_EXPORT DistanceFieldVectorGL: public GL::AbstractShaderProgram { public: + class CompileState; + /** * @brief Vertex position * @@ -218,6 +220,34 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector typedef Implementation::DistanceFieldVectorGLFlags Flags; #endif + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref DistanceFieldVectorGL(Flags) can perform an + * asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref DistanceFieldVectorGL(CompileState&&), + * @ref compile(Flags, UnsignedInt, UnsignedInt) + */ + static CompileState compile(Flags flags = {}); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref DistanceFieldVectorGL(Flags, UnsignedInt, UnsignedInt) + * can perform an asynchronous compilation and linking. See + * @ref shaders-async for more information. + * @see @ref DistanceFieldVectorGL(CompileState&&), @ref compile(Flags) + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + static CompileState compile(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -226,6 +256,7 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * scenario (without @ref Flag::UniformBuffers set), it's equivalent to * @ref DistanceFieldVectorGL(Flags, UnsignedInt, UnsignedInt) with * @p materialCount and @p drawCount set to @cpp 1 @ce. + * @see @ref compile(Flags) */ explicit DistanceFieldVectorGL(Flags flags = {}); @@ -251,6 +282,7 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * If @p flags don't contain @ref Flag::UniformBuffers, * @p materialCount and @p drawCount is ignored and the constructor * behaves the same as @ref DistanceFieldVectorGL(Flags). + * @see @ref compile(Flags, UnsignedInt, UnsignedInt) * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. @@ -267,6 +299,16 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector explicit DistanceFieldVectorGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); #endif + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit DistanceFieldVectorGL(CompileState&& state); + /** * @brief Construct without creating the underlying OpenGL object * @@ -281,20 +323,6 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector */ explicit DistanceFieldVectorGL(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} - class CompileState; - - explicit DistanceFieldVectorGL(CompileState&& cs); - - static CompileState compile(Flags flags - #ifndef MAGNUM_TARGET_GLES2 - , UnsignedInt materialCount, UnsignedInt drawCount - #endif - ); - - #ifndef MAGNUM_TARGET_GLES2 - static CompileState compile(Flags flags); - #endif - /** @brief Copying is not allowed */ DistanceFieldVectorGL(const DistanceFieldVectorGL&) = delete; @@ -617,6 +645,8 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector #endif private: + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ explicit DistanceFieldVectorGL(NoInitT); /* Prevent accidentally calling irrelevant functions */ @@ -644,15 +674,19 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector #endif }; +/** +@brief Asynchronous compilation state +@m_since_latest +Returned by @ref compile(). See @ref shaders-async for more information. +*/ template class DistanceFieldVectorGL::CompileState: public DistanceFieldVectorGL { -private: + /* Everything deliberately private except for the inheritance */ friend class DistanceFieldVectorGL; explicit CompileState(NoCreateT): DistanceFieldVectorGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} - CompileState(DistanceFieldVectorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): - DistanceFieldVectorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + explicit CompileState(DistanceFieldVectorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): DistanceFieldVectorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} GL::Shader _vert, _frag; GL::Version _version; diff --git a/src/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index 645d7eade..3cc8084c4 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -67,10 +67,10 @@ namespace { #endif } -template typename FlatGL::CompileState FlatGL::compile(Flags flags -#ifndef MAGNUM_TARGET_GLES2 -, UnsignedInt materialCount, UnsignedInt drawCount -#endif +template typename FlatGL::CompileState FlatGL::compile(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif ) { #ifndef CORRADE_NO_ASSERT { @@ -237,13 +237,13 @@ template typename FlatGL::CompileState FlatG return CompileState{std::move(out), std::move(vert), std::move(frag), version}; } -template FlatGL::FlatGL(CompileState&& cs): FlatGL{static_cast(std::move(cs))} { - if (id() == 0) return; +template FlatGL::FlatGL(CompileState&& state): FlatGL{static_cast(std::move(state))} { + if(!id()) return; CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); const GL::Context& context = GL::Context::current(); - const GL::Version version = cs._version; + const GL::Version version = state._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) diff --git a/src/Magnum/Shaders/FlatGL.h b/src/Magnum/Shaders/FlatGL.h index 354d43240..d7775f7e8 100644 --- a/src/Magnum/Shaders/FlatGL.h +++ b/src/Magnum/Shaders/FlatGL.h @@ -202,6 +202,8 @@ all shaders, see @ref shaders-usage-multidraw for an example. */ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL::AbstractShaderProgram { public: + class CompileState; + /** * @brief Vertex position * @@ -541,6 +543,34 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: typedef Implementation::FlatGLFlags Flags; #endif + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref FlatGL(Flags) can perform an asynchronous + * compilation and linking. See @ref shaders-async for more + * information. + * @see @ref FlatGL(CompileState&&), + * @ref compile(Flags, UnsignedInt, UnsignedInt) + */ + static CompileState compile(Flags flags = {}); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref FlatGL(Flags, UnsignedInt, UnsignedInt) can perform + * an asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref FlatGL(CompileState&&), @ref compile(Flags) + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + static CompileState compile(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -549,6 +579,7 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * scenario (without @ref Flag::UniformBuffers set), it's equivalent to * @ref FlatGL(Flags, UnsignedInt, UnsignedInt) with @p materialCount * and @p drawCount set to @cpp 1 @ce. + * @see @ref compile(Flags) */ explicit FlatGL(Flags flags = {}); @@ -573,6 +604,7 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * If @p flags don't contain @ref Flag::UniformBuffers, * @p materialCount and @p drawCount is ignored and the constructor * behaves the same as @ref FlatGL(Flags). + * @see @ref compile(Flags, UnsignedInt, UnsignedInt) * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. @@ -589,6 +621,16 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: explicit FlatGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); #endif + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit FlatGL(CompileState&& state); + /** * @brief Construct without creating the underlying OpenGL object * @@ -603,20 +645,6 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: */ explicit FlatGL(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} - class CompileState; - - explicit FlatGL(CompileState&& cs); - - static CompileState compile(Flags flags - #ifndef MAGNUM_TARGET_GLES2 - , UnsignedInt materialCount, UnsignedInt drawCount - #endif - ); - - #ifndef MAGNUM_TARGET_GLES2 - static CompileState compile(Flags flags); - #endif - /** @brief Copying is not allowed */ FlatGL(const FlatGL&) = delete; @@ -1026,7 +1054,8 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: #endif private: - /* Creates the GL shader program object but nothing else. Internal, used by compile(). */ + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ explicit FlatGL(NoInitT); /* Prevent accidentally calling irrelevant functions */ @@ -1056,14 +1085,19 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: #endif }; +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ template class FlatGL::CompileState: public FlatGL { -private: + /* Everything deliberately private except for the inheritance */ friend class FlatGL; explicit CompileState(NoCreateT): FlatGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} - CompileState(FlatGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): - FlatGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + explicit CompileState(FlatGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): FlatGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} GL::Shader _vert, _frag; GL::Version _version; diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index 6aaaac536..b08ae189c 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -139,10 +139,9 @@ void MeshVisualizerGLBase::assertExtensions(const FlagsBase flags) { #endif } -GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs, - const FlagsBase flags +GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs, const FlagsBase flags #ifndef MAGNUM_TARGET_GLES2 - , UnsignedInt materialCount, UnsignedInt drawCount + , const UnsignedInt materialCount, UnsignedInt const drawCount #endif ) { GL::Context& context = GL::Context::current(); @@ -376,12 +375,12 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::bindObjectIdTexture(GL::Texture2DArr } -MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(Flags flags +MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(const Flags flags #ifndef MAGNUM_TARGET_GLES2 , const UnsignedInt materialCount, const UnsignedInt drawCount #endif ) { - FlagsBase baseFlags = Implementation::MeshVisualizerGLBase::FlagBase(UnsignedInt(flags)); + const FlagsBase baseFlags = Implementation::MeshVisualizerGLBase::FlagBase(UnsignedInt(flags)); assertExtensions(baseFlags); #ifndef MAGNUM_TARGET_GLES2 @@ -410,9 +409,9 @@ MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(Flags flags GL::Shader vert{NoCreate}; GL::Shader frag{NoCreate}; const GL::Version version = setupShaders(vert, frag, rs, baseFlags - #ifndef MAGNUM_TARGET_GLES2 - , materialCount, drawCount - #endif + #ifndef MAGNUM_TARGET_GLES2 + , materialCount, drawCount + #endif ); Containers::Optional geom; @@ -472,7 +471,7 @@ MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(Flags flags vert.submitCompile(); frag.submitCompile(); - if (geom) geom->submitCompile(); + if(geom) geom->submitCompile(); MeshVisualizerGL2D out{NoInit}; out._flags = baseFlags; @@ -482,7 +481,7 @@ MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(Flags flags #endif out.attachShaders({vert, frag}); - if (geom) out.attachShader(*geom); + if(geom) out.attachShader(*geom); /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) @@ -519,26 +518,24 @@ MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(Flags flags return CompileState{std::move(out), std::move(vert), std::move(frag), std::move(geom), flags, version}; } -MeshVisualizerGL2D::MeshVisualizerGL2D(Flags flags) : MeshVisualizerGL2D{compile(flags)} {} +MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): MeshVisualizerGL2D{compile(flags)} {} #ifndef MAGNUM_TARGET_GLES2 -MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(Flags flags) { +MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(const Flags flags) { return compile(flags, 1, 1); } -MeshVisualizerGL2D::MeshVisualizerGL2D(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount) - : MeshVisualizerGL2D{compile(flags, materialCount, drawCount)} {} +MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags, const UnsignedInt materialCount, const UnsignedInt drawCount): MeshVisualizerGL2D{compile(flags, materialCount, drawCount)} {} #endif -MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& cs) -: MeshVisualizerGL2D{static_cast(std::move(cs))} { - if (id() == 0) return; +MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& state): MeshVisualizerGL2D{static_cast(std::move(state))} { + if(!id()) return; CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); const GL::Context& context = GL::Context::current(); - const GL::Version version = cs._version; - Flags flags = cs._flags; + const GL::Version version = state._version; + const Flags flags = state._flags; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) @@ -744,9 +741,9 @@ MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(Flags flags GL::Shader vert{NoCreate}; GL::Shader frag{NoCreate}; const GL::Version version = setupShaders(vert, frag, rs, baseFlags - #ifndef MAGNUM_TARGET_GLES2 - , materialCount, drawCount - #endif + #ifndef MAGNUM_TARGET_GLES2 + , materialCount, drawCount + #endif ); Containers::Optional geom; @@ -840,7 +837,7 @@ MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(Flags flags vert.submitCompile(); frag.submitCompile(); - if (geom) geom->submitCompile(); + if(geom) geom->submitCompile(); MeshVisualizerGL3D out{NoInit}; out._flags = baseFlags; @@ -850,7 +847,7 @@ MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(Flags flags #endif out.attachShaders({vert, frag}); - if (geom) out.attachShader(*geom); + if(geom) out.attachShader(*geom); /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) @@ -903,14 +900,14 @@ MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(Flags flags return CompileState{std::move(out), std::move(vert), std::move(frag), std::move(geom), flags, version}; } -MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& cs): MeshVisualizerGL3D{static_cast(std::move(cs))} { - if (id() == 0) return; +MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D{static_cast(std::move(state))} { + if(!id()) return; CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); const GL::Context& context = GL::Context::current(); - const GL::Version version = cs._version; - Flags flags = cs._flags; + const GL::Version version = state._version; + Flags flags = state._flags; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) @@ -1043,15 +1040,14 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& cs): MeshVisualizerGL3D{st static_cast(version); } -MeshVisualizerGL3D::MeshVisualizerGL3D(Flags flags) : MeshVisualizerGL3D{compile(flags)} {} +MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): MeshVisualizerGL3D{compile(flags)} {} #ifndef MAGNUM_TARGET_GLES2 -MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(Flags flags) { +MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(const Flags flags) { return compile(flags, 1, 1); } -MeshVisualizerGL3D::MeshVisualizerGL3D(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount): - MeshVisualizerGL3D{compile(flags, materialCount, drawCount)} {} +MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags, const UnsignedInt materialCount, const UnsignedInt drawCount): MeshVisualizerGL3D{compile(flags, materialCount, drawCount)} {} #endif MeshVisualizerGL3D& MeshVisualizerGL3D::setTransformationMatrix(const Matrix4& matrix) { diff --git a/src/Magnum/Shaders/MeshVisualizerGL.h b/src/Magnum/Shaders/MeshVisualizerGL.h index aa58e7f45..8857663ee 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -76,8 +76,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr explicit MeshVisualizerGLBase(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} static MAGNUM_SHADERS_LOCAL void assertExtensions(const FlagsBase flags); - static MAGNUM_SHADERS_LOCAL GL::Version setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs, - const FlagsBase flags + static MAGNUM_SHADERS_LOCAL GL::Version setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs, const FlagsBase flags #ifndef MAGNUM_TARGET_GLES2 , UnsignedInt materialCount, UnsignedInt drawCount #endif @@ -189,6 +188,8 @@ texture offset (or offset and layer). */ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisualizerGLBase { public: + class CompileState; + /** * @brief Vertex position * @@ -446,6 +447,35 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua /** @brief Flags */ typedef Containers::EnumSet Flags; + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref MeshVisualizerGL2D(Flags) can perform an + * asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref MeshVisualizerGL2D(CompileState&&), + * @ref compile(Flags, UnsignedInt, UnsignedInt) + */ + /* No default value, consistently with MeshVisualizerGL2D(Flags) */ + static CompileState compile(Flags flags); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref MeshVisualizerGL2D(Flags, UnsignedInt, UnsignedInt) + * can perform an asynchronous compilation and linking. See + * @ref shaders-async for more information. + * @see @ref MeshVisualizerGL2D(CompileState&&), @ref compile(Flags) + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + static CompileState compile(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -456,6 +486,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * scenario (without @ref Flag::UniformBuffers set), it's equivalent to * @ref MeshVisualizerGL2D(Flags, UnsignedInt, UnsignedInt) with * @p materialCount and @p drawCount set to @cpp 1 @ce. + * @see @ref compile(Flags) */ explicit MeshVisualizerGL2D(Flags flags); @@ -481,6 +512,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * If @p flags don't contain @ref Flag::UniformBuffers, * @p materialCount and @p drawCount is ignored and the constructor * behaves the same as @ref MeshVisualizerGL2D(Flags). + * @see @ref compile(Flags, UnsignedInt, UnsignedInt) * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. @@ -496,6 +528,16 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua explicit MeshVisualizerGL2D(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); #endif + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit MeshVisualizerGL2D(CompileState&& state); + /** * @brief Construct without creating the underlying OpenGL object * @@ -510,20 +552,6 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua */ explicit MeshVisualizerGL2D(NoCreateT) noexcept: Implementation::MeshVisualizerGLBase{NoCreate} {} - class CompileState; - - explicit MeshVisualizerGL2D(CompileState&& cs); - - static CompileState compile(Flags flags - #ifndef MAGNUM_TARGET_GLES2 - , UnsignedInt materialCount, UnsignedInt drawCount - #endif - ); - - #ifndef MAGNUM_TARGET_GLES2 - static CompileState compile(Flags flags); - #endif - /** @brief Copying is not allowed */ MeshVisualizerGL2D(const MeshVisualizerGL2D&) = delete; @@ -881,19 +909,26 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua #endif private: - explicit MeshVisualizerGL2D(NoInitT) : Implementation::MeshVisualizerGLBase{NoInit} {} + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ + explicit MeshVisualizerGL2D(NoInitT): Implementation::MeshVisualizerGLBase{NoInit} {} Int _transformationProjectionMatrixUniform{9}; }; +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ class MeshVisualizerGL2D::CompileState: public MeshVisualizerGL2D { -private: + /* Everything deliberately private except for the inheritance */ friend class MeshVisualizerGL2D; explicit CompileState(NoCreateT): MeshVisualizerGL2D{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} - CompileState(MeshVisualizerGL2D&& shader, GL::Shader&& vert, GL::Shader&& frag, Containers::Optional&& geom, Flags flags, GL::Version version): - MeshVisualizerGL2D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{std::move(geom)}, _flags{flags}, _version{version} {} + explicit CompileState(MeshVisualizerGL2D&& shader, GL::Shader&& vert, GL::Shader&& frag, Containers::Optional&& geom, Flags flags, GL::Version version): MeshVisualizerGL2D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{std::move(geom)}, _flags{flags}, _version{version} {} GL::Shader _vert, _frag; Containers::Optional _geom; @@ -1139,6 +1174,8 @@ similar for all shaders, see @ref shaders-usage-multidraw for an example. */ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisualizerGLBase { public: + class CompileState; + /** * @brief Vertex position * @@ -1653,6 +1690,35 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua /** @brief Flags */ typedef Containers::EnumSet Flags; + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref MeshVisualizerGL3D(Flags) can perform an + * asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref MeshVisualizerGL3D(CompileState&&), + * @ref compile(Flags, UnsignedInt, UnsignedInt) + */ + /* No default value, consistently with MeshVisualizerGL3D(Flags) */ + static CompileState compile(Flags flags); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref MeshVisualizerGL3D(Flags, UnsignedInt, UnsignedInt) + * can perform an asynchronous compilation and linking. See + * @ref shaders-async for more information. + * @see @ref MeshVisualizerGL3D(CompileState&&), @ref compile(Flags) + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + static CompileState compile(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -1666,6 +1732,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * scenario (without @ref Flag::UniformBuffers set), it's equivalent to * @ref MeshVisualizerGL3D(Flags, UnsignedInt, UnsignedInt) with * @p materialCount and @p drawCount set to @cpp 1 @ce. + * @see @ref compile(Flags) */ explicit MeshVisualizerGL3D(Flags flags); @@ -1702,8 +1769,8 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref MeshVisualizerDrawUniform3D::materialId. * * If @p flags don't contain @ref Flag::UniformBuffers, - * @p materialCount and @p drawCount is ignored and the constructor - * behaves the same as @ref MeshVisualizerGL3D(Flags). + * @p materialCount and @p drawCount is ignored and the constructo + * @see @ref compile(Flags, UnsignedInt, UnsignedInt) * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. @@ -1734,20 +1801,15 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua */ explicit MeshVisualizerGL3D(NoCreateT) noexcept: Implementation::MeshVisualizerGLBase{NoCreate} {} - class CompileState; - - explicit MeshVisualizerGL3D(CompileState&& cs); - - static CompileState compile(Flags flags - #ifndef MAGNUM_TARGET_GLES2 - , UnsignedInt materialCount, UnsignedInt drawCount - #endif - ); - - #ifndef MAGNUM_TARGET_GLES2 - static CompileState compile(Flags flags); - #endif - + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit MeshVisualizerGL3D(CompileState&& state); /** @brief Copying is not allowed */ MeshVisualizerGL3D(const MeshVisualizerGL3D&) = delete; @@ -2377,7 +2439,9 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua #endif private: - explicit MeshVisualizerGL3D(NoInitT) : Implementation::MeshVisualizerGLBase{NoInit} {} + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ + explicit MeshVisualizerGL3D(NoInitT): Implementation::MeshVisualizerGLBase{NoInit} {} Int _transformationMatrixUniform{9}, _projectionMatrixUniform{10}; @@ -2388,14 +2452,19 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua #endif }; -class MeshVisualizerGL3D::CompileState : public MeshVisualizerGL3D { -private: +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ +class MeshVisualizerGL3D::CompileState: public MeshVisualizerGL3D { + /* Everything deliberately private except for the inheritance */ friend class MeshVisualizerGL3D; - explicit CompileState(NoCreateT) : MeshVisualizerGL3D{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + explicit CompileState(NoCreateT): MeshVisualizerGL3D{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} - CompileState(MeshVisualizerGL3D&& shader, GL::Shader&& vert, GL::Shader&& frag, Containers::Optional&& geom, Flags flags, GL::Version version) : - MeshVisualizerGL3D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{std::move(geom)}, _flags{flags}, _version{version} {} + explicit CompileState(MeshVisualizerGL3D&& shader, GL::Shader&& vert, GL::Shader&& frag, Containers::Optional&& geom, Flags flags, GL::Version version): MeshVisualizerGL3D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{std::move(geom)}, _flags{flags}, _version{version} {} GL::Shader _vert, _frag; Containers::Optional _geom; @@ -2403,7 +2472,6 @@ private: GL::Version _version; }; - /** @debugoperatorclassenum{MeshVisualizerGL2D,MeshVisualizerGL2D::Flag} */ MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, MeshVisualizerGL2D::Flag value); diff --git a/src/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index bd8bf9e65..4e4538e63 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -347,13 +347,13 @@ PhongGL::CompileState PhongGL::compile(const Flags flags, const UnsignedInt ligh return CompileState{std::move(out), std::move(vert), std::move(frag), version}; } -PhongGL::PhongGL(CompileState&& cs): PhongGL{static_cast(std::move(cs))} { - if (id() == 0) return; +PhongGL::PhongGL(CompileState&& state): PhongGL{static_cast(std::move(state))} { + if(!id()) return; CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); const GL::Context& context = GL::Context::current(); - const GL::Version version = cs._version; + const GL::Version version = state._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) @@ -463,15 +463,14 @@ PhongGL::PhongGL(CompileState&& cs): PhongGL{static_cast(std::move(cs static_cast(version); } -PhongGL::PhongGL(Flags flags, UnsignedInt lightCount): PhongGL{compile(flags, lightCount)} {} +PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): PhongGL{compile(flags, lightCount)} {} #ifndef MAGNUM_TARGET_GLES2 -PhongGL::CompileState PhongGL::compile(Flags flags, UnsignedInt lightCount) { +PhongGL::CompileState PhongGL::compile(const Flags flags, const UnsignedInt lightCount) { return compile(flags, lightCount, 1, 1); } -PhongGL::PhongGL(Flags flags, UnsignedInt lightCount, UnsignedInt materialCount, UnsignedInt drawCount): - PhongGL{compile(flags, lightCount, materialCount, drawCount)} {} +PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount, const UnsignedInt materialCount, const UnsignedInt drawCount): PhongGL{compile(flags, lightCount, materialCount, drawCount)} {} #endif PhongGL& PhongGL::setAmbientColor(const Magnum::Color4& color) { diff --git a/src/Magnum/Shaders/PhongGL.h b/src/Magnum/Shaders/PhongGL.h index 8a06e2ccc..e496e5850 100644 --- a/src/Magnum/Shaders/PhongGL.h +++ b/src/Magnum/Shaders/PhongGL.h @@ -297,6 +297,8 @@ Besides that, the usage is similar for all shaders, see */ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { public: + class CompileState; + /** * @brief Vertex position * @@ -754,6 +756,34 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { */ typedef Containers::EnumSet Flags; + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref PhongGL(Flags, UnsignedInt) can perform an + * asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref PhongGL(CompileState&&), + * @ref compile(Flags, UnsignedInt, UnsignedInt, UnsignedInt) + */ + static CompileState compile(Flags flags = {}, UnsignedInt lightCount = 1); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref PhongGL(Flags, UnsignedInt, UnsignedInt, UnsignedInt) + * can perform an asynchronous compilation and linking. See + * @ref shaders-async for more information. + * @see @ref PhongGL(CompileState&&), @ref compile(Flags, UnsignedInt) + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + static CompileState compile(Flags flags, UnsignedInt lightCount, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -763,6 +793,7 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * scenario (without @ref Flag::UniformBuffers set), it's equivalent to * @ref PhongGL(Flags, UnsignedInt, UnsignedInt, UnsignedInt) with * @p materialCount and @p drawCount set to @cpp 1 @ce. + * @see @ref compile(Flags, UnsignedInt) */ explicit PhongGL(Flags flags = {}, UnsignedInt lightCount = 1); @@ -792,6 +823,7 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * If @p flags don't contain @ref Flag::UniformBuffers, * @p materialCount and @p drawCount is ignored and the constructor * behaves the same as @ref PhongGL(Flags, UnsignedInt). + * @see @ref compile(Flags, UnsignedInt, UnsignedInt, UnsignedInt) * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. @@ -808,6 +840,16 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { explicit PhongGL(Flags flags, UnsignedInt lightCount, UnsignedInt materialCount, UnsignedInt drawCount); #endif + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit PhongGL(CompileState&& state); + /** * @brief Construct without creating the underlying OpenGL object * @@ -822,20 +864,6 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { */ explicit PhongGL(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} - class CompileState; - - explicit PhongGL(CompileState&& cs); - - static CompileState compile(Flags flags, UnsignedInt lightCount - #ifndef MAGNUM_TARGET_GLES2 - , UnsignedInt materialCount, UnsignedInt drawCount - #endif - ); - - #ifndef MAGNUM_TARGET_GLES2 - static CompileState compile(Flags flags, UnsignedInt lightCount); - #endif - /** @brief Copying is not allowed */ PhongGL(const PhongGL&) = delete; @@ -1759,6 +1787,8 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { #endif private: + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ explicit PhongGL(NoInitT) {} /* Prevent accidentally calling irrelevant functions */ @@ -1801,14 +1831,19 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { #endif }; +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ class PhongGL::CompileState: public PhongGL { -private: + /* Everything deliberately private except for the inheritance */ friend class PhongGL; explicit CompileState(NoCreateT): PhongGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} - CompileState(PhongGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): - PhongGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + explicit CompileState(PhongGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): PhongGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} GL::Shader _vert, _frag; GL::Version _version; diff --git a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp index e23db4a99..dd5f72242 100644 --- a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp @@ -31,8 +31,8 @@ #include #include #include -#include #include +#include #ifdef CORRADE_TARGET_APPLE #include @@ -388,13 +388,13 @@ template void DistanceFieldVectorGLTest::construct() { template void DistanceFieldVectorGLTest::constructAsync() { setTestCaseTemplateName(Utility::format("{}", dimensions)); - auto compileState = DistanceFieldVectorGL::compile(DistanceFieldVectorGL2D::Flag::TextureTransformation); - CORRADE_COMPARE(compileState.flags(), DistanceFieldVectorGL2D::Flag::TextureTransformation); + typename DistanceFieldVectorGL::CompileState state = DistanceFieldVectorGL::compile(DistanceFieldVectorGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(state.flags(), DistanceFieldVectorGL2D::Flag::TextureTransformation); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - DistanceFieldVectorGL shader{std::move(compileState)}; + DistanceFieldVectorGL shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), DistanceFieldVectorGL2D::Flag::TextureTransformation); CORRADE_VERIFY(shader.isLinkFinished()); CORRADE_VERIFY(shader.id()); @@ -451,24 +451,23 @@ template void DistanceFieldVectorGLTest::constructUnifor template void DistanceFieldVectorGLTest::constructUniformBuffersAsync() { setTestCaseTemplateName(Utility::format("{}", dimensions)); - #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); #endif - auto compileState = DistanceFieldVectorGL::compile(DistanceFieldVectorGL2D::Flag::UniformBuffers, 16, 4); - CORRADE_COMPARE(compileState.flags(), DistanceFieldVectorGL2D::Flag::UniformBuffers); - CORRADE_COMPARE(compileState.materialCount(), 16); - CORRADE_COMPARE(compileState.drawCount(), 4); + typename DistanceFieldVectorGL::CompileState state = DistanceFieldVectorGL::compile(DistanceFieldVectorGL2D::Flag::UniformBuffers, 16, 48); + CORRADE_COMPARE(state.flags(), DistanceFieldVectorGL2D::Flag::UniformBuffers); + CORRADE_COMPARE(state.materialCount(), 16); + CORRADE_COMPARE(state.drawCount(), 48); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - DistanceFieldVectorGL shader{std::move(compileState)}; + DistanceFieldVectorGL shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), DistanceFieldVectorGL2D::Flag::UniformBuffers); CORRADE_COMPARE(shader.materialCount(), 16); - CORRADE_COMPARE(shader.drawCount(), 4); + CORRADE_COMPARE(shader.drawCount(), 48); CORRADE_VERIFY(shader.isLinkFinished()); CORRADE_VERIFY(shader.id()); { diff --git a/src/Magnum/Shaders/Test/FlatGLTest.cpp b/src/Magnum/Shaders/Test/FlatGLTest.cpp index 192c5fca2..94485a545 100644 --- a/src/Magnum/Shaders/Test/FlatGLTest.cpp +++ b/src/Magnum/Shaders/Test/FlatGLTest.cpp @@ -31,8 +31,8 @@ #include #include #include -#include #include +#include #ifdef CORRADE_TARGET_APPLE #include @@ -866,13 +866,14 @@ template void FlatGLTest::construct() { template void FlatGLTest::constructAsync() { setTestCaseTemplateName(Utility::format("{}", dimensions)); - auto compileState = FlatGL::compile(FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation); - CORRADE_COMPARE(compileState.flags(), FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation); - while(!compileState.isLinkFinished()) + typename FlatGL::CompileState state = FlatGL::compile(FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(state.flags(), FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation); + + while(!state.isLinkFinished()) Utility::System::sleep(100); - FlatGL shader{std::move(compileState)}; + FlatGL shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation); CORRADE_VERIFY(shader.id()); @@ -938,18 +939,18 @@ template void FlatGLTest::constructUniformBuffersAsync() CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); #endif - auto compileState = FlatGL::compile(FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask, 1, 1); - CORRADE_COMPARE(compileState.flags(), FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask); - CORRADE_COMPARE(compileState.materialCount(), 1); - CORRADE_COMPARE(compileState.drawCount(), 1); + typename FlatGL::CompileState state = FlatGL::compile(FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask, 8, 48); + CORRADE_COMPARE(state.flags(), FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask); + CORRADE_COMPARE(state.materialCount(), 8); + CORRADE_COMPARE(state.drawCount(), 48); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - FlatGL shader{std::move(compileState)}; + FlatGL shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask); - CORRADE_COMPARE(shader.materialCount(), 1); - CORRADE_COMPARE(shader.drawCount(), 1); + CORRADE_COMPARE(shader.materialCount(), 8); + CORRADE_COMPARE(shader.drawCount(), 48); CORRADE_VERIFY(shader.id()); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index fbe96056e..957f33a9a 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -31,8 +31,8 @@ #include #include #include -#include #include +#include #ifdef CORRADE_TARGET_APPLE #include @@ -1416,13 +1416,13 @@ void MeshVisualizerGLTest::construct2D() { void MeshVisualizerGLTest::construct2DAsync() { - auto compileState = MeshVisualizerGL2D::compile(MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); - CORRADE_COMPARE(compileState.flags(), MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + MeshVisualizerGL2D::CompileState state = MeshVisualizerGL2D::compile(MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(state.flags(), MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - MeshVisualizerGL2D shader{std::move(compileState)}; + MeshVisualizerGL2D shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); CORRADE_VERIFY(shader.isLinkFinished()); CORRADE_VERIFY(shader.id()); @@ -1521,15 +1521,15 @@ void MeshVisualizerGLTest::constructUniformBuffers2DAsync() { CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); #endif - auto compileState = MeshVisualizerGL2D::compile( MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 8, 55); - CORRADE_COMPARE(compileState.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); - CORRADE_COMPARE(compileState.materialCount(), 8); - CORRADE_COMPARE(compileState.drawCount(), 55); + MeshVisualizerGL2D::CompileState state = MeshVisualizerGL2D::compile( MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 8, 55); + CORRADE_COMPARE(state.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(state.materialCount(), 8); + CORRADE_COMPARE(state.drawCount(), 55); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - MeshVisualizerGL2D shader{std::move(compileState)}; + MeshVisualizerGL2D shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); CORRADE_COMPARE(shader.materialCount(), 8); CORRADE_COMPARE(shader.drawCount(), 55); @@ -1607,13 +1607,13 @@ void MeshVisualizerGLTest::construct3D() { } void MeshVisualizerGLTest::construct3DAsync() { - auto compileState = MeshVisualizerGL3D::compile(MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); - CORRADE_COMPARE(compileState.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + MeshVisualizerGL3D::CompileState state = MeshVisualizerGL3D::compile(MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(state.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - MeshVisualizerGL3D shader{std::move(compileState)}; + MeshVisualizerGL3D shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); CORRADE_VERIFY(shader.isLinkFinished()); CORRADE_VERIFY(shader.id()); @@ -1711,18 +1711,18 @@ void MeshVisualizerGLTest::constructUniformBuffers3DAsync() { CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); #endif - auto compileState = MeshVisualizerGL3D::compile(MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 6, 28); - CORRADE_COMPARE(compileState.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); - CORRADE_COMPARE(compileState.materialCount(), 6); - CORRADE_COMPARE(compileState.drawCount(), 28); + MeshVisualizerGL3D::CompileState state = MeshVisualizerGL3D::compile(MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 6, 28); + CORRADE_COMPARE(state.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(state.materialCount(), 6); + CORRADE_COMPARE(state.drawCount(), 28); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - MeshVisualizerGL3D shader{std::move(compileState)}; + MeshVisualizerGL3D shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); - CORRADE_COMPARE(compileState.materialCount(), 6); - CORRADE_COMPARE(compileState.drawCount(), 28); + CORRADE_COMPARE(state.materialCount(), 6); + CORRADE_COMPARE(state.drawCount(), 28); CORRADE_VERIFY(shader.isLinkFinished()); CORRADE_VERIFY(shader.id()); { diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index d0df58ec4..a9d6f2f4e 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -31,9 +31,9 @@ #include #include #include -#include #include #include +#include #ifdef CORRADE_TARGET_APPLE #include @@ -1199,14 +1199,14 @@ void PhongGLTest::construct() { } void PhongGLTest::constructAsync() { - auto compileState = PhongGL::compile(PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset, 3); - CORRADE_COMPARE(compileState.flags(), PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset); - CORRADE_COMPARE(compileState.lightCount(), 3); + PhongGL::CompileState state = PhongGL::compile(PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset, 3); + CORRADE_COMPARE(state.flags(), PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset); + CORRADE_COMPARE(state.lightCount(), 3); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - PhongGL shader{std::move(compileState)}; + PhongGL shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset); CORRADE_COMPARE(shader.lightCount(), 3); CORRADE_VERIFY(shader.isLinkFinished()); @@ -1270,16 +1270,16 @@ void PhongGLTest::constructUniformBuffersAsync() { CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); #endif - auto compileState = PhongGL::compile(PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling, 8, 8, 24); - CORRADE_COMPARE(compileState.flags(), PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling); - CORRADE_COMPARE(compileState.lightCount(), 8); - CORRADE_COMPARE(compileState.materialCount(), 8); - CORRADE_COMPARE(compileState.drawCount(), 24); + PhongGL::CompileState state = PhongGL::compile(PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling, 8, 8, 24); + CORRADE_COMPARE(state.flags(), PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling); + CORRADE_COMPARE(state.lightCount(), 8); + CORRADE_COMPARE(state.materialCount(), 8); + CORRADE_COMPARE(state.drawCount(), 24); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - PhongGL shader{std::move(compileState)}; + PhongGL shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling); CORRADE_COMPARE(shader.lightCount(), 8); CORRADE_COMPARE(shader.materialCount(), 8); diff --git a/src/Magnum/Shaders/Test/VectorGLTest.cpp b/src/Magnum/Shaders/Test/VectorGLTest.cpp index 715688e1e..a160640f7 100644 --- a/src/Magnum/Shaders/Test/VectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VectorGLTest.cpp @@ -31,8 +31,8 @@ #include #include #include -#include #include +#include #ifdef CORRADE_TARGET_APPLE #include @@ -384,13 +384,13 @@ template void VectorGLTest::construct() { template void VectorGLTest::constructAsync() { setTestCaseTemplateName(Utility::format("{}", dimensions)); - auto compileState = VectorGL::compile(VectorGL2D::Flag::TextureTransformation); - CORRADE_COMPARE(compileState.flags(), VectorGL2D::Flag::TextureTransformation); + typename VectorGL::CompileState state = VectorGL::compile(VectorGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(state.flags(), VectorGL2D::Flag::TextureTransformation); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - VectorGL shader{std::move(compileState)}; + VectorGL shader{std::move(state)}; CORRADE_VERIFY(shader.isLinkFinished()); CORRADE_COMPARE(shader.flags(), VectorGL2D::Flag::TextureTransformation); CORRADE_VERIFY(shader.id()); @@ -452,19 +452,19 @@ template void VectorGLTest::constructUniformBuffersAsync CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); #endif - auto compileState = VectorGL::compile(VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation, 1, 1); - CORRADE_COMPARE(compileState.flags(), VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation); - CORRADE_COMPARE(compileState.materialCount(), 1); - CORRADE_COMPARE(compileState.drawCount(), 1); + typename VectorGL::CompileState state = VectorGL::compile(VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation, 15, 42); + CORRADE_COMPARE(state.flags(), VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(state.materialCount(), 15); + CORRADE_COMPARE(state.drawCount(), 42); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - VectorGL shader{std::move(compileState)}; + VectorGL shader{std::move(state)}; CORRADE_VERIFY(shader.isLinkFinished()); CORRADE_COMPARE(shader.flags(), VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation); - CORRADE_COMPARE(shader.materialCount(), 1); - CORRADE_COMPARE(shader.drawCount(), 1); + CORRADE_COMPARE(shader.materialCount(), 15); + CORRADE_COMPARE(shader.drawCount(), 42); CORRADE_VERIFY(shader.id()); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) diff --git a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp index 451c7348f..b62e6232c 100644 --- a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp @@ -29,8 +29,8 @@ #include #include #include -#include #include +#include #ifdef CORRADE_TARGET_APPLE #include @@ -321,12 +321,12 @@ template void VertexColorGLTest::construct() { template void VertexColorGLTest::constructAsync() { setTestCaseTemplateName(Utility::format("{}", dimensions)); - auto compileState = VertexColorGL::compile({}); + typename VertexColorGL::CompileState state = VertexColorGL::compile({}); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - VertexColorGL shader{std::move(compileState)}; + VertexColorGL shader{std::move(state)}; CORRADE_VERIFY(shader.isLinkFinished()); CORRADE_VERIFY(shader.id()); { @@ -387,14 +387,14 @@ template void VertexColorGLTest::constructUniformBuffers CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); #endif - auto compileState = VertexColorGL::compile(VertexColorGL2D::Flag::UniformBuffers, 63); - CORRADE_COMPARE(compileState.flags(), VertexColorGL2D::Flag::UniformBuffers); - CORRADE_COMPARE(compileState.drawCount(), 63); + typename VertexColorGL::CompileState state = VertexColorGL::compile(VertexColorGL2D::Flag::UniformBuffers, 63); + CORRADE_COMPARE(state.flags(), VertexColorGL2D::Flag::UniformBuffers); + CORRADE_COMPARE(state.drawCount(), 63); - while(!compileState.isLinkFinished()) + while(!state.isLinkFinished()) Utility::System::sleep(100); - VertexColorGL shader{std::move(compileState)}; + VertexColorGL shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), VertexColorGL2D::Flag::UniformBuffers); CORRADE_COMPARE(shader.drawCount(), 63); CORRADE_VERIFY(shader.isLinkFinished()); diff --git a/src/Magnum/Shaders/VectorGL.cpp b/src/Magnum/Shaders/VectorGL.cpp index dd6ff2c07..a58c784f4 100644 --- a/src/Magnum/Shaders/VectorGL.cpp +++ b/src/Magnum/Shaders/VectorGL.cpp @@ -164,14 +164,13 @@ template typename VectorGL::CompileState Vec return CompileState{std::move(out), std::move(vert), std::move(frag), version}; } -template VectorGL::VectorGL(CompileState&& cs): VectorGL{static_cast(std::move(cs))} { - if (id() == 0) return; +template VectorGL::VectorGL(CompileState&& state): VectorGL{static_cast(std::move(state))} { + if(!id()) return; CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); const GL::Context& context = GL::Context::current(); - const GL::Version version = cs._version; - + const GL::Version version = state._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) @@ -227,15 +226,14 @@ template VectorGL::VectorGL(CompileState&& c static_cast(version); } -template VectorGL::VectorGL(Flags flags): VectorGL{compile(flags)} {} +template VectorGL::VectorGL(const Flags flags): VectorGL{compile(flags)} {} #ifndef MAGNUM_TARGET_GLES2 -template typename VectorGL::CompileState VectorGL::compile(Flags flags) { +template typename VectorGL::CompileState VectorGL::compile(const Flags flags) { return compile(flags, 1, 1); } -template VectorGL::VectorGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount): - VectorGL{compile(flags, materialCount, drawCount)} {} +template VectorGL::VectorGL(const Flags flags, const UnsignedInt materialCount, const UnsignedInt drawCount): VectorGL{compile(flags, materialCount, drawCount)} {} #endif template VectorGL::VectorGL(NoInitT) {} diff --git a/src/Magnum/Shaders/VectorGL.h b/src/Magnum/Shaders/VectorGL.h index 057dfc620..601bd0a44 100644 --- a/src/Magnum/Shaders/VectorGL.h +++ b/src/Magnum/Shaders/VectorGL.h @@ -116,6 +116,8 @@ example. */ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL::AbstractShaderProgram { public: + class CompileState; + /** * @brief Vertex position * @@ -213,6 +215,34 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL typedef Implementation::VectorGLFlags Flags; #endif + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref VectorGL(Flags) can perform an asynchronous + * compilation and linking. See @ref shaders-async for more + * information. + * @see @ref VectorGL(CompileState&&), + * @ref compile(Flags, UnsignedInt, UnsignedInt) + */ + static CompileState compile(Flags flags = {}); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref VectorGL(Flags, UnsignedInt, UnsignedInt) can + * perform an asynchronous compilation and linking. See + * @ref shaders-async for more information. + * @see @ref VectorGL(CompileState&&), @ref compile(Flags) + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + static CompileState compile(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -221,6 +251,7 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * scenario (without @ref Flag::UniformBuffers set), it's equivalent to * @ref VectorGL(Flags, UnsignedInt, UnsignedInt) with @p materialCount * and @p drawCount set to @cpp 1 @ce. + * @see @ref compile(Flags) */ explicit VectorGL(Flags flags = {}); @@ -244,6 +275,7 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * * If @p flags don't contain @ref Flag::UniformBuffers, @p drawCount is * ignored and the constructor behaves the same as @ref VectorGL(Flags). + * @see @ref compile(Flags, UnsignedInt, UnsignedInt) * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. @@ -260,6 +292,16 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL explicit VectorGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); #endif + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit VectorGL(CompileState&& state); + /** * @brief Construct without creating the underlying OpenGL object * @@ -274,20 +316,6 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL */ explicit VectorGL(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} - class CompileState; - - explicit VectorGL(CompileState&& cs); - - static CompileState compile(Flags flags - #ifndef MAGNUM_TARGET_GLES2 - , UnsignedInt materialCount, UnsignedInt drawCount - #endif - ); - - #ifndef MAGNUM_TARGET_GLES2 - static CompileState compile(Flags flags); - #endif - /** @brief Copying is not allowed */ VectorGL(const VectorGL&) = delete; @@ -569,7 +597,8 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL #endif private: - /* Creates the GL shader program object but nothing else. Internal, used by compile(). */ + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ explicit VectorGL(NoInitT); /* Prevent accidentally calling irrelevant functions */ @@ -595,14 +624,19 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL #endif }; +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ template class VectorGL::CompileState: public VectorGL { -private: + /* Everything deliberately private except for the inheritance */ friend class VectorGL; explicit CompileState(NoCreateT): VectorGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} - CompileState(VectorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): - VectorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + explicit CompileState(VectorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): VectorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} GL::Shader _vert, _frag; GL::Version _version; diff --git a/src/Magnum/Shaders/VertexColorGL.cpp b/src/Magnum/Shaders/VertexColorGL.cpp index 01af44cd1..c2667666c 100644 --- a/src/Magnum/Shaders/VertexColorGL.cpp +++ b/src/Magnum/Shaders/VertexColorGL.cpp @@ -142,13 +142,13 @@ template typename VertexColorGL::CompileStat return CompileState{std::move(out), std::move(vert), std::move(frag), version}; } -template VertexColorGL::VertexColorGL(CompileState&& cs): VertexColorGL{static_cast(std::move(cs))} { - if (id() == 0) return; +template VertexColorGL::VertexColorGL(CompileState&& state): VertexColorGL{static_cast(std::move(state))} { + if(!id()) return; CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); const GL::Context& context = GL::Context::current(); - const GL::Version version = cs._version; + const GL::Version version = state._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) @@ -190,15 +190,14 @@ template VertexColorGL::VertexColorGL(Compil static_cast(version); } -template VertexColorGL::VertexColorGL(Flags flags): VertexColorGL{compile(flags)} {} +template VertexColorGL::VertexColorGL(const Flags flags): VertexColorGL{compile(flags)} {} #ifndef MAGNUM_TARGET_GLES2 -template typename VertexColorGL::CompileState VertexColorGL::compile(Flags flags) { +template typename VertexColorGL::CompileState VertexColorGL::compile(const Flags flags) { return compile(flags, 1); } -template VertexColorGL::VertexColorGL(Flags flags, UnsignedInt drawCount): - VertexColorGL{compile(flags, drawCount)} {} +template VertexColorGL::VertexColorGL(const Flags flags, const UnsignedInt drawCount): VertexColorGL{compile(flags, drawCount)} {} #endif template VertexColorGL::VertexColorGL(NoInitT) {} diff --git a/src/Magnum/Shaders/VertexColorGL.h b/src/Magnum/Shaders/VertexColorGL.h index 988af7811..1c671c779 100644 --- a/src/Magnum/Shaders/VertexColorGL.h +++ b/src/Magnum/Shaders/VertexColorGL.h @@ -111,6 +111,8 @@ similar for all shaders, see @ref shaders-usage-multidraw for an example. */ template class MAGNUM_SHADERS_EXPORT VertexColorGL: public GL::AbstractShaderProgram { public: + class CompileState; + /** * @brief Vertex position * @@ -208,6 +210,34 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ typedef Implementation::VertexColorGLFlags Flags; #endif + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref VertexColorGL(Flags) can perform an asynchronous + * compilation and linking. See @ref shaders-async for more + * information. + * @see @ref VertexColorGL(CompileState&&), + * @ref compile(Flags, UnsignedInt) + */ + static CompileState compile(Flags flags = {}); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref VertexColorGL(Flags, UnsignedInt) can perform an + * asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref VertexColorGL(CompileState&&), @ref compile(Flags) + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + static CompileState compile(Flags flags, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -216,6 +246,7 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ * scenario (without @ref Flag::UniformBuffers set), it's equivalent to * @ref VertexColorGL(Flags, UnsignedInt) with @p drawCount set to * @cpp 1 @ce. + * @see @ref compile(Flags) */ explicit VertexColorGL(Flags flags = {}); @@ -235,6 +266,7 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ * If @p flags don't contain @ref Flag::UniformBuffers, @p drawCount is * ignored and the constructor behaves the same as * @ref VertexColorGL(Flags). + * @see @ref compile(Flags, UnsignedInt) * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. @@ -251,6 +283,16 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ explicit VertexColorGL(Flags flags, UnsignedInt drawCount); #endif + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit VertexColorGL(CompileState&& state); + /** * @brief Construct without creating the underlying OpenGL object * @@ -265,20 +307,6 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ */ explicit VertexColorGL(NoCreateT) noexcept: AbstractShaderProgram{NoCreate} {} - class CompileState; - - explicit VertexColorGL(CompileState&& cs); - - static CompileState compile(Flags flags - #ifndef MAGNUM_TARGET_GLES2 - , UnsignedInt drawCount - #endif - ); - - #ifndef MAGNUM_TARGET_GLES2 - static CompileState compile(Flags flags); - #endif - /** @brief Copying is not allowed */ VertexColorGL(const VertexColorGL&) = delete; @@ -421,6 +449,8 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ #endif private: + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ explicit VertexColorGL(NoInitT); /* Prevent accidentally calling irrelevant functions */ @@ -443,14 +473,19 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ #endif }; +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ template class VertexColorGL::CompileState: public VertexColorGL { -private: + /* Everything deliberately private except for the inheritance */ friend class VertexColorGL; explicit CompileState(NoCreateT): VertexColorGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} - CompileState(VertexColorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): - VertexColorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + explicit CompileState(VertexColorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): VertexColorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} GL::Shader _vert, _frag; GL::Version _version; From 40fa631f2b6a481e9ae973fa8e3b0ba59defbb6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 15:09:24 +0200 Subject: [PATCH 56/93] Shaders: these constructors shuld've been marked as new. "New". I should really make a release already. --- src/Magnum/Shaders/DistanceFieldVectorGL.h | 1 + src/Magnum/Shaders/FlatGL.h | 1 + src/Magnum/Shaders/MeshVisualizerGL.h | 2 ++ src/Magnum/Shaders/PhongGL.h | 1 + src/Magnum/Shaders/VectorGL.h | 1 + src/Magnum/Shaders/VertexColorGL.h | 1 + 6 files changed, 7 insertions(+) diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.h b/src/Magnum/Shaders/DistanceFieldVectorGL.h index c4332e200..febf8cee8 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.h +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.h @@ -272,6 +272,7 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * @ref TextureTransformationUniform buffer bound with * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() * and @ref bindTextureTransformationBuffer() + * @m_since_latest * * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and * @p drawCount describe the uniform buffer sizes as these are required diff --git a/src/Magnum/Shaders/FlatGL.h b/src/Magnum/Shaders/FlatGL.h index d7775f7e8..e54c3da92 100644 --- a/src/Magnum/Shaders/FlatGL.h +++ b/src/Magnum/Shaders/FlatGL.h @@ -594,6 +594,7 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * / @ref TextureTransformationUniform buffer bound with * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() * and @ref bindTextureTransformationBuffer() + * @m_since_latest * * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and * @p drawCount describe the uniform buffer sizes as these are required diff --git a/src/Magnum/Shaders/MeshVisualizerGL.h b/src/Magnum/Shaders/MeshVisualizerGL.h index 8857663ee..b519cb63a 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -500,6 +500,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * / @ref MeshVisualizerMaterialUniform buffer bound with * @ref bindTransformationProjectionBuffer() and * @ref bindDrawBuffer() + * @m_since_latest * * At least @ref Flag::Wireframe is expected to be enabled. * @@ -1756,6 +1757,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref MeshVisualizerMaterialUniform buffer bound with * @ref bindProjectionBuffer(), @ref bindTransformationBuffer() * and @ref bindDrawBuffer() + * @m_since_latest * * At least @ref Flag::Wireframe or one of @ref Flag::TangentDirection, * @ref Flag::BitangentFromTangentDirection, diff --git a/src/Magnum/Shaders/PhongGL.h b/src/Magnum/Shaders/PhongGL.h index e496e5850..6b0aa8dc7 100644 --- a/src/Magnum/Shaders/PhongGL.h +++ b/src/Magnum/Shaders/PhongGL.h @@ -810,6 +810,7 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @ref TextureTransformationUniform buffer bound with * @ref bindProjectionBuffer(), @ref bindTransformationBuffer(), * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() + * @m_since_latest * * If @p flags contains @ref Flag::UniformBuffers, @p lightCount, * @p materialCount and @p drawCount describe the uniform buffer sizes diff --git a/src/Magnum/Shaders/VectorGL.h b/src/Magnum/Shaders/VectorGL.h index 601bd0a44..ffa2821b2 100644 --- a/src/Magnum/Shaders/VectorGL.h +++ b/src/Magnum/Shaders/VectorGL.h @@ -266,6 +266,7 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * @ref VectorDrawUniform / @ref TextureTransformationUniform * buffer bound with @ref bindTransformationProjectionBuffer(), * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() + * @m_since_latest * * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and * @p drawCount describe the uniform buffer sizes as these are required diff --git a/src/Magnum/Shaders/VertexColorGL.h b/src/Magnum/Shaders/VertexColorGL.h index 1c671c779..e025eda77 100644 --- a/src/Magnum/Shaders/VertexColorGL.h +++ b/src/Magnum/Shaders/VertexColorGL.h @@ -257,6 +257,7 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ * @param drawCount Size of a @ref TransformationProjectionUniform2D * / @ref TransformationProjectionUniform3D buffer bound with * @ref bindTransformationProjectionBuffer() + * @m_since_latest * * If @p flags contains @ref Flag::UniformBuffers, @p drawCount * describes the uniform buffer sizes as these are required to have a From 10dffc659221717792483d43daf170d3d426e6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 15:21:11 +0200 Subject: [PATCH 57/93] doc: updated credits & changelog. --- doc/changelog.dox | 12 +++++++++--- doc/credits.dox | 2 ++ src/Magnum/GL/AbstractShaderProgram.cpp | 1 + src/Magnum/GL/AbstractShaderProgram.h | 1 + src/Magnum/GL/Shader.cpp | 1 + src/Magnum/GL/Shader.h | 1 + src/Magnum/Shaders/DistanceFieldVectorGL.cpp | 1 + src/Magnum/Shaders/DistanceFieldVectorGL.h | 1 + src/Magnum/Shaders/FlatGL.cpp | 1 + src/Magnum/Shaders/FlatGL.h | 1 + src/Magnum/Shaders/MeshVisualizerGL.cpp | 1 + src/Magnum/Shaders/MeshVisualizerGL.h | 1 + src/Magnum/Shaders/PhongGL.cpp | 1 + src/Magnum/Shaders/PhongGL.h | 1 + .../Shaders/Test/DistanceFieldVectorGLTest.cpp | 1 + src/Magnum/Shaders/Test/FlatGLTest.cpp | 1 + src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp | 1 + src/Magnum/Shaders/Test/PhongGLTest.cpp | 1 + src/Magnum/Shaders/Test/VectorGLTest.cpp | 1 + src/Magnum/Shaders/Test/VertexColorGLTest.cpp | 1 + src/Magnum/Shaders/VectorGL.cpp | 1 + src/Magnum/Shaders/VectorGL.h | 1 + src/Magnum/Shaders/VertexColorGL.cpp | 1 + src/Magnum/Shaders/VertexColorGL.h | 1 + 24 files changed, 33 insertions(+), 3 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 15ab0f554..404b267b3 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -110,9 +110,11 @@ See also: - Recognizing @webgl_extension{EXT,float_blend} and @webgl_extension{WEBGL,debug_shaders} WebGL extensions, no implementation done yet -- Recognizing @gl_extension{KHR,parallel_shader_compile} GL, GLES and - @webgl_extension{KHR,parallel_shader_compile} WebGL extensions, no - implementation done yet +- Implemented @ref GL-AbstractShaderProgram-async "Async shader compilation and linking" + including the @gl_extension{KHR,parallel_shader_compile} GL, GLES and + @webgl_extension{KHR,parallel_shader_compile} WebGL extension (see + [mosra/magnum#534](https://github.com/mosra/magnum/issues/534) and + [mosra/magnum#576](https://github.com/mosra/magnum/pull/576)) - Recognizing ANGLE GLES and WebGL base vertex, base instance and multi-draw extensions and using them in @ref GL::AbstractShaderProgram::draw(Mesh&) and @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>): @@ -229,6 +231,10 @@ See also: OpenGL ES 3.0+ and WebGL 2.0, including multi-draw functionality for massive driver overhead reduction. The @ref shaders overview page was updated with an introduction the new features. +- All builtin shaders now have opt-in capability of + @ref shaders-async "async compilation and linking" (see + [mosra/magnum534](https://github.com/mosra/magnum/issues/534) and + [mosra/magnum#576](https://github.com/mosra/magnum/pull/576)) - @ref Shaders::FlatGL and @ref Shaders::PhongGL now support texture arrays, available also in multi-draw and instanced scenarios - @ref Shaders::FlatGL and @ref Shaders::PhongGL now support object ID diff --git a/doc/credits.dox b/doc/credits.dox index 2a5b8ba1d..0d46ab2b2 100644 --- a/doc/credits.dox +++ b/doc/credits.dox @@ -228,6 +228,8 @@ Are the below lists missing your name or something's wrong? --- OpenGL ES compatibility improvements - **Travis Watkins** ([\@amaranth](https://github.com/amaranth)) --- support for windowless applications under macOS +- **Vladislav** ([\@dranikpg](https://github.com/dranikpg)) --- Async @ref GL + shader compilation */ } diff --git a/src/Magnum/GL/AbstractShaderProgram.cpp b/src/Magnum/GL/AbstractShaderProgram.cpp index c551d876b..59768aac7 100644 --- a/src/Magnum/GL/AbstractShaderProgram.cpp +++ b/src/Magnum/GL/AbstractShaderProgram.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/GL/AbstractShaderProgram.h b/src/Magnum/GL/AbstractShaderProgram.h index 682f46c70..abba41a1e 100644 --- a/src/Magnum/GL/AbstractShaderProgram.h +++ b/src/Magnum/GL/AbstractShaderProgram.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/GL/Shader.cpp b/src/Magnum/GL/Shader.cpp index d45b304f7..fdd3036dc 100644 --- a/src/Magnum/GL/Shader.cpp +++ b/src/Magnum/GL/Shader.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/GL/Shader.h b/src/Magnum/GL/Shader.h index f42eee3c6..1ae65dcfc 100644 --- a/src/Magnum/GL/Shader.h +++ b/src/Magnum/GL/Shader.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/DistanceFieldVectorGL.cpp b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp index eb7b1f45e..ca80137a0 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/DistanceFieldVectorGL.h b/src/Magnum/Shaders/DistanceFieldVectorGL.h index febf8cee8..cf1944a61 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.h +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index 3cc8084c4..939cc04d4 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/FlatGL.h b/src/Magnum/Shaders/FlatGL.h index e54c3da92..98b33cafc 100644 --- a/src/Magnum/Shaders/FlatGL.h +++ b/src/Magnum/Shaders/FlatGL.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index b08ae189c..f77d1ac07 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/MeshVisualizerGL.h b/src/Magnum/Shaders/MeshVisualizerGL.h index b519cb63a..ab7667c9f 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index 4e4538e63..d857b2bc8 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/PhongGL.h b/src/Magnum/Shaders/PhongGL.h index 6b0aa8dc7..bd7a8cf52 100644 --- a/src/Magnum/Shaders/PhongGL.h +++ b/src/Magnum/Shaders/PhongGL.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp index dd5f72242..f81bd17aa 100644 --- a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/Test/FlatGLTest.cpp b/src/Magnum/Shaders/Test/FlatGLTest.cpp index 94485a545..60198e313 100644 --- a/src/Magnum/Shaders/Test/FlatGLTest.cpp +++ b/src/Magnum/Shaders/Test/FlatGLTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index 957f33a9a..5c091d384 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index a9d6f2f4e..6248629cc 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/Test/VectorGLTest.cpp b/src/Magnum/Shaders/Test/VectorGLTest.cpp index a160640f7..bc7b560ed 100644 --- a/src/Magnum/Shaders/Test/VectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VectorGLTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/Test/VertexColorGLTest.cpp b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp index b62e6232c..352cf96d0 100644 --- a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/VectorGL.cpp b/src/Magnum/Shaders/VectorGL.cpp index a58c784f4..061c0a620 100644 --- a/src/Magnum/Shaders/VectorGL.cpp +++ b/src/Magnum/Shaders/VectorGL.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/VectorGL.h b/src/Magnum/Shaders/VectorGL.h index ffa2821b2..ecc4ccde7 100644 --- a/src/Magnum/Shaders/VectorGL.h +++ b/src/Magnum/Shaders/VectorGL.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/VertexColorGL.cpp b/src/Magnum/Shaders/VertexColorGL.cpp index c2667666c..5ba57d09a 100644 --- a/src/Magnum/Shaders/VertexColorGL.cpp +++ b/src/Magnum/Shaders/VertexColorGL.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko 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/Magnum/Shaders/VertexColorGL.h b/src/Magnum/Shaders/VertexColorGL.h index e025eda77..7048b2743 100644 --- a/src/Magnum/Shaders/VertexColorGL.h +++ b/src/Magnum/Shaders/VertexColorGL.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), From 56cc8734c77423b1cf7df0c4ae7ef8fba3cde87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 15:31:54 +0200 Subject: [PATCH 58/93] Shaders: the early exit is only for assertion testing, document that. I knew it had to be there, but then forgot and wanted to remove it as superfluous because such case can't happen from user code. Well... --- src/Magnum/Shaders/DistanceFieldVectorGL.cpp | 4 ++++ src/Magnum/Shaders/FlatGL.cpp | 4 ++++ src/Magnum/Shaders/MeshVisualizerGL.cpp | 8 ++++++++ src/Magnum/Shaders/PhongGL.cpp | 4 ++++ src/Magnum/Shaders/VectorGL.cpp | 4 ++++ src/Magnum/Shaders/VertexColorGL.cpp | 4 ++++ 6 files changed, 28 insertions(+) diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp index ca80137a0..be4dd12f5 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp @@ -164,7 +164,11 @@ template typename DistanceFieldVectorGL::Com } template DistanceFieldVectorGL::DistanceFieldVectorGL(CompileState&& state): DistanceFieldVectorGL{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ if(!id()) return; + #endif CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); diff --git a/src/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index 939cc04d4..af12286a3 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -239,7 +239,11 @@ template typename FlatGL::CompileState FlatG } template FlatGL::FlatGL(CompileState&& state): FlatGL{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ if(!id()) return; + #endif CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index f77d1ac07..80cde4f35 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -530,7 +530,11 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags, const UnsignedInt mate #endif MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& state): MeshVisualizerGL2D{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ if(!id()) return; + #endif CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); @@ -902,7 +906,11 @@ MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(Flags flags } MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ if(!id()) return; + #endif CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); diff --git a/src/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index d857b2bc8..f68258dc8 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -349,7 +349,11 @@ PhongGL::CompileState PhongGL::compile(const Flags flags, const UnsignedInt ligh } PhongGL::PhongGL(CompileState&& state): PhongGL{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ if(!id()) return; + #endif CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); diff --git a/src/Magnum/Shaders/VectorGL.cpp b/src/Magnum/Shaders/VectorGL.cpp index 061c0a620..b9f1b597b 100644 --- a/src/Magnum/Shaders/VectorGL.cpp +++ b/src/Magnum/Shaders/VectorGL.cpp @@ -166,7 +166,11 @@ template typename VectorGL::CompileState Vec } template VectorGL::VectorGL(CompileState&& state): VectorGL{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ if(!id()) return; + #endif CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); diff --git a/src/Magnum/Shaders/VertexColorGL.cpp b/src/Magnum/Shaders/VertexColorGL.cpp index 5ba57d09a..0853e52f2 100644 --- a/src/Magnum/Shaders/VertexColorGL.cpp +++ b/src/Magnum/Shaders/VertexColorGL.cpp @@ -144,7 +144,11 @@ template typename VertexColorGL::CompileStat } template VertexColorGL::VertexColorGL(CompileState&& state): VertexColorGL{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ if(!id()) return; + #endif CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); From 70fc232dbc32f7476a0bf3460b2c40483c455916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 15:43:57 +0200 Subject: [PATCH 59/93] GL: unconditionally trim shader compilation/linking messages. Until now it relied on the driver adding a \n after each message. Which happened in 99% cases, but sometimes not, and it is really annoying in that case. Right now I witnessed Mesa giving me a non-newlined message if I call checkLink() and a shader compilation failed before, resulting in nasty output like error: linking with uncompiled/unspecialized shaderAssertion checkLink() failed at /home/mosra/Code/magnum/src/Magnum/Shaders/FlatGL.cpp:248 And I bet I encountered other weird cases, like there being two newlines, or the message being just a newline without any other content, thus spamming the output on every compilation. It's not practical to try to find driver-specific workarounds here, so I'm just trimming always and then rely on Debug adding its own newline after. --- src/Magnum/GL/AbstractShaderProgram.cpp | 22 +++++++++++++++------- src/Magnum/GL/Shader.cpp | 23 ++++++++++++++++------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/Magnum/GL/AbstractShaderProgram.cpp b/src/Magnum/GL/AbstractShaderProgram.cpp index 59768aac7..4b5e53be7 100644 --- a/src/Magnum/GL/AbstractShaderProgram.cpp +++ b/src/Magnum/GL/AbstractShaderProgram.cpp @@ -31,6 +31,7 @@ #ifndef MAGNUM_TARGET_WEBGL #include #endif +#include /** @todo remove once -free */ #include #include @@ -606,17 +607,24 @@ bool AbstractShaderProgram::checkLink() { be said, handle that as well. */ Context::current().state().shaderProgram.cleanLogImplementation(message); + /* Usually the driver messages contain a newline at the end. But sometimes + not, such as in case of a program link error due to shaders not being + compiled yet on Mesa; sometimes there's two newlines, sometimes just a + newline and nothing else etc. Because trying do this in driver-specific + workarounds would involve an impossible task of checking all possible + error messages on every possible driver, just trim all whitespace around + the message always and let Debug add its own newline. */ + const Containers::StringView messageTrimmed = Containers::StringView{message}.trimmed(); + /* Show error log */ if(!success) { - Error out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::AbstractShaderProgram::link(): linking failed with the following message:" - << Debug::newline << message; + Error{} << "GL::AbstractShaderProgram::link(): linking failed with the following message:" + << Debug::newline << messageTrimmed; /* Or just warnings, if any */ - } else if(!message.empty()) { - Warning out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::AbstractShaderProgram::link(): linking succeeded with the following message:" - << Debug::newline << message; + } else if(messageTrimmed) { + Warning{} << "GL::AbstractShaderProgram::link(): linking succeeded with the following message:" + << Debug::newline << messageTrimmed; } return success; diff --git a/src/Magnum/GL/Shader.cpp b/src/Magnum/GL/Shader.cpp index fdd3036dc..9966b75cf 100644 --- a/src/Magnum/GL/Shader.cpp +++ b/src/Magnum/GL/Shader.cpp @@ -783,17 +783,26 @@ bool Shader::checkCompile() { be said, handle that as well. */ Context::current().state().shader.cleanLogImplementation(message); + /* Usually the driver messages contain a newline at the end. But sometimes + not, such as in case of a program link error due to shaders not being + compiled yet on Mesa; sometimes there's two newlines, sometimes just a + newline and nothing else etc. Because trying do this in driver-specific + workarounds would involve an impossible task of checking all possible + error messages on every possible driver, just trim all whitespace around + the message always and let Debug add its own newline. */ + const Containers::StringView messageTrimmed = Containers::StringView{message}.trimmed(); + /* Show error log */ if(!success) { - Error out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::Shader::compile(): compilation of" << shaderName(_type) << "shader" - << "failed with the following message:" << Debug::newline << message; + Error{} << "GL::Shader::compile(): compilation of" << shaderName(_type) + << "shader failed with the following message:" << Debug::newline + << messageTrimmed; /* Or just warnings, if any */ - } else if(!message.empty()) { - Warning out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::Shader::compile(): compilation of" << shaderName(_type) << "shader" - << "succeeded with the following message:" << Debug::newline << message; + } else if(messageTrimmed) { + Warning{} << "GL::Shader::compile(): compilation of" << shaderName(_type) + << "shader succeeded with the following message:" << Debug::newline + << messageTrimmed; } return success; From 6e7b92b25a015618fa4831f0342efc204c228c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 17:31:45 +0200 Subject: [PATCH 60/93] GL: unify tests for Shader/ShaderProgram compile/link failures. In particular, test also output of the non-async variant. --- .../GL/Test/AbstractShaderProgramGLTest.cpp | 27 ++++++++-- src/Magnum/GL/Test/ShaderGLTest.cpp | 51 ++++++++++++------- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp index e378aca0e..20753b820 100644 --- a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp +++ b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp @@ -485,6 +485,7 @@ void AbstractShaderProgramGLTest::linkFailure() { , Shader::Type::Fragment); shader.addSource("[fu] bleh error #:! stuff\n"); + /* The compilation should fail */ { Error redirectError{nullptr}; CORRADE_VERIFY(!shader.compile()); @@ -492,10 +493,19 @@ void AbstractShaderProgramGLTest::linkFailure() { MyPublicShader program; program.attachShaders({shader}); - CORRADE_VERIFY(!program.link()); + + /* And thus linking as well, saying something like "error: linking with + uncompiled/unspecialized shader" */ + std::ostringstream out; + { + Error redirectError{&out}; + CORRADE_VERIFY(!program.link()); + } Utility::System::sleep(200); CORRADE_VERIFY(program.isLinkFinished()); + CORRADE_COMPARE_AS(out.str(), "GL::AbstractShaderProgram::link(): linking failed with the following message:", + TestSuite::Compare::StringHasPrefix); } void AbstractShaderProgramGLTest::linkFailureAsync() { @@ -512,6 +522,7 @@ void AbstractShaderProgramGLTest::linkFailureAsync() { , Shader::Type::Fragment); shader.addSource("[fu] bleh error #:! stuff\n"); + /* The compilation should fail */ { Error redirectError{nullptr}; CORRADE_VERIFY(!shader.compile()); @@ -520,16 +531,24 @@ void AbstractShaderProgramGLTest::linkFailureAsync() { MyPublicShader program; program.attachShaders({shader}); + /* The link submission should not print anything ... */ std::ostringstream out; - Error redirectError{&out}; - program.submitLink(); + { + Error redirectError{&out}; + program.submitLink(); + } while(!program.isLinkFinished()) Utility::System::sleep(100); CORRADE_VERIFY(out.str().empty()); - CORRADE_VERIFY(!program.checkLink()); + /* ... only the final check should. In this case it's "error: linking with + uncompiled/unspecialized shader" as well. */ + { + Error redirectError{&out}; + CORRADE_VERIFY(!program.checkLink()); + } CORRADE_VERIFY(program.isLinkFinished()); CORRADE_COMPARE_AS(out.str(), "GL::AbstractShaderProgram::link(): linking failed with the following message:", TestSuite::Compare::StringHasPrefix); diff --git a/src/Magnum/GL/Test/ShaderGLTest.cpp b/src/Magnum/GL/Test/ShaderGLTest.cpp index 49ef6cc66..bd74c93e9 100644 --- a/src/Magnum/GL/Test/ShaderGLTest.cpp +++ b/src/Magnum/GL/Test/ShaderGLTest.cpp @@ -58,9 +58,9 @@ struct ShaderGLTest: OpenGLTester { void addSourceNoVersion(); void addFile(); void compile(); - void compileFailure(); void compileAsync(); - void compileAsyncFailure(); + void compileFailure(); + void compileFailureAsync(); void compileUtf8(); void compileNoVersion(); }; @@ -78,9 +78,9 @@ ShaderGLTest::ShaderGLTest() { &ShaderGLTest::addSourceNoVersion, &ShaderGLTest::addFile, &ShaderGLTest::compile, - &ShaderGLTest::compileFailure, &ShaderGLTest::compileAsync, - &ShaderGLTest::compileAsyncFailure, + &ShaderGLTest::compileFailure, + &ShaderGLTest::compileFailureAsync, &ShaderGLTest::compileUtf8, &ShaderGLTest::compileNoVersion}); } @@ -290,7 +290,7 @@ void ShaderGLTest::compile() { CORRADE_VERIFY(shader.isCompileFinished()); } -void ShaderGLTest::compileFailure() { +void ShaderGLTest::compileAsync() { #ifndef MAGNUM_TARGET_GLES constexpr Version v = #ifndef CORRADE_TARGET_APPLE @@ -304,13 +304,17 @@ void ShaderGLTest::compileFailure() { #endif Shader shader(v, Shader::Type::Fragment); - shader.addSource("[fu] bleh error #:! stuff\n"); + shader.addSource("void main() {}\n"); + shader.submitCompile(); + + while(!shader.isCompileFinished()) + Utility::System::sleep(100); - CORRADE_VERIFY(!shader.compile()); + CORRADE_VERIFY(shader.checkCompile()); CORRADE_VERIFY(shader.isCompileFinished()); } -void ShaderGLTest::compileAsync() { +void ShaderGLTest::compileFailure() { #ifndef MAGNUM_TARGET_GLES constexpr Version v = #ifndef CORRADE_TARGET_APPLE @@ -323,18 +327,20 @@ void ShaderGLTest::compileAsync() { constexpr Version v = Version::GLES200; #endif - Shader shader(v, Shader::Type::Fragment); - shader.addSource("void main() {}\n"); - shader.submitCompile(); - - while(!shader.isCompileFinished()) - Utility::System::sleep(100); + Shader shader(v, Shader::Type::Vertex); + shader.addSource("[fu] bleh error #:! stuff\n"); - CORRADE_VERIFY(shader.checkCompile()); + std::ostringstream out; + { + Error redirectError{&out}; + CORRADE_VERIFY(!shader.compile()); + } CORRADE_VERIFY(shader.isCompileFinished()); + CORRADE_COMPARE_AS(out.str(), "GL::Shader::compile(): compilation of vertex shader failed with the following message:", + TestSuite::Compare::StringHasPrefix); } -void ShaderGLTest::compileAsyncFailure() { +void ShaderGLTest::compileFailureAsync() { #ifndef MAGNUM_TARGET_GLES constexpr Version v = #ifndef CORRADE_TARGET_APPLE @@ -350,16 +356,23 @@ void ShaderGLTest::compileAsyncFailure() { Shader shader(v, Shader::Type::Fragment); shader.addSource("[fu] bleh error #:! stuff\n"); + /* The compile submission should not print anything ... */ std::ostringstream out; - Error redirectError{&out}; - shader.submitCompile(); + { + Error redirectError{&out}; + shader.submitCompile(); + } while(!shader.isCompileFinished()) Utility::System::sleep(100); CORRADE_VERIFY(out.str().empty()); - CORRADE_VERIFY(!shader.checkCompile()); + /* ... only the final check should */ + { + Error redirectError{&out}; + CORRADE_VERIFY(!shader.checkCompile()); + } CORRADE_VERIFY(shader.isCompileFinished()); CORRADE_COMPARE_AS(out.str(), "GL::Shader::compile(): compilation of fragment shader failed with the following message:", TestSuite::Compare::StringHasPrefix); From c9d7365937fa702c9a96f8a79e3d20b9f7e243cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 17:56:42 +0200 Subject: [PATCH 61/93] GL: better handle a case of async link failures. The new async APIs were just checking the link status, and printing the linker error. Because drivers commonly do all that in a single step, without really separating compilation from linking (or at least that's what I thought?), I assumed the linker error would contain *also* the compilation error, if any. But on a quick check with Mesa that's not the case, I only get "error: linking with uncompiled/unspecialized shader", which is very useless. Which means, to get proper error output, the checkLink() function now explicitly takes a list of the input shaders. It will unconditionally go through them at the beginning and call checkCompile() on each. To further encourage the shaders to be passed, there's no default argument -- so if the application calls checkCompile() on its own for some reason, it has to pass an empty list to checkLink(). --- doc/snippets/MagnumGL.cpp | 3 +- src/Magnum/GL/AbstractShaderProgram.cpp | 11 ++- src/Magnum/GL/AbstractShaderProgram.h | 37 +++++++--- src/Magnum/GL/Shader.h | 34 +++++---- .../GL/Test/AbstractShaderProgramGLTest.cpp | 69 ++++++++++++++++++- src/Magnum/Shaders/DistanceFieldVectorGL.cpp | 3 +- src/Magnum/Shaders/FlatGL.cpp | 3 +- src/Magnum/Shaders/MeshVisualizerGL.cpp | 11 ++- src/Magnum/Shaders/PhongGL.cpp | 3 +- src/Magnum/Shaders/VectorGL.cpp | 3 +- src/Magnum/Shaders/VertexColorGL.cpp | 3 +- 11 files changed, 142 insertions(+), 38 deletions(-) diff --git a/doc/snippets/MagnumGL.cpp b/doc/snippets/MagnumGL.cpp index 06adbcf01..98755e21a 100644 --- a/doc/snippets/MagnumGL.cpp +++ b/doc/snippets/MagnumGL.cpp @@ -25,6 +25,7 @@ #include /* for std::tie() :( */ #include +#include #include #include @@ -300,7 +301,7 @@ MyShader::MyShader(NoInitT) {} MyShader::MyShader(CompileState&& state): MyShader{static_cast(std::move(state))} { - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); DOXYGEN_ELLIPSIS() } diff --git a/src/Magnum/GL/AbstractShaderProgram.cpp b/src/Magnum/GL/AbstractShaderProgram.cpp index 4b5e53be7..c26f0cab9 100644 --- a/src/Magnum/GL/AbstractShaderProgram.cpp +++ b/src/Magnum/GL/AbstractShaderProgram.cpp @@ -27,6 +27,7 @@ #include "AbstractShaderProgram.h" #include +#include #include #ifndef MAGNUM_TARGET_WEBGL #include @@ -591,7 +592,13 @@ void AbstractShaderProgram::submitLink() { glLinkProgram(_id); } -bool AbstractShaderProgram::checkLink() { +bool AbstractShaderProgram::checkLink(const Containers::Iterable shaders) { + /* If any compilation failed, abort without even checking the link status. + The checkCompile() API is called always, to print also compilation + warnings even in case everything still manages to link well. */ + for(Shader& shader: shaders) + if(!shader.checkCompile()) return false; + GLint success, logLength; glGetProgramiv(_id, GL_LINK_STATUS, &success); glGetProgramiv(_id, GL_INFO_LOG_LENGTH, &logLength); @@ -633,7 +640,7 @@ bool AbstractShaderProgram::checkLink() { bool AbstractShaderProgram::link(std::initializer_list> shaders) { for(AbstractShaderProgram& shader: shaders) shader.submitLink(); bool allSuccess = true; - for(AbstractShaderProgram& shader: shaders) allSuccess = allSuccess && shader.checkLink(); + for(AbstractShaderProgram& shader: shaders) allSuccess = allSuccess && shader.checkLink({}); return allSuccess; } diff --git a/src/Magnum/GL/AbstractShaderProgram.h b/src/Magnum/GL/AbstractShaderProgram.h index abba41a1e..10b804461 100644 --- a/src/Magnum/GL/AbstractShaderProgram.h +++ b/src/Magnum/GL/AbstractShaderProgram.h @@ -446,9 +446,9 @@ the application to schedule other work in the meantime. Async compilation and linking can be implemented by using @ref Shader::submitCompile() and @ref submitLink(), followed by -@ref checkLink() (and optionally @ref Shader::checkCompile()), instead of -@ref Shader::compile() and @ref link(). Calling the submit functions will -trigger a (potentially async) compilation and linking, calling the check +@ref checkLink() (which optionally delegates to @ref Shader::checkCompile()), +instead of @ref Shader::compile() and @ref link(). Calling the submit functions +will trigger a (potentially async) compilation and linking, calling the check functions will check the operation result, potentially stalling if the async operation isn't finished yet. @@ -476,9 +476,9 @@ creation capability while keeping also the simple constructor is the following: @cpp CompileState @ce instance. 4. A @cpp MyShader(CompileState&&) @ce constructor then takes over the base of @cpp CompileState @ce by delegating it into the move constructor. Then - it calls @ref checkLink() (and if that fails also @ref Shader::checkCompile() - to provide more context) and then performs any remaining post-link steps - such as uniform setup. + it calls @ref checkLink(), passing all input shaders to it for a complete + context in case of an error, and finally performs any remaining post-link + steps such as uniform setup. 5. The original @cpp MyShader(…) @ce constructor now only passes the result of @cpp compile() @ce to @cpp MyShader(CompileState&&) @ce. @@ -1551,16 +1551,31 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { * @m_since_latest * * Has to be called only if @ref submitLink() was called before. + * + * If @p shaders are not empty, first calls @ref Shader::checkCompile() + * on each. If a compilation failure is reached, returns @cpp false @ce + * without even checking link status. To have error messages with full + * context in case of a failed shader compilation or linking, an + * application is encouraged to pass all input @ref Shader instances to + * this function or, if not possible, explicitly call + * @ref Shader::checkCompile() on each. + * + * Then, link status is checked and a message (if any) is printed * Returns @cpp false @ce if linking failed, @cpp true @ce on success. - * Linker message (if any) is printed to error output. The function - * will stall until a (potentially async) linking operation finishes, - * you can use @ref isLinkFinished() to check the status instead. See - * @ref GL-AbstractShaderProgram-async for more information. + * If linking failed, it first goes through @p shaders and calls + * @ref Shader::checkCompile() on each until a failure is reached. If + * no compilation failed, a linker message is printed to error output. + * The function will stall until a (potentially async) linking + * operation finishes, you can use @ref isLinkFinished() to check the + * status instead. See @ref GL-AbstractShaderProgram-async for more + * information. * @see @ref Shader::checkCompile(), @fn_gl_keyword{GetProgram} with * @def_gl{LINK_STATUS} and @def_gl{INFO_LOG_LENGTH}, * @fn_gl_keyword{GetProgramInfoLog} */ - bool checkLink(); + /* No default argument is provided in order to *really* encourage apps + to pass the shaders here */ + bool checkLink(Containers::Iterable shaders); /** * @brief Get uniform location diff --git a/src/Magnum/GL/Shader.h b/src/Magnum/GL/Shader.h index 1ae65dcfc..5850e0ba6 100644 --- a/src/Magnum/GL/Shader.h +++ b/src/Magnum/GL/Shader.h @@ -645,13 +645,16 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { * @m_since_latest * * You can call @ref isCompileFinished() or @ref checkCompile() after, - * but in most cases it's enough to defer that to after + * but it's recommended to instead immediately call * @ref AbstractShaderProgram::attachShader() and - * @relativeref{AbstractShaderProgram,submitLink()} were called, and - * then continuing with @relativeref{AbstractShaderProgram,isLinkFinished()} - * or @relativeref{AbstractShaderProgram,checkLink()} on the final - * program --- if compilation would fail, subsequent linking will as - * well. See @ref GL-AbstractShaderProgram-async for more information. + * @relativeref{AbstractShaderProgram,submitLink()}, then optionally + * continue with @relativeref{AbstractShaderProgram,isLinkFinished()} + * and pass all input shaders to + * @relativeref{AbstractShaderProgram,checkLink()} on the final program + * --- if compilation would fail, subsequent linking will as well, and + * @relativeref{AbstractShaderProgram,checkLink()} will print the + * compilation error if linking failed due to that. See + * @ref GL-AbstractShaderProgram-async for more information. * @see @fn_gl_keyword{ShaderSource}, @fn_gl_keyword{CompileShader} */ void submitCompile(); @@ -660,14 +663,17 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { * @brief Check shader compilation status and await completion * @m_since_latest * - * Has to be called only if @ref submitCompile() was called before. In - * most cases it's enough to defer this check to after + * Has to be called only if @ref submitCompile() was called before. + * It's however recommended to instead immediately call * @ref AbstractShaderProgram::attachShader() and - * @relativeref{AbstractShaderProgram,submitLink()} were called, and - * then continuing with @relativeref{AbstractShaderProgram,isLinkFinished()} - * or @relativeref{AbstractShaderProgram,checkLink()} on the final - * program --- if compilation would fail, subsequent linking will as - * well. See @ref GL-AbstractShaderProgram-async for more information. + * @relativeref{AbstractShaderProgram,submitLink()}, then optionally + * continue with @relativeref{AbstractShaderProgram,isLinkFinished()} + * and pass all input shaders to + * @relativeref{AbstractShaderProgram,checkLink()} on the final program + * --- if compilation would fail, subsequent linking will as well, and + * @relativeref{AbstractShaderProgram,checkLink()} will print the + * compilation error if linking failed due to that. See + * @ref GL-AbstractShaderProgram-async for more information. * @see @fn_gl_keyword{GetShader} with @def_gl{COMPILE_STATUS} and * @def_gl{INFO_LOG_LENGTH}, @fn_gl_keyword{GetShaderInfoLog} */ @@ -684,7 +690,7 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { * available, the function always returns @cpp true @ce --- i.e., as if * the compilation was done synchronously. * - * In most cases it's enough to only wait for the final link to finish, + * It's however recommended to wait only for the final link to finish, * and not for particular compilations --- i.e., right after * @ref submitCompile() continue with * @ref AbstractShaderProgram::attachShader() and diff --git a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp index 20753b820..9b90f75da 100644 --- a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp +++ b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include /** @todo remove when Shader is -free */ #include @@ -74,6 +75,7 @@ struct AbstractShaderProgramGLTest: OpenGLTester { void linkFailure(); void linkFailureAsync(); + void linkFailureAsyncShaderList(); void uniformNotFound(); @@ -116,6 +118,7 @@ AbstractShaderProgramGLTest::AbstractShaderProgramGLTest() { &AbstractShaderProgramGLTest::linkFailure, &AbstractShaderProgramGLTest::linkFailureAsync, + &AbstractShaderProgramGLTest::linkFailureAsyncShaderList, &AbstractShaderProgramGLTest::uniformNotFound, &AbstractShaderProgramGLTest::uniform, @@ -335,7 +338,7 @@ void AbstractShaderProgramGLTest::createAsync() { while(!program.isLinkFinished()) Utility::System::sleep(100); - CORRADE_VERIFY(program.checkLink()); + CORRADE_VERIFY(program.checkLink({vert, frag})); CORRADE_VERIFY(program.isLinkFinished()); const bool valid = program.validate().first; @@ -544,16 +547,76 @@ void AbstractShaderProgramGLTest::linkFailureAsync() { CORRADE_VERIFY(out.str().empty()); /* ... only the final check should. In this case it's "error: linking with - uncompiled/unspecialized shader" as well. */ + uncompiled/unspecialized shader" as well, but if the shaders would be + supplied like in linkFailureAsyncShaderList() below, it'd print the + shader failure instead. */ { Error redirectError{&out}; - CORRADE_VERIFY(!program.checkLink()); + CORRADE_VERIFY(!program.checkLink({})); } CORRADE_VERIFY(program.isLinkFinished()); CORRADE_COMPARE_AS(out.str(), "GL::AbstractShaderProgram::link(): linking failed with the following message:", TestSuite::Compare::StringHasPrefix); } +void AbstractShaderProgramGLTest::linkFailureAsyncShaderList() { + Shader vert( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Vertex); + vert.addSource("void main() {}\n"); + + Shader frag( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Fragment); + frag.addSource("[fu] bleh error #:! stuff\n"); + + vert.submitCompile(); + frag.submitCompile(); + + MyPublicShader program; + program.attachShaders({vert, frag}); + + /* The link submission should not print anything ... */ + { + std::ostringstream out; + Error redirectError{&out}; + program.submitLink(); + CORRADE_VERIFY(out.str().empty()); + } + + /* ... only the final check should. Vertex shader should be fine, but + fragment should fail. */ + std::ostringstream out; + { + Error redirectError{&out}; + CORRADE_VERIFY(!program.checkLink({vert, frag})); + } + CORRADE_COMPARE_AS(out.str(), "GL::Shader::compile(): compilation of fragment shader failed with the following message:", + TestSuite::Compare::StringHasPrefix); + + /* The linker error (which would most probably say something like "error: + linking with uncompiled/unspecialized shader") should not be even + printed */ + CORRADE_COMPARE_AS(out.str(), "GL::AbstractShaderProgram::link(): linking failed with the following message:", + TestSuite::Compare::StringNotContains); +} + void AbstractShaderProgramGLTest::uniformNotFound() { MyPublicShader program; diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp index be4dd12f5..648330122 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp @@ -27,6 +27,7 @@ #include "DistanceFieldVectorGL.h" #include +#include #include #include @@ -170,7 +171,7 @@ template DistanceFieldVectorGL::DistanceFiel if(!id()) return; #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; diff --git a/src/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index af12286a3..1d61fed6c 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -27,6 +27,7 @@ #include "FlatGL.h" #include +#include #include #include @@ -245,7 +246,7 @@ template FlatGL::FlatGL(CompileState&& state if(!id()) return; #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index 80cde4f35..6c079655b 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -27,6 +27,7 @@ #include "MeshVisualizerGL.h" #include +#include #include #include #include @@ -536,7 +537,10 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& state): MeshVisualizerGL2D if(!id()) return; #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + if(state._geom) + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag, *state._geom})); + else + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; @@ -912,7 +916,10 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D if(!id()) return; #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + if(state._geom) + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag, *state._geom})); + else + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; diff --git a/src/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index f68258dc8..6e511a0ba 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -30,6 +30,7 @@ #include #endif #include +#include #include #include #include @@ -355,7 +356,7 @@ PhongGL::PhongGL(CompileState&& state): PhongGL{static_cast(std::move if(!id()) return; #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; diff --git a/src/Magnum/Shaders/VectorGL.cpp b/src/Magnum/Shaders/VectorGL.cpp index b9f1b597b..89b5353e3 100644 --- a/src/Magnum/Shaders/VectorGL.cpp +++ b/src/Magnum/Shaders/VectorGL.cpp @@ -27,6 +27,7 @@ #include "VectorGL.h" #include +#include #include #include @@ -172,7 +173,7 @@ template VectorGL::VectorGL(CompileState&& s if(!id()) return; #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; diff --git a/src/Magnum/Shaders/VertexColorGL.cpp b/src/Magnum/Shaders/VertexColorGL.cpp index 0853e52f2..048e95653 100644 --- a/src/Magnum/Shaders/VertexColorGL.cpp +++ b/src/Magnum/Shaders/VertexColorGL.cpp @@ -27,6 +27,7 @@ #include "VertexColorGL.h" #include +#include #include #include @@ -150,7 +151,7 @@ template VertexColorGL::VertexColorGL(Compil if(!id()) return; #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; From c0c712cc332b859ad2db59c2c1c86817d94863f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 18:30:15 +0200 Subject: [PATCH 62/93] GL: deprecate list-taking Shader::compile() and ShaderProgram::link(). There's no reason for those to exist anymore -- origiinally they were added in a hopeful attempt to make use of parallel shader compilation, but in practice that meant compiling at most two or three shaders at once and still stalling until that was done, so not that great at all. The new APIs provide much better opportunities for parallelism. Fun fact: CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); is actually one character shorter than CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); so not even typing convenience would be a reason to keep these. --- doc/changelog.dox | 10 +++++++ doc/snippets/MagnumGL.cpp | 4 +-- src/Magnum/DebugTools/TextureImage.cpp | 2 +- src/Magnum/GL/AbstractShaderProgram.cpp | 7 ++++- src/Magnum/GL/AbstractShaderProgram.h | 18 ++++++++---- src/Magnum/GL/Shader.cpp | 9 +++++- src/Magnum/GL/Shader.h | 23 +++++++++------ .../GL/Test/AbstractShaderProgramGLTest.cpp | 28 ++++++++++--------- src/Magnum/GL/Test/MeshGLTest.cpp | 12 ++++---- src/Magnum/GL/Test/RendererGLTest.cpp | 2 +- src/Magnum/GL/Test/SampleQueryGLTest.cpp | 2 +- .../GL/Test/TransformFeedbackGLTest.cpp | 5 ++-- .../Test/FullScreenTriangleGLTest.cpp | 2 +- src/Magnum/TextureTools/DistanceField.cpp | 2 +- 14 files changed, 83 insertions(+), 43 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 404b267b3..676d9749c 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -821,6 +821,16 @@ See also: @ref DebugTools::FrameProfilerGL. The new name plays better with IDE autocompletion and makes the GL-specific class appear next to the API-independent base in alphabetically sorted lists. +- List-taking @cpp GL::Shader::compile() @ce and + @cpp GL::AbstractShaderProgram::link() @ce functions are deprecated in + favor of new @ref GL::Shader::submitCompile(), + @relativeref{GL::Shader,checkCompile()}, + @ref GL::AbstractShaderProgram::submitLink() and + @relativeref{GL::AbstractShaderProgram,checkLink()} APIs. These were + originally meant to make use of parallel shader compilation, but in + practice that meant compiling at most two or three shaders at once. The new + API allows for much larger parallelism as well as an ability to query + completion status. - @cpp Shaders::DistanceFieldVector @ce, @cpp Shaders::Flat @ce, @cpp Shaders::Generic @ce, @cpp Shaders::MeshVisualizer2D @ce, @cpp Shaders::MeshVisualizer3D @ce, @cpp Shaders::Phong @ce, diff --git a/doc/snippets/MagnumGL.cpp b/doc/snippets/MagnumGL.cpp index 98755e21a..26a25311f 100644 --- a/doc/snippets/MagnumGL.cpp +++ b/doc/snippets/MagnumGL.cpp @@ -147,8 +147,8 @@ explicit MyShader() { vert.addFile("MyShader.vert"); frag.addFile("MyShader.frag"); - /* Invoke parallel compilation for best performance */ - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + /* Compile them */ + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); /* Attach the shaders */ attachShaders({vert, frag}); diff --git a/src/Magnum/DebugTools/TextureImage.cpp b/src/Magnum/DebugTools/TextureImage.cpp index 64987f240..253394413 100644 --- a/src/Magnum/DebugTools/TextureImage.cpp +++ b/src/Magnum/DebugTools/TextureImage.cpp @@ -87,7 +87,7 @@ FloatReinterpretShader::FloatReinterpretShader() { vert.addSource(rs.getString("TextureImage.vert")); frag.addSource(rs.getString("TextureImage.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); if(!GL::Context::current().isExtensionSupported()) { diff --git a/src/Magnum/GL/AbstractShaderProgram.cpp b/src/Magnum/GL/AbstractShaderProgram.cpp index c26f0cab9..dcc1b4915 100644 --- a/src/Magnum/GL/AbstractShaderProgram.cpp +++ b/src/Magnum/GL/AbstractShaderProgram.cpp @@ -586,7 +586,10 @@ void AbstractShaderProgram::transformFeedbackVaryingsImplementationDanglingWorka #endif #endif -bool AbstractShaderProgram::link() { return link({*this}); } +bool AbstractShaderProgram::link() { + submitLink(); + return checkLink({}); +} void AbstractShaderProgram::submitLink() { glLinkProgram(_id); @@ -637,12 +640,14 @@ bool AbstractShaderProgram::checkLink(const Containers::Iterable shaders return success; } +#ifdef MAGNUM_BUILD_DEPRECATED bool AbstractShaderProgram::link(std::initializer_list> shaders) { for(AbstractShaderProgram& shader: shaders) shader.submitLink(); bool allSuccess = true; for(AbstractShaderProgram& shader: shaders) allSuccess = allSuccess && shader.checkLink({}); return allSuccess; } +#endif bool AbstractShaderProgram::isLinkFinished() { GLint success; diff --git a/src/Magnum/GL/AbstractShaderProgram.h b/src/Magnum/GL/AbstractShaderProgram.h index 10b804461..c01eb6e6f 100644 --- a/src/Magnum/GL/AbstractShaderProgram.h +++ b/src/Magnum/GL/AbstractShaderProgram.h @@ -43,6 +43,7 @@ #endif #ifdef MAGNUM_BUILD_DEPRECATED +#include /* For label() / setLabel(), which used to be a std::string */ #include #endif @@ -1345,15 +1346,22 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { bool isLinkFinished(); protected: - /** @brief Link the shaders + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Link multiple shaders simultaenously + * @m_deprecated_since_latest Originally meant to batch multiple link + * operations together in a way that allowed the driver to perform + * the linking in multiple threads. Superseded by @ref submitLink() + * and @ref checkLink(), use either those or the zero-argument + * @ref link() instead. See @ref GL-AbstractShaderProgram-async + * for more information. * * Calls @ref submitLink() on all shaders first, then @ref checkLink(). * Returns @cpp false @ce if linking of any shader failed, @cpp true @ce - * if everything succeeded. The operation is batched in a - * way that allows the driver to link multiple shaders simultaneously - * (i.e. in multiple threads). + * if everything succeeded. */ - static bool link(std::initializer_list> shaders); + static CORRADE_DEPRECATED("use either submitLink() and checkLink() or the zero-argument link() instead") bool link(std::initializer_list> shaders); + #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) /** diff --git a/src/Magnum/GL/Shader.cpp b/src/Magnum/GL/Shader.cpp index 9966b75cf..40e5cf2dc 100644 --- a/src/Magnum/GL/Shader.cpp +++ b/src/Magnum/GL/Shader.cpp @@ -27,7 +27,9 @@ #include "Shader.h" #include +#ifdef MAGNUM_BUILD_DEPRECATED #include +#endif #ifndef MAGNUM_TARGET_WEBGL #include #endif @@ -748,7 +750,10 @@ Shader& Shader::addFile(const std::string& filename) { return *this; } -bool Shader::compile() { return compile({*this}); } +bool Shader::compile() { + submitCompile(); + return checkCompile(); +} void Shader::submitCompile() { CORRADE_ASSERT(_sources.size() > 1, "GL::Shader::compile(): no files added", ); @@ -808,6 +813,7 @@ bool Shader::checkCompile() { return success; } +#ifdef MAGNUM_BUILD_DEPRECATED bool Shader::compile(std::initializer_list> shaders) { /* Invoke (possibly parallel) compilation on all shaders */ for(Shader& shader: shaders) shader.submitCompile(); @@ -815,6 +821,7 @@ bool Shader::compile(std::initializer_list> shader for(Shader& shader: shaders) allSuccess = allSuccess && shader.checkCompile(); return allSuccess; } +#endif bool Shader::isCompileFinished() { GLint success; diff --git a/src/Magnum/GL/Shader.h b/src/Magnum/GL/Shader.h index 5850e0ba6..b9385e877 100644 --- a/src/Magnum/GL/Shader.h +++ b/src/Magnum/GL/Shader.h @@ -39,6 +39,7 @@ #include "Magnum/GL/GL.h" #ifdef MAGNUM_BUILD_DEPRECATED +#include /* For label() / setLabel(), which used to be a std::string. Not ideal for the return type, but at least something. */ #include @@ -508,16 +509,22 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { static Int maxCombinedUniformComponents(Type type); #endif + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Compile multiple shaders simultaneously - * - * Calls @ref submitCompile() on all shaders first, then @ref checkCompile(). - * Returns @cpp false @ce if compilation of any shader failed, - * @cpp true @ce if everything succeeded. The operation is batched in a way that - * allows the driver to perform multiple compilations simultaneously - * (i.e. in multiple threads). - */ - static bool compile(std::initializer_list> shaders); + * @m_deprecated_since_latest Originally meant to batch multiple + * compile operations together in a way that allowed the driver to + * perform the compilation in multiple threads. Superseded by + * @ref submitCompile() and @ref checkCompile(), use either those + * or the zero-argument @ref compile() instead. See + * @ref GL-AbstractShaderProgram-async for more information. + * + * Calls @ref submitCompile() on all shaders first, then + * @ref checkCompile(). Returns @cpp false @ce if compilation of any + * shader failed, @cpp true @ce if everything succeeded. + */ + static CORRADE_DEPRECATED("use either submitCompile() and checkCompile() or the zero-argument compile() instead") bool compile(std::initializer_list> shaders); + #endif /** * @brief Constructor diff --git a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp index 9b90f75da..201e5fee4 100644 --- a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp +++ b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp @@ -652,7 +652,8 @@ void AbstractShaderProgramGLTest::uniformNotFound() { #endif ); - CORRADE_VERIFY(Shader::compile({vert, frag})); + CORRADE_VERIFY(vert.compile() && frag.compile()); + program.attachShaders({vert, frag}); CORRADE_VERIFY(program.link()); @@ -702,10 +703,10 @@ MyShader::MyShader() { Version::GLES200 #endif , Shader::Type::Fragment); - vert.addSource(rs.getString("MyShader.vert")); - frag.addSource(rs.getString("MyShader.frag")); - - Shader::compile({vert, frag}); + vert.addSource(rs.getString("MyShader.vert")) + .compile(); + frag.addSource(rs.getString("MyShader.frag")) + .compile(); attachShaders({vert, frag}); @@ -786,10 +787,10 @@ MyDoubleShader::MyDoubleShader() { Shader vert(Version::GL320, Shader::Type::Vertex); Shader frag(Version::GL320, Shader::Type::Fragment); - vert.addSource(rs.getString("MyDoubleShader.vert")); - frag.addSource(rs.getString("MyDoubleShader.frag")); - - Shader::compile({vert, frag}); + vert.addSource(rs.getString("MyDoubleShader.vert")) + .compile(); + frag.addSource(rs.getString("MyDoubleShader.frag")) + .compile(); attachShaders({vert, frag}); @@ -948,8 +949,8 @@ void AbstractShaderProgramGLTest::uniformBlockIndexNotFound() { vert.addSource("void main() { gl_Position = vec4(0.0); }"); frag.addSource("out lowp vec4 color;\n" "void main() { color = vec4(1.0); }"); + CORRADE_VERIFY(vert.compile() && frag.compile()); - CORRADE_VERIFY(Shader::compile({vert, frag})); program.attachShaders({vert, frag}); CORRADE_VERIFY(program.link()); @@ -989,10 +990,11 @@ UniformBlockShader::UniformBlockShader() { Version::GLES300 #endif , Shader::Type::Fragment); - vert.addSource(rs.getString("UniformBlockShader.vert")); - frag.addSource(rs.getString("UniformBlockShader.frag")); + vert.addSource(rs.getString("UniformBlockShader.vert")) + .compile(); + frag.addSource(rs.getString("UniformBlockShader.frag")) + .compile(); - Shader::compile({vert, frag}); attachShaders({vert, frag}); link(); diff --git a/src/Magnum/GL/Test/MeshGLTest.cpp b/src/Magnum/GL/Test/MeshGLTest.cpp index 85137729b..a56257f88 100644 --- a/src/Magnum/GL/Test/MeshGLTest.cpp +++ b/src/Magnum/GL/Test/MeshGLTest.cpp @@ -1023,7 +1023,7 @@ FloatShader::FloatShader(const std::string& type, const std::string& conversion) "#endif\n" "void main() { result = " + conversion + "; }\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); @@ -1065,7 +1065,7 @@ IntegerShader::IntegerShader(const std::string& type) { "out mediump " + type + " result;\n" "void main() { result = valueInterpolated; }\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); @@ -1100,7 +1100,7 @@ DoubleShader::DoubleShader(const std::string& type, const std::string& outputTyp "out " + outputType + " result;\n" "void main() { result = valueInterpolated; }\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); @@ -2147,7 +2147,7 @@ MultipleShader::MultipleShader() { "#endif\n" "void main() { result = valueInterpolated; }\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); @@ -3836,7 +3836,7 @@ MultiDrawShader::MultiDrawShader(bool vertexId, bool drawId) { "#endif\n" "void main() { result.r = valueInterpolated; }\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); @@ -4641,7 +4641,7 @@ MultiDrawInstancedShader::MultiDrawInstancedShader(bool vertexId, bool drawId "#endif\n" "void main() { result.r = valueInterpolated; }\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); diff --git a/src/Magnum/GL/Test/RendererGLTest.cpp b/src/Magnum/GL/Test/RendererGLTest.cpp index fd8084748..5b93a64c5 100644 --- a/src/Magnum/GL/Test/RendererGLTest.cpp +++ b/src/Magnum/GL/Test/RendererGLTest.cpp @@ -203,7 +203,7 @@ void RendererGLTest::pointCoord() { color = vec4(gl_PointCoord.x, gl_PointCoord.y, 0.0, 1.0); })GLSL"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); diff --git a/src/Magnum/GL/Test/SampleQueryGLTest.cpp b/src/Magnum/GL/Test/SampleQueryGLTest.cpp index 1929de2fb..8df0ac97f 100644 --- a/src/Magnum/GL/Test/SampleQueryGLTest.cpp +++ b/src/Magnum/GL/Test/SampleQueryGLTest.cpp @@ -207,7 +207,7 @@ MyShader::MyShader() { " color = vec4(1.0, 1.0, 1.0, 1.0);\n" "}\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); diff --git a/src/Magnum/GL/Test/TransformFeedbackGLTest.cpp b/src/Magnum/GL/Test/TransformFeedbackGLTest.cpp index 25a3b008f..ad6268e87 100644 --- a/src/Magnum/GL/Test/TransformFeedbackGLTest.cpp +++ b/src/Magnum/GL/Test/TransformFeedbackGLTest.cpp @@ -635,7 +635,8 @@ void TransformFeedbackGLTest::draw() { else geom.addSource( " EmitVertex();\n"); geom.addSource("}\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, geom})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && geom.compile()); + attachShaders({vert, geom}); setTransformFeedbackOutputs({"geomOutput"}, TransformFeedbackBufferMode::SeparateAttributes); CORRADE_INTERNAL_ASSERT_OUTPUT(link()); @@ -696,8 +697,8 @@ void TransformFeedbackGLTest::draw() { "void main() {\n" " outputData = interleaved.x + 2*interleaved.y;\n" "}\n"); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); attachShaders({vert, frag}); bindAttributeLocation(Input::Location, "inputData"); CORRADE_INTERNAL_ASSERT_OUTPUT(link()); diff --git a/src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp b/src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp index 3bed21b7d..ebfab2401 100644 --- a/src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp +++ b/src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp @@ -102,7 +102,7 @@ void main() { } )"); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); diff --git a/src/Magnum/TextureTools/DistanceField.cpp b/src/Magnum/TextureTools/DistanceField.cpp index 9865b7dea..c600dc2af 100644 --- a/src/Magnum/TextureTools/DistanceField.cpp +++ b/src/Magnum/TextureTools/DistanceField.cpp @@ -104,7 +104,7 @@ DistanceFieldShader::DistanceFieldShader(const UnsignedInt radius) { frag.addSource(Utility::formatString("#define RADIUS {}\n", radius)) .addSource(rs.getString("DistanceFieldShader.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); From ce313a51c2d0d531c087c0e9334cf7e7afe59c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 18:36:00 +0200 Subject: [PATCH 63/93] doc: mark KHR_parallel_shader_compile as (mostly) done. --- doc/opengl-support.dox | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/opengl-support.dox b/doc/opengl-support.dox index cf85a07b6..7568a28fc 100644 --- a/doc/opengl-support.dox +++ b/doc/opengl-support.dox @@ -297,7 +297,7 @@ Extension | Status @gl_extension{KHR,blend_equation_advanced} | done @gl_extension2{KHR,blend_equation_advanced_coherent,KHR_blend_equation_advanced} | done @gl_extension{KHR,texture_compression_astc_sliced_3d} | done (nothing to do) -@gl_extension{KHR,parallel_shader_compile} | | +@gl_extension{KHR,parallel_shader_compile} | missing thread count setting @subsection opengl-support-extensions-vendor Vendor OpenGL extensions @@ -490,7 +490,7 @@ Extension | Status @gl_extension{KHR,context_flush_control} | | @gl_extension{KHR,no_error} | done @gl_extension{KHR,texture_compression_astc_sliced_3d} | done (nothing to do) -@gl_extension{KHR,parallel_shader_compile} | | +@gl_extension{KHR,parallel_shader_compile} | missing thread count setting @gl_extension2{NV,read_buffer_front,NV_read_buffer} | done @gl_extension2{NV,read_depth,NV_read_depth_stencil} | done @gl_extension2{NV,read_stencil,NV_read_depth_stencil} | done @@ -566,7 +566,7 @@ Extension | Status @webgl_extension{EXT,clip_cull_distance} | done @webgl_extension{EXT,texture_norm16} | done @webgl_extension{EXT,draw_buffers_indexed} | done -@webgl_extension{KHR,parallel_shader_compile} | | +@webgl_extension{KHR,parallel_shader_compile} | done @webgl_extension{OES,texture_float_linear} | done @webgl_extension{OVR,multiview2} | | @webgl_extension{WEBGL,lose_context} | | From 966b94873e0fae6071c377f2bc58439398f88c14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 18:38:17 +0200 Subject: [PATCH 64/93] Shaders: add a benchmarking TODO. --- src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp b/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp index 0e720f4b3..7624ad78e 100644 --- a/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp +++ b/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp @@ -94,6 +94,8 @@ namespace Magnum { namespace Shaders { namespace Test { namespace { struct ShadersGLBenchmark: GL::OpenGLTester { explicit ShadersGLBenchmark(); + /** @todo async vs sync setup? could be useful especially on WebGL & ANGLE */ + void renderSetup(); void renderTeardown(); From a494b47d4b1a7ccb289c67d11bc2ade75a04f0ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 19:05:55 +0200 Subject: [PATCH 65/93] GL: add Shader::wrap() and release(). The whole time I thought this class doesn't need such APIs due to being rather short-lived. But now with async shader compilation it's no longer so short-lived. --- doc/changelog.dox | 1 + doc/opengl-wrapping.dox | 4 +-- src/Magnum/GL/Shader.cpp | 8 +++-- src/Magnum/GL/Shader.h | 46 +++++++++++++++++++++++++++-- src/Magnum/GL/Test/ShaderGLTest.cpp | 16 ++++++++++ 5 files changed, 67 insertions(+), 8 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 676d9749c..13826a031 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -412,6 +412,7 @@ See also: complement @relativeref{GL::Framebuffer::InvalidationAttachment,Depth} and @relativeref{GL::Framebuffer::InvalidationAttachment,Stencil} (see [mosra/magnum#554](https://github.com/mosra/magnum/pull/554)) +- Added @ref GL::Shader::wrap() and @relativeref{GL::Shader,release()} @subsubsection changelog-latest-changes-math Math library diff --git a/doc/opengl-wrapping.dox b/doc/opengl-wrapping.dox index 84db5b8fc..2efda4083 100644 --- a/doc/opengl-wrapping.dox +++ b/doc/opengl-wrapping.dox @@ -61,8 +61,8 @@ Magnum object instance using @cpp wrap() @ce: @snippet MagnumGL.cpp opengl-wrapping-transfer The @cpp wrap() @ce and @cpp release() @ce functions are available for all -OpenGL classes except for @ref GL::Shader, instances of which are rather -short-lived and thus wrapping external instances makes less sense. +OpenGL classes except for @ref GL::AbstractShaderProgram, where the desired +usage via subclassing isn't really suited for wrapping external objects. @attention Note that interaction with third-party OpenGL code as shown above usually diff --git a/src/Magnum/GL/Shader.cpp b/src/Magnum/GL/Shader.cpp index 40e5cf2dc..3167ffe0e 100644 --- a/src/Magnum/GL/Shader.cpp +++ b/src/Magnum/GL/Shader.cpp @@ -645,7 +645,7 @@ Int Shader::maxCombinedUniformComponents(const Type type) { } #endif -Shader::Shader(const Version version, const Type type): _type(type), _id(0) { +Shader::Shader(const Version version, const Type type): _type{type}, _flags{ObjectFlag::DeleteOnDestruction|ObjectFlag::Created} { _id = glCreateShader(GLenum(_type)); switch(version) { @@ -678,9 +678,11 @@ Shader::Shader(const Version version, const Type type): _type(type), _id(0) { CORRADE_ASSERT_UNREACHABLE("GL::Shader::Shader(): unsupported version" << version, ); } +Shader::Shader(const Type type, const GLuint id, ObjectFlags flags) noexcept: _type{type}, _id{id}, _flags{flags} {} + Shader::~Shader() { - /* Moved out, nothing to do */ - if(!_id) return; + /* Moved out or not deleting on destruction, nothing to do */ + if(!_id || !(_flags & ObjectFlag::DeleteOnDestruction)) return; glDeleteShader(_id); } diff --git a/src/Magnum/GL/Shader.h b/src/Magnum/GL/Shader.h index b9385e877..da8943c44 100644 --- a/src/Magnum/GL/Shader.h +++ b/src/Magnum/GL/Shader.h @@ -526,6 +526,23 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { static CORRADE_DEPRECATED("use either submitCompile() and checkCompile() or the zero-argument compile() instead") bool compile(std::initializer_list> shaders); #endif + /** + * @brief Wrap existing OpenGL shader object + * @param type Shader type + * @param id OpenGL shader ID + * @param flags Object creation flags + * @m_since_latest + * + * The @p id is expected to be of an existing OpenGL shader object. + * Unlike a shader created using a constructor, the OpenGL object is by + * default not deleted on destruction, use @p flags for different + * behavior. + * @see @ref release() + */ + static Shader wrap(Type type, GLuint id, ObjectFlags flags = {}) { + return Shader{type, id, flags}; + } + /** * @brief Constructor * @param version Target version @@ -535,7 +552,8 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { * corresponding to @p version parameter at the beginning. If * @ref Version::None is specified, (not) adding the @glsl #version @ce * directive is left to the user. - * @see @fn_gl_keyword{CreateShader} + * @see @ref Shader(NoCreateT), @ref wrap(), + * @fn_gl_keyword{CreateShader} */ explicit Shader(Version version, Type type); @@ -564,7 +582,7 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { * @brief Destructor * * Deletes associated OpenGL shader. - * @see @fn_gl_keyword{DeleteShader} + * @see @ref wrap(), @ref release(), @fn_gl_keyword{DeleteShader} */ ~Shader(); @@ -577,6 +595,18 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { /** @brief OpenGL shader ID */ GLuint id() const { return _id; } + /** + * @brief Release the underlying OpenGL object + * @m_since_latest + * + * Releases ownership of the OpenGL shader object and returns its ID so + * it's not deleted on destruction. The internal state is then + * equivalent to a moved-from state. + * @see @ref wrap() + */ + /* MinGW complains loudly if the declaration doesn't also have inline */ + inline GLuint release(); + #ifndef MAGNUM_TARGET_WEBGL /** * @brief Shader label @@ -711,6 +741,8 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { bool isCompileFinished(); private: + explicit Shader(Type type, GLuint id, ObjectFlags flags) noexcept; + void MAGNUM_GL_LOCAL addSourceImplementationDefault(std::string source); #if defined(CORRADE_TARGET_EMSCRIPTEN) && defined(__EMSCRIPTEN_PTHREADS__) void MAGNUM_GL_LOCAL addSourceImplementationEmscriptenPthread(std::string source); @@ -725,6 +757,7 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { Type _type; GLuint _id; + ObjectFlags _flags; std::vector _sources; }; @@ -732,7 +765,7 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { /** @debugoperatorclassenum{Shader,Shader::Type} */ MAGNUM_GL_EXPORT Debug& operator<<(Debug& debug, Shader::Type value); -inline Shader::Shader(Shader&& other) noexcept: _type(other._type), _id(other._id), _sources(std::move(other._sources)) { +inline Shader::Shader(Shader&& other) noexcept: _type{other._type}, _id{other._id}, _flags{other._flags}, _sources{std::move(other._sources)} { other._id = 0; } @@ -740,10 +773,17 @@ inline Shader& Shader::operator=(Shader&& other) noexcept { using std::swap; swap(_type, other._type); swap(_id, other._id); + swap(_flags, other._flags); swap(_sources, other._sources); return *this; } +inline GLuint Shader::release() { + const GLuint id = _id; + _id = 0; + return id; +} + }} #endif diff --git a/src/Magnum/GL/Test/ShaderGLTest.cpp b/src/Magnum/GL/Test/ShaderGLTest.cpp index bd74c93e9..ba8194c05 100644 --- a/src/Magnum/GL/Test/ShaderGLTest.cpp +++ b/src/Magnum/GL/Test/ShaderGLTest.cpp @@ -49,6 +49,7 @@ struct ShaderGLTest: OpenGLTester { void construct(); void constructNoVersion(); void constructMove(); + void wrap(); #ifndef MAGNUM_TARGET_WEBGL void label(); @@ -69,6 +70,7 @@ ShaderGLTest::ShaderGLTest() { addTests({&ShaderGLTest::construct, &ShaderGLTest::constructNoVersion, &ShaderGLTest::constructMove, + &ShaderGLTest::wrap, #ifndef MAGNUM_TARGET_WEBGL &ShaderGLTest::label, @@ -160,6 +162,20 @@ void ShaderGLTest::constructMove() { CORRADE_VERIFY(std::is_nothrow_move_assignable::value); } +void ShaderGLTest::wrap() { + GLuint id = glCreateShader(GL_FRAGMENT_SHADER); + + /* Releasing won't delete anything */ + { + auto shader = Shader::wrap(Shader::Type::Fragment, id, ObjectFlag::DeleteOnDestruction); + CORRADE_COMPARE(shader.release(), id); + } + + /* ...so we can wrap it again */ + Shader::wrap(Shader::Type::Fragment, id); + glDeleteShader(id); +} + #ifndef MAGNUM_TARGET_WEBGL void ShaderGLTest::label() { /* No-Op version is tested in AbstractObjectGLTest */ From eede67175546407ec1973773116fc52b68c0ddd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 20:05:40 +0200 Subject: [PATCH 66/93] Shaders: avoid including GL/Shader.h in headers. The class is rather heavy (strings, STL vector) and it'll stay heavier than strictly needed even after the planned STL cleanup -- shader users should not bear the overhead of Array, StringView etc. that it needs in order to compile the shader sources. I might eventually come to a different conclusion (maybe separating GL::Shader population and usage like doing in Vulkan with CreateInfos), but right now this commit has the best available solution -- converting the instance to a lightweight class containing just ID and type, and then converting that back to a GL::Shader upon checking compilation/link status. While at it, also removed the not-strictly-needed Optional usage from the header. It wouldn't work with forward-declared GL::Shader anyway. --- src/Magnum/Shaders/CMakeLists.txt | 5 +- src/Magnum/Shaders/DistanceFieldVectorGL.cpp | 3 +- src/Magnum/Shaders/DistanceFieldVectorGL.h | 4 +- src/Magnum/Shaders/FlatGL.cpp | 3 +- src/Magnum/Shaders/FlatGL.h | 4 +- src/Magnum/Shaders/MeshVisualizerGL.cpp | 16 +- src/Magnum/Shaders/MeshVisualizerGL.h | 21 ++- src/Magnum/Shaders/PhongGL.cpp | 3 +- src/Magnum/Shaders/PhongGL.h | 4 +- src/Magnum/Shaders/Test/CMakeLists.txt | 3 + .../Shaders/Test/GLShaderWrapperGLTest.cpp | 171 ++++++++++++++++++ .../Shaders/Test/GLShaderWrapperTest.cpp | 58 ++++++ src/Magnum/Shaders/VectorGL.cpp | 2 +- src/Magnum/Shaders/VectorGL.h | 4 +- src/Magnum/Shaders/VertexColorGL.cpp | 2 +- src/Magnum/Shaders/VertexColorGL.h | 4 +- src/Magnum/Shaders/glShaderWrapper.cpp | 61 +++++++ src/Magnum/Shaders/glShaderWrapper.h | 62 +++++++ 18 files changed, 396 insertions(+), 34 deletions(-) create mode 100644 src/Magnum/Shaders/Test/GLShaderWrapperGLTest.cpp create mode 100644 src/Magnum/Shaders/Test/GLShaderWrapperTest.cpp create mode 100644 src/Magnum/Shaders/glShaderWrapper.cpp create mode 100644 src/Magnum/Shaders/glShaderWrapper.h diff --git a/src/Magnum/Shaders/CMakeLists.txt b/src/Magnum/Shaders/CMakeLists.txt index 4a5f9532c..3c05a0d49 100644 --- a/src/Magnum/Shaders/CMakeLists.txt +++ b/src/Magnum/Shaders/CMakeLists.txt @@ -42,7 +42,9 @@ set(MagnumShaders_GracefulAssert_SRCS MeshVisualizerGL.cpp PhongGL.cpp VectorGL.cpp - VertexColorGL.cpp) + VertexColorGL.cpp + + glShaderWrapper.cpp) set(MagnumShaders_HEADERS DistanceFieldVector.h @@ -60,6 +62,7 @@ set(MagnumShaders_HEADERS VectorGL.h VertexColorGL.h + glShaderWrapper.h visibility.h) if(MAGNUM_BUILD_DEPRECATED) diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp index 648330122..834c14b7b 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp @@ -33,6 +33,7 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" +#include "Magnum/GL/Shader.h" #include "Magnum/GL/Texture.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" @@ -171,7 +172,7 @@ template DistanceFieldVectorGL::DistanceFiel if(!id()) return; #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.h b/src/Magnum/Shaders/DistanceFieldVectorGL.h index cf1944a61..36f087e87 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.h +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.h @@ -33,8 +33,8 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" -#include "Magnum/GL/Shader.h" #include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { @@ -690,7 +690,7 @@ template class DistanceFieldVectorGL::Compil explicit CompileState(DistanceFieldVectorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): DistanceFieldVectorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} - GL::Shader _vert, _frag; + Implementation::GLShaderWrapper _vert, _frag; GL::Version _version; }; diff --git a/src/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index 1d61fed6c..03a99e54b 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -33,6 +33,7 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" +#include "Magnum/GL/Shader.h" #include "Magnum/GL/Texture.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" @@ -246,7 +247,7 @@ template FlatGL::FlatGL(CompileState&& state if(!id()) return; #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; diff --git a/src/Magnum/Shaders/FlatGL.h b/src/Magnum/Shaders/FlatGL.h index 98b33cafc..adedb7c82 100644 --- a/src/Magnum/Shaders/FlatGL.h +++ b/src/Magnum/Shaders/FlatGL.h @@ -33,8 +33,8 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" -#include "Magnum/GL/Shader.h" #include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { @@ -1101,7 +1101,7 @@ template class FlatGL::CompileState: public explicit CompileState(FlatGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): FlatGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} - GL::Shader _vert, _frag; + Implementation::GLShaderWrapper _vert, _frag; GL::Version _version; }; diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index 6c079655b..e3c57d044 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -517,7 +517,7 @@ MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(const Flags flags out.submitLink(); - return CompileState{std::move(out), std::move(vert), std::move(frag), std::move(geom), flags, version}; + return CompileState{std::move(out), std::move(vert), std::move(frag), geom ? &*geom : nullptr, flags, version}; } MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): MeshVisualizerGL2D{compile(flags)} {} @@ -537,10 +537,10 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& state): MeshVisualizerGL2D if(!id()) return; #endif - if(state._geom) - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag, *state._geom})); + if(state._geom.id) + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag), GL::Shader(state._geom)})); else - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; @@ -906,7 +906,7 @@ MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(Flags flags out.submitLink(); - return CompileState{std::move(out), std::move(vert), std::move(frag), std::move(geom), flags, version}; + return CompileState{std::move(out), std::move(vert), std::move(frag), geom ? &*geom : nullptr, flags, version}; } MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D{static_cast(std::move(state))} { @@ -916,10 +916,10 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D if(!id()) return; #endif - if(state._geom) - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag, *state._geom})); + if(state._geom.id) + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag), GL::Shader(state._geom)})); else - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; diff --git a/src/Magnum/Shaders/MeshVisualizerGL.h b/src/Magnum/Shaders/MeshVisualizerGL.h index ab7667c9f..2d2cd5e84 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -33,11 +33,10 @@ #include -#include #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" -#include "Magnum/GL/Shader.h" #include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { @@ -928,12 +927,13 @@ class MeshVisualizerGL2D::CompileState: public MeshVisualizerGL2D { /* Everything deliberately private except for the inheritance */ friend class MeshVisualizerGL2D; - explicit CompileState(NoCreateT): MeshVisualizerGL2D{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + explicit CompileState(NoCreateT): MeshVisualizerGL2D{NoCreate}, _vert{NoCreate}, _frag{NoCreate}, _geom{NoCreate} {} - explicit CompileState(MeshVisualizerGL2D&& shader, GL::Shader&& vert, GL::Shader&& frag, Containers::Optional&& geom, Flags flags, GL::Version version): MeshVisualizerGL2D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{std::move(geom)}, _flags{flags}, _version{version} {} + explicit CompileState(MeshVisualizerGL2D&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Shader* geom, Flags flags, GL::Version version): MeshVisualizerGL2D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{NoCreate}, _flags{flags}, _version{version} { + if(geom) _geom = Implementation::GLShaderWrapper{std::move(*geom)}; + } - GL::Shader _vert, _frag; - Containers::Optional _geom; + Implementation::GLShaderWrapper _vert, _frag, _geom; Flags _flags; GL::Version _version; }; @@ -2465,12 +2465,13 @@ class MeshVisualizerGL3D::CompileState: public MeshVisualizerGL3D { /* Everything deliberately private except for the inheritance */ friend class MeshVisualizerGL3D; - explicit CompileState(NoCreateT): MeshVisualizerGL3D{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + explicit CompileState(NoCreateT): MeshVisualizerGL3D{NoCreate}, _vert{NoCreate}, _frag{NoCreate}, _geom{NoCreate} {} - explicit CompileState(MeshVisualizerGL3D&& shader, GL::Shader&& vert, GL::Shader&& frag, Containers::Optional&& geom, Flags flags, GL::Version version): MeshVisualizerGL3D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{std::move(geom)}, _flags{flags}, _version{version} {} + explicit CompileState(MeshVisualizerGL3D&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Shader* geom, Flags flags, GL::Version version): MeshVisualizerGL3D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{NoCreate}, _flags{flags}, _version{version} { + if(geom) _geom = Implementation::GLShaderWrapper{std::move(*geom)}; + } - GL::Shader _vert, _frag; - Containers::Optional _geom; + Implementation::GLShaderWrapper _vert, _frag, _geom; Flags _flags; GL::Version _version; }; diff --git a/src/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index 6e511a0ba..d0fd391d9 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -39,6 +39,7 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" +#include "Magnum/GL/Shader.h" #include "Magnum/GL/Texture.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" @@ -356,7 +357,7 @@ PhongGL::PhongGL(CompileState&& state): PhongGL{static_cast(std::move if(!id()) return; #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; diff --git a/src/Magnum/Shaders/PhongGL.h b/src/Magnum/Shaders/PhongGL.h index bd7a8cf52..12396bc30 100644 --- a/src/Magnum/Shaders/PhongGL.h +++ b/src/Magnum/Shaders/PhongGL.h @@ -32,8 +32,8 @@ */ #include "Magnum/GL/AbstractShaderProgram.h" -#include "Magnum/GL/Shader.h" #include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { @@ -1847,7 +1847,7 @@ class PhongGL::CompileState: public PhongGL { explicit CompileState(PhongGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): PhongGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} - GL::Shader _vert, _frag; + Implementation::GLShaderWrapper _vert, _frag; GL::Version _version; }; diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index 968adb16c..91ab6651a 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -30,6 +30,7 @@ set(CMAKE_FOLDER "Magnum/Shaders/Test") corrade_add_test(ShadersDistanceFieldVectorTest DistanceFieldVectorTest.cpp LIBRARIES MagnumShaders) corrade_add_test(ShadersFlatTest FlatTest.cpp LIBRARIES MagnumShaders) corrade_add_test(ShadersGenericTest GenericTest.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersGLShaderWrapperTest GLShaderWrapperTest.cpp LIBRARIES MagnumShaders) corrade_add_test(ShadersMeshVisualizerTest MeshVisualizerTest.cpp LIBRARIES MagnumShaders) corrade_add_test(ShadersPhongTest PhongTest.cpp LIBRARIES MagnumShaders) corrade_add_test(ShadersVectorTest VectorTest.cpp LIBRARIES MagnumShaders) @@ -164,6 +165,8 @@ if(MAGNUM_BUILD_GL_TESTS) endif() endif() + corrade_add_test(ShadersGLShaderWrapperGLTest GLShaderWrapperGLTest.cpp LIBRARIES MagnumShaders MagnumOpenGLTester) + set(ShadersMeshVisualizerGLTest_SRCS MeshVisualizerGLTest.cpp) if(CORRADE_TARGET_IOS) list(APPEND ShadersMeshVisualizerGLTest_SRCS FlatTestFiles MeshVisualizerTestFiles) diff --git a/src/Magnum/Shaders/Test/GLShaderWrapperGLTest.cpp b/src/Magnum/Shaders/Test/GLShaderWrapperGLTest.cpp new file mode 100644 index 000000000..f4a83ed58 --- /dev/null +++ b/src/Magnum/Shaders/Test/GLShaderWrapperGLTest.cpp @@ -0,0 +1,171 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Magnum/GL/OpenGLTester.h" +#include "Magnum/GL/Shader.h" +#include "Magnum/GL/Version.h" +#include "Magnum/Shaders/glShaderWrapper.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct GLShaderWrapperGLTest: GL::OpenGLTester { + explicit GLShaderWrapperGLTest(); + + void construct(); + void constructMove(); + + void convert(); + void convertRvalue(); +}; + +GLShaderWrapperGLTest::GLShaderWrapperGLTest() { + addTests({&GLShaderWrapperGLTest::construct, + &GLShaderWrapperGLTest::constructMove, + + &GLShaderWrapperGLTest::convert, + &GLShaderWrapperGLTest::convertRvalue}); +} + +void GLShaderWrapperGLTest::construct() { + { + GL::Shader glShader{ + #ifndef MAGNUM_TARGET_GLES + GL::Version::GL300, + #else + GL::Version::GLES300, + #endif + GL::Shader::Type::Fragment}; + + GLuint id = glShader.id(); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(id > 0); + + Implementation::GLShaderWrapper shader{std::move(glShader)}; + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(shader.id, id); + CORRADE_COMPARE(shader.type, GL_FRAGMENT_SHADER); + CORRADE_VERIFY(!glShader.id()); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + +void GLShaderWrapperGLTest::constructMove() { + GL::Shader glShaderA{ + #ifndef MAGNUM_TARGET_GLES + GL::Version::GL300, + #else + GL::Version::GLES300, + #endif + GL::Shader::Type::Fragment}; + Implementation::GLShaderWrapper a{std::move(glShaderA)}; + + GLuint id = a.id; + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(id > 0); + + Implementation::GLShaderWrapper b{std::move(a)}; + CORRADE_VERIFY(!a.id); + CORRADE_COMPARE(b.id, id); + CORRADE_COMPARE(b.type, GL_FRAGMENT_SHADER); + + GL::Shader glShaderB{ + #ifndef MAGNUM_TARGET_GLES + GL::Version::GL210, + #else + GL::Version::GLES200, + #endif + GL::Shader::Type::Vertex}; + Implementation::GLShaderWrapper c{std::move(glShaderB)}; + + GLuint cId = c.id; + c = std::move(b); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(cId > 0); + CORRADE_COMPARE(b.id, cId); + CORRADE_COMPARE(c.id, id); + CORRADE_COMPARE(c.type, GL_FRAGMENT_SHADER); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} + +void GLShaderWrapperGLTest::convert() { + { + GL::Shader glShader{ + #ifndef MAGNUM_TARGET_GLES + GL::Version::GL300, + #else + GL::Version::GLES300, + #endif + GL::Shader::Type::Fragment}; + Implementation::GLShaderWrapper shader{std::move(glShader)}; + + GLuint id = shader.id; + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(id > 0); + + GL::Shader glShader2 = shader; + MAGNUM_VERIFY_NO_GL_ERROR(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(glShader2.id(), id); + CORRADE_COMPARE(glShader2.type(), GL::Shader::Type::Fragment); + CORRADE_VERIFY(shader.id); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + +void GLShaderWrapperGLTest::convertRvalue() { + { + GL::Shader glShader{ + #ifndef MAGNUM_TARGET_GLES + GL::Version::GL300, + #else + GL::Version::GLES300, + #endif + GL::Shader::Type::Fragment}; + Implementation::GLShaderWrapper shader{std::move(glShader)}; + + GLuint id = shader.id; + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(id > 0); + + GL::Shader glShader2 = std::move(shader); + MAGNUM_VERIFY_NO_GL_ERROR(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(glShader2.id(), id); + CORRADE_COMPARE(glShader2.type(), GL::Shader::Type::Fragment); + CORRADE_VERIFY(!shader.id); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::GLShaderWrapperGLTest) diff --git a/src/Magnum/Shaders/Test/GLShaderWrapperTest.cpp b/src/Magnum/Shaders/Test/GLShaderWrapperTest.cpp new file mode 100644 index 000000000..aaf462921 --- /dev/null +++ b/src/Magnum/Shaders/Test/GLShaderWrapperTest.cpp @@ -0,0 +1,58 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include "Magnum/Magnum.h" +#include "Magnum/Shaders/glShaderWrapper.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct GLShaderWrapperTest: TestSuite::Tester { + explicit GLShaderWrapperTest(); + + void constructNoCreate(); + void constructCopy(); +}; + +GLShaderWrapperTest::GLShaderWrapperTest() { + addTests({&GLShaderWrapperTest::constructNoCreate, + &GLShaderWrapperTest::constructCopy}); +} + +void GLShaderWrapperTest::constructNoCreate() { + Implementation::GLShaderWrapper shader{NoCreate}; + CORRADE_COMPARE(shader.type, 0); + CORRADE_COMPARE(shader.id, 0); +} + +void GLShaderWrapperTest::constructCopy() { + CORRADE_VERIFY(!std::is_copy_constructible{}); + CORRADE_VERIFY(!std::is_copy_assignable{}); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::GLShaderWrapperTest) diff --git a/src/Magnum/Shaders/VectorGL.cpp b/src/Magnum/Shaders/VectorGL.cpp index 89b5353e3..272d0c5e8 100644 --- a/src/Magnum/Shaders/VectorGL.cpp +++ b/src/Magnum/Shaders/VectorGL.cpp @@ -173,7 +173,7 @@ template VectorGL::VectorGL(CompileState&& s if(!id()) return; #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; diff --git a/src/Magnum/Shaders/VectorGL.h b/src/Magnum/Shaders/VectorGL.h index ecc4ccde7..5196ce5da 100644 --- a/src/Magnum/Shaders/VectorGL.h +++ b/src/Magnum/Shaders/VectorGL.h @@ -33,8 +33,8 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" -#include "Magnum/GL/Shader.h" #include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { @@ -640,7 +640,7 @@ template class VectorGL::CompileState: publi explicit CompileState(VectorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): VectorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} - GL::Shader _vert, _frag; + Implementation::GLShaderWrapper _vert, _frag; GL::Version _version; }; diff --git a/src/Magnum/Shaders/VertexColorGL.cpp b/src/Magnum/Shaders/VertexColorGL.cpp index 048e95653..848ba907f 100644 --- a/src/Magnum/Shaders/VertexColorGL.cpp +++ b/src/Magnum/Shaders/VertexColorGL.cpp @@ -151,7 +151,7 @@ template VertexColorGL::VertexColorGL(Compil if(!id()) return; #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; diff --git a/src/Magnum/Shaders/VertexColorGL.h b/src/Magnum/Shaders/VertexColorGL.h index 7048b2743..d09e95bdd 100644 --- a/src/Magnum/Shaders/VertexColorGL.h +++ b/src/Magnum/Shaders/VertexColorGL.h @@ -33,8 +33,8 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" -#include "Magnum/GL/Shader.h" #include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { @@ -489,7 +489,7 @@ template class VertexColorGL::CompileState: explicit CompileState(VertexColorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): VertexColorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} - GL::Shader _vert, _frag; + Implementation::GLShaderWrapper _vert, _frag; GL::Version _version; }; diff --git a/src/Magnum/Shaders/glShaderWrapper.cpp b/src/Magnum/Shaders/glShaderWrapper.cpp new file mode 100644 index 000000000..63f43ca63 --- /dev/null +++ b/src/Magnum/Shaders/glShaderWrapper.cpp @@ -0,0 +1,61 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "glShaderWrapper.h" + +#include "Magnum/GL/Shader.h" + +namespace Magnum { namespace Shaders { namespace Implementation { + +GLShaderWrapper::GLShaderWrapper(GL::Shader&& shader) noexcept: type{GLenum(shader.type())}, id{shader.release()} {} + +GLShaderWrapper::GLShaderWrapper(GLShaderWrapper&& other) noexcept: type{other.type}, id{other.id} { + other.id = 0; +} + +GLShaderWrapper& GLShaderWrapper::operator=(GLShaderWrapper&& other) noexcept { + using std::swap; + swap(other.type, type); + swap(other.id, id); + return *this; +} + +GLShaderWrapper::~GLShaderWrapper() { + /* Convert itself to a temporary GL::Shader, triggering deletion in its + destructor */ + if(id) GL::Shader{std::move(*this)}; +} + +GLShaderWrapper::operator GL::Shader() & noexcept { + return GL::Shader::wrap(GL::Shader::Type(type), id); +} + +GLShaderWrapper::operator GL::Shader() && noexcept { + GL::Shader out = GL::Shader::wrap(GL::Shader::Type(type), id, GL::ObjectFlag::DeleteOnDestruction); + id = 0; + return out; +} + +}}} diff --git a/src/Magnum/Shaders/glShaderWrapper.h b/src/Magnum/Shaders/glShaderWrapper.h new file mode 100644 index 000000000..bc4d77b2f --- /dev/null +++ b/src/Magnum/Shaders/glShaderWrapper.h @@ -0,0 +1,62 @@ +#ifndef Magnum_Shaders_glShaderWrapper_h +#define Magnum_Shaders_glShaderWrapper_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Magnum/Tags.h" +#include "Magnum/GL/GL.h" +#include "Magnum/Shaders/visibility.h" + +namespace Magnum { namespace Shaders { namespace Implementation { + +/* A lightweight alternative to GL::Shader that holds just the type and ID, + used by CompileState instances of all shaders. There it's used just to + retrieve error messages in case of a compilation failures, so it doesn't + make sense to pull in stuff needed to store shader sources such as strings + and vectors/arrays. + + Might get revisited once GL::Shader gets the deSTLification treatment, but + even then this class seems significantly lighter weight. */ +struct MAGNUM_SHADERS_EXPORT GLShaderWrapper { + /*implicit*/ GLShaderWrapper(NoCreateT) noexcept: type{}, id{} {} + /*implicit*/ GLShaderWrapper(GL::Shader&& shader) noexcept; + + GLShaderWrapper(const GLShaderWrapper&) = delete; + GLShaderWrapper(GLShaderWrapper&& other) noexcept; + + GLShaderWrapper& operator=(const GLShaderWrapper&) = delete; + GLShaderWrapper& operator=(GLShaderWrapper&& other) noexcept; + + ~GLShaderWrapper(); + /*implicit*/ operator GL::Shader() & noexcept; + /*implicit*/ operator GL::Shader() && noexcept; + + GLenum type; + GLuint id; +}; + +}}} + +#endif From 13015bd8ff5b04a11a8937f5351e4ea4f383c211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 20:30:11 +0200 Subject: [PATCH 67/93] Shaders: no need to store MeshVisualizer flags in CompileState. Although stored in a weird type, these are accessible via flags() on the base class. --- src/Magnum/Shaders/MeshVisualizerGL.cpp | 8 ++++---- src/Magnum/Shaders/MeshVisualizerGL.h | 6 ++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index e3c57d044..7ec8b1671 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -517,7 +517,7 @@ MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(const Flags flags out.submitLink(); - return CompileState{std::move(out), std::move(vert), std::move(frag), geom ? &*geom : nullptr, flags, version}; + return CompileState{std::move(out), std::move(vert), std::move(frag), geom ? &*geom : nullptr, version}; } MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): MeshVisualizerGL2D{compile(flags)} {} @@ -544,7 +544,7 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& state): MeshVisualizerGL2D const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; - const Flags flags = state._flags; + const Flags flags = state.flags(); #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) @@ -906,7 +906,7 @@ MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(Flags flags out.submitLink(); - return CompileState{std::move(out), std::move(vert), std::move(frag), geom ? &*geom : nullptr, flags, version}; + return CompileState{std::move(out), std::move(vert), std::move(frag), geom ? &*geom : nullptr, version}; } MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D{static_cast(std::move(state))} { @@ -923,7 +923,7 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D const GL::Context& context = GL::Context::current(); const GL::Version version = state._version; - Flags flags = state._flags; + Flags flags = state.flags(); #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) diff --git a/src/Magnum/Shaders/MeshVisualizerGL.h b/src/Magnum/Shaders/MeshVisualizerGL.h index 2d2cd5e84..cbf503757 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -929,12 +929,11 @@ class MeshVisualizerGL2D::CompileState: public MeshVisualizerGL2D { explicit CompileState(NoCreateT): MeshVisualizerGL2D{NoCreate}, _vert{NoCreate}, _frag{NoCreate}, _geom{NoCreate} {} - explicit CompileState(MeshVisualizerGL2D&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Shader* geom, Flags flags, GL::Version version): MeshVisualizerGL2D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{NoCreate}, _flags{flags}, _version{version} { + explicit CompileState(MeshVisualizerGL2D&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Shader* geom, GL::Version version): MeshVisualizerGL2D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{NoCreate}, _version{version} { if(geom) _geom = Implementation::GLShaderWrapper{std::move(*geom)}; } Implementation::GLShaderWrapper _vert, _frag, _geom; - Flags _flags; GL::Version _version; }; @@ -2467,12 +2466,11 @@ class MeshVisualizerGL3D::CompileState: public MeshVisualizerGL3D { explicit CompileState(NoCreateT): MeshVisualizerGL3D{NoCreate}, _vert{NoCreate}, _frag{NoCreate}, _geom{NoCreate} {} - explicit CompileState(MeshVisualizerGL3D&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Shader* geom, Flags flags, GL::Version version): MeshVisualizerGL3D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{NoCreate}, _flags{flags}, _version{version} { + explicit CompileState(MeshVisualizerGL3D&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Shader* geom, GL::Version version): MeshVisualizerGL3D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{NoCreate}, _version{version} { if(geom) _geom = Implementation::GLShaderWrapper{std::move(*geom)}; } Implementation::GLShaderWrapper _vert, _frag, _geom; - Flags _flags; GL::Version _version; }; From 4ff655740e6fab52783be9ef984683e7d1031395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 6 Sep 2022 20:45:01 +0200 Subject: [PATCH 68/93] GL: take an Iterable in AbstractShaderProgram::attachShaders(). Consistently with checkLink(), this avoids having to explicitly include both Iterable and Reference in shader code. Alsod allowing people to have direct arrays of shaders, runtime-sized lists of shaders etc. A compat include is provided on a deprecated build to avoid breaking existing code. --- src/Magnum/GL/AbstractShaderProgram.cpp | 2 +- src/Magnum/GL/AbstractShaderProgram.h | 4 +++- src/Magnum/GL/Test/MeshGLTest.cpp | 1 + src/Magnum/GL/Test/RendererGLTest.cpp | 2 +- src/Magnum/GL/Test/SampleQueryGLTest.cpp | 2 +- src/Magnum/GL/Test/TransformFeedbackGLTest.cpp | 2 +- src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp | 2 +- src/Magnum/Shaders/DistanceFieldVectorGL.cpp | 1 - src/Magnum/Shaders/FlatGL.cpp | 1 - src/Magnum/Shaders/MeshVisualizerGL.cpp | 1 - src/Magnum/Shaders/PhongGL.cpp | 1 - src/Magnum/Shaders/VectorGL.cpp | 1 - src/Magnum/Shaders/VertexColorGL.cpp | 1 - src/Magnum/TextureTools/DistanceField.cpp | 2 +- 14 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/Magnum/GL/AbstractShaderProgram.cpp b/src/Magnum/GL/AbstractShaderProgram.cpp index dcc1b4915..d14161ebd 100644 --- a/src/Magnum/GL/AbstractShaderProgram.cpp +++ b/src/Magnum/GL/AbstractShaderProgram.cpp @@ -536,7 +536,7 @@ void AbstractShaderProgram::attachShader(Shader& shader) { glAttachShader(_id, shader.id()); } -void AbstractShaderProgram::attachShaders(std::initializer_list> shaders) { +void AbstractShaderProgram::attachShaders(Containers::Iterable shaders) { for(Shader& s: shaders) attachShader(s); } diff --git a/src/Magnum/GL/AbstractShaderProgram.h b/src/Magnum/GL/AbstractShaderProgram.h index c01eb6e6f..b0a3bd9d8 100644 --- a/src/Magnum/GL/AbstractShaderProgram.h +++ b/src/Magnum/GL/AbstractShaderProgram.h @@ -44,6 +44,8 @@ #ifdef MAGNUM_BUILD_DEPRECATED #include +/* For attachShaders(), which used to take a std::initializer_list */ +#include /* For label() / setLabel(), which used to be a std::string */ #include #endif @@ -1414,7 +1416,7 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { * than one shader at once. Other than that there is no other * (performance) difference when using this function. */ - void attachShaders(std::initializer_list> shaders); + void attachShaders(Containers::Iterable shaders); /** * @brief Bind an attribute to given location diff --git a/src/Magnum/GL/Test/MeshGLTest.cpp b/src/Magnum/GL/Test/MeshGLTest.cpp index a56257f88..01c17f3ea 100644 --- a/src/Magnum/GL/Test/MeshGLTest.cpp +++ b/src/Magnum/GL/Test/MeshGLTest.cpp @@ -25,6 +25,7 @@ */ #include +#include #include #include #include diff --git a/src/Magnum/GL/Test/RendererGLTest.cpp b/src/Magnum/GL/Test/RendererGLTest.cpp index 5b93a64c5..e9818be2c 100644 --- a/src/Magnum/GL/Test/RendererGLTest.cpp +++ b/src/Magnum/GL/Test/RendererGLTest.cpp @@ -23,9 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include -#include #include #include diff --git a/src/Magnum/GL/Test/SampleQueryGLTest.cpp b/src/Magnum/GL/Test/SampleQueryGLTest.cpp index 8df0ac97f..1481db0c4 100644 --- a/src/Magnum/GL/Test/SampleQueryGLTest.cpp +++ b/src/Magnum/GL/Test/SampleQueryGLTest.cpp @@ -23,7 +23,7 @@ DEALINGS IN THE SOFTWARE. */ -#include +#include #include #include diff --git a/src/Magnum/GL/Test/TransformFeedbackGLTest.cpp b/src/Magnum/GL/Test/TransformFeedbackGLTest.cpp index ad6268e87..08eba636a 100644 --- a/src/Magnum/GL/Test/TransformFeedbackGLTest.cpp +++ b/src/Magnum/GL/Test/TransformFeedbackGLTest.cpp @@ -24,7 +24,7 @@ */ #include -#include +#include #include "Magnum/Image.h" #include "Magnum/GL/AbstractShaderProgram.h" diff --git a/src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp b/src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp index ebfab2401..9fa791b77 100644 --- a/src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp +++ b/src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp @@ -24,7 +24,7 @@ */ #include -#include +#include #include #include diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp index 834c14b7b..9b852be30 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include "Magnum/GL/Context.h" diff --git a/src/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index 03a99e54b..2bc1cb68e 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include "Magnum/GL/Context.h" diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index 7ec8b1671..7a093c2ee 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include diff --git a/src/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index d0fd391d9..6d1aaa13b 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -31,7 +31,6 @@ #endif #include #include -#include #include #include #include diff --git a/src/Magnum/Shaders/VectorGL.cpp b/src/Magnum/Shaders/VectorGL.cpp index 272d0c5e8..f06d5cba4 100644 --- a/src/Magnum/Shaders/VectorGL.cpp +++ b/src/Magnum/Shaders/VectorGL.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include "Magnum/GL/Context.h" diff --git a/src/Magnum/Shaders/VertexColorGL.cpp b/src/Magnum/Shaders/VertexColorGL.cpp index 848ba907f..77907b903 100644 --- a/src/Magnum/Shaders/VertexColorGL.cpp +++ b/src/Magnum/Shaders/VertexColorGL.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include "Magnum/GL/Context.h" diff --git a/src/Magnum/TextureTools/DistanceField.cpp b/src/Magnum/TextureTools/DistanceField.cpp index c600dc2af..841ead75a 100644 --- a/src/Magnum/TextureTools/DistanceField.cpp +++ b/src/Magnum/TextureTools/DistanceField.cpp @@ -25,7 +25,7 @@ #include "DistanceField.h" -#include +#include #include #include From fe6a387515e1bb6a3b685c9c7844349e2812f1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 7 Sep 2022 09:06:03 +0200 Subject: [PATCH 69/93] TextureTools,Trade: fix unused var warning on a non-deprecated build. ... by actually using that variable outside of asserts. --- src/Magnum/TextureTools/Atlas.cpp | 2 +- src/Magnum/Trade/SceneData.cpp | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Magnum/TextureTools/Atlas.cpp b/src/Magnum/TextureTools/Atlas.cpp index a5b573894..d58768c5b 100644 --- a/src/Magnum/TextureTools/Atlas.cpp +++ b/src/Magnum/TextureTools/Atlas.cpp @@ -83,7 +83,7 @@ Containers::Pair> atlasArrayPowerOfTwo(const Ve CORRADE_ASSERT(size.product() && size.x() == size.y() && (size & (size - Vector2i{1})).isZero(), "TextureTools::atlasArrayPowerOfTwo(): expected size" << i << "to be a non-zero power-of-two square, got" << Debug::packed << size, {}); - sortedSizes[i].xy() = sizes[i]; + sortedSizes[i].xy() = size; sortedSizes[i].z() = i; } diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 61d9b8387..3ebbac9ca 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -584,14 +584,14 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp checked against a map and only custom fields are checked in an O(n^2) way with the assumption there isn't many of them (and that they'll gradually become builtin). */ - if(!isSceneFieldCustom(_fields[i]._name)) { - CORRADE_INTERNAL_ASSERT(UnsignedInt(_fields[i]._name) < fieldsPresent.Size); - CORRADE_ASSERT(!fieldsPresent[UnsignedInt(_fields[i]._name)], - "Trade::SceneData: duplicate field" << _fields[i]._name, ); - fieldsPresent.set(UnsignedInt(_fields[i]._name), true); + if(!isSceneFieldCustom(field._name)) { + CORRADE_INTERNAL_ASSERT(UnsignedInt(field._name) < fieldsPresent.Size); + CORRADE_ASSERT(!fieldsPresent[UnsignedInt(field._name)], + "Trade::SceneData: duplicate field" << field._name, ); + fieldsPresent.set(UnsignedInt(field._name), true); } else for(std::size_t j = 0; j != i; ++j) { - CORRADE_ASSERT(_fields[j]._name != _fields[i]._name, - "Trade::SceneData: duplicate field" << _fields[i]._name, ); + CORRADE_ASSERT(_fields[j]._name != field._name, + "Trade::SceneData: duplicate field" << field._name, ); } /* Check that both the mapping and field view fits into the provided @@ -634,21 +634,21 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp /* Remember TRS and mesh/material fields to figure out whether the scene is 2D or 3D and check their object mapping consistency outside of the loop below */ - if(_fields[i]._name == SceneField::Transformation) { + if(field._name == SceneField::Transformation) { transformationField = i; - } else if(_fields[i]._name == SceneField::Translation) { + } else if(field._name == SceneField::Translation) { translationField = i; - } else if(_fields[i]._name == SceneField::Rotation) { + } else if(field._name == SceneField::Rotation) { rotationField = i; - } else if(_fields[i]._name == SceneField::Scaling) { + } else if(field._name == SceneField::Scaling) { scalingField = i; } #ifndef CORRADE_NO_ASSERT - else if(_fields[i]._name == SceneField::Mesh) { + else if(field._name == SceneField::Mesh) { meshField = i; - } else if(_fields[i]._name == SceneField::MeshMaterial) { + } else if(field._name == SceneField::MeshMaterial) { meshMaterialField = i; - } else if(_fields[i]._name == SceneField::Skin) { + } else if(field._name == SceneField::Skin) { skinField = i; } #endif From da5d2fa958f22844adaec90e894e85270ed0a631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 7 Sep 2022 09:34:55 +0200 Subject: [PATCH 70/93] MeshTools: fix more unused var warnings on a non-deprecated build. --- src/Magnum/MeshTools/Combine.cpp | 7 +++---- src/Magnum/MeshTools/Interleave.cpp | 9 ++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Magnum/MeshTools/Combine.cpp b/src/Magnum/MeshTools/Combine.cpp index fcf08792f..0accd4459 100644 --- a/src/Magnum/MeshTools/Combine.cpp +++ b/src/Magnum/MeshTools/Combine.cpp @@ -189,12 +189,11 @@ Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, const Trade:: CORRADE_ASSERT(!isMeshIndexTypeImplementationSpecific(meshIndexType), "MeshTools::combineFaceAttributes(): vertex mesh has an implementation-specific index type" << reinterpret_cast(meshIndexTypeUnwrap(meshIndexType)), (Trade::MeshData{MeshPrimitive{}, 0})); - const UnsignedInt meshIndexSize = meshIndexTypeSize(mesh.indexType()); + const UnsignedInt meshIndexSize = meshIndexTypeSize(meshIndexType); UnsignedInt faceIndexSize; if(faceAttributes.isIndexed()) { - const MeshIndexType faceIndexType = faceAttributes.indexType(); - CORRADE_ASSERT(!isMeshIndexTypeImplementationSpecific(faceIndexType), - "MeshTools::combineFaceAttributes(): face mesh has an implementation-specific index type" << reinterpret_cast(meshIndexTypeUnwrap(faceIndexType)), + CORRADE_ASSERT(!isMeshIndexTypeImplementationSpecific(faceAttributes.indexType()), + "MeshTools::combineFaceAttributes(): face mesh has an implementation-specific index type" << reinterpret_cast(meshIndexTypeUnwrap(faceAttributes.indexType())), (Trade::MeshData{MeshPrimitive{}, 0})); faceIndexSize = meshIndexTypeSize(faceAttributes.indexType()); } else faceIndexSize = 4; diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp index 497623b9f..5cf734497 100644 --- a/src/Magnum/MeshTools/Interleave.cpp +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -139,9 +139,8 @@ Containers::Array interleavedLayout(Trade::MeshData&& stride = 0; minOffset = 0; for(UnsignedInt i = 0, max = data.attributeCount(); i != max; ++i) { - const VertexFormat format = data.attributeFormat(i); - CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), - "MeshTools::interleavedLayout(): attribute" << i << "has an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(data.attributeFormat(i)), + "MeshTools::interleavedLayout(): attribute" << i << "has an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(data.attributeFormat(i))), {}); stride += attributeSize(data, i); } } @@ -149,12 +148,12 @@ Containers::Array interleavedLayout(Trade::MeshData&& /* Add the extra attributes and explicit padding */ std::size_t extraAttributeCount = 0; for(std::size_t i = 0; i != extra.size(); ++i) { - if(extra[i].format() == VertexFormat{}) { + const VertexFormat format = extra[i].format(); + if(format == VertexFormat{}) { CORRADE_ASSERT(extra[i].stride() > 0 || stride >= std::size_t(-extra[i].stride()), "MeshTools::interleavedLayout(): negative padding" << extra[i].stride() << "in extra attribute" << i << "too large for stride" << stride, {}); stride += extra[i].stride(); } else { - const VertexFormat format = extra[i].format(); CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), "MeshTools::interleavedLayout(): extra attribute" << i << "has an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); stride += attributeSize(extra[i]); From d0d0f978f9dcfad9e009392600b4ac94d25efb16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 7 Sep 2022 14:06:37 +0200 Subject: [PATCH 71/93] Test: start testing code in various command line utilities. Initially, if possible, by extracting the code into separate files and testing those, instead of having to execute a binary and deal with output redirection and other crazy things. The option propagation is already extracted, so do that first. --- src/Magnum/Test/CMakeLists.txt | 3 + src/Magnum/Test/ConverterUtilitiesTest.cpp | 174 +++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 src/Magnum/Test/ConverterUtilitiesTest.cpp diff --git a/src/Magnum/Test/CMakeLists.txt b/src/Magnum/Test/CMakeLists.txt index cab58ee1a..e22f69069 100644 --- a/src/Magnum/Test/CMakeLists.txt +++ b/src/Magnum/Test/CMakeLists.txt @@ -27,6 +27,9 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "Magnum/Test") +find_package(Corrade REQUIRED PluginManager) + +corrade_add_test(ConverterUtilitiesTest ConverterUtilitiesTest.cpp LIBRARIES Magnum Corrade::PluginManager) corrade_add_test(FileCallbackTest FileCallbackTest.cpp LIBRARIES Magnum) corrade_add_test(ImageTest ImageTest.cpp LIBRARIES MagnumTestLib) corrade_add_test(ImageFlagsTest ImageFlagsTest.cpp LIBRARIES Magnum) diff --git a/src/Magnum/Test/ConverterUtilitiesTest.cpp b/src/Magnum/Test/ConverterUtilitiesTest.cpp new file mode 100644 index 000000000..e825673f9 --- /dev/null +++ b/src/Magnum/Test/ConverterUtilitiesTest.cpp @@ -0,0 +1,174 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +#include "Magnum/Implementation/converterUtilities.h" + +namespace Magnum { namespace Test { namespace { + +struct ConverterUtilitiesTest: TestSuite::Tester { + explicit ConverterUtilitiesTest(); + + void setOptions(); +}; + +const struct { + const char* name; + const char* options; + const char* anyPluginName; + const char* expectedConfig; + const char* expectedWarning; +} SetOptionsData[]{ + {"", "option=value", "AnyPlugin", R"([configuration] +option=value +another= +[configuration/group] +option= +[configuration/group/nested] +option= +)", nullptr}, + {"two options", "option=value,another=yes", "AnyPlugin", R"([configuration] +option=value +another=yes +[configuration/group] +option= +[configuration/group/nested] +option= +)", nullptr}, + {"implicit true", "option=value,another", "AnyPlugin", R"([configuration] +option=value +another=true +[configuration/group] +option= +[configuration/group/nested] +option= +)", nullptr}, + {"group", "group/option=value", "AnyPlugin", R"([configuration] +option= +another= +[configuration/group] +option=value +[configuration/group/nested] +option= +)", nullptr}, + {"nested group + root option after", "group/nested/option=value,another=yes", "AnyPlugin", R"([configuration] +option= +another=yes +[configuration/group] +option= +[configuration/group/nested] +option=value +)", nullptr}, + {"unrecognized option", "notFound=value", "AnyPlugin", R"([configuration] +option= +another= +notFound=value +[configuration/group] +option= +[configuration/group/nested] +option= +)", + /* The trailing space is there because the plugin name is empty */ + "Option notFound not recognized by \n"}, + {"unrecognized option in Any plugin", "notFound=value", "", R"([configuration] +option= +another= +notFound=value +[configuration/group] +option= +[configuration/group/nested] +option= +)", nullptr}, + {"unrecognized group", "notFound/option=value", "AnyPlugin", R"([configuration] +option= +another= +[configuration/group] +option= +[configuration/group/nested] +option= +[configuration/notFound] +option=value +)", + /* The trailing space is there because the plugin name is empty */ + "Option notFound/option not recognized by \n"}, + {"unrecognized nested group", "group/notFound/nested/option=value", "AnyPlugin", R"([configuration] +option= +another= +[configuration/group] +option= +[configuration/group/nested] +option= +[configuration/group/notFound/nested] +option=value +)", + /* The trailing space is there because the plugin name is empty */ + "Option group/notFound/nested/option not recognized by \n"} +}; + +ConverterUtilitiesTest::ConverterUtilitiesTest() { + addInstancedTests({&ConverterUtilitiesTest::setOptions}, + Containers::arraySize(SetOptionsData)); +} + +void ConverterUtilitiesTest::setOptions() { + auto&& data = SetOptionsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Plugin: PluginManager::AbstractPlugin { + explicit Plugin() { + /* Populate default config */ + configuration().setValue("option", ""); + configuration().setValue("another", ""); + Utility::ConfigurationGroup& group = *configuration().addGroup("group"); + group.setValue("option", ""); + group.addGroup("nested")->setValue("option", ""); + } + } plugin; + + { + std::ostringstream out; + Warning redirectWarning{&out}; + Implementation::setOptions(plugin, data.anyPluginName, data.options); + if(data.expectedWarning) + CORRADE_COMPARE(out.str(), data.expectedWarning); + else + CORRADE_COMPARE(out.str(), ""); + } + + Utility::Configuration conf; + /** @todo ugh, is there no better way to serialize a ConfigurationGroup? */ + conf.addGroup("configuration", new Utility::ConfigurationGroup{plugin.configuration()}); + CORRADE_COMPARE(conf.group("configuration")->configuration(), &conf); + std::ostringstream out; + conf.save(out); + CORRADE_COMPARE(out.str(), data.expectedConfig); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Test::ConverterUtilitiesTest) From 1f1041b4b09f3b28b371e247bce0183a2527fc6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 7 Sep 2022 18:10:57 +0200 Subject: [PATCH 72/93] Math: update Half doc snippets to reflect what's actually printed. WTF was I doing there. --- doc/snippets/MagnumMath.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/snippets/MagnumMath.cpp b/doc/snippets/MagnumMath.cpp index d99c97154..7de1317bc 100644 --- a/doc/snippets/MagnumMath.cpp +++ b/doc/snippets/MagnumMath.cpp @@ -994,8 +994,8 @@ static_cast(bClamped); using namespace Math::Literals; Half a = 3.14159_h; -Debug{} << a; // Prints 3.14159 -Debug{} << Float(a); // Prints 3.14159 +Debug{} << a; // Prints 3.141 +Debug{} << Float(a); // Prints 3.14062 Debug{} << UnsignedShort(a); // Prints 25675 /* [Half-usage] */ } @@ -1004,7 +1004,7 @@ Debug{} << UnsignedShort(a); // Prints 25675 /* [Half-usage-vector] */ Vector3h a{3.14159_h, -1.4142_h, 1.618_h}; Vector3 b{a}; // converts to 32-bit floats -Debug{} << a; // prints {3.14159, -1.4142, 1.618} +Debug{} << a; // prints {3.141, -1.414, 1.618} Debug{} << Vector3us{a}; // prints {16968, 48552, 15993} /* [Half-usage-vector] */ } From df8da4e27b7130f77cad61374bc7635da5d8ce78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 7 Sep 2022 18:57:34 +0200 Subject: [PATCH 73/93] Trade: missing include in an internal header. --- src/Magnum/Trade/Implementation/converterUtilities.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Magnum/Trade/Implementation/converterUtilities.h b/src/Magnum/Trade/Implementation/converterUtilities.h index f856f83b2..6c1d4926f 100644 --- a/src/Magnum/Trade/Implementation/converterUtilities.h +++ b/src/Magnum/Trade/Implementation/converterUtilities.h @@ -27,6 +27,7 @@ #include #include +#include #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" From 6af8c0b4d0cfa0555a09562f5a3f7d1e654504f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 25 Aug 2022 12:35:28 +0200 Subject: [PATCH 74/93] sceneconverter: reorder all plugin managers to be in a single place. --- src/Magnum/SceneTools/sceneconverter.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Magnum/SceneTools/sceneconverter.cpp b/src/Magnum/SceneTools/sceneconverter.cpp index f8ae8dcef..9191b9e3d 100644 --- a/src/Magnum/SceneTools/sceneconverter.cpp +++ b/src/Magnum/SceneTools/sceneconverter.cpp @@ -334,10 +334,16 @@ is specified as well, the IDs reference attributes of the first mesh.)") Warning{} << "Ignoring output file for --info:" << args.value("output"); } + /* Importer manager */ PluginManager::Manager importerManager{ args.value("plugin-dir").empty() ? Containers::String{} : Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths().back())}; + /* Scene converter manager */ + PluginManager::Manager converterManager{ + args.value("plugin-dir").empty() ? Containers::String{} : + Utility::Path::join(args.value("plugin-dir"), Trade::AbstractSceneConverter::pluginSearchPaths().back())}; + Containers::Pointer importer = importerManager.loadAndInstantiate(args.value("importer")); if(!importer) { Debug{} << "Available importer plugins:" << ", "_s.join(importerManager.aliasList()); @@ -1409,10 +1415,6 @@ is specified as well, the IDs reference attributes of the first mesh.)") Debug{} << "Fuzzy duplicate removal:" << beforeVertexCount << "->" << mesh->vertexCount() << "vertices"; } - PluginManager::Manager converterManager{ - args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractSceneConverter::pluginSearchPaths().back())}; - /* Assume there's always one passed --converter option less, and the last is implicitly AnySceneConverter. All converters except the last one are expected to support ConvertMesh and the mesh is "piped" from one to the From 597fa89e4ea98f8f51c99b65f3deac15b3a18b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 25 Aug 2022 12:55:48 +0200 Subject: [PATCH 75/93] sceneconverter: move colored output logic at the top as well. Will eventually be needed for more than just --info. --- src/Magnum/SceneTools/sceneconverter.cpp | 50 ++++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Magnum/SceneTools/sceneconverter.cpp b/src/Magnum/SceneTools/sceneconverter.cpp index 9191b9e3d..a663d1e90 100644 --- a/src/Magnum/SceneTools/sceneconverter.cpp +++ b/src/Magnum/SceneTools/sceneconverter.cpp @@ -325,6 +325,31 @@ attributes that are present in the first mesh are taken, if --only-attributes is specified as well, the IDs reference attributes of the first mesh.)") .parse(argc, argv); + /* Colored output. Enable only if a TTY. */ + Debug::Flags useColor; + bool useColor24; + if(args.value("color") == "on") { + useColor = Debug::Flags{}; + useColor24 = true; + } else if(args.value("color") == "4bit") { + useColor = Debug::Flags{}; + useColor24 = false; + } else if(args.value("color") == "off") { + useColor = Debug::Flag::DisableColors; + useColor24 = false; + } else if(Debug::isTty()) { + useColor = Debug::Flags{}; + /* https://unix.stackexchange.com/a/450366, not perfect but good enough + I'd say */ + /** @todo make this more robust and put directly on Debug, + including a "Disable 24 colors" flag */ + const Containers::StringView colorterm = std::getenv("COLORTERM"); + useColor24 = colorterm == "truecolor"_s || colorterm == "24bit"_s; + } else { + useColor = Debug::Flag::DisableColors; + useColor24 = false; + } + /* Generic checks */ if(!args.value("output").isEmpty()) { /* Not an error in this case, it should be possible to just append @@ -801,31 +826,6 @@ is specified as well, the IDs reference attributes of the first mesh.)") imageInfos = Trade::Implementation::imageInfo(*importer, error, importTime); } - /* Colored output. Enable only if a TTY. */ - Debug::Flags useColor; - bool useColor24; - if(args.value("color") == "on") { - useColor = Debug::Flags{}; - useColor24 = true; - } else if(args.value("color") == "4bit") { - useColor = Debug::Flags{}; - useColor24 = false; - } else if(args.value("color") == "off") { - useColor = Debug::Flag::DisableColors; - useColor24 = false; - } else if(Debug::isTty()) { - useColor = Debug::Flags{}; - /* https://unix.stackexchange.com/a/450366, not perfect but good - enough I'd say */ - /** @todo make this more robust and put directly on Debug, - including a "Disable 24 colors" flag */ - const Containers::StringView colorterm = std::getenv("COLORTERM"); - useColor24 = colorterm == "truecolor"_s || colorterm == "24bit"_s; - } else { - useColor = Debug::Flag::DisableColors; - useColor24 = false; - } - std::size_t totalSceneDataSize = 0; for(const SceneInfo& info: sceneInfos) { Debug d{useColor}; From cc29cbcc7393cdc08945449a63f7421f02930171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 9 Sep 2022 00:32:56 +0200 Subject: [PATCH 76/93] {image,scene}converter: extract and test --info printing code. It was quite a pile, and all of it was written just once, relying only on hopefully-available model files that would hopefully touch most code paths. Which means, extremely annoying to make changes in. I extracted the code to a header that can be tested with a mocked-up importer and without having to execute the utility itself, deduplicated the image info printing code, fixed various inconsistencies (such as data/field flags sometimes denoted with superfluous "flags:" and sometimes not) and TODOs (such as 2D/3D skins, where there was no format whatsoever that would have 2D skin support, so the code couldn't get written). Now it's finally possible to easily add the remaining missing features, such as printing camera info. --- src/Magnum/SceneTools/CMakeLists.txt | 3 +- .../Implementation/sceneConverterUtilities.h | 966 ++++++++++++++ src/Magnum/SceneTools/Test/CMakeLists.txt | 26 + .../SceneTools/Test/SceneConverterTest.cpp | 1131 +++++++++++++++++ .../info-animations.txt | 12 + .../SceneConverterTestFiles/info-images.txt | 3 + .../SceneConverterTestFiles/info-lights.txt | 8 + .../info-materials.txt | 28 + .../info-meshes-bounds.txt | 21 + .../SceneConverterTestFiles/info-meshes.txt | 16 + .../SceneConverterTestFiles/info-objects.txt | 5 + .../info-references.txt | 98 ++ .../info-scenes-objects.txt | 25 + .../SceneConverterTestFiles/info-scenes.txt | 11 + .../SceneConverterTestFiles/info-skins.txt | 10 + .../SceneConverterTestFiles/info-textures.txt | 8 + src/Magnum/SceneTools/Test/configure.h.cmake | 26 + src/Magnum/SceneTools/sceneconverter.cpp | 941 +------------- .../Trade/Implementation/converterUtilities.h | 81 +- src/Magnum/Trade/Test/CMakeLists.txt | 7 + src/Magnum/Trade/Test/ImageConverterTest.cpp | 192 +++ .../Test/ImageConverterTestFiles/info.txt | 23 + src/Magnum/Trade/imageconverter.cpp | 42 +- 23 files changed, 2699 insertions(+), 984 deletions(-) create mode 100644 src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTest.cpp create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt create mode 100644 src/Magnum/SceneTools/Test/configure.h.cmake create mode 100644 src/Magnum/Trade/Test/ImageConverterTest.cpp create mode 100644 src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt diff --git a/src/Magnum/SceneTools/CMakeLists.txt b/src/Magnum/SceneTools/CMakeLists.txt index 9cce7c8bd..ab7e8fcc2 100644 --- a/src/Magnum/SceneTools/CMakeLists.txt +++ b/src/Magnum/SceneTools/CMakeLists.txt @@ -44,7 +44,8 @@ set(MagnumSceneTools_HEADERS set(MagnumSceneTools_PRIVATE_HEADERS Implementation/combine.h - Implementation/convertToSingleFunctionObjects.h) + Implementation/convertToSingleFunctionObjects.h + Implementation/sceneConverterUtilities.h) ## Objects shared between main and test library #add_library(MagnumSceneToolsObjects OBJECT diff --git a/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h b/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h new file mode 100644 index 000000000..6c41538d2 --- /dev/null +++ b/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h @@ -0,0 +1,966 @@ +#ifndef Magnum_SceneTools_Implementation_sceneConverterUtilities_h +#define Magnum_SceneTools_Implementation_sceneConverterUtilities_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include /* std::isupper() */ +#include /* sceneFieldNames */ +#include +#include +#include + +#include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/Trade/AnimationData.h" +#include "Magnum/Trade/LightData.h" +#include "Magnum/Trade/MaterialData.h" +#include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/SceneData.h" +#include "Magnum/Trade/SkinData.h" +#include "Magnum/Trade/TextureData.h" + +#include "Magnum/Trade/Implementation/converterUtilities.h" + +namespace Magnum { namespace SceneTools { namespace Implementation { + +using namespace Containers::Literals; + +/* Used only in executables where we don't want it to be exported -- in + particular magnum-sceneconverter and its tests */ +namespace { + +/** @todo const Array& doesn't work, minmax() would fail to match */ +template Containers::String calculateBounds(Containers::Array&& attribute) { + /** @todo clean up when Debug::toString() exists */ + std::ostringstream out; + Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << Debug::packed << Math::minmax(attribute); + return out.str(); +} + +/* Named attribute index from a global index */ +/** @todo some helper for this directly on the MeshData class? */ +UnsignedInt namedAttributeId(const Trade::MeshData& mesh, UnsignedInt id) { + const Trade::MeshAttribute name = mesh.attributeName(id); + for(UnsignedInt i = 0; i != mesh.attributeCount(name); ++i) + if(mesh.attributeId(name, i) == id) return i; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +bool printInfo(const Debug::Flags useColor, const bool useColor24, const Utility::Arguments& args, Trade::AbstractImporter& importer, std::chrono::high_resolution_clock::duration& importTime) { + struct AnimationInfo { + UnsignedInt animation; + Trade::AnimationData data{{}, {}}; + Containers::String name; + }; + + struct SkinInfo { + bool twoDimensions; + UnsignedInt skin; + UnsignedInt jointCount; + Containers::String name; + }; + + struct LightInfo { + UnsignedInt light; + Trade::LightData data{{}, {}, {}}; + Containers::String name; + }; + + struct MaterialInfo { + UnsignedInt material; + Trade::MaterialData data{{}, {}}; + Containers::String name; + }; + + struct TextureInfo { + UnsignedInt texture; + Trade::TextureData data{{}, {}, {}, {}, {}, {}}; + Containers::String name; + }; + + struct MeshAttributeInfo { + std::size_t offset; + Int stride; + UnsignedInt arraySize; + Trade::MeshAttribute name; + Containers::String customName; + VertexFormat format; + Containers::String bounds; + }; + + struct MeshInfo { + UnsignedInt mesh, level; + MeshPrimitive primitive; + UnsignedInt indexCount, vertexCount; + std::size_t indexOffset; + Int indexStride; + Containers::String indexBounds; + MeshIndexType indexType; + Containers::Array attributes; + std::size_t indexDataSize, vertexDataSize; + Trade::DataFlags indexDataFlags, vertexDataFlags; + Containers::String name; + }; + + struct SceneFieldInfo { + Trade::SceneField name; + Trade::SceneFieldFlags flags; + Trade::SceneFieldType type; + UnsignedInt arraySize; + std::size_t size; + }; + + struct SceneInfo { + UnsignedInt scene; + Trade::SceneMappingType mappingType; + UnsignedLong mappingBound; + Containers::Array fields; + std::size_t dataSize; + Trade::DataFlags dataFlags; + Containers::String name; + }; + + struct ObjectInfo { + UnsignedLong object; + /* A bitfield, assuming no more than 32 scenes */ + /** @todo might be too little? */ + UnsignedInt scenes; + Containers::Array> fields; + Containers::String name; + }; + + /* Parse everything first to avoid errors interleaved with output */ + bool error = false; + + /* Object properties */ + Containers::Array objectInfos; + if(args.isSet("info") || args.isSet("info-objects")) { + objectInfos = Containers::Array{std::size_t(importer.objectCount())}; + + for(UnsignedLong i = 0; i != importer.objectCount(); ++i) { + objectInfos[i].object = i; + objectInfos[i].name = importer.objectName(i); + } + } + + /* Scene properties, together with counting how much is each mesh / light / + material / skin / object referenced (which gets used only if both + --info-scenes and --info-{lights,materials,skins,objects} is passed and + the file has at least one scene). Texture reference count is calculated + when parsing materials. */ + Containers::Array sceneInfos; + /* Only the very latest GCC seems to support enum classes as keys and I + can't be bothered to write a std::hash specialization, so just making + the key typeless */ + std::unordered_map sceneFieldNames; + Containers::Array materialReferenceCount; + Containers::Array lightReferenceCount; + Containers::Array meshReferenceCount; + Containers::Array skin2DReferenceCount; + Containers::Array skin3DReferenceCount; + if((args.isSet("info") || args.isSet("info-scenes")) && importer.sceneCount()) { + materialReferenceCount = Containers::Array{importer.materialCount()}; + lightReferenceCount = Containers::Array{importer.lightCount()}; + meshReferenceCount = Containers::Array{importer.meshCount()}; + skin2DReferenceCount = Containers::Array{importer.skin2DCount()}; + skin3DReferenceCount = Containers::Array{importer.skin3DCount()}; + + for(UnsignedInt i = 0; i != importer.sceneCount(); ++i) { + Containers::Optional scene = importer.scene(i); + if(!scene) { + error = true; + continue; + } + + SceneInfo info{}; + info.scene = i; + info.mappingType = scene->mappingType(); + info.mappingBound = scene->mappingBound(); + info.dataSize = scene->data().size(); + info.dataFlags = scene->dataFlags(); + info.name = importer.sceneName(i); + for(UnsignedInt j = 0; j != scene->fieldCount(); ++j) { + const Trade::SceneField name = scene->fieldName(j); + + if(name == Trade::SceneField::Mesh) for(const Containers::Pair>& meshMaterial: scene->meshesMaterialsAsArray()) { + if(meshMaterial.second().first() < meshReferenceCount.size()) + ++meshReferenceCount[meshMaterial.second().first()]; + if(UnsignedInt(meshMaterial.second().second()) < materialReferenceCount.size()) + ++materialReferenceCount[meshMaterial.second().second()]; + } + + if(name == Trade::SceneField::Skin) for(const Containers::Pair skin: scene->skinsAsArray()) { + if(scene->is2D() && skin.second() < skin2DReferenceCount.size()) + ++skin2DReferenceCount[skin.second()]; + if(scene->is3D() && skin.second() < skin3DReferenceCount.size()) + ++skin3DReferenceCount[skin.second()]; + } + + if(name == Trade::SceneField::Light) for(const Containers::Pair& light: scene->lightsAsArray()) { + if(light.second() < lightReferenceCount.size()) + ++lightReferenceCount[light.second()]; + } + + arrayAppend(info.fields, InPlaceInit, + name, + scene->fieldFlags(j), + scene->fieldType(j), + scene->fieldArraySize(j), + scene->fieldSize(j)); + + /* If the field has a custom name, save it into the map. Not + putting it into the fields array as the map is reused by + object info as well. */ + if(Trade::isSceneFieldCustom(name)) { + /* Fetch the name only if it's not already there */ + const auto inserted = sceneFieldNames.emplace(sceneFieldCustom(name), Containers::String{}); + if(inserted.second) + inserted.first->second = importer.sceneFieldName(name); + } + + if(objectInfos) for(const UnsignedInt object: scene->mappingAsArray(j)) { + if(object >= objectInfos.size()) continue; + + objectInfos[object].object = object; + objectInfos[object].scenes |= 1 << i; + + /* If the field is repeated, increase the count instead */ + if(!objectInfos[object].fields.isEmpty() && objectInfos[object].fields.back().first() == name) + ++objectInfos[object].fields.back().second(); + else + arrayAppend(objectInfos[object].fields, InPlaceInit, name, 1u); + } + } + + arrayAppend(sceneInfos, std::move(info)); + } + } + + /* Animation properties */ + Containers::Array animationInfos; + if(args.isSet("info") || args.isSet("info-animations")) for(UnsignedInt i = 0; i != importer.animationCount(); ++i) { + Containers::Optional animation; + { + Trade::Implementation::Duration d{importTime}; + if(!(animation = importer.animation(i))) { + error = true; + continue; + } + } + + AnimationInfo info{}; + info.animation = i; + info.name = importer.animationName(i); + info.data = *std::move(animation); + + arrayAppend(animationInfos, std::move(info)); + } + + /* Skin properties */ + Containers::Array skinInfos; + if(args.isSet("info") || args.isSet("info-skins")) { + for(UnsignedInt i = 0; i != importer.skin2DCount(); ++i) { + Containers::Optional skin; + { + Trade::Implementation::Duration d{importTime}; + if(!(skin = importer.skin2D(i))) { + error = true; + continue; + } + } + + SkinInfo info{}; + info.twoDimensions = true; + info.skin = i; + info.name = importer.skin2DName(i); + info.jointCount = skin->joints().size(); + + arrayAppend(skinInfos, std::move(info)); + } + + for(UnsignedInt i = 0; i != importer.skin3DCount(); ++i) { + Containers::Optional skin; + { + Trade::Implementation::Duration d{importTime}; + if(!(skin = importer.skin3D(i))) { + error = true; + continue; + } + } + + SkinInfo info{}; + info.twoDimensions = false; + info.skin = i; + info.name = importer.skin3DName(i); + info.jointCount = skin->joints().size(); + + arrayAppend(skinInfos, std::move(info)); + } + } + + /* Light properties */ + Containers::Array lightInfos; + if(args.isSet("info") || args.isSet("info-lights")) for(UnsignedInt i = 0; i != importer.lightCount(); ++i) { + Containers::Optional light; + { + Trade::Implementation::Duration d{importTime}; + if(!(light = importer.light(i))) { + error = true; + continue; + } + } + + LightInfo info{}; + info.light = i; + info.name = importer.lightName(i); + info.data = *std::move(light); + + arrayAppend(lightInfos, std::move(info)); + } + + /* Material properties, together with how much is each texture shared + (which gets used only if both --info-materials and --info-textures is + passed and the file has at least one material). */ + Containers::Array materialInfos; + Containers::Array textureReferenceCount; + if((args.isSet("info") || args.isSet("info-materials")) && importer.materialCount()) { + textureReferenceCount = Containers::Array{importer.textureCount()}; + + for(UnsignedInt i = 0; i != importer.materialCount(); ++i) { + Containers::Optional material; + { + Trade::Implementation::Duration d{importTime}; + if(!(material = importer.material(i))) { + error = true; + continue; + } + } + + /* Calculate texture reference count for all properties that look + like a texture */ + for(UnsignedInt j = 0; j != material->layerCount(); ++j) { + for(UnsignedInt k = 0; k != material->attributeCount(j); ++k) { + if(material->attributeType(j, k) != Trade::MaterialAttributeType::UnsignedInt || !material->attributeName(j, k).hasSuffix("Texture"_s)) + continue; + + const UnsignedInt texture = material->attribute(j, k); + /** @todo once StridedBitArrayView2D exists, fix this to + count each material only once by having one bit for + every material and texture */ + if(texture < textureReferenceCount.size()) + ++textureReferenceCount[texture]; + } + } + + MaterialInfo info{}; + info.material = i; + info.name = importer.materialName(i); + info.data = *std::move(material); + + arrayAppend(materialInfos, std::move(info)); + } + } + + /* Mesh properties */ + Containers::Array meshInfos; + if(args.isSet("info") || args.isSet("info-meshes")) for(UnsignedInt i = 0; i != importer.meshCount(); ++i) { + for(UnsignedInt j = 0; j != importer.meshLevelCount(i); ++j) { + Containers::Optional mesh; + { + Trade::Implementation::Duration d{importTime}; + if(!(mesh = importer.mesh(i, j))) { + error = true; + continue; + } + } + + MeshInfo info{}; + info.mesh = i; + info.level = j; + info.primitive = mesh->primitive(); + info.vertexCount = mesh->vertexCount(); + info.vertexDataSize = mesh->vertexData().size(); + info.vertexDataFlags = mesh->vertexDataFlags(); + if(!j) { + info.name = importer.meshName(i); + } + if(mesh->isIndexed()) { + info.indexCount = mesh->indexCount(); + info.indexType = mesh->indexType(); + info.indexOffset = mesh->indexOffset(); + info.indexStride = mesh->indexStride(); + info.indexDataSize = mesh->indexData().size(); + info.indexDataFlags = mesh->indexDataFlags(); + if(args.isSet("bounds")) + info.indexBounds = calculateBounds(mesh->indicesAsArray()); + } + for(UnsignedInt k = 0; k != mesh->attributeCount(); ++k) { + const Trade::MeshAttribute name = mesh->attributeName(k); + + /* Calculate bounds, if requested, if this is not an + implementation-specific format and if it's not a custom + attribute */ + Containers::String bounds; + if(args.isSet("bounds") && !isVertexFormatImplementationSpecific(mesh->attributeFormat(k))) switch(name) { + case Trade::MeshAttribute::Position: + bounds = calculateBounds(mesh->positions3DAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::Tangent: + bounds = calculateBounds(mesh->tangentsAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::Bitangent: + bounds = calculateBounds(mesh->bitangentsAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::Normal: + bounds = calculateBounds(mesh->normalsAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::TextureCoordinates: + bounds = calculateBounds(mesh->textureCoordinates2DAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::Color: + bounds = calculateBounds(mesh->colorsAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::ObjectId: + bounds = calculateBounds(mesh->objectIdsAsArray(namedAttributeId(*mesh, k))); + break; + } + + arrayAppend(info.attributes, InPlaceInit, + mesh->attributeOffset(k), + mesh->attributeStride(k), + mesh->attributeArraySize(k), + name, Trade::isMeshAttributeCustom(name) ? + importer.meshAttributeName(name) : "", + mesh->attributeFormat(k), + bounds); + } + + arrayAppend(meshInfos, std::move(info)); + } + } + + /* Texture properties, together with how much is each image shared (which + gets used only if both --info-textures and --info-images is passed and + the file has at least one texture). */ + Containers::Array textureInfos; + Containers::Array image1DReferenceCount; + Containers::Array image2DReferenceCount; + Containers::Array image3DReferenceCount; + if((args.isSet("info") || args.isSet("info-textures")) && importer.textureCount()) { + image1DReferenceCount = Containers::Array{importer.image1DCount()}; + image2DReferenceCount = Containers::Array{importer.image2DCount()}; + image3DReferenceCount = Containers::Array{importer.image3DCount()}; + for(UnsignedInt i = 0; i != importer.textureCount(); ++i) { + Containers::Optional texture; + { + Trade::Implementation::Duration d{importTime}; + if(!(texture = importer.texture(i))) { + error = true; + continue; + } + } + + switch(texture->type()) { + case Trade::TextureType::Texture1D: + if(texture->image() < image1DReferenceCount.size()) + ++image1DReferenceCount[texture->image()]; + break; + case Trade::TextureType::Texture1DArray: + case Trade::TextureType::Texture2D: + if(texture->image() < image2DReferenceCount.size()) + ++image2DReferenceCount[texture->image()]; + break; + case Trade::TextureType::CubeMap: + case Trade::TextureType::CubeMapArray: + case Trade::TextureType::Texture2DArray: + case Trade::TextureType::Texture3D: + if(texture->image() < image3DReferenceCount.size()) + ++image3DReferenceCount[texture->image()]; + break; + } + + TextureInfo info{}; + info.texture = i; + info.name = importer.textureName(i); + info.data = *std::move(texture); + + arrayAppend(textureInfos, std::move(info)); + } + } + + Containers::Array imageInfos; + if(args.isSet("info") || args.isSet("info-images")) { + imageInfos = Trade::Implementation::imageInfo(importer, error, importTime); + } + + std::size_t totalSceneDataSize = 0; + for(const SceneInfo& info: sceneInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Scene" << info.scene << Debug::nospace << ":" << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; + d << Debug::newline; + d << " Bound:" << info.mappingBound << "objects" + << Debug::color(Debug::Color::Blue) << "@" << Debug::packed + << Debug::color(Debug::Color::Cyan) << info.mappingType + << Debug::resetColor << "(" << Debug::nospace + << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; + if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) << info.dataFlags + << Debug::resetColor; + d << Debug::nospace << ")"; + + d << Debug::newline << " Fields:"; + for(const SceneFieldInfo& field: info.fields) { + d << Debug::newline << " " + << Debug::boldColor(Debug::Color::Default); + if(Trade::isSceneFieldCustom(field.name)) { + d << "Custom(" << Debug::nospace + << Trade::sceneFieldCustom(field.name) + << Debug::nospace << ":" << Debug::nospace + << Debug::color(Debug::Color::Yellow) + << sceneFieldNames[sceneFieldCustom(field.name)] + << Debug::nospace + << Debug::boldColor(Debug::Color::Default) << ")"; + } else d << Debug::packed << field.name; + + d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << field.type; + if(field.arraySize) + d << Debug::nospace << Utility::format("[{}]", field.arraySize); + d << Debug::resetColor; + if(field.flags) d << Debug::nospace << "," + << Debug::packed << Debug::color(Debug::Color::Green) + << field.flags << Debug::resetColor; + d << Debug::nospace << "," << field.size << "entries"; + } + + totalSceneDataSize += info.dataSize; + } + if(!sceneInfos.isEmpty()) + Debug{} << "Total scene data size:" << Utility::format("{:.1f}", totalSceneDataSize/1024.0f) << "kB"; + + for(const ObjectInfo& info: objectInfos) { + /* Objects without a name and not referenced by any scenes are useless, + ignore */ + if(!info.name && !info.scenes) continue; + + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Object" << info.object << Debug::resetColor; + + if(sceneInfos) { + const UnsignedInt count = Math::popcount(info.scenes); + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "scenes)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + if(info.scenes) { + d << Debug::newline << " Fields:"; + + for(std::size_t i = 0; i != info.fields.size(); ++i) { + if(i) d << Debug::nospace << ","; + const Containers::Pair nameCount = info.fields[i]; + d << Debug::color(Debug::Color::Cyan); + if(Trade::isSceneFieldCustom(nameCount.first())) { + d << "Custom(" << Debug::nospace + << Trade::sceneFieldCustom(nameCount.first()) + << Debug::nospace << ":" << Debug::nospace + << Debug::color(Debug::Color::Yellow) + << sceneFieldNames[sceneFieldCustom(nameCount.first())] + << Debug::nospace + << Debug::color(Debug::Color::Cyan) << ")"; + } else d << Debug::packed << nameCount.first(); + if(nameCount.second() != 1) + d << Debug::nospace << Utility::format("[{}]", nameCount.second()); + d << Debug::resetColor; + } + } + } + + std::size_t totalAnimationDataSize = 0; + for(const AnimationInfo& info: animationInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Animation" << info.animation << Debug::nospace << ":" << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; + + d << Debug::newline << " Duration: {" << Debug::nospace + /** @todo have a nice packed printing for Range instead */ + << info.data.duration().min() << Debug::nospace << "," + << info.data.duration().max() << Debug::nospace << "} (" + << Debug::nospace << Utility::format("{:.1f}", info.data.data().size()/1024.0f) << "kB"; + if(info.data.dataFlags() != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) + << info.data.dataFlags() << Debug::resetColor; + d << Debug::nospace << ")"; + + for(UnsignedInt i = 0; i != info.data.trackCount(); ++i) { + d << Debug::newline << " Track" << i << Debug::nospace << ":" + << Debug::packed << Debug::boldColor(Debug::Color::Default) + << info.data.trackTargetType(i) + << Debug::color(Debug::Color::Blue) << "@" + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.trackType(i) << Debug::resetColor; + if(info.data.trackType(i) != info.data.trackResultType(i)) + d << Debug::color(Debug::Color::Blue) << "->" + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.trackResultType(i) << Debug::resetColor; + d << Debug::nospace << "," << info.data.track(i).size() + << "keyframes"; + if(info.data.track(i).duration() != info.data.duration()) + d << Debug::newline << " Duration: {" << Debug::nospace + /** @todo have a nice packed printing for Range instead */ + << info.data.track(i).duration().min() << Debug::nospace + << "," << info.data.track(i).duration().max() + << Debug::nospace << "}"; + d << Debug::newline + << " Interpolation:" + << Debug::packed << Debug::color(info.data.track(i).interpolation() == Animation::Interpolation::Custom ? Debug::Color::Yellow : Debug::Color::Cyan) + << info.data.track(i).interpolation() << Debug::resetColor + << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Cyan) + << info.data.track(i).before() << Debug::resetColor + << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Cyan) + << info.data.track(i).after() << Debug::resetColor; + /** @todo might be useful to show bounds here as well, though not + so much for things like complex numbers or quats */ + } + + totalAnimationDataSize += info.data.data().size(); + } + if(!animationInfos.isEmpty()) + Debug{} << "Total animation data size:" << Utility::format("{:.1f}", totalAnimationDataSize/1024.0f) << "kB"; + + for(const SkinInfo& info: skinInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << (info.twoDimensions ? "2D skin" : "3D skin") << info.skin + << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if((info.twoDimensions && skin2DReferenceCount) || + (!info.twoDimensions && skin3DReferenceCount)) + { + const UnsignedInt count = info.twoDimensions ? skin2DReferenceCount[info.skin] : skin3DReferenceCount[info.skin]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + + d << Debug::newline << " " << info.jointCount << "joints"; + } + + for(const LightInfo& info: lightInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Light" << info.light << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if(lightReferenceCount) { + const UnsignedInt count = lightReferenceCount[info.light]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + + d << Debug::newline << " Type:" << Debug::packed + << Debug::color(Debug::Color::Cyan) + << info.data.type() << Debug::resetColor; + if(info.data.type() == Trade::LightData::Type::Spot) + d << Debug::nospace << "," << Debug::packed + << Deg(info.data.innerConeAngle()) << Debug::nospace + << "° -" << Debug::packed << Deg(info.data.outerConeAngle()) + << Debug::nospace << "°"; + d << Debug::newline << " Color:"; + if(useColor24) d << Debug::color + << Math::pack(info.data.color()); + d << Debug::packed << info.data.color(); + if(!Math::equal(info.data.intensity(), 1.0f)) + d << "*" << info.data.intensity(); + if(info.data.type() != Trade::LightData::Type::Ambient && + info.data.type() != Trade::LightData::Type::Directional) + d << Debug::newline << " Attenuation:" << Debug::packed + << info.data.attenuation(); + if(info.data.range() != Constants::inf()) + d << Debug::newline << " Range:" << Debug::packed + << info.data.range(); + } + + for(const MaterialInfo& info: materialInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Material" << info.material << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if(materialReferenceCount) { + const UnsignedInt count = materialReferenceCount[info.material]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; + + d << Debug::newline << " Type:" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.data.types() << Debug::resetColor; + + for(UnsignedInt i = 0; i != info.data.layerCount(); ++i) { + /* Print extra layers with extra indent */ + const char* indent; + if(info.data.layerCount() != 1 && i != 0) { + d << Debug::newline << " Layer" << i << Debug::nospace << ":"; + if(!info.data.layerName(i).isEmpty()) { + if(std::isupper(info.data.layerName(i)[0])) + d << Debug::boldColor(Debug::Color::Default); + else + d << Debug::color(Debug::Color::Yellow); + d << info.data.layerName(i) << Debug::resetColor; + } + indent = " "; + } else { + d << Debug::newline << " Base layer:"; + indent = " "; + } + + for(UnsignedInt j = 0; j != info.data.attributeCount(i); ++j) { + /* Ignore layer name (which is always first) unless it's in the + base material, in which case we print it as it wouldn't + otherwise be shown anywhere */ + if(i && !j && info.data.attributeName(i, j) == " LayerName") + continue; + + d << Debug::newline << indent; + if(std::isupper(info.data.attributeName(i, j)[0])) + d << Debug::boldColor(Debug::Color::Default); + else + d << Debug::color(Debug::Color::Yellow); + d << info.data.attributeName(i, j) << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.attributeType(i, j) << Debug::resetColor << Debug::nospace + << ":"; + switch(info.data.attributeType(i, j)) { + #define _c(type) case Trade::MaterialAttributeType::type: \ + d << Debug::packed << info.data.attribute(i, j); \ + break; + /* LCOV_EXCL_START */ + _c(Float) + _c(Deg) + _c(Rad) + _c(UnsignedInt) + _c(Int) + _c(UnsignedLong) + _c(Long) + _c(Vector2) + _c(Vector2ui) + _c(Vector2i) + /* Vector3 handled below */ + _c(Vector3ui) + _c(Vector3i) + /* Vector4 handled below */ + _c(Vector4ui) + _c(Vector4i) + _c(Matrix2x2) + _c(Matrix2x3) + _c(Matrix2x4) + _c(Matrix3x2) + _c(Matrix3x3) + _c(Matrix3x4) + _c(Matrix4x2) + _c(Matrix4x3) + /* LCOV_EXCL_STOP */ + #undef _c + case Trade::MaterialAttributeType::Bool: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::Vector3: + /** @todo hasSuffix() might be more robust against + false positives, but KHR_materials_specular in glTF + uses ColorFactor :/ */ + if(useColor24 && info.data.attributeName(i, j).contains("Color"_s)) + d << Debug::color << Math::pack(info.data.attribute(i, j)); + d << Debug::packed << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::Vector4: + /** @todo hasSuffix() might be more robust against + false positives, but KHR_materials_specular in glTF + uses ColorFactor :/ */ + if(useColor24 && info.data.attributeName(i, j).contains("Color"_s)) + d << Debug::color << Math::pack(info.data.attribute(i, j).rgb()); + d << Debug::packed << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::Pointer: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::MutablePointer: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::String: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::TextureSwizzle: + d << Debug::packed << info.data.attribute(i, j); + break; + } + } + } + } + + std::size_t totalMeshDataSize = 0; + for(const MeshInfo& info: meshInfos) { + Debug d{useColor}; + if(info.level == 0) { + d << Debug::boldColor(Debug::Color::Default) << "Mesh" << info.mesh << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if(meshReferenceCount) { + const UnsignedInt count = meshReferenceCount[info.mesh]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; + d << Debug::newline; + } + d << " Level" << info.level << Debug::nospace << ":" + << info.vertexCount << "vertices" << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.primitive << Debug::resetColor << "(" << Debug::nospace + << Utility::format("{:.1f}", info.vertexDataSize/1024.0f) + << "kB"; + if(info.vertexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) + << info.vertexDataFlags << Debug::resetColor; + d << Debug::nospace << ")"; + + for(const MeshAttributeInfo& attribute: info.attributes) { + d << Debug::newline << " " + << Debug::boldColor(Debug::Color::Default); + if(Trade::isMeshAttributeCustom(attribute.name)) { + d << "Custom(" << Debug::nospace + << Trade::meshAttributeCustom(attribute.name) + << Debug::nospace << ":" << Debug::nospace + << Debug::color(Debug::Color::Yellow) + << attribute.customName << Debug::nospace + << Debug::boldColor(Debug::Color::Default) << ")"; + } else d << Debug::packed << attribute.name; + + d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << attribute.format; + if(attribute.arraySize) + d << Debug::nospace << Utility::format("[{}]", attribute.arraySize); + d << Debug::resetColor; + d << Debug::nospace << ", offset" << attribute.offset; + d << Debug::nospace << ", stride" + << attribute.stride; + if(attribute.bounds) + d << Debug::newline << " Bounds:" << attribute.bounds; + } + + if(info.indexType != MeshIndexType{}) { + d << Debug::newline << " " << info.indexCount << "indices" << Debug::color(Debug::Color::Blue) << "@" + << Debug::packed << Debug::color(Debug::Color::Cyan) << info.indexType << Debug::resetColor << Debug::nospace << ", offset" << info.indexOffset << Debug::nospace << ", stride" << info.indexStride << "(" << Debug::nospace + << Utility::format("{:.1f}", info.indexDataSize/1024.0f) + << "kB"; + if(info.indexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) << info.indexDataFlags << Debug::resetColor; + d << Debug::nospace << ")"; + if(info.indexBounds) + d << Debug::newline << " Bounds:" << info.indexBounds; + } + + totalMeshDataSize += info.vertexDataSize + info.indexDataSize; + } + if(!meshInfos.isEmpty()) + Debug{} << "Total mesh data size:" << Utility::format("{:.1f}", totalMeshDataSize/1024.0f) << "kB"; + + for(const TextureInfo& info: textureInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Texture" << info.texture << Debug::resetColor; + + /* Print reference count only if there actually are materials and they + were parsed, otherwise this information is useless */ + if(textureReferenceCount) { + const UnsignedInt count = textureReferenceCount[info.texture]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "material attributes)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + d << Debug::newline; + d << " Type:" + << Debug::packed + << Debug::color(Debug::Color::Cyan) << info.data.type() + << Debug::resetColor << Debug::nospace << ", image" + << info.data.image(); + d << Debug::newline << " Minification, mipmap and magnification:" + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.minificationFilter() << Debug::nospace << "," + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.mipmapFilter() << Debug::nospace << "," + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.magnificationFilter() << Debug::resetColor; + /** @todo show only the dimensions that matter for a particular texture + type */ + d << Debug::newline << " Wrapping:" << Debug::resetColor << "{" << Debug::nospace + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.wrapping()[0] << Debug::resetColor + << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1] + << Debug::resetColor << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1] + << Debug::resetColor << Debug::nospace << "}"; + } + + Trade::Implementation::printImageInfo(useColor, imageInfos, image1DReferenceCount, image2DReferenceCount, image3DReferenceCount); + + return error; +} + +} + +}}} + +#endif diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index ac1e1e4e8..e021cff00 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -27,7 +27,33 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "Magnum/SceneTools/Test") +if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) + set(SCENETOOLS_TEST_DIR ".") +else() + set(SCENETOOLS_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/configure.h) + corrade_add_test(SceneToolsCombineTest CombineTest.cpp LIBRARIES MagnumTrade) corrade_add_test(SceneToolsConvertToSingleFun___Test ConvertToSingleFunctionObjectsTest.cpp LIBRARIES MagnumTrade) corrade_add_test(SceneToolsFlattenMeshHierarchyTest FlattenMeshHierarchyTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsOrderClusterParentsTest OrderClusterParentsTest.cpp LIBRARIES MagnumSceneToolsTestLib) + +corrade_add_test(SceneToolsSceneConverterTest SceneConverterTest.cpp + LIBRARIES MagnumSceneTools + FILES + SceneConverterTestFiles/info-animations.txt + SceneConverterTestFiles/info-images.txt + SceneConverterTestFiles/info-lights.txt + SceneConverterTestFiles/info-materials.txt + SceneConverterTestFiles/info-meshes-bounds.txt + SceneConverterTestFiles/info-meshes.txt + SceneConverterTestFiles/info-objects.txt + SceneConverterTestFiles/info-references.txt + SceneConverterTestFiles/info-scenes-objects.txt + SceneConverterTestFiles/info-scenes.txt + SceneConverterTestFiles/info-skins.txt + SceneConverterTestFiles/info-textures.txt) +target_include_directories(SceneToolsSceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp new file mode 100644 index 000000000..f6bc6c649 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp @@ -0,0 +1,1131 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "Magnum/Math/CubicHermite.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" + +#include "Magnum/SceneTools/Implementation/sceneConverterUtilities.h" + +#include "configure.h" + +namespace Magnum { namespace SceneTools { namespace Test { namespace { + +struct SceneConverterTest: TestSuite::Tester { + explicit SceneConverterTest(); + + void infoImplementationEmpty(); + void infoImplementationScenesObjects(); + void infoImplementationAnimations(); + void infoImplementationSkins(); + void infoImplementationLights(); + void infoImplementationMaterials(); + void infoImplementationMeshes(); + void infoImplementationMeshesBounds(); + void infoImplementationTextures(); + void infoImplementationImages(); + /* Image info further tested in ImageConverterTest */ + void infoImplementationReferenceCount(); + void infoImplementationError(); + + Utility::Arguments _infoArgs; +}; + +using namespace Math::Literals; + +const struct { + const char* name; + const char* arg; + const char* expected; + bool printVisualCheck; +} InfoImplementationScenesObjectsData[]{ + {"", "--info", "info-scenes-objects.txt", true}, + {"only scenes", "--info-scenes", "info-scenes.txt", false}, + {"only objects", "--info-objects", "info-objects.txt", false}, +}; + +const struct { + const char* name; + bool oneOrAll; + bool printVisualCheck; +} InfoImplementationOneOrAllData[]{ + {"", true, true}, + {"--info", false, false}, +}; + +SceneConverterTest::SceneConverterTest() { + addTests({&SceneConverterTest::infoImplementationEmpty}); + + addInstancedTests({&SceneConverterTest::infoImplementationScenesObjects}, + Containers::arraySize(InfoImplementationScenesObjectsData)); + + addInstancedTests({&SceneConverterTest::infoImplementationAnimations, + &SceneConverterTest::infoImplementationSkins, + &SceneConverterTest::infoImplementationLights, + &SceneConverterTest::infoImplementationMaterials, + &SceneConverterTest::infoImplementationMeshes}, + Containers::arraySize(InfoImplementationOneOrAllData)); + + addTests({&SceneConverterTest::infoImplementationMeshesBounds}); + + addInstancedTests({&SceneConverterTest::infoImplementationTextures, + &SceneConverterTest::infoImplementationImages}, + Containers::arraySize(InfoImplementationOneOrAllData)); + + addTests({&SceneConverterTest::infoImplementationReferenceCount, + &SceneConverterTest::infoImplementationError}); + + /* A subset of arguments needed by the info printing code */ + _infoArgs.addBooleanOption("info") + .addBooleanOption("info-scenes") + .addBooleanOption("info-objects") + .addBooleanOption("info-animations") + .addBooleanOption("info-skins") + .addBooleanOption("info-lights") + .addBooleanOption("info-materials") + .addBooleanOption("info-meshes") + .addBooleanOption("info-textures") + .addBooleanOption("info-images") + .addBooleanOption("bounds"); +} + +void SceneConverterTest::infoImplementationEmpty() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + const char* argv[]{"", "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == false); + CORRADE_COMPARE(out.str(), ""); +} + +void SceneConverterTest::infoImplementationScenesObjects() { + auto&& data = InfoImplementationScenesObjectsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + /* First scene has 4, second 7, the last three are not in any scene and + thus not listed. Object 5 has no fields and thus not listed either. */ + UnsignedLong doObjectCount() const override { return 10; } + UnsignedInt doSceneCount() const override { return 2; } + Containers::String doSceneName(UnsignedInt id) override { + return id == 0 ? "A simple scene" : ""; + } + Containers::String doObjectName(UnsignedLong id) override { + if(id == 0) return "Parent-less mesh"; + if(id == 2) return "Two meshes, shared among two scenes"; + if(id == 4) return "Two custom arrays"; + if(id == 6) return "Only in the second scene, but no fields, thus same as unreferenced"; + if(id == 8) return "Not in any scene"; + return ""; + } + Containers::String doSceneFieldName(UnsignedInt name) override { + if(name == 1337) return "DirectionVector"; + return ""; + } + Containers::Optional doScene(UnsignedInt id) override { + /* Builtin fields, some duplicated, one marked as ordered */ + if(id == 0) { + Containers::ArrayView parentMapping; + Containers::ArrayView parents; + Containers::ArrayView meshMapping; + Containers::ArrayView meshes; + Containers::ArrayTuple data{ + {NoInit, 3, parentMapping}, + {ValueInit, 3, parents}, + {NoInit, 4, meshMapping}, + {ValueInit, 4, meshes}, + }; + Utility::copy({1, 3, 2}, parentMapping); + Utility::copy({2, 0, 2, 1}, meshMapping); + /* No need to fill the data, zero-init is fine */ + return Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 4, std::move(data), { + Trade::SceneFieldData{Trade::SceneField::Parent, parentMapping, parents}, + Trade::SceneFieldData{Trade::SceneField::Mesh, meshMapping, meshes, Trade::SceneFieldFlag::OrderedMapping}, + }}; + } + + /* Two custom fields, one array. Stored as an external memory. */ + if(id == 1) { + return Trade::SceneData{Trade::SceneMappingType::UnsignedByte, 8, Trade::DataFlag::ExternallyOwned|Trade::DataFlag::Mutable, scene2Data, { + Trade::SceneFieldData{Trade::sceneFieldCustom(42), Containers::arrayView(scene2Data->customMapping), Containers::arrayView(scene2Data->custom)}, + Trade::SceneFieldData{Trade::sceneFieldCustom(1337), Trade::SceneMappingType::UnsignedByte, scene2Data->customArrayMapping, Trade::SceneFieldType::Short, scene2Data->customArray, 3}, + }}; + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + struct { + UnsignedByte customMapping[2]; + Double custom[2]; + UnsignedByte customArrayMapping[3]; + Vector3s customArray[3]; + } scene2Data[1]{{ + /* No need to fill the data, zero-init is fine */ + {7, 3}, {}, {2, 4, 4}, {} + }}; + } importer; + + const char* argv[]{"", data.arg}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join({SCENETOOLS_TEST_DIR, "SceneConverterTestFiles", data.expected}), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationAnimations() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doAnimationCount() const override { return 2; } + Containers::String doAnimationName(UnsignedInt id) override { + return id == 1 ? "Custom track duration and interpolator function" : ""; + } + Containers::Optional doAnimation(UnsignedInt id) override { + /* First has two tracks with a shared time and implicit duration, + one with a different result type. */ + if(id == 0) { + Containers::ArrayView time; + Containers::ArrayView translation; + Containers::ArrayView rotation; + Containers::ArrayTuple data{ + {ValueInit, 3, time}, + {ValueInit, 3, translation}, + {ValueInit, 3, rotation} + }; + Utility::copy({0.5f, 1.0f, 1.25f}, time); + return Trade::AnimationData{std::move(data), { + /** @todo cleanup once AnimationTrackData has sane + constructors */ + Trade::AnimationTrackData{Trade::AnimationTrackTargetType::Translation2D, 17, Animation::TrackView{time, translation, Animation::Interpolation::Linear, Animation::Extrapolation::DefaultConstructed, Animation::Extrapolation::Constant}}, + Trade::AnimationTrackData{Trade::AnimationTrackTargetType::Rotation2D, 17, Animation::TrackView{time, rotation, Animation::Interpolation::Constant, Animation::Extrapolation::Extrapolated}}, + }}; + } + + /* Second has track duration different from animation duration and + a custom interpolator. Stored as an external memory. */ + if(id == 1) { + return Trade::AnimationData{Trade::DataFlag::ExternallyOwned, animation2Data, { + /** @todo cleanup once AnimationTrackData has sane + constructors */ + Trade::AnimationTrackData{Trade::AnimationTrackTargetType::Scaling3D, 666, Animation::TrackView{animation2Data->time, animation2Data->scaling, Math::lerp, Animation::Extrapolation::DefaultConstructed, Animation::Extrapolation::Constant}}, + }, {0.1f, 1.3f}}; + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + struct { + Float time[5]; + Vector3 scaling[5]; + } animation2Data[1]{{ + {0.75f, 0.75f, 1.0f, 1.0f, 1.25f}, + {} + }}; + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-animations" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-animations.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationSkins() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSkin2DCount() const override { return 2; } + Containers::String doSkin2DName(UnsignedInt id) override { + return id == 1 ? "Second 2D skin, external data" : ""; + } + Containers::Optional doSkin2D(UnsignedInt id) override { + /* First a regular skin, second externally owned */ + if(id == 0) return Trade::SkinData2D{ + {3, 6, 7, 12, 22}, + {{}, {}, {}, {}, {}} + }; + + if(id == 1) return Trade::SkinData2D{Trade::DataFlag::ExternallyOwned, skin2JointData, Trade::DataFlag::ExternallyOwned, skin2MatrixData}; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doSkin3DCount() const override { return 3; } + Containers::String doSkin3DName(UnsignedInt id) override { + return id == 0 ? "First 3D skin, external data" : ""; + } + Containers::Optional doSkin3D(UnsignedInt id) override { + /* Reverse order in 3D, plus one more to ensure the count isn't + mismatched between 2D and 3D */ + if(id == 0) return Trade::SkinData3D{Trade::DataFlag::ExternallyOwned, skin3JointData, Trade::DataFlag::ExternallyOwned, skin3MatrixData}; + + if(id == 1) return Trade::SkinData3D{ + {3, 22}, + {{}, {}} + }; + + if(id == 2) return Trade::SkinData3D{ + {3}, + {{}} + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt skin2JointData[15]; + Matrix3 skin2MatrixData[15]; + UnsignedInt skin3JointData[12]; + Matrix4 skin3MatrixData[12]; + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-skins" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-skins.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationLights() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doLightCount() const override { return 2; } + Containers::String doLightName(UnsignedInt id) override { + return id == 1 ? "Directional light with always-implicit attenuation and range" : ""; + } + Containers::Optional doLight(UnsignedInt id) override { + /* First a blue spot light */ + if(id == 0) return Trade::LightData{ + Trade::LightData::Type::Spot, + 0x3457ff_rgbf, + 15.0f, + {1.2f, 0.3f, 0.04f}, + 100.0f, + 55.0_degf, + 85.0_degf + }; + + /* Second a yellow directional light with infinite range */ + if(id == 1) return Trade::LightData{ + Trade::LightData::Type::Directional, + 0xff5734_rgbf, + 5.0f + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-lights" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-lights.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationMaterials() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMaterialCount() const override { return 2; } + Containers::String doMaterialName(UnsignedInt id) override { + return id == 1 ? "Lots o' laierz" : ""; + } + Containers::Optional doMaterial(UnsignedInt id) override { + /* First has custom attributes */ + if(id == 0) return Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::BaseColor, 0x3bd26799_rgbaf}, + {Trade::MaterialAttribute::DoubleSided, true}, + {Trade::MaterialAttribute::EmissiveColor, 0xe9eca_rgbf}, + {Trade::MaterialAttribute::RoughnessTexture, 67u}, + {Trade::MaterialAttribute::RoughnessTextureMatrix, Matrix3::translation({0.25f, 0.75f})}, + {Trade::MaterialAttribute::RoughnessTextureSwizzle, Trade::MaterialTextureSwizzle::B}, + {"reflectionAngle", 35.0_degf}, + /* These shouldn't have a color swatch rendered */ + {"notAColour4", Vector4{0.1f, 0.2f, 0.3f, 0.4f}}, + {"notAColour3", Vector3{0.2f, 0.3f, 0.4f}}, + {"deadBeef", reinterpret_cast(0xdeadbeef)}, + {"undeadBeef", reinterpret_cast(0xbeefbeef)}, + }}; + + /* Second has layers, custom layers, unnamed layers and a name */ + if(id == 1) return Trade::MaterialData{Trade::MaterialType::PbrClearCoat|Trade::MaterialType::Phong, { + {Trade::MaterialAttribute::DiffuseColor, 0xc7cf2f99_rgbaf}, + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::LayerFactor, 0.5f}, + {Trade::MaterialAttribute::LayerFactorTexture, 3u}, + {Trade::MaterialAttribute::LayerName, "anEmptyLayer"}, + {Trade::MaterialAttribute::LayerFactor, 0.25f}, + {Trade::MaterialAttribute::LayerFactorTexture, 2u}, + {"yes", "a string"}, + }, {1, 4, 5, 8}}; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-materials" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-materials.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationMeshes() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 3; } + UnsignedInt doMeshLevelCount(UnsignedInt id) override { + return id == 1 ? 2 : 1; + } + Containers::String doMeshName(UnsignedInt id) override { + return id == 1 ? "LODs? No, meshets." : ""; + } + Containers::String doMeshAttributeName(UnsignedShort name) override { + if(name == 25) return "vertices"; + if(name == 26) return "triangles"; + /* 37 (triangleCount) deliberately not named */ + if(name == 116) return "vertexCount"; + + return ""; + } + Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override { + /* First is indexed & externally owned */ + if(id == 0 && level == 0) return Trade::MeshData{MeshPrimitive::Points, + Trade::DataFlag::ExternallyOwned, indices, + Trade::MeshIndexData{indices}, + Trade::DataFlag::ExternallyOwned|Trade::DataFlag::Mutable, points, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(points)} + }}; + + /* Second is multi-level, with second level being indexed meshlets + with custom (array) attributes */ + if(id == 1 && level == 0) { + Containers::ArrayView positions; + Containers::ArrayView tangents; + Containers::ArrayTuple data{ + {NoInit, 250, positions}, + {NoInit, 250, tangents}, + }; + return Trade::MeshData{MeshPrimitive::Triangles, std::move(data), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}, + Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, tangents}, + }}; + } + if(id == 1 && level == 1) { + Containers::StridedArrayView2D vertices; + Containers::StridedArrayView2D indices; + Containers::ArrayView triangleCount; + Containers::ArrayView vertexCount; + Containers::ArrayTuple data{ + {NoInit, {135, 64}, vertices}, + {NoInit, {135, 126}, indices}, + {NoInit, 135, triangleCount}, + {NoInit, 135, vertexCount}, + }; + return Trade::MeshData{MeshPrimitive::Meshlets, std::move(data), { + Trade::MeshAttributeData{Trade::meshAttributeCustom(25), vertices}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(26), indices}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(37), triangleCount}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(116), vertexCount}, + }}; + } + + /* Third is an empty instance mesh */ + if(id == 2 && level == 0) return Trade::MeshData{MeshPrimitive::Instances, 15}; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedShort indices[70]; + Vector3 points[50]; + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-meshes" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-meshes.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationMeshesBounds() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { + return Trade::MeshData{MeshPrimitive::Lines, + {}, indexData, Trade::MeshIndexData{indexData}, + {}, vertexData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(vertexData->positions)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, Containers::arrayView(vertexData->tangent)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Bitangent, Containers::arrayView(vertexData->bitangent)}, + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, Containers::arrayView(vertexData->objectId)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(vertexData->normal)}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, Containers::arrayView(vertexData->textureCoordinates)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, Containers::arrayView(vertexData->color)}, + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, Containers::arrayView(vertexData->objectIdSecondary)}, + }}; + } + + UnsignedByte indexData[3]{15, 3, 176}; + + struct { + Vector3 positions[2]; + Vector3 tangent[2]; + Vector3 bitangent[2]; + UnsignedShort objectId[2]; + Vector3 normal[2]; + Vector2 textureCoordinates[2]; + Vector4 color[2]; + UnsignedInt objectIdSecondary[2]; + } vertexData[1]{{ + {{0.1f, -0.1f, 0.2f}, {0.2f, 0.0f, -0.2f}}, + {{0.2f, -0.2f, 0.8f}, {0.3f, 0.8f, 0.2f}}, + {{0.4f, 0.2f, 1.0f}, {0.3f, 0.9f, 0.0f}}, + {155, 12}, + {{0.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 1.0f}}, + {{0.5f, 0.5f}, {1.5f, 0.5f}}, + {0x99336600_rgbaf, 0xff663333_rgbaf}, + {15, 337}, + }}; + } importer; + + const char* argv[]{"", "--info-meshes", "--bounds"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-meshes-bounds.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationTextures() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doTextureCount() const override { return 2; } + Containers::String doTextureName(UnsignedInt id) override { + return id == 1 ? "Name!" : ""; + } + Containers::Optional doTexture(UnsignedInt id) override { + /* First a 1D texture */ + if(id == 0) return Trade::TextureData{ + Trade::TextureType::Texture1D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 666 + }; + + /* Second a 2D array texture */ + if(id == 1) return Trade::TextureData{ + Trade::TextureType::Texture2DArray, + SamplerFilter::Linear, + SamplerFilter::Nearest, + SamplerMipmap::Linear, + {SamplerWrapping::MirroredRepeat, SamplerWrapping::ClampToEdge, SamplerWrapping::MirrorClampToEdge}, + 3 + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-textures" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-textures.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationImages() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Just the very basics to ensure image info *is* printed. Tested in full + in ImageConverterTest. */ + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage1DCount() const override { return 1; } + Containers::Optional doImage1D(UnsignedInt, UnsignedInt) override { + return Trade::ImageData1D{PixelFormat::R32F, 1024, Containers::Array{NoInit, 4096}}; + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-images" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-images.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationReferenceCount() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + /* One data of each kind should be always referenced twice+, one once, + one not at all, and one reference should be OOB */ + + UnsignedLong doObjectCount() const override { return 4; } + Containers::String doObjectName(UnsignedLong id) override { + return id == 2 ? "Not referenced" : ""; + } + UnsignedInt doSceneCount() const override { return 2; } + Containers::Optional doScene(UnsignedInt id) override { + if(id == 0) return Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 2, {}, sceneData3D, { + /* To mark the scene as 3D */ + Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix4x4, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->meshes)}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->materials)}, + Trade::SceneFieldData{Trade::SceneField::Light, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->lights)}, + Trade::SceneFieldData{Trade::SceneField::Skin, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->skins)}, + }}; + if(id == 1) return Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 4, {}, sceneData2D, { + /* To mark the scene as 2D */ + Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix3x3, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(sceneData2D->mapping), + Containers::arrayView(sceneData2D->meshes)}, + Trade::SceneFieldData{Trade::SceneField::Skin, + Containers::arrayView(sceneData2D->mapping), + Containers::arrayView(sceneData2D->skins)}, + }}; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doSkin2DCount() const override { return 3; } + Containers::String doSkin2DName(UnsignedInt id) override { + return id == 2 ? "Not referenced" : ""; + } + Containers::Optional doSkin2D(UnsignedInt id) override { + if(id == 0) return Trade::SkinData2D{ + {35, 22}, + {{}, {}} + }; + if(id == 1) return Trade::SkinData2D{ + {33, 10, 100}, + {{}, {}, {}} + }; + if(id == 2) return Trade::SkinData2D{ + {66}, + {{}} + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doSkin3DCount() const override { return 3; } + Containers::String doSkin3DName(UnsignedInt id) override { + return id == 0 ? "Not referenced" : ""; + } + Containers::Optional doSkin3D(UnsignedInt id) override { + if(id == 0) return Trade::SkinData3D{ + {35, 22}, + {{}, {}} + }; + if(id == 1) return Trade::SkinData3D{ + {37}, + {{}} + }; + if(id == 2) return Trade::SkinData3D{ + {300, 10, 1000}, + {{}, {}, {}} + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doLightCount() const override { return 3; } + Containers::String doLightName(UnsignedInt id) override { + return id == 1 ? "Not referenced" : ""; + } + Containers::Optional doLight(UnsignedInt id) override { + if(id == 0) return Trade::LightData{ + Trade::LightData::Type::Directional, + 0x57ff34_rgbf, + 5.0f + }; + if(id == 1) return Trade::LightData{ + Trade::LightData::Type::Ambient, + 0xff5734_rgbf, + 0.1f + }; + if(id == 2) return Trade::LightData{ + Trade::LightData::Type::Directional, + 0x3457ff_rgbf, + 1.0f + }; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doMaterialCount() const override { return 3; } + Containers::String doMaterialName(UnsignedInt id) override { + return id == 2 ? "Not referenced" : ""; + } + Containers::Optional doMaterial(UnsignedInt id) override { + if(id == 0) return Trade::MaterialData{{}, { + {Trade::MaterialAttribute::DiffuseTexture, 2u}, + {Trade::MaterialAttribute::BaseColorTexture, 2u}, + }}; + if(id == 1) return Trade::MaterialData{{}, { + {"lookupTexture", 0u}, + {"volumeTexture", 3u}, + {Trade::MaterialAttribute::NormalTexture, 17u}, + {Trade::MaterialAttribute::EmissiveTexture, 4u}, + }}; + if(id == 2) return Trade::MaterialData{{}, {}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doMeshCount() const override { return 3; } + Containers::String doMeshName(UnsignedInt id) override { + return id == 1 ? "Not referenced" : ""; + } + Containers::Optional doMesh(UnsignedInt id, UnsignedInt) override { + if(id == 0) return Trade::MeshData{MeshPrimitive::Points, 5}; + if(id == 1) return Trade::MeshData{MeshPrimitive::Lines, 4}; + if(id == 2) return Trade::MeshData{MeshPrimitive::TriangleFan, 4}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doTextureCount() const override { return 5; } + Containers::String doTextureName(UnsignedInt id) override { + return id == 1 ? "Not referenced" : ""; + } + Containers::Optional doTexture(UnsignedInt id) override { + if(id == 0) return Trade::TextureData{ + Trade::TextureType::Texture1D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 1 + }; + if(id == 1) return Trade::TextureData{ + Trade::TextureType::Texture1DArray, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 225 + }; + if(id == 2) return Trade::TextureData{ + Trade::TextureType::Texture2D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 0 + }; + if(id == 3) return Trade::TextureData{ + Trade::TextureType::Texture3D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 1 + }; + if(id == 4) return Trade::TextureData{ + Trade::TextureType::Texture2D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 0 + }; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doImage1DCount() const override { return 2; } + Containers::String doImage1DName(UnsignedInt id) override { + return id == 0 ? "Not referenced" : ""; + } + Containers::Optional doImage1D(UnsignedInt id, UnsignedInt) override { + if(id == 0) + return Trade::ImageData1D{PixelFormat::RGBA8I, 1, Containers::Array{NoInit, 4}}; + if(id == 1) + return Trade::ImageData1D{PixelFormat::R8I, 4, Containers::Array{NoInit, 4}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doImage2DCount() const override { return 2; } + Containers::String doImage2DName(UnsignedInt id) override { + return id == 1 ? "Not referenced" : ""; + } + Containers::Optional doImage2D(UnsignedInt id, UnsignedInt) override { + if(id == 0) + return Trade::ImageData2D{PixelFormat::RGBA8I, {1, 2}, Containers::Array{NoInit, 8}}; + if(id == 1) + return Trade::ImageData2D{PixelFormat::R8I, {4, 1}, Containers::Array{NoInit, 4}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doImage3DCount() const override { return 2; } + Containers::String doImage3DName(UnsignedInt id) override { + return id == 0 ? "Not referenced" : ""; + } + Containers::Optional doImage3D(UnsignedInt id, UnsignedInt) override { + if(id == 0) + return Trade::ImageData3D{PixelFormat::RGBA8I, {1, 2, 1}, Containers::Array{NoInit, 8}}; + if(id == 1) + return Trade::ImageData3D{PixelFormat::R8I, {4, 1, 1}, Containers::Array{NoInit, 4}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + struct { + UnsignedInt mapping[4]; + UnsignedInt meshes[4]; + Int materials[4]; + UnsignedInt lights[4]; + UnsignedInt skins[4]; + } sceneData3D[1]{{ + {0, 1, 1, 25}, + {2, 0, 2, 67}, + {0, 1, 23, 0}, + {0, 17, 0, 2}, + {1, 1, 22, 2} + }}; + + struct { + UnsignedInt mapping[3]; + UnsignedInt meshes[3]; + UnsignedInt skins[3]; + } sceneData2D[1]{{ + {3, 116, 1}, + {2, 0, 23}, + {177, 0, 1} + }}; + } importer; + + const char* argv[]{"", "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-references.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationError() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + /* The one single object is named, and that name should be printed + after all error messages */ + UnsignedLong doObjectCount() const override { return 1; } + Containers::String doObjectName(UnsignedLong) override { return "A name"; } + + UnsignedInt doSceneCount() const override { return 2; } + Containers::Optional doScene(UnsignedInt id) override { + Error{} << "Scene" << id << "error!"; + return {}; + } + + UnsignedInt doAnimationCount() const override { return 2; } + Containers::Optional doAnimation(UnsignedInt id) override { + Error{} << "Animation" << id << "error!"; + return {}; + } + + UnsignedInt doSkin2DCount() const override { return 2; } + Containers::Optional doSkin2D(UnsignedInt id) override { + Error{} << "2D skin" << id << "error!"; + return {}; + } + + UnsignedInt doSkin3DCount() const override { return 2; } + Containers::Optional doSkin3D(UnsignedInt id) override { + Error{} << "3D skin" << id << "error!"; + return {}; + } + + UnsignedInt doLightCount() const override { return 2; } + Containers::Optional doLight(UnsignedInt id) override { + Error{} << "Light" << id << "error!"; + return {}; + } + + UnsignedInt doMaterialCount() const override { return 2; } + Containers::Optional doMaterial(UnsignedInt id) override { + Error{} << "Material" << id << "error!"; + return {}; + } + + UnsignedInt doMeshCount() const override { return 2; } + Containers::Optional doMesh(UnsignedInt id, UnsignedInt) override { + Error{} << "Mesh" << id << "error!"; + return {}; + } + + UnsignedInt doTextureCount() const override { return 2; } + Containers::Optional doTexture(UnsignedInt id) override { + Error{} << "Texture" << id << "error!"; + return {}; + } + + /* Errors for all image types tested in ImageConverterTest */ + UnsignedInt doImage2DCount() const override { return 2; } + Containers::Optional doImage2D(UnsignedInt id, UnsignedInt) override { + Error{} << "Image" << id << "error!"; + return {}; + } + } importer; + + const char* argv[]{"", "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + std::ostringstream out; + Debug redirectOutput{&out}; + Error redirectError{&out}; + /* It should return a failure */ + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == true); + CORRADE_COMPARE(out.str(), + /* It should not exit after first error... */ + "Scene 0 error!\n" + "Scene 1 error!\n" + "Animation 0 error!\n" + "Animation 1 error!\n" + "2D skin 0 error!\n" + "2D skin 1 error!\n" + "3D skin 0 error!\n" + "3D skin 1 error!\n" + "Light 0 error!\n" + "Light 1 error!\n" + "Material 0 error!\n" + "Material 1 error!\n" + "Mesh 0 error!\n" + "Mesh 1 error!\n" + "Texture 0 error!\n" + "Texture 1 error!\n" + "Image 0 error!\n" + "Image 1 error!\n" + /* ... and it should print all info output after the errors */ + "Object 0: A name\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::SceneTools::Test::SceneConverterTest) diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt new file mode 100644 index 000000000..df71d5da7 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt @@ -0,0 +1,12 @@ +Animation 0: + Duration: {0.5, 1.25} (0.1 kB) + Track 0: Translation2D @ Vector2, 3 keyframes + Interpolation: Linear, DefaultConstructed, Constant + Track 1: Rotation2D @ CubicHermite2D -> Vector2, 3 keyframes + Interpolation: Constant, Extrapolated, Extrapolated +Animation 1: Custom track duration and interpolator function + Duration: {0.1, 1.3} (0.1 kB, ExternallyOwned) + Track 0: Scaling3D @ Vector3, 5 keyframes + Duration: {0.75, 1.25} + Interpolation: Custom, DefaultConstructed, Constant +Total animation data size: 0.2 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt new file mode 100644 index 000000000..7455a53f2 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt @@ -0,0 +1,3 @@ +1D image 0: + Level 0: {1024} @ R32F (4.0 kB) +Total image data size: 4.0 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt new file mode 100644 index 000000000..7942af9c9 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt @@ -0,0 +1,8 @@ +Light 0: + Type: Spot, 55° - 85° + Color: {0.203922, 0.341176, 1} * 15 + Attenuation: {1.2, 0.3, 0.04} + Range: 100 +Light 1: Directional light with always-implicit attenuation and range + Type: Directional + Color: {1, 0.341176, 0.203922} * 5 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt new file mode 100644 index 000000000..35175acc6 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt @@ -0,0 +1,28 @@ +Material 0: + Type: PbrMetallicRoughness + Base layer: + BaseColor @ Vector4: {0.231373, 0.823529, 0.403922, 0.6} + DoubleSided @ Bool: true + EmissiveColor @ Vector3: {0.054902, 0.619608, 0.792157} + RoughnessTexture @ UnsignedInt: 67 + RoughnessTextureMatrix @ Matrix3x3: {1, 0, 0.25, + 0, 1, 0.75, + 0, 0, 1} + RoughnessTextureSwizzle @ TextureSwizzle: B + deadBeef @ Pointer: 0xdeadbeef + notAColour3 @ Vector3: {0.2, 0.3, 0.4} + notAColour4 @ Vector4: {0.1, 0.2, 0.3, 0.4} + reflectionAngle @ Deg: 35 + undeadBeef @ MutablePointer: 0xbeefbeef +Material 1: Lots o' laierz + Type: Phong|PbrClearCoat + Base layer: + DiffuseColor @ Vector4: {0.780392, 0.811765, 0.184314, 0.6} + Layer 1: ClearCoat + LayerFactor @ Float: 0.5 + LayerFactorTexture @ UnsignedInt: 3 + Layer 2: anEmptyLayer + Layer 3: + LayerFactor @ Float: 0.25 + LayerFactorTexture @ UnsignedInt: 2 + yes @ String: a string diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt new file mode 100644 index 000000000..e5195e9f7 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt @@ -0,0 +1,21 @@ +Mesh 0: + Level 0: 2 vertices @ Lines (0.2 kB, {}) + Position @ Vector3, offset 0, stride 12 + Bounds: ({0.1, -0.1, -0.2}, {0.2, 0, 0.2}) + Tangent @ Vector3, offset 24, stride 12 + Bounds: ({0.2, -0.2, 0.2}, {0.3, 0.8, 0.8}) + Bitangent @ Vector3, offset 48, stride 12 + Bounds: ({0.3, 0.2, 0}, {0.4, 0.9, 1}) + ObjectId @ UnsignedShort, offset 72, stride 2 + Bounds: (12, 155) + Normal @ Vector3, offset 76, stride 12 + Bounds: ({0, 0, 0}, {1, 1, 1}) + TextureCoordinates @ Vector2, offset 100, stride 8 + Bounds: ({0.5, 0.5}, {1.5, 0.5}) + Color @ Vector4, offset 116, stride 16 + Bounds: ({0.6, 0.2, 0.2, 0}, {1, 0.4, 0.4, 0.2}) + ObjectId @ UnsignedInt, offset 148, stride 4 + Bounds: (15, 337) + 3 indices @ UnsignedByte, offset 0, stride 1 (0.0 kB, {}) + Bounds: (3, 176) +Total mesh data size: 0.2 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt new file mode 100644 index 000000000..b96fb94c9 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt @@ -0,0 +1,16 @@ +Mesh 0: + Level 0: 50 vertices @ Points (0.6 kB, ExternallyOwned|Mutable) + Position @ Vector3, offset 0, stride 12 + 70 indices @ UnsignedShort, offset 0, stride 2 (0.1 kB, ExternallyOwned) +Mesh 1: LODs? No, meshets. + Level 0: 250 vertices @ Triangles (6.8 kB) + Position @ Vector3, offset 0, stride 12 + Tangent @ Vector4, offset 3000, stride 16 + Level 1: 135 vertices @ Meshlets (83.8 kB) + Custom(25:vertices) @ UnsignedInt[64], offset 0, stride 256 + Custom(26:triangles) @ Vector3ub[126], offset 34560, stride 378 + Custom(37:) @ UnsignedByte, offset 85590, stride 1 + Custom(116:vertexCount) @ UnsignedByte, offset 85725, stride 1 +Mesh 2: + Level 0: 15 vertices @ Instances (0.0 kB) +Total mesh data size: 91.4 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt new file mode 100644 index 000000000..89a64cfb8 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt @@ -0,0 +1,5 @@ +Object 0: Parent-less mesh +Object 2: Two meshes, shared among two scenes +Object 4: Two custom arrays +Object 6: Only in the second scene, but no fields, thus same as unreferenced +Object 8: Not in any scene diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt new file mode 100644 index 000000000..1cda5540b --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt @@ -0,0 +1,98 @@ +Scene 0: + Bound: 2 objects @ UnsignedInt (0.1 kB, {}) + Fields: + Transformation @ Matrix4x4, 0 entries + Mesh @ UnsignedInt, 4 entries + MeshMaterial @ Int, 4 entries + Light @ UnsignedInt, 4 entries + Skin @ UnsignedInt, 4 entries +Scene 1: + Bound: 4 objects @ UnsignedInt (0.0 kB, {}) + Fields: + Transformation @ Matrix3x3, 0 entries + Mesh @ UnsignedInt, 3 entries + Skin @ UnsignedInt, 3 entries +Total scene data size: 0.1 kB +Object 0 (referenced by 1 scenes): + Fields: Mesh, MeshMaterial, Light, Skin +Object 1 (referenced by 2 scenes): + Fields: Mesh[2], MeshMaterial[2], Light[2], Skin[2], Mesh, Skin +Object 2 (referenced by 0 scenes): Not referenced +Object 3 (referenced by 1 scenes): + Fields: Mesh, Skin +2D skin 0 (referenced by 1 objects): + 2 joints +2D skin 1 (referenced by 1 objects): + 3 joints +2D skin 2 (referenced by 0 objects): Not referenced + 1 joints +3D skin 0 (referenced by 0 objects): Not referenced + 2 joints +3D skin 1 (referenced by 2 objects): + 1 joints +3D skin 2 (referenced by 1 objects): + 3 joints +Light 0 (referenced by 2 objects): + Type: Directional + Color: {0.341176, 1, 0.203922} * 5 +Light 1 (referenced by 0 objects): Not referenced + Type: Ambient + Color: {1, 0.341176, 0.203922} * 0.1 +Light 2 (referenced by 1 objects): + Type: Directional + Color: {0.203922, 0.341176, 1} +Material 0 (referenced by 2 objects): + Type: {} + Base layer: + BaseColorTexture @ UnsignedInt: 2 + DiffuseTexture @ UnsignedInt: 2 +Material 1 (referenced by 1 objects): + Type: {} + Base layer: + EmissiveTexture @ UnsignedInt: 4 + NormalTexture @ UnsignedInt: 17 + lookupTexture @ UnsignedInt: 0 + volumeTexture @ UnsignedInt: 3 +Material 2 (referenced by 0 objects): Not referenced + Type: {} + Base layer: +Mesh 0 (referenced by 2 objects): + Level 0: 5 vertices @ Points (0.0 kB) +Mesh 1 (referenced by 0 objects): Not referenced + Level 0: 4 vertices @ Lines (0.0 kB) +Mesh 2 (referenced by 3 objects): + Level 0: 4 vertices @ TriangleFan (0.0 kB) +Total mesh data size: 0.0 kB +Texture 0 (referenced by 1 material attributes): + Type: Texture1D, image 1 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 1 (referenced by 0 material attributes): Not referenced + Type: Texture1DArray, image 225 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 2 (referenced by 2 material attributes): + Type: Texture2D, image 0 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 3 (referenced by 1 material attributes): + Type: Texture3D, image 1 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 4 (referenced by 1 material attributes): + Type: Texture2D, image 0 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +1D image 0 (referenced by 0 textures): Not referenced + Level 0: {1} @ RGBA8I (0.0 kB) +1D image 1 (referenced by 1 textures): + Level 0: {4} @ R8I (0.0 kB) +2D image 0 (referenced by 2 textures): + Level 0: {1, 2} @ RGBA8I (0.0 kB) +2D image 1 (referenced by 0 textures): Not referenced + Level 0: {4, 1} @ R8I (0.0 kB) +3D image 0 (referenced by 0 textures): Not referenced + Level 0: {1, 2, 1} @ RGBA8I (0.0 kB) +3D image 1 (referenced by 1 textures): + Level 0: {4, 1, 1} @ R8I (0.0 kB) +Total image data size: 0.0 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt new file mode 100644 index 000000000..781d20482 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt @@ -0,0 +1,25 @@ +Scene 0: A simple scene + Bound: 4 objects @ UnsignedInt (0.1 kB) + Fields: + Parent @ Int, 3 entries + Mesh @ UnsignedInt, OrderedMapping, 4 entries +Scene 1: + Bound: 8 objects @ UnsignedByte (0.0 kB, ExternallyOwned|Mutable) + Fields: + Custom(42:) @ Double, 2 entries + Custom(1337:DirectionVector) @ Short[3], 3 entries +Total scene data size: 0.1 kB +Object 0 (referenced by 1 scenes): Parent-less mesh + Fields: Mesh +Object 1 (referenced by 1 scenes): + Fields: Parent, Mesh +Object 2 (referenced by 2 scenes): Two meshes, shared among two scenes + Fields: Parent, Mesh[2], Custom(1337:DirectionVector) +Object 3 (referenced by 2 scenes): + Fields: Parent, Custom(42:) +Object 4 (referenced by 1 scenes): Two custom arrays + Fields: Custom(1337:DirectionVector)[2] +Object 6 (referenced by 0 scenes): Only in the second scene, but no fields, thus same as unreferenced +Object 7 (referenced by 1 scenes): + Fields: Custom(42:) +Object 8 (referenced by 0 scenes): Not in any scene diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt new file mode 100644 index 000000000..acd050cc3 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt @@ -0,0 +1,11 @@ +Scene 0: A simple scene + Bound: 4 objects @ UnsignedInt (0.1 kB) + Fields: + Parent @ Int, 3 entries + Mesh @ UnsignedInt, OrderedMapping, 4 entries +Scene 1: + Bound: 8 objects @ UnsignedByte (0.0 kB, ExternallyOwned|Mutable) + Fields: + Custom(42:) @ Double, 2 entries + Custom(1337:DirectionVector) @ Short[3], 3 entries +Total scene data size: 0.1 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt new file mode 100644 index 000000000..e8eb3a983 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt @@ -0,0 +1,10 @@ +2D skin 0: + 5 joints +2D skin 1: Second 2D skin, external data + 15 joints +3D skin 0: First 3D skin, external data + 12 joints +3D skin 1: + 2 joints +3D skin 2: + 1 joints diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt new file mode 100644 index 000000000..0b3afe593 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt @@ -0,0 +1,8 @@ +Texture 0: + Type: Texture1D, image 666 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 1: Name! + Type: Texture2DArray, image 3 + Minification, mipmap and magnification: Linear, Linear, Nearest + Wrapping: {MirroredRepeat, ClampToEdge, ClampToEdge} diff --git a/src/Magnum/SceneTools/Test/configure.h.cmake b/src/Magnum/SceneTools/Test/configure.h.cmake new file mode 100644 index 000000000..dcb0c8113 --- /dev/null +++ b/src/Magnum/SceneTools/Test/configure.h.cmake @@ -0,0 +1,26 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#define SCENETOOLS_TEST_DIR "${SCENETOOLS_TEST_DIR}" diff --git a/src/Magnum/SceneTools/sceneconverter.cpp b/src/Magnum/SceneTools/sceneconverter.cpp index a663d1e90..4dff2f194 100644 --- a/src/Magnum/SceneTools/sceneconverter.cpp +++ b/src/Magnum/SceneTools/sceneconverter.cpp @@ -23,9 +23,7 @@ DEALINGS IN THE SOFTWARE. */ -#include /* std::isupper() */ #include -#include /* sceneFieldNames */ #include #include #include @@ -35,25 +33,16 @@ #include #include -#include "Magnum/PixelFormat.h" -#include "Magnum/Implementation/converterUtilities.h" -#include "Magnum/Math/Color.h" -#include "Magnum/Math/Matrix4.h" -#include "Magnum/Math/FunctionsBatch.h" #include "Magnum/MeshTools/Concatenate.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Transform.h" #include "Magnum/SceneTools/FlattenMeshHierarchy.h" #include "Magnum/Trade/AbstractImporter.h" -#include "Magnum/Trade/AnimationData.h" -#include "Magnum/Trade/LightData.h" -#include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" -#include "Magnum/Trade/SceneData.h" -#include "Magnum/Trade/SkinData.h" -#include "Magnum/Trade/TextureData.h" #include "Magnum/Trade/AbstractSceneConverter.h" -#include "Magnum/Trade/Implementation/converterUtilities.h" + +#include "Magnum/Implementation/converterUtilities.h" +#include "Magnum/SceneTools/Implementation/sceneConverterUtilities.h" namespace Magnum { @@ -225,23 +214,6 @@ using namespace Containers::Literals; namespace { -/** @todo const Array& doesn't work, minmax() would fail to match */ -template Containers::String calculateBounds(Containers::Array&& attribute) { - /** @todo clean up when Debug::toString() exists */ - std::ostringstream out; - Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << Math::minmax(attribute); - return out.str(); -} - -/* Named attribute index from a global index */ -/** @todo some helper for this directly on the MeshData class? */ -UnsignedInt namedAttributeId(const Trade::MeshData& mesh, UnsignedInt id) { - const Trade::MeshAttribute name = mesh.attributeName(id); - for(UnsignedInt i = 0; i != mesh.attributeCount(name); ++i) - if(mesh.attributeId(name, i) == id) return i; - CORRADE_INTERNAL_ASSERT_UNREACHABLE(); -} - bool isInfoRequested(const Utility::Arguments& args) { return args.isSet("info-animations") || args.isSet("info-images") || @@ -403,912 +375,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") /* Print file info, if requested */ if(isInfoRequested(args)) { - struct AnimationInfo { - UnsignedInt animation; - Trade::AnimationData data{{}, {}}; - Containers::String name; - }; - - struct SkinInfo { - UnsignedInt skin; - Trade::SkinData3D data{{}, {}}; - Containers::String name; - }; - - struct LightInfo { - UnsignedInt light; - Trade::LightData data{{}, {}, {}}; - Containers::String name; - }; - - struct MaterialInfo { - UnsignedInt material; - Trade::MaterialData data{{}, {}}; - Containers::String name; - }; - - struct TextureInfo { - UnsignedInt texture; - Trade::TextureData data{{}, {}, {}, {}, {}, {}}; - Containers::String name; - }; - - struct MeshAttributeInfo { - std::size_t offset; - Int stride; - UnsignedInt arraySize; - Trade::MeshAttribute name; - Containers::String customName; - VertexFormat format; - Containers::String bounds; - }; - - struct MeshInfo { - UnsignedInt mesh, level; - MeshPrimitive primitive; - UnsignedInt indexCount, vertexCount; - std::size_t indexOffset; - Int indexStride; - Containers::String indexBounds; - MeshIndexType indexType; - Containers::Array attributes; - std::size_t indexDataSize, vertexDataSize; - Trade::DataFlags indexDataFlags, vertexDataFlags; - Containers::String name; - }; - - struct SceneFieldInfo { - Trade::SceneField name; - Trade::SceneFieldFlags flags; - Trade::SceneFieldType type; - UnsignedInt arraySize; - std::size_t size; - }; - - struct SceneInfo { - UnsignedInt scene; - Trade::SceneMappingType mappingType; - UnsignedLong mappingBound; - Containers::Array fields; - std::size_t dataSize; - Trade::DataFlags dataFlags; - Containers::String name; - }; - - struct ObjectInfo { - UnsignedLong object; - /* A bitfield, assuming no more than 32 scenes */ - /** @todo might be too little? */ - UnsignedInt scenes; - Containers::Array> fields; - Containers::String name; - }; - - /* Parse everything first to avoid errors interleaved with output */ - bool error = false; - - /* Object properties */ - Containers::Array objectInfos; - if(args.isSet("info") || args.isSet("info-objects")) { - objectInfos = Containers::Array{std::size_t(importer->objectCount())}; - - for(UnsignedLong i = 0; i != importer->objectCount(); ++i) { - objectInfos[i].object = i; - objectInfos[i].name = importer->objectName(i); - } - } - - /* Scene properties, together with counting how much is each mesh / - light / material / skin / object referenced (which gets used only if - both --info-scenes and --info-{lights,materials,skins,objects} is - passed and the file has at least one scene). Texture reference count - is calculated when parsing materials. */ - Containers::Array sceneInfos; - /* Only the very latest GCC seems to support enum classes as keys and - I can't be bothered to write a std::hash specialization, so just - making the key typeless */ - std::unordered_map sceneFieldNames; - Containers::Array materialReferenceCount; - Containers::Array lightReferenceCount; - Containers::Array meshReferenceCount; - Containers::Array skinReferenceCount; - if((args.isSet("info") || args.isSet("info-scenes")) && importer->sceneCount()) { - materialReferenceCount = Containers::Array{importer->materialCount()}; - lightReferenceCount = Containers::Array{importer->lightCount()}; - meshReferenceCount = Containers::Array{importer->meshCount()}; - skinReferenceCount = Containers::Array{importer->skin3DCount()}; - - for(UnsignedInt i = 0; i != importer->sceneCount(); ++i) { - Containers::Optional scene = importer->scene(i); - if(!scene) { - error = true; - continue; - } - - SceneInfo info{}; - info.scene = i; - info.mappingType = scene->mappingType(); - info.mappingBound = scene->mappingBound(); - info.dataSize = scene->data().size(); - info.dataFlags = scene->dataFlags(); - info.name = importer->sceneName(i); - for(UnsignedInt j = 0; j != scene->fieldCount(); ++j) { - const Trade::SceneField name = scene->fieldName(j); - - if(name == Trade::SceneField::Mesh) for(const Containers::Pair>& meshMaterial: scene->meshesMaterialsAsArray()) { - if(meshMaterial.second().first() < meshReferenceCount.size()) - ++meshReferenceCount[meshMaterial.second().first()]; - if(UnsignedInt(meshMaterial.second().second()) < materialReferenceCount.size()) - ++materialReferenceCount[meshMaterial.second().second()]; - } - - if(name == Trade::SceneField::Skin) for(const Containers::Pair skin: scene->skinsAsArray()) { - if(skin.second() < skinReferenceCount.size()) - ++skinReferenceCount[skin.second()]; - /** @todo 2D/3D distinction */ - } - - if(name == Trade::SceneField::Light) for(const Containers::Pair& light: scene->lightsAsArray()) { - if(light.second() < lightReferenceCount.size()) - ++lightReferenceCount[light.second()]; - } - - arrayAppend(info.fields, InPlaceInit, - name, - scene->fieldFlags(j), - scene->fieldType(j), - scene->fieldArraySize(j), - scene->fieldSize(j)); - - /* If the field has a custom name, save it into the map. - Not putting it into the fields array as the map is - reused by object info as well. */ - if(Trade::isSceneFieldCustom(name)) { - /* Fetch the name only if it's not already there */ - const auto inserted = sceneFieldNames.emplace(sceneFieldCustom(name), Containers::String{}); - if(inserted.second) - inserted.first->second = importer->sceneFieldName(name); - } - - if(objectInfos) for(const UnsignedInt object: scene->mappingAsArray(j)) { - if(object >= objectInfos.size()) continue; - - objectInfos[object].object = object; - objectInfos[object].scenes |= 1 << i; - - /* If the field is repeated, increase the count - instead */ - if(!objectInfos[object].fields.isEmpty() && objectInfos[object].fields.back().first() == name) - ++objectInfos[object].fields.back().second(); - else - arrayAppend(objectInfos[object].fields, InPlaceInit, name, 1u); - } - } - - arrayAppend(sceneInfos, std::move(info)); - } - } - - /* Animation properties */ - Containers::Array animationInfos; - if(args.isSet("info") || args.isSet("info-animations")) for(UnsignedInt i = 0; i != importer->animationCount(); ++i) { - Containers::Optional animation; - { - Trade::Implementation::Duration d{importTime}; - if(!(animation = importer->animation(i))) { - error = true; - continue; - } - } - - AnimationInfo info{}; - info.animation = i; - info.name = importer->animationName(i); - info.data = *std::move(animation); - - arrayAppend(animationInfos, std::move(info)); - } - - /* Skin properties */ - Containers::Array skinInfos; - if(args.isSet("info") || args.isSet("info-skins")) for(UnsignedInt i = 0; i != importer->skin3DCount(); ++i) { - Containers::Optional skin; - { - Trade::Implementation::Duration d{importTime}; - if(!(skin = importer->skin3D(i))) { - error = true; - continue; - } - } - - SkinInfo info{}; - info.skin = i; - info.name = importer->skin3DName(i); - info.data = *std::move(skin); - - arrayAppend(skinInfos, std::move(info)); - } - - /* Light properties */ - Containers::Array lightInfos; - if(args.isSet("info") || args.isSet("info-lights")) for(UnsignedInt i = 0; i != importer->lightCount(); ++i) { - Containers::Optional light; - { - Trade::Implementation::Duration d{importTime}; - if(!(light = importer->light(i))) { - error = true; - continue; - } - } - - LightInfo info{}; - info.light = i; - info.name = importer->lightName(i); - info.data = *std::move(light); - - arrayAppend(lightInfos, std::move(info)); - } - - /* Material properties, together with how much is each texture shared - (which gets used only if both --info-materials and --info-textures - is passed and the file has at least one material). */ - Containers::Array materialInfos; - Containers::Array textureReferenceCount; - if((args.isSet("info") || args.isSet("info-materials")) && importer->materialCount()) { - textureReferenceCount = Containers::Array{importer->textureCount()}; - - for(UnsignedInt i = 0; i != importer->materialCount(); ++i) { - Containers::Optional material; - { - Trade::Implementation::Duration d{importTime}; - if(!(material = importer->material(i))) { - error = true; - continue; - } - } - - /* Calculate texture reference count for all properties that - look like a texture */ - for(UnsignedInt j = 0; j != material->layerCount(); ++j) { - for(UnsignedInt k = 0; k != material->attributeCount(j); ++k) { - if(material->attributeType(j, k) != Trade::MaterialAttributeType::UnsignedInt || !Utility::String::endsWith(material->attributeName(j, k), "Texture")) - continue; - - const UnsignedInt texture = material->attribute(j, k); - /** @todo once StridedBitArrayView2D exists, fix this - to count each material only once by having one bit - for every material and texture */ - if(texture < textureReferenceCount.size()) - ++textureReferenceCount[texture]; - } - } - - MaterialInfo info{}; - info.material = i; - info.name = importer->materialName(i); - info.data = *std::move(material); - - arrayAppend(materialInfos, std::move(info)); - } - } - - /* Mesh properties */ - Containers::Array meshInfos; - if(args.isSet("info") || args.isSet("info-meshes")) for(UnsignedInt i = 0; i != importer->meshCount(); ++i) { - for(UnsignedInt j = 0; j != importer->meshLevelCount(i); ++j) { - Containers::Optional mesh; - { - Trade::Implementation::Duration d{importTime}; - if(!(mesh = importer->mesh(i, j))) { - error = true; - continue; - } - } - - MeshInfo info{}; - info.mesh = i; - info.level = j; - info.primitive = mesh->primitive(); - info.vertexCount = mesh->vertexCount(); - info.vertexDataSize = mesh->vertexData().size(); - info.vertexDataFlags = mesh->vertexDataFlags(); - if(!j) { - info.name = importer->meshName(i); - } - if(mesh->isIndexed()) { - info.indexCount = mesh->indexCount(); - info.indexType = mesh->indexType(); - info.indexOffset = mesh->indexOffset(); - info.indexStride = mesh->indexStride(); - info.indexDataSize = mesh->indexData().size(); - info.indexDataFlags = mesh->indexDataFlags(); - if(args.isSet("bounds")) - info.indexBounds = calculateBounds(mesh->indicesAsArray()); - } - for(UnsignedInt k = 0; k != mesh->attributeCount(); ++k) { - const Trade::MeshAttribute name = mesh->attributeName(k); - - /* Calculate bounds, if requested, if this is not an - implementation-specific format and if it's not a custom - attribute */ - Containers::String bounds; - if(args.isSet("bounds") && !isVertexFormatImplementationSpecific(mesh->attributeFormat(k))) switch(name) { - case Trade::MeshAttribute::Position: - bounds = calculateBounds(mesh->positions3DAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::Tangent: - bounds = calculateBounds(mesh->tangentsAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::Bitangent: - bounds = calculateBounds(mesh->bitangentsAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::Normal: - bounds = calculateBounds(mesh->normalsAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::TextureCoordinates: - bounds = calculateBounds(mesh->textureCoordinates2DAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::Color: - bounds = calculateBounds(mesh->colorsAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::ObjectId: - Debug{} << mesh->objectIdsAsArray(namedAttributeId(*mesh, k)); - bounds = calculateBounds(mesh->objectIdsAsArray(namedAttributeId(*mesh, k))); - break; - } - - arrayAppend(info.attributes, InPlaceInit, - mesh->attributeOffset(k), - mesh->attributeStride(k), - mesh->attributeArraySize(k), - name, Trade::isMeshAttributeCustom(name) ? - importer->meshAttributeName(name) : "", - mesh->attributeFormat(k), - bounds); - } - - arrayAppend(meshInfos, std::move(info)); - } - } - - /* Texture properties, together with how much is each image shared - (which gets used only if both --info-textures and --info-images is - passed and the file has at least one texture). */ - Containers::Array textureInfos; - Containers::Array image1DReferenceCount; - Containers::Array image2DReferenceCount; - Containers::Array image3DReferenceCount; - if((args.isSet("info") || args.isSet("info-textures")) && importer->textureCount()) { - image1DReferenceCount = Containers::Array{importer->image1DCount()}; - image2DReferenceCount = Containers::Array{importer->image2DCount()}; - image3DReferenceCount = Containers::Array{importer->image3DCount()}; - for(UnsignedInt i = 0; i != importer->textureCount(); ++i) { - Containers::Optional texture; - { - Trade::Implementation::Duration d{importTime}; - if(!(texture = importer->texture(i))) { - error = true; - continue; - } - } - - switch(texture->type()) { - case Trade::TextureType::Texture1D: - if(texture->image() < image1DReferenceCount.size()) - ++image1DReferenceCount[texture->image()]; - break; - case Trade::TextureType::Texture1DArray: - case Trade::TextureType::Texture2D: - if(texture->image() < image2DReferenceCount.size()) - ++image2DReferenceCount[texture->image()]; - break; - case Trade::TextureType::CubeMap: - case Trade::TextureType::CubeMapArray: - case Trade::TextureType::Texture2DArray: - case Trade::TextureType::Texture3D: - if(texture->image() < image3DReferenceCount.size()) - ++image3DReferenceCount[texture->image()]; - break; - } - - TextureInfo info{}; - info.texture = i; - info.name = importer->textureName(i); - info.data = *std::move(texture); - - arrayAppend(textureInfos, std::move(info)); - } - } - - - Containers::Array imageInfos; - if(args.isSet("info") || args.isSet("info-images")) { - imageInfos = Trade::Implementation::imageInfo(*importer, error, importTime); - } - - std::size_t totalSceneDataSize = 0; - for(const SceneInfo& info: sceneInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Scene" << info.scene << Debug::nospace << ":" << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; - d << Debug::newline; - d << " Bound:" << info.mappingBound << "objects" - << Debug::color(Debug::Color::Blue) << "@" << Debug::packed - << Debug::color(Debug::Color::Cyan) << info.mappingType - << Debug::resetColor << "(" << Debug::nospace - << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; - if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Green) << info.dataFlags - << Debug::resetColor; - d << Debug::nospace << ")"; - - d << Debug::newline << " Fields:"; - for(const SceneFieldInfo& field: info.fields) { - d << Debug::newline << " " - << Debug::boldColor(Debug::Color::Default); - if(Trade::isSceneFieldCustom(field.name)) { - d << "Custom(" << Debug::nospace - << Trade::sceneFieldCustom(field.name) - << Debug::nospace << ":" << Debug::nospace - << Debug::color(Debug::Color::Yellow) - << sceneFieldNames[sceneFieldCustom(field.name)] - << Debug::nospace - << Debug::boldColor(Debug::Color::Default) << ")"; - } else d << Debug::packed << field.name; - - d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << field.type; - if(field.arraySize) - d << Debug::nospace << Utility::format("[{}]", field.arraySize); - d << Debug::resetColor; - if(field.flags) d << Debug::nospace << ", flags:" - << Debug::packed << Debug::color(Debug::Color::Green) - << field.flags << Debug::resetColor; - d << Debug::nospace << "," << field.size << "entries"; - } - - totalSceneDataSize += info.dataSize; - } - if(!sceneInfos.isEmpty()) - Debug{} << "Total scene data size:" << Utility::format("{:.1f}", totalSceneDataSize/1024.0f) << "kB"; - - for(const ObjectInfo& info: objectInfos) { - /* Objects without a name and not referenced by any scenes are - useless, ignore */ - if(!info.name && !info.scenes) continue; - - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Object" << info.object << Debug::resetColor; - - if(sceneInfos) { - const UnsignedInt count = Math::popcount(info.scenes); - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "scenes)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - if(info.scenes) { - d << Debug::newline << " Fields:"; - - for(std::size_t i = 0; i != info.fields.size(); ++i) { - if(i) d << Debug::nospace << ","; - const Containers::Pair nameCount = info.fields[i]; - d << Debug::color(Debug::Color::Cyan); - if(Trade::isSceneFieldCustom(nameCount.first())) { - d << "Custom(" << Debug::nospace - << Trade::sceneFieldCustom(nameCount.first()) - << Debug::nospace << ":" << Debug::nospace - << Debug::color(Debug::Color::Yellow) - << sceneFieldNames[sceneFieldCustom(nameCount.first())] - << Debug::nospace - << Debug::color(Debug::Color::Cyan) << ")"; - } else d << Debug::packed << nameCount.first(); - if(nameCount.second() != 1) - d << Debug::nospace << Utility::format("[{}]", nameCount.second()); - d << Debug::resetColor; - } - } - } - - std::size_t totalAnimationDataSize = 0; - for(const AnimationInfo& info: animationInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Animation" << info.animation << Debug::nospace << ":" << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; - - d << Debug::newline << " Duration:" << info.data.duration() - << "(" << Debug::nospace << Utility::format("{:.1f}", info.data.data().size()/1024.0f) << "kB"; - if(info.data.dataFlags() != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Green) - << info.data.dataFlags() << Debug::resetColor; - d << Debug::nospace << ")"; - - for(UnsignedInt i = 0; i != info.data.trackCount(); ++i) { - d << Debug::newline << " Track" << i << Debug::nospace << ":" - << Debug::packed << Debug::boldColor(Debug::Color::Default) - << info.data.trackTargetType(i) - << Debug::color(Debug::Color::Blue) << "@" - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.trackType(i) << Debug::resetColor; - if(info.data.trackType(i) != info.data.trackResultType(i)) - d << Debug::color(Debug::Color::Blue) << "->" - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.trackResultType(i) << Debug::resetColor; - d << Debug::nospace << "," << info.data.track(i).size() - << "keyframes"; - if(info.data.track(i).duration() != info.data.duration()) - d << Debug::nospace << "," << info.data.track(i).duration(); - d << Debug::newline - << " Interpolation:" - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.track(i).interpolation() << Debug::resetColor - << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Cyan) - << info.data.track(i).before() << Debug::resetColor - << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Cyan) - << info.data.track(i).after() << Debug::resetColor; - /** @todo might be useful to show bounds here as well, though - not so much for things like complex numbers or quats */ - } - - totalAnimationDataSize += info.data.data().size(); - } - if(!animationInfos.isEmpty()) - Debug{} << "Total animation data size:" << Utility::format("{:.1f}", totalAnimationDataSize/1024.0f) << "kB"; - - for(const SkinInfo& info: skinInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Skin" << info.skin - << Debug::resetColor; - - /* Print reference count only if there actually are scenes and they - were parsed, otherwise this information is useless */ - if(skinReferenceCount) { - const UnsignedInt count = skinReferenceCount[info.skin]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "objects)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - - d << Debug::newline << " " << info.data.joints().size() << "joints"; - } - - for(const LightInfo& info: lightInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Light" << info.light << Debug::resetColor; - - /* Print reference count only if there actually are scenes and they - were parsed, otherwise this information is useless */ - if(lightReferenceCount) { - const UnsignedInt count = lightReferenceCount[info.light]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "objects)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - - d << Debug::newline << " Type:" << Debug::packed - << Debug::color(Debug::Color::Cyan) - << info.data.type() << Debug::resetColor; - if(info.data.type() == Trade::LightData::Type::Spot) - d << Debug::nospace << "," << Debug::packed - << Deg(info.data.innerConeAngle()) << Debug::nospace - << "° -" << Debug::packed << Deg(info.data.outerConeAngle()) - << Debug::nospace << "°"; - d << Debug::newline << " Color:"; - if(useColor24) d << Debug::color - << Math::pack(info.data.color()); - d << Debug::packed << info.data.color(); - if(!Math::equal(info.data.intensity(), 1.0f)) - d << "*" << info.data.intensity(); - d << Debug::newline << " Attenuation:" << Debug::packed - << info.data.attenuation(); - if(info.data.range() != Constants::inf()) - d << Debug::newline << " Range:" << Debug::packed - << info.data.range(); - } - - for(const MaterialInfo& info: materialInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Material" << info.material << Debug::resetColor; - - /* Print reference count only if there actually are scenes and they - were parsed, otherwise this information is useless */ - if(materialReferenceCount) { - const UnsignedInt count = materialReferenceCount[info.material]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "objects)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; - - d << Debug::newline << " Type:" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.data.types() << Debug::resetColor; - - for(UnsignedInt i = 0; i != info.data.layerCount(); ++i) { - /* Print extra layers with extra indent */ - const char* indent; - if(info.data.layerCount() != 1 && i != 0) { - d << Debug::newline << " Layer" << i << Debug::nospace << ":"; - if(!info.data.layerName(i).isEmpty()) { - if(std::isupper(info.data.layerName(i)[0])) - d << Debug::boldColor(Debug::Color::Default); - else - d << Debug::color(Debug::Color::Yellow); - d << info.data.layerName(i) << Debug::resetColor; - } - indent = " "; - } else { - d << Debug::newline << " Base layer:"; - indent = " "; - } - - for(UnsignedInt j = 0; j != info.data.attributeCount(i); ++j) { - /* Ignore layer name (which is always first) unless it's in - the base material, in which case we print it as it - wouldn't otherwise be shown anywhere */ - if(i && !j && info.data.attributeName(i, j) == " LayerName") - continue; - - d << Debug::newline << indent; - if(std::isupper(info.data.attributeName(i, j)[0])) - d << Debug::boldColor(Debug::Color::Default); - else - d << Debug::color(Debug::Color::Yellow); - d << info.data.attributeName(i, j) << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.attributeType(i, j) << Debug::resetColor << Debug::nospace - << ":"; - switch(info.data.attributeType(i, j)) { - case Trade::MaterialAttributeType::Bool: - d << info.data.attribute(i, j); - break; - #define _c(type) case Trade::MaterialAttributeType::type: \ - d << Debug::packed << info.data.attribute(i, j); \ - break; - _c(Float) - _c(Deg) - _c(Rad) - _c(UnsignedInt) - _c(Int) - _c(UnsignedLong) - _c(Long) - _c(Vector2) - _c(Vector2ui) - _c(Vector2i) - case Trade::MaterialAttributeType::Vector3: - /** @todo hasSuffix() might be more robust against - false positives, but KHR_materials_specular in - glTF uses ColorFactor :/ */ - if(useColor24 && info.data.attributeName(i, j).contains("Color"_s)) - d << Debug::color << Math::pack(info.data.attribute(i, j)); - d << Debug::packed << info.data.attribute(i, j); - break; - _c(Vector3ui) - _c(Vector3i) - case Trade::MaterialAttributeType::Vector4: - /** @todo hasSuffix() might be more robust against - false positives, but KHR_materials_specular in - glTF uses ColorFactor :/ */ - if(useColor24 && info.data.attributeName(i, j).contains("Color"_s)) - d << Debug::color << Math::pack(info.data.attribute(i, j).rgb()); - d << Debug::packed << info.data.attribute(i, j); - break; - _c(Vector4ui) - _c(Vector4i) - _c(Matrix2x2) - _c(Matrix2x3) - _c(Matrix2x4) - _c(Matrix3x2) - _c(Matrix3x3) - _c(Matrix3x4) - _c(Matrix4x2) - _c(Matrix4x3) - #undef _c - case Trade::MaterialAttributeType::Pointer: - d << info.data.attribute(i, j); - break; - case Trade::MaterialAttributeType::MutablePointer: - d << info.data.attribute(i, j); - break; - case Trade::MaterialAttributeType::String: - d << info.data.attribute(i, j); - break; - case Trade::MaterialAttributeType::TextureSwizzle: - d << Debug::packed << info.data.attribute(i, j); - break; - } - } - } - } - - std::size_t totalMeshDataSize = 0; - for(const MeshInfo& info: meshInfos) { - Debug d{useColor}; - if(info.level == 0) { - d << Debug::boldColor(Debug::Color::Default) << "Mesh" << info.mesh << Debug::resetColor; - - /* Print reference count only if there actually are scenes and - they were parsed, otherwise this information is useless */ - if(meshReferenceCount) { - const UnsignedInt count = meshReferenceCount[info.mesh]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "objects)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; - d << Debug::newline; - } - d << " Level" << info.level << Debug::nospace << ":" - << info.vertexCount << "vertices" << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.primitive << Debug::resetColor << "(" << Debug::nospace - << Utility::format("{:.1f}", info.vertexDataSize/1024.0f) - << "kB"; - if(info.vertexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << ", flags:" << Debug::packed - << Debug::color(Debug::Color::Green) - << info.vertexDataFlags << Debug::resetColor; - d << Debug::nospace << ")"; - - for(const MeshAttributeInfo& attribute: info.attributes) { - d << Debug::newline << " " - << Debug::boldColor(Debug::Color::Default); - if(Trade::isMeshAttributeCustom(attribute.name)) { - d << "Custom(" << Debug::nospace - << Trade::meshAttributeCustom(attribute.name) - << Debug::nospace << ":" << Debug::nospace - << Debug::color(Debug::Color::Yellow) - << attribute.customName << Debug::nospace - << Debug::boldColor(Debug::Color::Default) << ")"; - } else d << Debug::packed << attribute.name; - - d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << attribute.format; - if(attribute.arraySize) - d << Debug::nospace << Utility::format("[{}]", attribute.arraySize); - d << Debug::resetColor; - d << Debug::nospace << ", offset" << attribute.offset; - d << Debug::nospace << ", stride" - << attribute.stride; - if(attribute.bounds) - d << Debug::newline << " bounds:" << attribute.bounds; - } - - if(info.indexType != MeshIndexType{}) { - d << Debug::newline << " " << info.indexCount << "indices" << Debug::color(Debug::Color::Blue) << "@" - << Debug::packed << Debug::color(Debug::Color::Cyan) << info.indexType << Debug::resetColor << Debug::nospace << ", offset" << info.indexOffset << Debug::nospace << ", stride" << info.indexStride << "(" << Debug::nospace - << Utility::format("{:.1f}", info.indexDataSize/1024.0f) - << "kB"; - if(info.indexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << ", flags:" << Debug::packed - << Debug::color(Debug::Color::Green) << info.indexDataFlags << Debug::resetColor; - d << Debug::nospace << ")"; - if(info.indexBounds) - d << Debug::newline << " bounds:" << info.indexBounds; - } - - totalMeshDataSize += info.vertexDataSize + info.indexDataSize; - } - if(!meshInfos.isEmpty()) - Debug{} << "Total mesh data size:" << Utility::format("{:.1f}", totalMeshDataSize/1024.0f) << "kB"; - - for(const TextureInfo& info: textureInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Texture" << info.texture << Debug::resetColor; - - /* Print reference count only if there actually are materials and - they were parsed, otherwise this information is useless */ - if(textureReferenceCount) { - const UnsignedInt count = textureReferenceCount[info.texture]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "material attributes)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - d << Debug::newline; - d << " Type:" - << Debug::packed - << Debug::color(Debug::Color::Cyan) << info.data.type() - << Debug::resetColor << Debug::nospace << ", image" - << info.data.image(); - d << Debug::newline << " Minification, mipmap and magnification:" - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.minificationFilter() << Debug::nospace << "," - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.mipmapFilter() << Debug::nospace << "," - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.magnificationFilter() << Debug::resetColor; - d << Debug::newline << " Wrapping:" << Debug::resetColor << "{" << Debug::nospace - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.wrapping()[0] << Debug::resetColor - << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1] - << Debug::resetColor << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1] - << Debug::resetColor << Debug::nospace << "}"; - } - - std::size_t totalImageDataSize = 0; - for(const Trade::Implementation::ImageInfo& info: imageInfos) { - Debug d{useColor}; - if(info.level == 0) { - d << Debug::boldColor(Debug::Color::Default); - if(info.size.z()) d << "3D image"; - else if(info.size.y()) d << "2D image"; - else d << "1D image"; - d << info.image << Debug::resetColor; - - /* Print reference count only if there actually are textures - and they were parsed otherwise this information is - useless */ - Containers::Optional count; - if(info.size.z() && image3DReferenceCount) { - count = image3DReferenceCount[info.image]; - } else if(info.size.y() && image2DReferenceCount) { - count = image2DReferenceCount[info.image]; - } else if(image1DReferenceCount) { - count = image1DReferenceCount[info.image]; - } - if(count) { - if(!*count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << *count << "textures)"; - if(!*count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - d << Debug::newline; - } - d << " Level" << info.level << Debug::nospace << ":"; - if(info.flags.one) { - d << Debug::packed << Debug::color(Debug::Color::Cyan); - if(info.size.z()) d << info.flags.three; - else if(info.size.y()) d << info.flags.two; - else d << info.flags.one; - d << Debug::resetColor; - } - d << Debug::packed; - if(info.size.z()) d << info.size; - else if(info.size.y()) d << info.size.xy(); - else d << Math::Vector<1, Int>(info.size.x()); - d << Debug::color(Debug::Color::Blue) << "@" << Debug::resetColor; - d << Debug::packed; - if(info.compressed) d << Debug::color(Debug::Color::Yellow) << info.compressedFormat; - else d << Debug::color(Debug::Color::Cyan) << info.format; - d << Debug::resetColor << "(" << Debug::nospace << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; - if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Green) << info.dataFlags - << Debug::resetColor; - d << Debug::nospace << ")"; - - totalImageDataSize += info.dataSize; - } - if(!imageInfos.isEmpty()) - Debug{} << "Total image data size:" << Utility::format("{:.1f}", totalImageDataSize/1024.0f) << "kB"; + const bool error = SceneTools::Implementation::printInfo(useColor, useColor24, args, *importer, importTime); if(args.isSet("profile")) { Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast(importTime).count())/1.0e3f << "seconds"; diff --git a/src/Magnum/Trade/Implementation/converterUtilities.h b/src/Magnum/Trade/Implementation/converterUtilities.h index 6c1d4926f..d6dbcf57d 100644 --- a/src/Magnum/Trade/Implementation/converterUtilities.h +++ b/src/Magnum/Trade/Implementation/converterUtilities.h @@ -28,13 +28,17 @@ #include #include #include +#include +#include +#include "Magnum/PixelFormat.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" namespace Magnum { namespace Trade { namespace Implementation { -/* Used only in executables where we don't want it to be exported */ +/* Used only in executables where we don't want it to be exported -- in + particular magnum-imageconverter, magnum-sceneconverter and their tests */ namespace { struct Duration { @@ -75,7 +79,7 @@ struct ImageInfo { Containers::Array imageInfo(AbstractImporter& importer, bool& error, std::chrono::high_resolution_clock::duration& importTime) { Containers::Array infos; for(UnsignedInt i = 0; i != importer.image1DCount(); ++i) { - const std::string name = importer.image1DName(i); + const Containers::String name = importer.image1DName(i); const UnsignedInt levelCount = importer.image1DLevelCount(i); for(UnsignedInt j = 0; j != levelCount; ++j) { @@ -97,11 +101,11 @@ Containers::Array imageInfo(AbstractImporter& importer, bool& error, image->data().size(), image->dataFlags(), ImageInfoFlags{image->flags()}, - j ? "" : importer.image1DName(i)); + j ? "" : name); } } for(UnsignedInt i = 0; i != importer.image2DCount(); ++i) { - const std::string name = importer.image2DName(i); + const Containers::String name = importer.image2DName(i); const UnsignedInt levelCount = importer.image2DLevelCount(i); for(UnsignedInt j = 0; j != levelCount; ++j) { @@ -127,7 +131,7 @@ Containers::Array imageInfo(AbstractImporter& importer, bool& error, } } for(UnsignedInt i = 0; i != importer.image3DCount(); ++i) { - const std::string name = importer.image3DName(i); + const Containers::String name = importer.image3DName(i); const UnsignedInt levelCount = importer.image3DLevelCount(i); for(UnsignedInt j = 0; j != levelCount; ++j) { @@ -156,6 +160,73 @@ Containers::Array imageInfo(AbstractImporter& importer, bool& error, return infos; } +void printImageInfo(const Debug::Flags useColor, const Containers::ArrayView imageInfos, const Containers::ArrayView image1DReferenceCount, const Containers::ArrayView image2DReferenceCount, const Containers::ArrayView image3DReferenceCount) { + std::size_t totalImageDataSize = 0; + for(const Trade::Implementation::ImageInfo& info: imageInfos) { + Debug d{useColor}; + if(info.level == 0) { + d << Debug::boldColor(Debug::Color::Default); + if(info.size.z()) d << "3D image"; + else if(info.size.y()) d << "2D image"; + else d << "1D image"; + d << info.image << Debug::resetColor; + + /* Print reference count only if there actually are any (i.e., the + arrays are non-empty) otherwise this information is useless */ + Containers::Optional count; + if(info.size.z() && image3DReferenceCount) { + count = image3DReferenceCount[info.image]; + } else if(info.size.y() && image2DReferenceCount) { + count = image2DReferenceCount[info.image]; + } else if(image1DReferenceCount) { + count = image1DReferenceCount[info.image]; + } + if(count) { + if(!*count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << *count << "textures)"; + if(!*count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + d << Debug::newline; + } + d << " Level" << info.level << Debug::nospace << ":"; + if(info.flags.one) { + d << Debug::packed << Debug::color(Debug::Color::Cyan); + if(info.size.z()) d << info.flags.three; + else if(info.size.y()) d << info.flags.two; + else d << info.flags.one; + d << Debug::resetColor; + } + d << Debug::packed; + if(info.size.z()) d << info.size; + else if(info.size.y()) d << info.size.xy(); + /* Kinda unnecessary, but makes the output more consistent if also 1D + size is in {}s */ + else d << Math::Vector<1, Int>(info.size.x()); + d << Debug::color(Debug::Color::Blue) << "@" << Debug::resetColor; + d << Debug::packed; + /* Compressed formats are printed yellow. That kinda conflicts with + custom fields / attributes elsewhere, but is significant enough to + have it highlighted. */ + if(info.compressed) d << Debug::color(Debug::Color::Yellow) << info.compressedFormat; + else d << Debug::color(Debug::Color::Cyan) << info.format; + d << Debug::resetColor << "(" << Debug::nospace << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; + if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) << info.dataFlags + << Debug::resetColor; + d << Debug::nospace << ")"; + + totalImageDataSize += info.dataSize; + } + if(!imageInfos.isEmpty()) + Debug{} << "Total image data size:" << Utility::format("{:.1f}", totalImageDataSize/1024.0f) << "kB"; +} + } }}} diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 967f91aa7..3360dd59c 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -54,6 +54,13 @@ corrade_add_test(TradeAnimationDataTest AnimationDataTest.cpp LIBRARIES MagnumTr corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeFlatMaterialDataTest FlatMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) + +corrade_add_test(TradeImageConverterTest ImageConverterTest.cpp + LIBRARIES MagnumTrade + FILES + ImageConverterTestFiles/info.txt) +target_include_directories(TradeImageConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) diff --git a/src/Magnum/Trade/Test/ImageConverterTest.cpp b/src/Magnum/Trade/Test/ImageConverterTest.cpp new file mode 100644 index 000000000..812caf3aa --- /dev/null +++ b/src/Magnum/Trade/Test/ImageConverterTest.cpp @@ -0,0 +1,192 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include /** @todo remove once Debug is stream-free */ +#include +#include +#include /** @todo remove once Debug is stream-free */ +#include + +#include "Magnum/Trade/Implementation/converterUtilities.h" + +#include "configure.h" + +namespace Magnum { namespace Trade { namespace Test { namespace { + +struct ImageConverterTest: TestSuite::Tester { + explicit ImageConverterTest(); + + void infoImplementation(); + void infoImplementationError(); +}; + +ImageConverterTest::ImageConverterTest() { + addTests({&ImageConverterTest::infoImplementation, + &ImageConverterTest::infoImplementationError}); +} + +void ImageConverterTest::infoImplementation() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + /* Three 1D images, one with two levels and named, one compressed, + one just to not have two of everything */ + UnsignedInt doImage1DCount() const override { return 3; } + UnsignedInt doImage1DLevelCount(UnsignedInt id) override { + return id == 1 ? 2 : 1; + } + Containers::String doImage1DName(UnsignedInt id) override { + return id == 2 ? "Third 1D image just so there aren't two" : ""; + } + Containers::Optional doImage1D(UnsignedInt id, UnsignedInt level) override { + if(id == 0 && level == 0) + return Trade::ImageData1D{CompressedPixelFormat::Astc10x10RGBAF, 1024, Containers::Array{NoInit, 4096}}; + if(id == 1 && level == 0) + return Trade::ImageData1D{PixelFormat::RGBA8Snorm, 16, Containers::Array{NoInit, 64}}; + if(id == 1 && level == 1) + return Trade::ImageData1D{PixelFormat::RGBA8Snorm, 8, Containers::Array{NoInit, 32}}; + if(id == 2 && level == 0) + return Trade::ImageData1D{PixelFormat::Depth16Unorm, 4, Containers::Array{NoInit, 8}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + /* Two 2D images, one with three levels and named, the other compressed + and array */ + UnsignedInt doImage2DCount() const override { return 2; } + UnsignedInt doImage2DLevelCount(UnsignedInt id) override { + return id == 0 ? 3 : 1; + } + Containers::String doImage2DName(UnsignedInt id) override { + return id == 0 ? "A very nice mipmapped 2D image" : ""; + } + Containers::Optional doImage2D(UnsignedInt id, UnsignedInt level) override { + if(id == 0 && level == 0) + return Trade::ImageData2D{PixelFormat::RG16F, {256, 128}, Containers::Array{NoInit, 131072}}; + if(id == 0 && level == 1) + return Trade::ImageData2D{PixelFormat::RG16F, {128, 64}, Containers::Array{NoInit, 32768}}; + if(id == 0 && level == 2) + return Trade::ImageData2D{PixelFormat::RG16F, {64, 32}, Containers::Array{NoInit, 8192}}; + if(id == 1) + return Trade::ImageData2D{CompressedPixelFormat::PvrtcRGB2bppUnorm, {4, 8}, Containers::Array{NoInit, 32}, ImageFlag2D::Array}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + /* One 2D cube map array image, one 3D mipmapped & named and two 2D + array; with one externally owned */ + UnsignedInt doImage3DCount() const override { return 4; } + UnsignedInt doImage3DLevelCount(UnsignedInt id) override { + return id == 1 ? 2 : 1; + } + Containers::String doImage3DName(UnsignedInt id) override { + return id == 1 ? "Volume kills!" : ""; + } + Containers::Optional doImage3D(UnsignedInt id, UnsignedInt level) override { + if(id == 0 && level == 0) + return Trade::ImageData3D{PixelFormat::R8Unorm, {16, 16, 12}, Containers::Array{NoInit, 3072}, ImageFlag3D::CubeMap|ImageFlag3D::Array}; + if(id == 1 && level == 0) + return Trade::ImageData3D{PixelFormat::R8Unorm, {16, 16, 16}, Containers::Array{NoInit, 4096}}; + if(id == 1 && level == 1) + return Trade::ImageData3D{PixelFormat::R8Unorm, {8, 8, 6}, Containers::Array{NoInit, 2048}}; + if(id == 2 && level == 0) + return Trade::ImageData3D{CompressedPixelFormat::Bc1RGBSrgb, {4, 1, 1}, Containers::Array{NoInit, 16}, ImageFlag3D::Array}; + if(id == 3 && level == 0) + return Trade::ImageData3D{PixelFormat::R32F, {1, 4, 1}, Trade::DataFlag::ExternallyOwned|Trade::DataFlag::Mutable, data, ImageFlag3D::Array}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + char data[16]; + } importer; + + bool error = false; + std::chrono::high_resolution_clock::duration time; + Containers::Array infos = Implementation::imageInfo(importer, error, time); + CORRADE_VERIFY(!error); + CORRADE_COMPARE(infos.size(), 13); + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printImageInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, infos, nullptr, nullptr, nullptr); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + Implementation::printImageInfo(Debug::Flag::DisableColors, infos, nullptr, nullptr, nullptr); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(TRADE_TEST_DIR, "ImageConverterTestFiles/info.txt"), + TestSuite::Compare::StringToFile); +} + +void ImageConverterTest::infoImplementationError() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage1DCount() const override { return 2; } + Containers::Optional doImage1D(UnsignedInt id, UnsignedInt) override { + Error{} << "1D image" << id << "error!"; + return {}; + } + + UnsignedInt doImage2DCount() const override { return 2; } + Containers::Optional doImage2D(UnsignedInt id, UnsignedInt) override { + Error{} << "2D image" << id << "error!"; + return {}; + } + + UnsignedInt doImage3DCount() const override { return 2; } + Containers::Optional doImage3D(UnsignedInt id, UnsignedInt) override { + Error{} << "3D image" << id << "error!"; + return {}; + } + } importer; + + bool error = false; + std::chrono::high_resolution_clock::duration time; + std::ostringstream out; + Debug redirectOutput{&out}; + Error redirectError{&out}; + Containers::Array infos = Implementation::imageInfo(importer, error, time); + /* It should return a failure and no output */ + CORRADE_VERIFY(error); + CORRADE_VERIFY(infos.isEmpty()); + /* But it should not exit after first error */ + CORRADE_COMPARE(out.str(), + "1D image 0 error!\n" + "1D image 1 error!\n" + "2D image 0 error!\n" + "2D image 1 error!\n" + "3D image 0 error!\n" + "3D image 1 error!\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Trade::Test::ImageConverterTest) diff --git a/src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt b/src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt new file mode 100644 index 000000000..bff917a68 --- /dev/null +++ b/src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt @@ -0,0 +1,23 @@ +1D image 0: + Level 0: {1024} @ Astc10x10RGBAF (4.0 kB) +1D image 1: + Level 0: {16} @ RGBA8Snorm (0.1 kB) + Level 1: {8} @ RGBA8Snorm (0.0 kB) +1D image 2: Third 1D image just so there aren't two + Level 0: {4} @ Depth16Unorm (0.0 kB) +2D image 0: A very nice mipmapped 2D image + Level 0: {256, 128} @ RG16F (128.0 kB) + Level 1: {128, 64} @ RG16F (32.0 kB) + Level 2: {64, 32} @ RG16F (8.0 kB) +2D image 1: + Level 0: Array {4, 8} @ PvrtcRGB2bppUnorm (0.0 kB) +3D image 0: + Level 0: Array|CubeMap {16, 16, 12} @ R8Unorm (3.0 kB) +3D image 1: Volume kills! + Level 0: {16, 16, 16} @ R8Unorm (4.0 kB) + Level 1: {8, 8, 6} @ R8Unorm (2.0 kB) +3D image 2: + Level 0: Array {4, 1, 1} @ Bc1RGBSrgb (0.0 kB) +3D image 3: + Level 0: Array {1, 4, 1} @ R32F (0.0 kB, ExternallyOwned|Mutable) +Total image data size: 181.2 kB diff --git a/src/Magnum/Trade/imageconverter.cpp b/src/Magnum/Trade/imageconverter.cpp index a2e45731a..95657e8ee 100644 --- a/src/Magnum/Trade/imageconverter.cpp +++ b/src/Magnum/Trade/imageconverter.cpp @@ -595,47 +595,7 @@ no -C / --converter is specified, AnyImageConverter is used.)") else useColor = Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors; - std::size_t totalImageDataSize = 0; - for(const Trade::Implementation::ImageInfo& info: infos) { - Debug d{useColor}; - if(info.level == 0) { - d << Debug::boldColor(Debug::Color::Default); - if(info.size.z()) d << "3D image"; - else if(info.size.y()) d << "2D image"; - else d << "1D image"; - d << info.image << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - d << Debug::newline; - } - d << " Level" << info.level << Debug::nospace << ":"; - if(info.flags.one) { - d << Debug::packed << Debug::color(Debug::Color::Cyan); - if(info.size.z()) d << info.flags.three; - else if(info.size.y()) d << info.flags.two; - else d << info.flags.one; - d << Debug::resetColor; - } - d << Debug::packed; - if(info.size.z()) d << info.size; - else if(info.size.y()) d << info.size.xy(); - else d << Math::Vector<1, Int>(info.size.x()); - d << Debug::color(Debug::Color::Blue) << "@" << Debug::resetColor; - d << Debug::packed; - if(info.compressed) d << Debug::color(Debug::Color::Yellow) << info.compressedFormat; - else d << Debug::color(Debug::Color::Cyan) << info.format; - d << Debug::resetColor << "(" << Debug::nospace << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; - if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Green) - << info.dataFlags << Debug::resetColor; - d << Debug::nospace << ")"; - - totalImageDataSize += info.dataSize; - } - if(!infos.isEmpty()) - Debug{} << "Total image data size:" << Utility::format("{:.1f}", totalImageDataSize/1024.0f) << "kB"; + Trade::Implementation::printImageInfo(useColor, infos, nullptr, nullptr, nullptr); if(args.isSet("profile")) { Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast(importTime).count())/1.0e3f << "seconds"; From adaaeeb8841f32a9c148c75b5c936b665520557a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 9 Sep 2022 01:12:35 +0200 Subject: [PATCH 77/93] sceneconverter: implement --info-cameras. The last missing piece. Ah yes so easy to add with proper test coverage. --- doc/changelog.dox | 8 +- .../Implementation/sceneConverterUtilities.h | 69 +++++++++++++ src/Magnum/SceneTools/Test/CMakeLists.txt | 1 + .../SceneTools/Test/SceneConverterTest.cpp | 98 +++++++++++++++++++ .../SceneConverterTestFiles/info-cameras.txt | 12 +++ .../info-references.txt | 17 +++- src/Magnum/SceneTools/sceneconverter.cpp | 7 +- 7 files changed, 204 insertions(+), 8 deletions(-) create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-cameras.txt diff --git a/doc/changelog.dox b/doc/changelog.dox index 13826a031..1c112c59f 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -495,10 +495,10 @@ See also: - Added a `--bounds` option to @ref magnum-sceneconverter "magnum-sceneconverter", showing data ranges of known attributes - @ref magnum-sceneconverter "magnum-sceneconverter" now has separate - `--info-animations`, `--info-images`, `--info-lights`, `--info-materials`, - `--info-meshes`, `--info-skins` and `--info-textures` for printing - information just about particular data type, with `--info` being a shortcut - for all specified together + `--info-animations`, `--info-images`, `--info-lights`, `--info-cameras`, + `--info-materials`, `--info-meshes`, `--info-skins` and `--info-textures` + for printing information just about particular data type, with `--info` + being a shortcut for all specified together @subsubsection changelog-latest-changes-shaders Shaders library diff --git a/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h b/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h index 6c41538d2..2de8de8f9 100644 --- a/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h +++ b/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h @@ -33,6 +33,7 @@ #include "Magnum/Math/FunctionsBatch.h" #include "Magnum/Trade/AnimationData.h" +#include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/LightData.h" #include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" @@ -87,6 +88,12 @@ bool printInfo(const Debug::Flags useColor, const bool useColor24, const Utility Containers::String name; }; + struct CameraInfo { + UnsignedInt camera; + Trade::CameraData data{{}, {}, {}, {}}; + Containers::String name; + }; + struct MaterialInfo { UnsignedInt material; Trade::MaterialData data{{}, {}}; @@ -176,12 +183,14 @@ bool printInfo(const Debug::Flags useColor, const bool useColor24, const Utility std::unordered_map sceneFieldNames; Containers::Array materialReferenceCount; Containers::Array lightReferenceCount; + Containers::Array cameraReferenceCount; Containers::Array meshReferenceCount; Containers::Array skin2DReferenceCount; Containers::Array skin3DReferenceCount; if((args.isSet("info") || args.isSet("info-scenes")) && importer.sceneCount()) { materialReferenceCount = Containers::Array{importer.materialCount()}; lightReferenceCount = Containers::Array{importer.lightCount()}; + cameraReferenceCount = Containers::Array{importer.cameraCount()}; meshReferenceCount = Containers::Array{importer.meshCount()}; skin2DReferenceCount = Containers::Array{importer.skin2DCount()}; skin3DReferenceCount = Containers::Array{importer.skin3DCount()}; @@ -222,6 +231,11 @@ bool printInfo(const Debug::Flags useColor, const bool useColor24, const Utility ++lightReferenceCount[light.second()]; } + if(name == Trade::SceneField::Camera) for(const Containers::Pair& camera: scene->camerasAsArray()) { + if(camera.second() < cameraReferenceCount.size()) + ++cameraReferenceCount[camera.second()]; + } + arrayAppend(info.fields, InPlaceInit, name, scene->fieldFlags(j), @@ -339,6 +353,26 @@ bool printInfo(const Debug::Flags useColor, const bool useColor24, const Utility arrayAppend(lightInfos, std::move(info)); } + /* Camera properties */ + Containers::Array cameraInfos; + if(args.isSet("info") || args.isSet("info-cameras")) for(UnsignedInt i = 0; i != importer.cameraCount(); ++i) { + Containers::Optional camera; + { + Trade::Implementation::Duration d{importTime}; + if(!(camera = importer.camera(i))) { + error = true; + continue; + } + } + + CameraInfo info{}; + info.camera = i; + info.name = importer.cameraName(i); + info.data = *std::move(camera); + + arrayAppend(cameraInfos, std::move(info)); + } + /* Material properties, together with how much is each texture shared (which gets used only if both --info-materials and --info-textures is passed and the file has at least one material). */ @@ -722,6 +756,41 @@ bool printInfo(const Debug::Flags useColor, const bool useColor24, const Utility << info.data.range(); } + for(const CameraInfo& info: cameraInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Camera" << info.camera << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if(cameraReferenceCount) { + const UnsignedInt count = cameraReferenceCount[info.camera]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + + d << Debug::newline << " Type:" << Debug::packed + << Debug::color(Debug::Color::Cyan) + << info.data.type() << Debug::resetColor << Debug::newline; + /* Print orthographic cameras with size, perspective with FoV */ + if(info.data.type() == Trade::CameraType::Orthographic2D || + info.data.type() == Trade::CameraType::Orthographic3D) { + d << " Size:" << Debug::packed << info.data.size(); + } else if(info.data.type() == Trade::CameraType::Perspective3D) { + d << " FoV:" << Debug::packed << Deg(info.data.fov()) + << Debug::nospace << "°"; + } + /* Near/far is implicitly 0 for 2D */ + if(info.data.type() != Trade::CameraType::Orthographic2D) + d << Debug::nospace << "," << info.data.near() << "-" << info.data.far(); + d << Debug::newline << " Aspect ratio:" << info.data.aspectRatio(); + } + for(const MaterialInfo& info: materialInfos) { Debug d{useColor}; d << Debug::boldColor(Debug::Color::Default) << "Material" << info.material << Debug::resetColor; diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index e021cff00..0ea8253b3 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -45,6 +45,7 @@ corrade_add_test(SceneToolsSceneConverterTest SceneConverterTest.cpp LIBRARIES MagnumSceneTools FILES SceneConverterTestFiles/info-animations.txt + SceneConverterTestFiles/info-cameras.txt SceneConverterTestFiles/info-images.txt SceneConverterTestFiles/info-lights.txt SceneConverterTestFiles/info-materials.txt diff --git a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp index f6bc6c649..abf4589e0 100644 --- a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp +++ b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp @@ -49,6 +49,7 @@ struct SceneConverterTest: TestSuite::Tester { void infoImplementationAnimations(); void infoImplementationSkins(); void infoImplementationLights(); + void infoImplementationCameras(); void infoImplementationMaterials(); void infoImplementationMeshes(); void infoImplementationMeshesBounds(); @@ -92,6 +93,7 @@ SceneConverterTest::SceneConverterTest() { addInstancedTests({&SceneConverterTest::infoImplementationAnimations, &SceneConverterTest::infoImplementationSkins, &SceneConverterTest::infoImplementationLights, + &SceneConverterTest::infoImplementationCameras, &SceneConverterTest::infoImplementationMaterials, &SceneConverterTest::infoImplementationMeshes}, Containers::arraySize(InfoImplementationOneOrAllData)); @@ -112,6 +114,7 @@ SceneConverterTest::SceneConverterTest() { .addBooleanOption("info-animations") .addBooleanOption("info-skins") .addBooleanOption("info-lights") + .addBooleanOption("info-cameras") .addBooleanOption("info-materials") .addBooleanOption("info-meshes") .addBooleanOption("info-textures") @@ -434,6 +437,65 @@ void SceneConverterTest::infoImplementationLights() { TestSuite::Compare::StringToFile); } +void SceneConverterTest::infoImplementationCameras() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doCameraCount() const override { return 3; } + Containers::String doCameraName(UnsignedInt id) override { + return id == 0 ? "Orthographic 2D" : ""; + } + Containers::Optional doCamera(UnsignedInt id) override { + /* First 2D ortho camera, where near/far will get omited */ + if(id == 0) return Trade::CameraData{ + Trade::CameraType::Orthographic2D, + {5.0f, 6.0f}, + 0.0f, 0.0f + }; + + /* 3D ortho camera */ + if(id == 1) return Trade::CameraData{ + Trade::CameraType::Orthographic3D, + {2.0f, 3.0f}, + -1.0f, 0.5f + }; + + /* Third a perspective camera, specified with size, but printed + with FoV */ + if(id == 2) return Trade::CameraData{ + Trade::CameraType::Perspective3D, + 35.0_degf, 4.0f/3.0f, 0.01f, 100.0f + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-cameras" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-cameras.txt"), + TestSuite::Compare::StringToFile); +} + void SceneConverterTest::infoImplementationMaterials() { auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -783,6 +845,9 @@ void SceneConverterTest::infoImplementationReferenceCount() { Trade::SceneFieldData{Trade::SceneField::Light, Containers::arrayView(sceneData3D->mapping), Containers::arrayView(sceneData3D->lights)}, + Trade::SceneFieldData{Trade::SceneField::Camera, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->cameras)}, Trade::SceneFieldData{Trade::SceneField::Skin, Containers::arrayView(sceneData3D->mapping), Containers::arrayView(sceneData3D->skins)}, @@ -866,6 +931,29 @@ void SceneConverterTest::infoImplementationReferenceCount() { CORRADE_INTERNAL_ASSERT_UNREACHABLE(); } + UnsignedInt doCameraCount() const override { return 3; } + Containers::String doCameraName(UnsignedInt id) override { + return id == 0 ? "Not referenced" : ""; + } + Containers::Optional doCamera(UnsignedInt id) override { + if(id == 0) return Trade::CameraData{ + Trade::CameraType::Orthographic3D, + {2.0f, 3.0f}, + -1.0f, 0.5f + }; + if(id == 1) return Trade::CameraData{ + Trade::CameraType::Orthographic3D, + {2.0f, 2.0f}, + 0.0f, 1.0f + }; + if(id == 2) return Trade::CameraData{ + Trade::CameraType::Orthographic2D, + {2.0f, 2.0f}, + 0.0f, 0.0f + }; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + UnsignedInt doMaterialCount() const override { return 3; } Containers::String doMaterialName(UnsignedInt id) override { return id == 2 ? "Not referenced" : ""; @@ -985,12 +1073,14 @@ void SceneConverterTest::infoImplementationReferenceCount() { UnsignedInt meshes[4]; Int materials[4]; UnsignedInt lights[4]; + UnsignedInt cameras[4]; UnsignedInt skins[4]; } sceneData3D[1]{{ {0, 1, 1, 25}, {2, 0, 2, 67}, {0, 1, 23, 0}, {0, 17, 0, 2}, + {166, 1, 2, 1}, {1, 1, 22, 2} }}; @@ -1066,6 +1156,12 @@ void SceneConverterTest::infoImplementationError() { return {}; } + UnsignedInt doCameraCount() const override { return 2; } + Containers::Optional doCamera(UnsignedInt id) override { + Error{} << "Camera" << id << "error!"; + return {}; + } + UnsignedInt doMaterialCount() const override { return 2; } Containers::Optional doMaterial(UnsignedInt id) override { Error{} << "Material" << id << "error!"; @@ -1114,6 +1210,8 @@ void SceneConverterTest::infoImplementationError() { "3D skin 1 error!\n" "Light 0 error!\n" "Light 1 error!\n" + "Camera 0 error!\n" + "Camera 1 error!\n" "Material 0 error!\n" "Material 1 error!\n" "Mesh 0 error!\n" diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-cameras.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-cameras.txt new file mode 100644 index 000000000..9977ea326 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-cameras.txt @@ -0,0 +1,12 @@ +Camera 0: Orthographic 2D + Type: Orthographic2D + Size: {5, 6} + Aspect ratio: 0.833333 +Camera 1: + Type: Orthographic3D + Size: {2, 3}, -1 - 0.5 + Aspect ratio: 0.666667 +Camera 2: + Type: Perspective3D + FoV: 35°, 0.01 - 100 + Aspect ratio: 1.33333 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt index 1cda5540b..927de37b7 100644 --- a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt @@ -5,6 +5,7 @@ Scene 0: Mesh @ UnsignedInt, 4 entries MeshMaterial @ Int, 4 entries Light @ UnsignedInt, 4 entries + Camera @ UnsignedInt, 4 entries Skin @ UnsignedInt, 4 entries Scene 1: Bound: 4 objects @ UnsignedInt (0.0 kB, {}) @@ -14,9 +15,9 @@ Scene 1: Skin @ UnsignedInt, 3 entries Total scene data size: 0.1 kB Object 0 (referenced by 1 scenes): - Fields: Mesh, MeshMaterial, Light, Skin + Fields: Mesh, MeshMaterial, Light, Camera, Skin Object 1 (referenced by 2 scenes): - Fields: Mesh[2], MeshMaterial[2], Light[2], Skin[2], Mesh, Skin + Fields: Mesh[2], MeshMaterial[2], Light[2], Camera[2], Skin[2], Mesh, Skin Object 2 (referenced by 0 scenes): Not referenced Object 3 (referenced by 1 scenes): Fields: Mesh, Skin @@ -41,6 +42,18 @@ Light 1 (referenced by 0 objects): Not referenced Light 2 (referenced by 1 objects): Type: Directional Color: {0.203922, 0.341176, 1} +Camera 0 (referenced by 0 objects): Not referenced + Type: Orthographic3D + Size: {2, 3}, -1 - 0.5 + Aspect ratio: 0.666667 +Camera 1 (referenced by 2 objects): + Type: Orthographic3D + Size: {2, 2}, 0 - 1 + Aspect ratio: 1 +Camera 2 (referenced by 1 objects): + Type: Orthographic2D + Size: {2, 2} + Aspect ratio: 1 Material 0 (referenced by 2 objects): Type: {} Base layer: diff --git a/src/Magnum/SceneTools/sceneconverter.cpp b/src/Magnum/SceneTools/sceneconverter.cpp index 4dff2f194..b7c015fc0 100644 --- a/src/Magnum/SceneTools/sceneconverter.cpp +++ b/src/Magnum/SceneTools/sceneconverter.cpp @@ -122,8 +122,8 @@ magnum-sceneconverter [-h|--help] [-I|--importer PLUGIN] [-i|--importer-options key=val,key2=val2,…] [-c|--converter-options key=val,key2=val2,…]... [--mesh MESH] [--level LEVEL] [--concatenate-meshes] [--info-animations] [--info-images] - [--info-lights] [--info-materials] [--info-meshes] [--info-objects] - [--info-scenes] [--info-skins] [--info-textures] [--info] + [--info-lights] [--info-cameras] [--info-materials] [--info-meshes] + [--info-objects] [--info-scenes] [--info-skins] [--info-textures] [--info] [--color on|4bit|off|auto] [--bounds] [-v|--verbose] [--profile] [--] input output @endcode @@ -161,6 +161,7 @@ Arguments: exit - `--info-images` --- print into about images in the input file and exit - `--info-lights` --- print into about lights in the input file and exit +- `--info-cameras` --- print into about cameras in the input file and exit - `--info-materials` --- print into about materials in the input file and exit - `--info-meshes` --- print into about meshes in the input file and exit @@ -218,6 +219,7 @@ bool isInfoRequested(const Utility::Arguments& args) { return args.isSet("info-animations") || args.isSet("info-images") || args.isSet("info-lights") || + args.isSet("info-cameras") || args.isSet("info-materials") || args.isSet("info-meshes") || args.isSet("info-objects") || @@ -250,6 +252,7 @@ int main(int argc, char** argv) { .addBooleanOption("info-animations").setHelp("info-animations", "print info about animations in the input file and exit") .addBooleanOption("info-images").setHelp("info-images", "print info about images in the input file and exit") .addBooleanOption("info-lights").setHelp("info-lights", "print info about images in the input file and exit") + .addBooleanOption("info-cameras").setHelp("info-cameras", "print info about cameras in the input file and exit") .addBooleanOption("info-materials").setHelp("info-materials", "print info about materials in the input file and exit") .addBooleanOption("info-meshes").setHelp("info-meshes", "print info about meshes in the input file and exit") .addBooleanOption("info-objects").setHelp("info-objects", "print info about objects in the input file and exit") From 983a99d5405bc4401255a221ebfa8b721600e4b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 9 Sep 2022 14:24:08 +0200 Subject: [PATCH 78/93] Animation,Trade: add various TODOs. The relatively older APIs don't have the usability as good as the recent MeshData/MaterialData/SkinData. Gotta fix that. --- src/Magnum/Animation/Track.h | 14 ++++++++++++++ src/Magnum/Trade/AbstractImporter.cpp | 2 ++ src/Magnum/Trade/AnimationData.h | 3 +++ src/Magnum/Trade/LightData.h | 1 + src/Magnum/Trade/SkinData.h | 2 ++ 5 files changed, 22 insertions(+) diff --git a/src/Magnum/Animation/Track.h b/src/Magnum/Animation/Track.h index 74736f244..dfcad07da 100644 --- a/src/Magnum/Animation/Track.h +++ b/src/Magnum/Animation/Track.h @@ -549,6 +549,8 @@ template @ce (where @p K_ / @p V_ are * with @cpp const @ce removed) when they are @cpp const @ce. */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ typedef typename std::conditional::value, const std::pair::type, typename std::remove_const::type>, std::pair>::type KeyValueType; /** @brief Animation result type */ @@ -599,12 +601,16 @@ template&, const Containers::StridedArrayView1D&, Interpolator, Extrapolation, Extrapolation). */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ /*implicit*/ TrackView(Containers::ArrayView data, Interpolator interpolator, Extrapolation before, Extrapolation after) noexcept: TrackView{Containers::StridedArrayView1D{data, data ? &data[0].first : nullptr, data.size(), sizeof(std::pair)}, Containers::StridedArrayView1D{data, data ? &data[0].second : nullptr, data.size(), sizeof(std::pair)}, interpolator, before, after} {} /** @overload * Equivalent to calling @ref TrackView(Containers::ArrayView, Interpolator, Extrapolation, Extrapolation) * with both @p before and @p after set to @p extrapolation. */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ explicit TrackView(Containers::ArrayView data, Interpolator interpolator, Extrapolation extrapolation = Extrapolation::Constant) noexcept: TrackView{data, interpolator, extrapolation, extrapolation} {} /** @@ -641,12 +647,16 @@ template&, const Containers::StridedArrayView1D&, Interpolator, Extrapolation, Extrapolation). */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ /*implicit*/ TrackView(Containers::ArrayView data, Interpolation interpolation, Interpolator interpolator, Extrapolation before, Extrapolation after) noexcept: TrackViewStorage{Containers::StridedArrayView1D{data, data ? &data[0].first : nullptr, data.size(), sizeof(std::pair)}, Containers::StridedArrayView1D{data, data ? &data[0].second : nullptr, data.size(), sizeof(std::pair)}, interpolation, interpolator, before, after} {} /** @overload * Equivalent to calling @ref TrackView(Containers::ArrayView, Interpolation, Interpolator, Extrapolation, Extrapolation) * with both @p before and @p after set to @p extrapolation. */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ /*implicit*/ TrackView(Containers::ArrayView data, Interpolation interpolation, Interpolator interpolator, Extrapolation extrapolation = Extrapolation::Constant) noexcept: TrackView{data, interpolation, interpolator, extrapolation, extrapolation} {} /** @@ -681,12 +691,16 @@ template&, const Containers::StridedArrayView1D&, Interpolator, Extrapolation, Extrapolation). */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ /*implicit*/ TrackView(Containers::ArrayView data, Interpolation interpolation, Extrapolation before, Extrapolation after) noexcept: TrackView{Containers::StridedArrayView1D{data, data ? &data[0].first : nullptr, data.size(), sizeof(std::pair)}, Containers::StridedArrayView1D{data, data ? &data[0].second : nullptr, data.size(), sizeof(std::pair)}, interpolation, before, after} {} /** @overload * Equivalent to calling @ref TrackView(Containers::ArrayView, Interpolation, Extrapolation, Extrapolation) * with both @p before and @p after set to @p extrapolation. */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ /*implicit*/ TrackView(Containers::ArrayView data, Interpolation interpolation, Extrapolation extrapolation = Extrapolation::Constant) noexcept: TrackView{data, interpolation, extrapolation, extrapolation} {} /** @brief Convert a mutable view to a const one */ diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index f363ae97c..8bda00fa6 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -429,6 +429,8 @@ Containers::Optional AbstractImporter::animation(const UnsignedIn CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::animation(): no file opened", {}); CORRADE_ASSERT(id < doAnimationCount(), "Trade::AbstractImporter::animation(): index" << id << "out of range for" << doAnimationCount() << "entries", {}); Containers::Optional animation = doAnimation(id); + /** @todo maybe this should also disallow custom interpolators? since thise + would be dangling on plugin unload */ CORRADE_ASSERT(!animation || ((!animation->_data.deleter() || animation->_data.deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || animation->_data.deleter() == ArrayAllocator::deleter) && (!animation->_tracks.deleter() || animation->_tracks.deleter() == static_cast(Implementation::nonOwnedArrayDeleter))), diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index 05a92eced..2de24976b 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -280,6 +280,9 @@ class AnimationTrackData { * @param target Track target * @param view Type-erased @ref Animation::TrackView instance */ + /** @todo stop taking TrackViewStorage and instead directly take the + key/value views, interpolator/interpolation and extrapolation -- + it's just 6 overloads and makes usage much better */ explicit AnimationTrackData(AnimationTrackType type, AnimationTrackType resultType, AnimationTrackTargetType targetType, UnsignedLong target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{resultType}, _targetType{targetType}, _target{target}, _view{view} {} /** @overload diff --git a/src/Magnum/Trade/LightData.h b/src/Magnum/Trade/LightData.h index f41bc9e93..902f2ef39 100644 --- a/src/Magnum/Trade/LightData.h +++ b/src/Magnum/Trade/LightData.h @@ -121,6 +121,7 @@ class MAGNUM_TRADE_EXPORT LightData { * @brief Light type * * @see @ref type() + * @todo move this to LightType outside of the class for consistency */ enum class Type: UnsignedByte { /* Zero reserved for an invalid value */ diff --git a/src/Magnum/Trade/SkinData.h b/src/Magnum/Trade/SkinData.h index d64f05c63..a3c5fa8f5 100644 --- a/src/Magnum/Trade/SkinData.h +++ b/src/Magnum/Trade/SkinData.h @@ -90,6 +90,8 @@ template class SkinData { /** @brief Move assignment */ SkinData& operator=(SkinData&& other) noexcept; + /** @todo expose DataFlags (so users can know if the data are externally owned, at least) */ + /** * @brief Joint IDs * From cc0aa876460b2e78e08e7a9f70ede94183a6e482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 9 Sep 2022 15:51:25 +0200 Subject: [PATCH 79/93] CMake: forgot to prefix a conditional option condition here. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de81e1920..3f73ebc92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,7 +213,7 @@ cmake_dependent_option(MAGNUM_TARGET_HEADLESS "Build command-line utilities for cmake_dependent_option(MAGNUM_TARGET_GL "Build libraries with OpenGL interoperability" ON "MAGNUM_WITH_GL" OFF) # EGL context and windowless EGL application, available everywhere -cmake_dependent_option(MAGNUM_WITH_WINDOWLESSEGLAPPLICATION "Build WindowlessEglApplication library" OFF "NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES OR NOT MAGNUM_WITH_GL_INFO;NOT TARGET_HEADLESS" ON) +cmake_dependent_option(MAGNUM_WITH_WINDOWLESSEGLAPPLICATION "Build WindowlessEglApplication library" OFF "NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES OR NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_TARGET_HEADLESS" ON) option(MAGNUM_WITH_EGLCONTEXT "Build EglContext library" OFF) # Vulkan, everywhere except Emscripten From 6d4758f534102ec071f82285807f35b6c379c0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 9 Sep 2022 18:23:27 +0200 Subject: [PATCH 80/93] Platform: properly handle the optional EGL surface in moves. I wonder what kind of bugs this caused. --- src/Magnum/Platform/WindowlessEglApplication.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Magnum/Platform/WindowlessEglApplication.cpp b/src/Magnum/Platform/WindowlessEglApplication.cpp index 15b237dde..25b7aec24 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.cpp +++ b/src/Magnum/Platform/WindowlessEglApplication.cpp @@ -526,12 +526,18 @@ WindowlessEglContext::WindowlessEglContext(WindowlessEglContext&& other) noexcep _sharedContext{other._sharedContext}, #endif _display{other._display}, _context{other._context} + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + , _surface{other._surface} + #endif { #ifndef MAGNUM_TARGET_WEBGL other._sharedContext = false; #endif other._display = {}; other._context = {}; + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + other._surface = {}; + #endif } WindowlessEglContext::~WindowlessEglContext() { @@ -570,6 +576,9 @@ WindowlessEglContext& WindowlessEglContext::operator=(WindowlessEglContext&& oth #endif swap(other._display, _display); swap(other._context, _context); + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + swap(other._surface, _surface); + #endif return *this; } From da569a41dea7856b0cc3c1e0a12926ec59e4a31b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 9 Sep 2022 19:00:45 +0200 Subject: [PATCH 81/93] Platform: merge WindowlessWindowsEglApplication into EglApp and remove. The whole class was a bad idea, why create something that's 99% similar to another application and has just one platform-specific workaround? Of course it resulted in this code being completely untested and not even built anywhere, because it served a tiny insignificant use case. To avoid losing all the code, I did my best in attempting to merge this into the WindowlessEglApplication. But since, again, EGL isn't really used on any Windows platform, I can't even say it builds properly. Maybe not even the original code built. --- CMakeLists.txt | 17 +- doc/building.dox | 3 - doc/changelog-old.dox | 2 +- doc/changelog.dox | 12 +- doc/cmake.dox | 1 - modules/FindMagnum.cmake | 16 +- src/Magnum/GL/OpenGLTester.h | 6 +- src/Magnum/Platform/CMakeLists.txt | 40 +- src/Magnum/Platform/Test/CMakeLists.txt | 5 - .../WindowlessWindowsEglApplicationTest.cpp | 55 -- .../Platform/WindowlessEglApplication.cpp | 72 ++- .../Platform/WindowlessEglApplication.h | 7 +- .../WindowlessWindowsEglApplication.cpp | 238 -------- .../WindowlessWindowsEglApplication.h | 538 ------------------ src/Magnum/Platform/gl-info.cpp | 4 +- src/Magnum/Text/CMakeLists.txt | 2 +- src/Magnum/Text/fontconverter.cpp | 2 +- src/Magnum/TextureTools/CMakeLists.txt | 2 +- .../TextureTools/distancefieldconverter.cpp | 2 +- 19 files changed, 103 insertions(+), 921 deletions(-) delete mode 100644 src/Magnum/Platform/Test/WindowlessWindowsEglApplicationTest.cpp delete mode 100644 src/Magnum/Platform/WindowlessWindowsEglApplication.cpp delete mode 100644 src/Magnum/Platform/WindowlessWindowsEglApplication.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f73ebc92..eb69f5e3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,7 +91,6 @@ set(_MAGNUM_DEPRECATED_UNPREFIXED_OPTIONS WITH_WINDOWLESSGLXAPPLICATION WITH_WINDOWLESSIOSAPPLICATION WITH_WINDOWLESSWGLAPPLICATION - WITH_WINDOWLESSWINDOWSEGLAPPLICATION WITH_CGLCONTEXT WITH_EGLCONTEXT WITH_GLXCONTEXT @@ -206,7 +205,7 @@ cmake_dependent_option(MAGNUM_WITH_SHADERTOOLS "Build ShaderTools library" ON "N cmake_dependent_option(MAGNUM_WITH_TEXT "Build Text library" ON "NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_MAGNUMFONT;NOT MAGNUM_WITH_MAGNUMFONTCONVERTER" ON) cmake_dependent_option(MAGNUM_WITH_TEXTURETOOLS "Build TextureTools library" ON "NOT MAGNUM_WITH_TEXT;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) cmake_dependent_option(MAGNUM_WITH_TRADE "Build Trade library" ON "NOT MAGNUM_WITH_MESHTOOLS;NOT MAGNUM_WITH_PRIMITIVES;NOT MAGNUM_WITH_SCENETOOLS;NOT MAGNUM_WITH_IMAGECONVERTER;NOT MAGNUM_WITH_ANYIMAGEIMPORTER;NOT MAGNUM_WITH_ANYIMAGECONVERTER;NOT MAGNUM_WITH_ANYSCENEIMPORTER;NOT MAGNUM_WITH_OBJIMPORTER;NOT MAGNUM_WITH_TGAIMAGECONVERTER;NOT MAGNUM_WITH_TGAIMPORTER" ON) -cmake_dependent_option(MAGNUM_WITH_GL "Build GL library" ON "NOT MAGNUM_WITH_SHADERS;NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_ANDROIDAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSIOSAPPLICATION;NOT MAGNUM_WITH_CGLCONTEXT;NOT MAGNUM_WITH_GLXAPPLICATION;NOT MAGNUM_WITH_GLXCONTEXT;NOT MAGNUM_WITH_XEGLAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSWGLAPPLICATION;NOT MAGNUM_WITH_WGLCONTEXT;NOT MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) +cmake_dependent_option(MAGNUM_WITH_GL "Build GL library" ON "NOT MAGNUM_WITH_SHADERS;NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_ANDROIDAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSIOSAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSCGLAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSGLXAPPLICATION;NOT MAGNUM_WITH_CGLCONTEXT;NOT MAGNUM_WITH_GLXAPPLICATION;NOT MAGNUM_WITH_GLXCONTEXT;NOT MAGNUM_WITH_XEGLAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSWGLAPPLICATION;NOT MAGNUM_WITH_WGLCONTEXT;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) option(MAGNUM_WITH_PRIMITIVES "Build Primitives library" ON) cmake_dependent_option(MAGNUM_TARGET_HEADLESS "Build command-line utilities for use on a headless machines" OFF "MAGNUM_WITH_GL" OFF) @@ -253,8 +252,6 @@ elseif(CORRADE_TARGET_WINDOWS) if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) cmake_dependent_option(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION "Build WindowlessWglApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) option(MAGNUM_WITH_WGLCONTEXT "Build WglContext library" OFF) - else() - cmake_dependent_option(MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION "Build WindowlessWindowsEglApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) endif() endif() @@ -404,8 +401,8 @@ if(_MAGNUM_ACCEPT_DEPRECATED_UNPREFIXED_OPTIONS AND MAGNUM_BUILD_DEPRECATED) set(WITH_WINDOWLESSWGLAPPLICATION ON) endif() else() - if(NOT DEFINED WITH_WINDOWLESSWINDOWSEGLAPPLICATION) - set(WITH_WINDOWLESSWINDOWSEGLAPPLICATION ON) + if(NOT DEFINED WITH_WINDOWLESSEGLAPPLICATION) + set(WITH_WINDOWLESSEGLAPPLICATION ON) endif() endif() endif() @@ -544,12 +541,12 @@ if(MAGNUM_WITH_OPENGLTESTER) set(OPENGLTESTER_APPLICATION MagnumWindowlessGlxApplication) endif() elseif(CORRADE_TARGET_WINDOWS) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) + if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) + set(MAGNUM_WITH_WINDOWLESSEGLAPPLICATION ON) + set(OPENGLTESTER_APPLICATION MagnumWindowlessEglApplication) + else() set(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION ON) set(OPENGLTESTER_APPLICATION MagnumWindowlessWglApplication) - else() - set(MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION ON) - set(OPENGLTESTER_APPLICATION MagnumWindowlessWindowsEglApplication) endif() else() # Assuming this gets hit only if MAGNUM_BUILD_GL_TESTS are enabled diff --git a/doc/building.dox b/doc/building.dox index 08123b1c8..8687d4dd6 100644 --- a/doc/building.dox +++ b/doc/building.dox @@ -546,9 +546,6 @@ going to build any of the @ref example-index "examples", you'll need it. - `MAGNUM_WITH_WINDOWLESSWGLAPPLICATION` --- Build the @ref Platform::WindowlessWglApplication "WindowlessWglApplication" library. Requires `MAGNUM_TARGET_GL` to be enabled. -- `MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION` --- Build the - @ref Platform::WindowlessWindowsEglApplication "WindowlessWindowsEglApplication" - library. Requires `MAGNUM_TARGET_GL` to be enabled. None of the context libraries is built by default. Similarly to the application libraries, they are always built as static. You need them only if you chose to diff --git a/doc/changelog-old.dox b/doc/changelog-old.dox index 385a258b5..4f2bb2b79 100644 --- a/doc/changelog-old.dox +++ b/doc/changelog-old.dox @@ -744,7 +744,7 @@ a high-level overview. [mosra/magnum-bootstrap#6](https://github.com/mosra/magnum-bootstrap/pull/6)) - Text input support in @ref Platform::Sdl2Application and @ref Platform::GlfwApplication (see [mosra/magnum#129](https://github.com/mosra/magnum/issues/129)) -- Added @ref Platform::WindowlessWindowsEglApplication and +- Added @cpp Platform::WindowlessWindowsEglApplication @ce and @ref Platform::WindowlessIosApplication for ANGLE and iOS - New @ref Platform::WindowlessEglApplication that works on headless NVidia, Mesa drivers and Emscripten (see [mosra/magnum#133](https://github.com/mosra/magnum/pull/133)) diff --git a/doc/changelog.dox b/doc/changelog.dox index 1c112c59f..1ca63c0ee 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -205,8 +205,7 @@ See also: - Implemented @relativeref{Platform::WindowlessEglApplication,Configuration::Flag::NoError} in @ref Platform::WindowlessEglApplication, @ref Platform::WindowlessGlxApplication, - @ref Platform::WindowlessWglApplication, - @ref Platform::WindowlessWindowsEglApplication and + @ref Platform::WindowlessWglApplication and @ref Platform::Sdl2Application @subsubsection changelog-latest-new-scenegraph SceneGraph library @@ -1084,6 +1083,12 @@ See also: @ref Math::RectangularMatrix::data() are no longer @cpp constexpr @ce in order to make them return a reference to a fixed-size array instead of a pointer, which was deemed a more useful property. +- @cpp Platform::WindowlessWindowsEglApplication @ce is now merged into + @ref Platform::WindowlessEglApplication. Since its use case was rather rare + (windowless applications on ANGLE on Windows) and it wasn't even built in + any packages, it's completely removed without providing any backwards + compatibility --- switch to @ref Platform::WindowlessEglApplication + instead. - @ref SceneGraph::Object::addChild() no longer requires the type constructor to have the last parameter a parent object object pointer, as that was quite limiting. Instead it's calling @ref SceneGraph::Object::setParent() @@ -1372,7 +1377,8 @@ Released 2020-06-27, tagged as @ref Platform::WindowlessEglApplication, @ref Platform::WindowlessGlxApplication, @ref Platform::WindowlessWglApplication and - @ref Platform::WindowlessWindowsEglApplication (see [mosra/magnum#433](https://github.com/mosra/magnum/pull/433) + @cpp Platform::WindowlessWindowsEglApplication @ce (see + [mosra/magnum#433](https://github.com/mosra/magnum/pull/433) and [mosra/magnum#437](https://github.com/mosra/magnum/pull/437)) - CUDA device selection in @ref Platform::WindowlessEglApplication (see [mosra/magnum#449](https://github.com/mosra/magnum/pull/449)) diff --git a/doc/cmake.dox b/doc/cmake.dox index 8db179b45..503e9bc66 100644 --- a/doc/cmake.dox +++ b/doc/cmake.dox @@ -217,7 +217,6 @@ Platform namespace is split into more components: - `WindowlessGlxApplication` --- @ref Platform::WindowlessGlxApplication "WindowlessGlxApplication" - `WindowlessIosApplication` --- @ref Platform::WindowlessIosApplication "WindowlessIosApplication" - `WindowlessWglApplication` --- @ref Platform::WindowlessWglApplication "WindowlessWglApplication" -- `WindowlessWindowsEglApplication` --- @ref Platform::WindowlessWindowsEglApplication "WindowlessWindowsEglApplication" For manual context creation (without application wrappers) there are also platform-specific context libraries (see @ref platform-custom for more diff --git a/modules/FindMagnum.cmake b/modules/FindMagnum.cmake index 365dc0d33..7012edc04 100644 --- a/modules/FindMagnum.cmake +++ b/modules/FindMagnum.cmake @@ -78,7 +78,6 @@ # WindowlessGlxApplication - Windowless GLX application # WindowlessIosApplication - Windowless iOS application # WindowlessWglApplication - Windowless WGL application -# WindowlessWindowsEglApplication - Windowless Windows/EGL application # CglContext - CGL context # EglContext - EGL context # GlxContext - GLX context @@ -395,7 +394,7 @@ if(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE) list(APPEND _MAGNUM_LIBRARY_COMPONENTS GlxApplication XEglApplication WindowlessGlxApplication GlxContext) endif() if(CORRADE_TARGET_WINDOWS) - list(APPEND _MAGNUM_LIBRARY_COMPONENTS WindowlessWglApplication WglContext WindowlessWindowsEglApplication) + list(APPEND _MAGNUM_LIBRARY_COMPONENTS WindowlessWglApplication WglContext) endif() if(CORRADE_TARGET_UNIX OR CORRADE_TARGET_WINDOWS) list(APPEND _MAGNUM_EXECUTABLE_COMPONENTS fontconverter distancefieldconverter) @@ -439,10 +438,10 @@ elseif(CORRADE_TARGET_UNIX) list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessGlxApplication) endif() elseif(CORRADE_TARGET_WINDOWS) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) - list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessWglApplication) + if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) + list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessEglApplication) else() - list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessWindowsEglApplication) + list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessWglApplication) endif() endif() @@ -492,7 +491,6 @@ set(_MAGNUM_WindowlessEglApplication_DEPENDENCIES GL) set(_MAGNUM_WindowlessGlxApplication_DEPENDENCIES GL) set(_MAGNUM_WindowlessIosApplication_DEPENDENCIES GL) set(_MAGNUM_WindowlessWglApplication_DEPENDENCIES GL) -set(_MAGNUM_WindowlessWindowsEglApplication_DEPENDENCIES GL) set(_MAGNUM_XEglApplication_DEPENDENCIES GL) set(_MAGNUM_CglContext_DEPENDENCIES GL) set(_MAGNUM_EglContext_DEPENDENCIES GL) @@ -799,12 +797,6 @@ foreach(_component ${Magnum_FIND_COMPONENTS}) # Windowless WGL application has no additional dependencies - # Windowless Windows/EGL application dependencies - elseif(_component STREQUAL WindowlessWindowsEglApplication) - find_package(EGL) - set_property(TARGET Magnum::${_component} APPEND PROPERTY - INTERFACE_LINK_LIBRARIES EGL::EGL) - # X/EGL application dependencies elseif(_component STREQUAL XEglApplication) find_package(EGL) diff --git a/src/Magnum/GL/OpenGLTester.h b/src/Magnum/GL/OpenGLTester.h index 576175ff2..4606655f7 100644 --- a/src/Magnum/GL/OpenGLTester.h +++ b/src/Magnum/GL/OpenGLTester.h @@ -50,10 +50,10 @@ #include "Magnum/Platform/WindowlessGlxApplication.h" #endif #elif defined(CORRADE_TARGET_WINDOWS) -#if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessWglApplication.h" +#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) +#include "Magnum/Platform/WindowlessEglApplication.h" #else -#include "Magnum/Platform/WindowlessWindowsEglApplication.h" +#include "Magnum/Platform/WindowlessWglApplication.h" #endif #else #error cannot run OpenGL tests on this platform diff --git a/src/Magnum/Platform/CMakeLists.txt b/src/Magnum/Platform/CMakeLists.txt index c37cced95..3534bfebb 100644 --- a/src/Magnum/Platform/CMakeLists.txt +++ b/src/Magnum/Platform/CMakeLists.txt @@ -623,44 +623,6 @@ if(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION) add_library(Magnum::WindowlessWglApplication ALIAS MagnumWindowlessWglApplication) endif() -# Windowless Windows/EGL application -if(MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION) - if(NOT MAGNUM_TARGET_GL) - message(SEND_ERROR "WindowlessWindowsEglApplication is available only if MAGNUM_TARGET_GL is enabled") - endif() - - set(NEED_EGLCONTEXT 1) - - set(MagnumWindowlessWindowsEglApplication_SRCS - WindowlessWindowsEglApplication.cpp - Implementation/Egl.cpp - $) - set(MagnumWindowlessWindowsEglApplication_HEADERS - WindowlessWindowsEglApplication.h) - set(MagnumWindowlessWindowsEglApplication_PRIVATE_HEADERS - Implementation/Egl.h) - - add_library(MagnumWindowlessWindowsEglApplication STATIC - ${MagnumWindowlessWindowsEglApplication_SRCS} - ${MagnumWindowlessWindowsEglApplication_HEADERS} - ${MagnumWindowlessWindowsEglApplication_PRIVATE_HEADERS}) - set_target_properties(MagnumWindowlessWindowsEglApplication PROPERTIES - DEBUG_POSTFIX "-d") - if(NOT MAGNUM_BUILD_STATIC OR MAGNUM_BUILD_STATIC_PIC) - set_target_properties(MagnumWindowlessWindowsEglApplication PROPERTIES POSITION_INDEPENDENT_CODE ON) - endif() - target_link_libraries(MagnumWindowlessWindowsEglApplication PUBLIC MagnumGL EGL::EGL) - - install(FILES ${MagnumWindowlessWindowsEglApplication_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Platform) - install(TARGETS MagnumWindowlessWindowsEglApplication - RUNTIME DESTINATION ${MAGNUM_BINARY_INSTALL_DIR} - LIBRARY DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR} - ARCHIVE DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR}) - - # Magnum WindowlessWindowsEglApplication target alias for superprojects - add_library(Magnum::WindowlessWindowsEglApplication ALIAS MagnumWindowlessWindowsEglApplication) -endif() - # Windowless CGL application if(MAGNUM_WITH_WINDOWLESSCGLAPPLICATION) if(NOT MAGNUM_TARGET_GL) @@ -955,7 +917,7 @@ if(MAGNUM_WITH_GL_INFO) endif() elseif(CORRADE_TARGET_WINDOWS) if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessWindowsEglApplication) + target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessEglApplication) else() target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessWglApplication) endif() diff --git a/src/Magnum/Platform/Test/CMakeLists.txt b/src/Magnum/Platform/Test/CMakeLists.txt index 8075b7fec..0bb325f49 100644 --- a/src/Magnum/Platform/Test/CMakeLists.txt +++ b/src/Magnum/Platform/Test/CMakeLists.txt @@ -176,8 +176,3 @@ if(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION) add_executable(PlatformWindowlessWglApplicationTest WindowlessWglApplicationTest.cpp) target_link_libraries(PlatformWindowlessWglApplicationTest PRIVATE MagnumWindowlessWglApplication) endif() - -if(MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION) - add_executable(PlatformWindowlessWindowsEglApplicationTest WindowlessWindowsEglApplicationTest.cpp) - target_link_libraries(PlatformWindowlessWindowsEglApplicationTest PRIVATE MagnumWindowlessWindowsEglApplication) -endif() diff --git a/src/Magnum/Platform/Test/WindowlessWindowsEglApplicationTest.cpp b/src/Magnum/Platform/Test/WindowlessWindowsEglApplicationTest.cpp deleted file mode 100644 index f45937652..000000000 --- a/src/Magnum/Platform/Test/WindowlessWindowsEglApplicationTest.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - This file is part of Magnum. - - Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020, 2021, 2022 Vladimír Vondruš - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. -*/ - -#include - -#include "Magnum/Platform/WindowlessWindowsEglApplication.h" - -namespace Magnum { namespace Platform { namespace Test { namespace { - -struct WindowlessWindowsEglApplicationTest: Platform::WindowlessApplication { - explicit WindowlessWindowsEglApplicationTest(const Arguments& arguments); - int exec() override { return 0; } -}; - -WindowlessWindowsEglApplicationTest::WindowlessWindowsEglApplicationTest(const Arguments& arguments): Platform::WindowlessApplication{arguments, NoCreate} { - Utility::Arguments args; - args.addSkippedPrefix("magnum", "engine-specific options") - .addBooleanOption("quiet").setHelp("quiet", "like --magnum-log quiet, but specified via a Context::Configuration instead") - .addBooleanOption("gpu-validation").setHelp("gpu-validation", "like --magnum-gpu-validation, but specified via a Context::Configuration instead") - .parse(arguments.argc, arguments.argv); - - Configuration conf; - if(args.isSet("quiet")) - conf.addFlags(Configuration::Flag::QuietLog); - /* No verbose logs in this app */ - if(args.isSet("gpu-validation")) - conf.addFlags(Configuration::Flag::GpuValidation); - createContext(conf); -} - -}}}} - -MAGNUM_WINDOWLESSAPPLICATION_MAIN(Magnum::Platform::Test::WindowlessWindowsEglApplicationTest) diff --git a/src/Magnum/Platform/WindowlessEglApplication.cpp b/src/Magnum/Platform/WindowlessEglApplication.cpp index 25b7aec24..c492e1468 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.cpp +++ b/src/Magnum/Platform/WindowlessEglApplication.cpp @@ -39,6 +39,14 @@ #include "Implementation/Egl.h" +/* ANGLE's EGL on Windows needs an actual window */ +/** @todo investigate if this is still needed */ +#ifdef CORRADE_TARGET_WINDOWS +#define WIN32_LEAN_AND_MEAN 1 +#define VC_EXTRALEAN +#include +#endif + /* None of this is in the Emscripten emulation layer, so no need to include that there */ #ifndef MAGNUM_TARGET_WEBGL @@ -252,10 +260,44 @@ WindowlessEglContext::WindowlessEglContext(const Configuration& configuration, G } #endif + /* ANGLE's EGL on Windows needs to get a display from an actual + window. Elsewhere EGL_DEFAULT_DISPLAY is fine. */ + /** @todo investigate if this is still needed */ + #ifdef CORRADE_TARGET_WINDOWS + /* Register the window class (if not yet done) */ + WNDCLASSW wc; + if(!GetClassInfoW(GetModuleHandleW(nullptr), L"Magnum Windowless Application", &wc)) { + wc = WNDCLASSW{ + 0, + DefWindowProcW, + 0, + 0, + GetModuleHandleW(nullptr), + nullptr, + nullptr, + HBRUSH(COLOR_BACKGROUND), + nullptr, + L"Magnum Windowless Application" + }; + + if(!RegisterClassW(&wc)) { + Error() << "Platform::WindowlessWglContext: cannot create window class:" << GetLastError(); + return; + } + } + + /* Create the window */ + _window = CreateWindowW(wc.lpszClassName, L"Magnum Windowless Application", + WS_OVERLAPPEDWINDOW, 0, 0, 32, 32, 0, 0, wc.hInstance, 0); + + /* Initialize */ + _display = eglGetDisplay(GetDC(_window)); + #else if(!(_display = eglGetDisplay(EGL_DEFAULT_DISPLAY))) { Error{} << "Platform::WindowlessEglApplication::tryCreateContext(): cannot get default EGL display:" << Implementation::eglErrorString(eglGetError()); return; } + #endif } if(!eglInitialize(_display, nullptr, nullptr)) { @@ -489,7 +531,14 @@ WindowlessEglContext::WindowlessEglContext(const Configuration& configuration, G return; } - #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + #ifdef CORRADE_TARGET_WINDOWS + /* ANGLE's EGL on Windows needs has an actual window, and so it also needs + a surface */ + /** @todo investigate if this is still needed */ + if(!(_surface = eglCreateWindowSurface(_display, config, _window, nullptr))) + Error() << "Platform::WindowlessEglContext: cannot create window surface:" << Implementation::eglErrorString(eglGetError()); + + #elif defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) /* Android Emulator can run with a SwiftShader GPU and thus needs some of the SwiftShader context creation workarounds. However, it's impossible to detect, as EGL_VERSION is always "1.4 Android META-EGL" and @@ -525,17 +574,23 @@ WindowlessEglContext::WindowlessEglContext(WindowlessEglContext&& other) noexcep #ifndef MAGNUM_TARGET_WEBGL _sharedContext{other._sharedContext}, #endif + #ifdef CORRADE_TARGET_WINDOWS + _window{other._window}, + #endif _display{other._display}, _context{other._context} - #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + #if defined(CORRADE_TARGET_WINDOWS) || (defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)) , _surface{other._surface} #endif { #ifndef MAGNUM_TARGET_WEBGL other._sharedContext = false; #endif + #ifdef CORRADE_TARGET_WINDOWS + other._window = {}; + #endif other._display = {}; other._context = {}; - #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + #if defined(CORRADE_TARGET_WINDOWS) || (defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)) other._surface = {}; #endif } @@ -554,7 +609,7 @@ WindowlessEglContext::~WindowlessEglContext() { eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(_display, _context); } - #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + #if defined(CORRADE_TARGET_WINDOWS) || (defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)) if(_surface) eglDestroySurface(_display, _surface); #endif @@ -567,6 +622,10 @@ WindowlessEglContext::~WindowlessEglContext() { !_sharedContext && #endif _display) eglTerminate(_display); + + #ifdef CORRADE_TARGET_WINDOWS + if(_window) DestroyWindow(_window); + #endif } WindowlessEglContext& WindowlessEglContext::operator=(WindowlessEglContext&& other) noexcept { @@ -576,9 +635,12 @@ WindowlessEglContext& WindowlessEglContext::operator=(WindowlessEglContext&& oth #endif swap(other._display, _display); swap(other._context, _context); - #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + #if defined(CORRADE_TARGET_WINDOWS) || (defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)) swap(other._surface, _surface); #endif + #ifdef CORRADE_TARGET_WINDOWS + swap(other._window, _window); + #endif return *this; } diff --git a/src/Magnum/Platform/WindowlessEglApplication.h b/src/Magnum/Platform/WindowlessEglApplication.h index 4233f3a56..fce480e14 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.h +++ b/src/Magnum/Platform/WindowlessEglApplication.h @@ -162,9 +162,14 @@ class WindowlessEglContext { #ifndef MAGNUM_TARGET_WEBGL bool _sharedContext = false; #endif + #ifdef CORRADE_TARGET_WINDOWS + /* It's a HWND, which is HANDLE, which is PVOID, which is void*. FFS + Windows you're really mad with the typedefs. */ + void* _window{}; + #endif EGLDisplay _display{}; EGLContext _context{}; - #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + #if defined(CORRADE_TARGET_WINDOWS) || (defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)) /* Needed only by SwiftShader, using EGL_NO_SURFACE everywhere else */ EGLSurface _surface = EGL_NO_SURFACE; #endif diff --git a/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp b/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp deleted file mode 100644 index b27c2ac9d..000000000 --- a/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp +++ /dev/null @@ -1,238 +0,0 @@ -/* - This file is part of Magnum. - - Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020, 2021, 2022 Vladimír Vondruš - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. -*/ - -#include "WindowlessWindowsEglApplication.h" - -#include -#include - -#include "Magnum/GL/Version.h" - -#include "Implementation/Egl.h" - -#ifndef EGL_KHR_create_context_no_error -#define EGL_CONTEXT_OPENGL_NO_ERROR_KHR 0x31B3 -#endif - -namespace Magnum { namespace Platform { - -WindowlessWindowsEglContext::WindowlessWindowsEglContext(const Configuration& configuration, GLContext* const magnumContext) { - /** @todo device selection and skipping of eglInitialize()/Terminate() the - same way as with WindowlessEglContext */ - - /* Register the window class (if not yet done) */ - WNDCLASSW wc; - if(!GetClassInfoW(GetModuleHandleW(nullptr), L"Magnum Windowless Application", &wc)) { - wc = WNDCLASSW{ - 0, - DefWindowProcW, - 0, - 0, - GetModuleHandleW(nullptr), - nullptr, - nullptr, - HBRUSH(COLOR_BACKGROUND), - nullptr, - L"Magnum Windowless Application" - }; - - if(!RegisterClassW(&wc)) { - Error() << "Platform::WindowlessWglContext: cannot create window class:" << GetLastError(); - return; - } - } - - /* Create the window */ - _window = CreateWindowW(wc.lpszClassName, L"Magnum Windowless Application", - WS_OVERLAPPEDWINDOW, 0, 0, 32, 32, 0, 0, wc.hInstance, 0); - - /* Initialize */ - _display = eglGetDisplay(GetDC(_window)); - if(!eglInitialize(_display, nullptr, nullptr)) { - Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): cannot initialize EGL:" << Implementation::eglErrorString(eglGetError()); - return; - } - - const EGLenum api = - #ifndef MAGNUM_TARGET_GLES - EGL_OPENGL_API - #else - EGL_OPENGL_ES_API - #endif - ; - if(!eglBindAPI(api)) { - Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): cannot bind EGL API:" << Implementation::eglErrorString(eglGetError()); - return; - } - - /* Choose EGL config */ - static const EGLint attribs[] = { - EGL_RED_SIZE, 1, - EGL_GREEN_SIZE, 1, - EGL_BLUE_SIZE, 1, - EGL_DEPTH_SIZE, 1, - #ifndef MAGNUM_TARGET_GLES - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, - #elif defined(MAGNUM_TARGET_GLES3) - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, - #elif defined(MAGNUM_TARGET_GLES2) - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - #else - #error unsupported OpenGL edition - #endif - EGL_NONE - }; - EGLint configCount; - EGLConfig config; - if(!eglChooseConfig(_display, attribs, &config, 1, &configCount)) { - Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): cannot get EGL visual config:" << Implementation::eglErrorString(eglGetError()); - return; - } - - if(!configCount) { - Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): no matching EGL visual config available"; - return; - } - - /* Request debug context if GpuValidation is enabled either via the - configuration or via command-line */ - Configuration::Flags flags = configuration.flags(); - if((flags & Configuration::Flag::GpuValidation) || (magnumContext && magnumContext->configurationFlags() & GL::Context::Configuration::Flag::GpuValidation)) - flags |= Configuration::Flag::Debug; - else if((flags & Configuration::Flag::GpuValidationNoError) || (magnumContext && magnumContext->configurationFlags() & GL::Context::Configuration::Flag::GpuValidationNoError)) - flags |= Configuration::Flag::NoError; - - /** @todo needs a growable DynamicArray with disabled alloc or somesuch */ - EGLint attributes[7] = { - #ifdef MAGNUM_TARGET_GLES - EGL_CONTEXT_CLIENT_VERSION, - #ifdef MAGNUM_TARGET_GLES3 - 3, - #elif defined(MAGNUM_TARGET_GLES2) - 2, - #else - #error unsupported OpenGL ES version - #endif - #endif - /* Mask out the upper 32bits used for other flags */ - EGL_CONTEXT_FLAGS_KHR, EGLint(UnsignedLong(flags) & 0xffffffffu), - - /* The rest is added optionally */ - EGL_NONE, EGL_NONE, /* EGL_CONTEXT_OPENGL_NO_ERROR_KHR */ - EGL_NONE - }; - - std::size_t nextAttribute = 4; - CORRADE_INTERNAL_ASSERT(attributes[nextAttribute] == EGL_NONE); - - if(flags & Configuration::Flag::NoError) { - attributes[nextAttribute++] = EGL_CONTEXT_OPENGL_NO_ERROR_KHR; - attributes[nextAttribute++] = true; - } - - CORRADE_INTERNAL_ASSERT(nextAttribute < Containers::arraySize(attributes)); - - if(!(_context = eglCreateContext(_display, config, configuration.sharedContext(), attributes))) { - Error() << "Platform::WindowlessWindowsEglContext: cannot create EGL context:" << Implementation::eglErrorString(eglGetError()); - return; - } - - if(!(_surface = eglCreateWindowSurface(_display, config, _window, nullptr))) - Error() << "Platform::WindowlessWindowsEglContext: cannot create window surface:" << Implementation::eglErrorString(eglGetError()); -} - -WindowlessWindowsEglContext::WindowlessWindowsEglContext(WindowlessWindowsEglContext&& other) noexcept: _window{other._window}, _display{other._display}, _surface{other._surface}, _context{other._context} { - other._window = {}; - other._display = {}; - other._surface = {}; - other._context = {}; -} - -WindowlessWindowsEglContext::~WindowlessWindowsEglContext() { - if(_context) eglDestroyContext(_display, _context); - if(_surface) eglDestroySurface(_display, _surface); - if(_display) eglTerminate(_display); - if(_window) DestroyWindow(_window); -} - -WindowlessWindowsEglContext& WindowlessWindowsEglContext::operator=(WindowlessWindowsEglContext&& other) noexcept { - using std::swap; - swap(other._window, _window); - swap(other._display, _display); - swap(other._surface, _surface); - swap(other._context, _context); - return *this; -} - -bool WindowlessWindowsEglContext::makeCurrent() { - if(eglMakeCurrent(_display, _surface, _surface, _context)) - return true; - - Error() << "Platform::WindowlessWindowsEglContext::makeCurrent(): cannot make context current:" << GetLastError(); - return false; -} - -bool WindowlessWindowsEglContext::release() { - if(eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) - return true; - - Error() << "Platform::WindowlessWindowsEglApplication::release(): cannot release current context:" << GetLastError(); - return false; -} - -WindowlessWindowsEglContext::Configuration::Configuration() { - GL::Context::Configuration::addFlags(GL::Context::Configuration::Flag::Windowless); -} - -#ifndef DOXYGEN_GENERATING_OUTPUT -WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(const Arguments& arguments): WindowlessWindowsEglApplication{arguments, Configuration{}} {} -#endif - -WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(const Arguments& arguments, const Configuration& configuration): WindowlessWindowsEglApplication{arguments, NoCreate} { - createContext(configuration); -} - -WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(const Arguments& arguments, NoCreateT): _glContext{NoCreate}, _context{NoCreate, arguments.argc, arguments.argv} {} - -void WindowlessWindowsEglApplication::createContext() { createContext({}); } - -void WindowlessWindowsEglApplication::createContext(const Configuration& configuration) { - if(!tryCreateContext(configuration)) std::exit(1); -} - -bool WindowlessWindowsEglApplication::tryCreateContext(const Configuration& configuration) { - CORRADE_ASSERT(_context.version() == Version::None, "Platform::WindowlessWindowsEglApplication::tryCreateContext(): context already created", false); - - WindowlessWindowsEglContext glContext{configuration, &_context}; - if(!glContext.isCreated() || !glContext.makeCurrent() || !_context.tryCreate(configuration)) - return false; - - _glContext = std::move(glContext); - return true; -} - -WindowlessWindowsEglApplication::~WindowlessWindowsEglApplication() = default; - -}} diff --git a/src/Magnum/Platform/WindowlessWindowsEglApplication.h b/src/Magnum/Platform/WindowlessWindowsEglApplication.h deleted file mode 100644 index 21a309379..000000000 --- a/src/Magnum/Platform/WindowlessWindowsEglApplication.h +++ /dev/null @@ -1,538 +0,0 @@ -#ifndef Magnum_Platform_WindowlessWindowsEglApplication_h -#define Magnum_Platform_WindowlessWindowsEglApplication_h -/* - This file is part of Magnum. - - Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020, 2021, 2022 Vladimír Vondruš - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. -*/ - -/** @file - * @brief Class @ref Magnum::Platform::WindowlessWindowsEglApplication, @ref Magnum::Platform::WindowlessWindowsEglContext, macro @ref MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN() - */ - -#include "Magnum/configure.h" - -#ifdef MAGNUM_TARGET_GL -#ifndef DOXYGEN_GENERATING_OUTPUT -#define WIN32_LEAN_AND_MEAN 1 -#define VC_EXTRALEAN -#endif -#include -#include -#include -#include - -#include "Magnum/Magnum.h" -#include "Magnum/Tags.h" -#include "Magnum/Platform/GLContext.h" - -namespace Magnum { namespace Platform { - -/** -@brief Windowless Windows/EGL context - -@m_keywords{WindowlessGLContext EGL} - -GL context using pure WINAPI and EGL, used in @ref WindowlessWindowsEglApplication. - -Meant to be used when there is a need to manage (multiple) GL contexts -manually. See @ref platform-windowless-contexts for more information. If no -other application header is included, this class is also aliased to -@cpp Platform::WindowlessGLContext @ce. - -@note This class is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. -*/ -class WindowlessWindowsEglContext { - public: - class Configuration; - - /** - * @brief Constructor - * @param configuration Context configuration - * @param context Optional Magnum context instance constructed - * using @ref NoCreate to manage driver workarounds - * - * Once the context is created, make it current using - * @ref makeCurrent() and create @ref Platform::GLContext instance to - * be able to use Magnum. - * @see @ref isCreated() - */ - explicit WindowlessWindowsEglContext(const Configuration& configuration, GLContext* context = nullptr); - - /** - * @brief Construct without creating the context - * - * Move a instance with created context over to make it usable. - */ - explicit WindowlessWindowsEglContext(NoCreateT) {} - - /** @brief Copying is not allowed */ - WindowlessWindowsEglContext(const WindowlessWindowsEglContext&) = delete; - - /** @brief Move constructor */ - WindowlessWindowsEglContext(WindowlessWindowsEglContext&& other) noexcept; - - /** @brief Copying is not allowed */ - WindowlessWindowsEglContext& operator=(const WindowlessWindowsEglContext&) = delete; - - /** @brief Move assignment */ - WindowlessWindowsEglContext& operator=(WindowlessWindowsEglContext&& other) noexcept; - - /** - * @brief Destructor - * - * Destroys the context, if any. - */ - ~WindowlessWindowsEglContext(); - - /** @brief Whether the context is created */ - bool isCreated() const { return _context; } - - /** - * @brief Make the context current - * - * Prints error message and returns @cpp false @ce on failure, - * otherwise returns @cpp true @ce. If the context is current on - * another thread, you have to @ref release() it there first --- an - * OpenGL context can't be current in multiple threads at the same - * time. - */ - bool makeCurrent(); - - /** - * @brief Release current context - * @m_since_latest - * - * Releases a context previously made current using @ref makeCurrent(). - * Prints error message and returns @cpp false @ce on failure, - * otherwise returns @cpp true @ce. - */ - bool release(); - - /** - * @brief Underlying OpenGL context - * @m_since{2020,06} - * - * Use in case you need to call EGL functionality directly or in order - * to create a shared context. Returns @cpp nullptr @ce in case the - * context was not created yet. - * @see @ref Configuration::setSharedContext() - */ - EGLContext glContext() { return _context; } - - private: - HWND _window{}; - EGLDisplay _display{}; - EGLSurface _surface{}; - EGLContext _context{}; -}; - -/** -@brief Configuration - -@see @ref WindowlessWindowsEglContext(), - @ref WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(), - @ref WindowlessWindowsEglApplication::createContext(), - @ref WindowlessWindowsEglApplication::tryCreateContext() -*/ -class WindowlessWindowsEglContext::Configuration: public GL::Context::Configuration { - public: - /** - * @brief Context flag - * - * Includes also everything from @ref GL::Context::Configuration::Flag - * except for @relativeref{GL::Context::Configuration,Flag::Windowless}, - * which is enabled implicitly by default. - * @see @ref Flags, @ref setFlags(), @ref GL::Context::Flag - */ - enum class Flag: UnsignedLong { - /** - * Debug context. Enabled automatically if supported by the driver - * and the @ref Flag::GpuValidation flag is set or if the - * `--magnum-gpu-validation` @ref GL-Context-usage-command-line "command-line option" - * is set to `on`. - */ - Debug = EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, - - /** - * Context without error reporting. Might result in better - * performance, but situations that would have generated errors - * instead cause undefined behavior. Enabled automatically if - * supported by the driver and the @ref Flag::GpuValidationNoError - * flag is set or if the `--magnum-gpu-validation` @ref GL-Context-usage-command-line "command-line option" - * is set to `no-error`. - * @m_since_latest - */ - /* Treated as a separate attribute and not a flag in EGL, thus - handling manually. */ - NoError = 1ull << 32, - - /** - * @copydoc GL::Context::Configuration::Flag::QuietLog - * @m_since_latest - */ - QuietLog = UnsignedLong(GL::Context::Configuration::Flag::QuietLog), - - /** - * @copydoc GL::Context::Configuration::Flag::VerboseLog - * @m_since_latest - */ - VerboseLog = UnsignedLong(GL::Context::Configuration::Flag::VerboseLog), - - /** - * @copydoc GL::Context::Configuration::Flag::GpuValidation - * @m_since_latest - */ - GpuValidation = UnsignedLong(GL::Context::Configuration::Flag::GpuValidation), - - /** - * @copydoc GL::Context::Configuration::Flag::GpuValidationNoError - * @m_since_latest - */ - GpuValidationNoError = UnsignedLong(GL::Context::Configuration::Flag::GpuValidationNoError) - }; - - /** - * @brief Context flags - * - * @see @ref setFlags(), @ref Context::Flags - */ - typedef Containers::EnumSet Flags; - - /*implicit*/ Configuration(); - - /** @brief Context flags */ - Flags flags() const { - return Flag(UnsignedLong(GL::Context::Configuration::flags())); - } - - /** - * @brief Set context flags - * @return Reference to self (for method chaining) - * - * Default is no flag. To avoid clearing default flags by accident, - * prefer to use @ref addFlags() and @ref clearFlags() instead. - * @see @ref GL::Context::flags() - */ - Configuration& setFlags(Flags flags) { - GL::Context::Configuration::setFlags(GL::Context::Configuration::Flag(UnsignedLong(flags))); - return *this; - } - - /** - * @brief Add context flags - * @return Reference to self (for method chaining) - * - * Unlike @ref setFlags(), ORs the flags with existing instead of - * replacing them. Useful for preserving the defaults. - * @see @ref clearFlags() - */ - Configuration& addFlags(Flags flags) { - GL::Context::Configuration::addFlags(GL::Context::Configuration::Flag(UnsignedLong(flags))); - return *this; - } - - /** - * @brief Clear context flags - * @return Reference to self (for method chaining) - * - * Unlike @ref setFlags(), ANDs the inverse of @p flags with existing - * instead of replacing them. Useful for removing default flags. - * @see @ref addFlags() - */ - Configuration& clearFlags(Flags flags) { - GL::Context::Configuration::clearFlags(GL::Context::Configuration::Flag(UnsignedLong(flags))); - return *this; - } - - /** - * @brief Create a shared context - * @return Reference to self (for method chaining) - * @m_since{2020,06} - * - * When set, the created context will share a subset of OpenGL objects - * with @p context, instead of being independent. Many caveats and - * limitations apply to shared OpenGL contexts, please consult the - * OpenGL specification for details. Default is `EGL_NO_CONTEXT`, i.e. - * no sharing. - * @see @ref WindowlessWindowsEglContext::glContext(), - * @ref WindowlessWindowsEglApplication::glContext() - */ - Configuration& setSharedContext(EGLContext context) { - _sharedContext = context; - return *this; - } - - /** - * @brief Shared context - * @m_since{2020,06} - */ - EGLContext sharedContext() const { return _sharedContext; } - - /* Overloads to remove WTF-factor from method chaining order */ - #ifndef DOXYGEN_GENERATING_OUTPUT - MAGNUM_GL_CONTEXT_CONFIGURATION_SUBCLASS_IMPLEMENTATION(Configuration) - #endif - - private: - EGLContext _sharedContext = EGL_NO_CONTEXT; -}; - -CORRADE_ENUMSET_OPERATORS(WindowlessWindowsEglContext::Configuration::Flags) - -/** -@brief Windowless Windows/EGL application - -@m_keywords{WindowlessApplication EGL} - -Application for offscreen rendering using @ref WindowlessWindowsEglContext. -This application library is available on OpenGL ES (also ANGLE) on Windows. - -@section Platform-WindowlessWindowsEglApplication-bootstrap Bootstrap application - -Fully contained windowless application using @ref WindowlessWindowsEglApplication -along with CMake setup is available in `windowless` branch of -[Magnum Bootstrap](https://github.com/mosra/magnum-bootstrap) repository, -download it as [tar.gz](https://github.com/mosra/magnum-bootstrap/archive/windowless.tar.gz) -or [zip](https://github.com/mosra/magnum-bootstrap/archive/windowless.zip) -file. After extracting the downloaded archive you can build and run the -application with these four commands: - -@code{.sh} -mkdir build && cd build -cmake .. -cmake --build . -./src/MyApplication # or ./src/Debug/MyApplication -@endcode - -See @ref cmake for more information. - -@section Platform-WindowlessWindowsEglApplication-usage General usage - -This application library is built if `MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION` -is enabled when building Magnum. To use this library from CMake, put -[FindEGL.cmake](https://github.com/mosra/magnum/blob/master/modules/FindEGL.cmake) -into your `modules/` directory, request the `WindowlessWindowsEglApplication` -component of the `Magnum` package and link to the -`Magnum::WindowlessWindowsEglApplication` target: - -@code{.cmake} -find_package(Magnum REQUIRED) -if(CORRADE_TARGET_WINDOWS) - find_package(Magnum REQUIRED WindowlessWindowsEglApplication) -endif() - -# ... -if(CORRADE_TARGET_WINDOWS) - target_link_libraries(your-app PRIVATE Magnum::WindowlessWindowsEglApplication) -endif() -@endcode - -Additionally, if you're using Magnum as a CMake subproject, do the following -* *before* calling @cmake find_package() @ce to ensure it's enabled, as the -library is not built by default: - -@code{.cmake} -set(MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION ON CACHE BOOL "" FORCE) -add_subdirectory(magnum EXCLUDE_FROM_ALL) -@endcode - -If no other application is requested, you can also use the generic -`Magnum::WindowlessApplication` alias to simplify porting. Again, see -@ref building and @ref cmake for more information. - -Place your code into @ref exec(). The subclass can be then used in main -function using @ref MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN() macro. See -@ref platform for more information. - -@code{.cpp} -class MyApplication: public Platform::WindowlessWindowsEglApplication { - // implement required methods... -}; -MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN(MyApplication) -@endcode - -If no other application header is included, this class is also aliased to -@cpp Platform::WindowlessApplication @ce and the macro is aliased to -@cpp MAGNUM_WINDOWLESSAPPLICATION_MAIN() @ce to simplify porting. - -@note This class is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. -*/ -class WindowlessWindowsEglApplication { - public: - /** @brief Application arguments */ - struct Arguments { - /** @brief Constructor */ - /*implicit*/ constexpr Arguments(int& argc, char** argv) noexcept: argc{argc}, argv{argv} {} - - int& argc; /**< @brief Argument count */ - char** argv; /**< @brief Argument values */ - }; - - /** - * @brief Configuration - * - * @see @ref WindowlessWindowsEglApplication(), @ref createContext(), - * @ref tryCreateContext() - */ - typedef WindowlessWindowsEglContext::Configuration Configuration; - - /** - * @brief Default constructor - * @param arguments Application arguments - * @param configuration Configuration - * - * Creates application with default or user-specified configuration. - * See @ref Configuration for more information. The program exits if - * the context cannot be created, see @ref tryCreateContext() for an - * alternative. - */ - #ifdef DOXYGEN_GENERATING_OUTPUT - explicit WindowlessWindowsEglApplication(const Arguments& arguments, const Configuration& configuration = Configuration()); - #else - /* To avoid "invalid use of incomplete type" */ - explicit WindowlessWindowsEglApplication(const Arguments& arguments, const Configuration& configuration); - explicit WindowlessWindowsEglApplication(const Arguments& arguments); - #endif - - /** - * @brief Constructor - * @param arguments Application arguments - * - * Unlike above, the context is not created and must be created later - * with @ref createContext() or @ref tryCreateContext(). - */ - explicit WindowlessWindowsEglApplication(const Arguments& arguments, NoCreateT); - - /** @brief Copying is not allowed */ - WindowlessWindowsEglApplication(const WindowlessWindowsEglApplication&) = delete; - - /** @brief Moving is not allowed */ - WindowlessWindowsEglApplication(WindowlessWindowsEglApplication&&) = delete; - - /** @brief Copying is not allowed */ - WindowlessWindowsEglApplication& operator=(const WindowlessWindowsEglApplication&) = delete; - - /** @brief Moving is not allowed */ - WindowlessWindowsEglApplication& operator=(WindowlessWindowsEglApplication&&) = delete; - - /** - * @brief Execute application - * @return Value for returning from @cpp main() @ce - * - * See @ref MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN() for usage - * information. - */ - virtual int exec() = 0; - - /** - * @brief Underlying OpenGL context - * @m_since{2020,06} - * - * Use in case you need to call EGL functionality directly or in order - * to create a shared context. Returns @cpp nullptr @ce in case the - * context was not created yet. - * @see @ref Configuration::setSharedContext() - */ - EGLContext glContext() { return _glContext.glContext(); } - - protected: - /* Nobody will need to have (and delete) WindowlessWindowsEglApplication*, - thus this is faster than public pure virtual destructor */ - ~WindowlessWindowsEglApplication(); - - /** - * @brief Create context with given configuration - * - * Must be called if and only if the context wasn't created by the - * constructor itself. Error message is printed and the program exits - * if the context cannot be created, see @ref tryCreateContext() for an - * alternative. - */ - #ifdef DOXYGEN_GENERATING_OUTPUT - void createContext(const Configuration& configuration = Configuration()); - #else - /* To avoid "invalid use of incomplete type" */ - void createContext(const Configuration& configuration); - void createContext(); - #endif - - /** - * @brief Try to create context with given configuration - * - * Unlike @ref createContext() returns @cpp false @ce if the context - * cannot be created, @cpp true @ce otherwise. - */ - bool tryCreateContext(const Configuration& configuration); - - private: - WindowlessWindowsEglContext _glContext; - Platform::GLContext _context; -}; - -/** @hideinitializer -@brief Entry point for windowless Windows/EGL application -@param className Class name - -@m_keywords{MAGNUM_WINDOWLESSAPPLICATION_MAIN()} - -See @ref Magnum::Platform::WindowlessWindowsEglApplication "Platform::WindowlessWindowsEglApplication" -for usage information.This macro abstracts out platform-specific entry point -code and is equivalent to the following, see @ref portability-applications for -more information. - -@code{.cpp} -int main(int argc, char** argv) { - className app({argc, argv}); - return app.exec(); -} -@endcode - -When no other windowless application header is included this macro is also -aliased to @cpp MAGNUM_WINDOWLESSAPPLICATION_MAIN() @ce. -*/ -#define MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN(className) \ - int main(int argc, char** argv) { \ - className app({argc, argv}); \ - return app.exec(); \ - } - -#ifndef DOXYGEN_GENERATING_OUTPUT -#ifndef MAGNUM_WINDOWLESSAPPLICATION_MAIN -typedef WindowlessWindowsEglApplication WindowlessApplication; -typedef WindowlessWindowsEglContext WindowlessGLContext; -#define MAGNUM_WINDOWLESSAPPLICATION_MAIN(className) MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN(className) -#else -#undef MAGNUM_WINDOWLESSAPPLICATION_MAIN -#endif -#endif - -}} -#else -#error this header is available only in the OpenGL build -#endif - -#endif diff --git a/src/Magnum/Platform/gl-info.cpp b/src/Magnum/Platform/gl-info.cpp index 2c18680e3..c3705df94 100644 --- a/src/Magnum/Platform/gl-info.cpp +++ b/src/Magnum/Platform/gl-info.cpp @@ -72,7 +72,7 @@ #endif #elif defined(CORRADE_TARGET_WINDOWS) #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessWindowsEglApplication.h" +#include "Magnum/Platform/WindowlessEglApplication.h" #else #include "Magnum/Platform/WindowlessWglApplication.h" #endif @@ -241,8 +241,6 @@ MagnumInfo::MagnumInfo(const Arguments& arguments): Platform::WindowlessApplicat Debug{} << "Used application: Platform::WindowlessGlxApplication"; #elif defined(MAGNUM_WINDOWLESSWGLAPPLICATION_MAIN) Debug{} << "Used application: Platform::WindowlessWglApplication"; - #elif defined(MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN) - Debug{} << "Used application: Platform::WindowlessWindowsEglApplication"; #else #error no windowless application available on this platform #endif diff --git a/src/Magnum/Text/CMakeLists.txt b/src/Magnum/Text/CMakeLists.txt index 6fc0e0c37..d5ac2d0dc 100644 --- a/src/Magnum/Text/CMakeLists.txt +++ b/src/Magnum/Text/CMakeLists.txt @@ -131,7 +131,7 @@ if(MAGNUM_WITH_FONTCONVERTER) endif() elseif(CORRADE_TARGET_WINDOWS) if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessWindowsEglApplication) + target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessEglApplication) else() target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessWglApplication) endif() diff --git a/src/Magnum/Text/fontconverter.cpp b/src/Magnum/Text/fontconverter.cpp index cff8872ec..5180df6d4 100644 --- a/src/Magnum/Text/fontconverter.cpp +++ b/src/Magnum/Text/fontconverter.cpp @@ -48,7 +48,7 @@ #endif #elif defined(CORRADE_TARGET_WINDOWS) #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessWindowsEglApplication.h" +#include "Magnum/Platform/WindowlessEglApplication.h" #else #include "Magnum/Platform/WindowlessWglApplication.h" #endif diff --git a/src/Magnum/TextureTools/CMakeLists.txt b/src/Magnum/TextureTools/CMakeLists.txt index 114f0f564..8fc9eafc8 100644 --- a/src/Magnum/TextureTools/CMakeLists.txt +++ b/src/Magnum/TextureTools/CMakeLists.txt @@ -99,7 +99,7 @@ if(MAGNUM_WITH_DISTANCEFIELDCONVERTER) endif() elseif(CORRADE_TARGET_WINDOWS) if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessWindowsEglApplication) + target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessEglApplication) else() target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessWglApplication) endif() diff --git a/src/Magnum/TextureTools/distancefieldconverter.cpp b/src/Magnum/TextureTools/distancefieldconverter.cpp index 50830ef32..226c01325 100644 --- a/src/Magnum/TextureTools/distancefieldconverter.cpp +++ b/src/Magnum/TextureTools/distancefieldconverter.cpp @@ -56,7 +56,7 @@ #endif #elif defined(CORRADE_TARGET_WINDOWS) #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessWindowsEglApplication.h" +#include "Magnum/Platform/WindowlessEglApplication.h" #else #include "Magnum/Platform/WindowlessWglApplication.h" #endif From e7321a07e769c2e83e805a0c023dfd58135efebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 9 Sep 2022 19:37:41 +0200 Subject: [PATCH 82/93] Platform: set SDL_HINT_OPENGL_ES_DRIVER only on GLES. I was setting it on desktop GL as well, and *somehow* it worked. That does not mean it's correct. --- src/Magnum/Platform/Sdl2Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index c1d6fa8be..f788803dc 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -142,7 +142,7 @@ Sdl2Application::Sdl2Application(const Arguments& arguments, NoCreateT): /* Available since 2.0.6, uses dedicated OpenGL ES drivers by default and a desktop GLES context only if MAGNUM_TARGET_DESKTOP_GLES is defined as well. */ - #if !defined(MAGNUM_TARGET_DESKTOP_GLES) && defined(SDL_HINT_OPENGL_ES_DRIVER) + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) && defined(SDL_HINT_OPENGL_ES_DRIVER) SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1"); #endif /* Available since 2.0.8, disables compositor bypass on X11, which causes From e62b2bb000d46cdd516c6d65d6703019be61ce75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 9 Sep 2022 20:08:24 +0200 Subject: [PATCH 83/93] Replace MAGNUM_TARGET_{HEADLESS,DESKTOP_GLES} with MAGNUM_TARGET_EGL. These two options were mutually exclusive, and both were doing the same thing -- switching to EGL on desktop GL, or switching away from EGL on GLES. That made all logic vastly more complicated than it should be, and unfortunately it took me half a decade to realize that. The new logic is significantly simpler everywhere. As usual, the old options are still recognized by CMake on a deprecated build (with a warning), and are still exposed both as CMake variables and a preprocessor define. But the logic for them was quite complicated, so I don't guarantee all cases are covered. I also tried to clean up the dependent CMake options to allow building GLX and WGL apps on GLES independently of whether EGL is used, but it's quite a mess due to the limitations of CMake < 3.22. Build directories that have the options switched randomly over a long time might start misbehaving, but the initial build should work well. --- CMakeLists.txt | 164 ++++++++++++------ doc/building.dox | 16 +- doc/changelog.dox | 15 +- doc/cmake.dox | 10 +- modules/FindMagnum.cmake | 67 +++---- package/archlinux/PKGBUILD-es2desktop | 2 +- package/archlinux/PKGBUILD-es3desktop | 2 +- package/ci/appveyor-desktop-gles.bat | 2 +- src/Magnum/GL/CMakeLists.txt | 6 +- src/Magnum/GL/OpenGL.h | 2 +- src/Magnum/GL/OpenGLTester.h | 14 +- src/Magnum/Magnum.h | 60 ++++--- src/Magnum/Platform/CMakeLists.txt | 49 +++--- src/Magnum/Platform/GlfwApplication.cpp | 10 +- src/Magnum/Platform/GlxApplication.h | 3 +- src/Magnum/Platform/Sdl2Application.cpp | 7 +- .../Platform/WindowlessGlxApplication.h | 5 +- src/Magnum/Platform/gl-info.cpp | 19 +- src/Magnum/Text/CMakeLists.txt | 16 +- src/Magnum/Text/fontconverter.cpp | 10 +- src/Magnum/TextureTools/CMakeLists.txt | 16 +- .../TextureTools/distancefieldconverter.cpp | 12 +- src/Magnum/configure.h.cmake | 26 ++- .../OpenGL/GLES2/CMakeLists.txt | 4 +- .../OpenGL/GLES3/CMakeLists.txt | 4 +- 25 files changed, 292 insertions(+), 249 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eb69f5e3e..1c04f11d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,27 +146,17 @@ if(NOT DEFINED _MAGNUM_ACCEPT_DEPRECATED_UNPREFIXED_OPTIONS) endforeach() endif() -# If targeting iOS, Android, Emscripten or Windows RT, set explicit OpenGL ES -# support -if(NOT CORRADE_TARGET_IOS AND NOT CORRADE_TARGET_ANDROID AND NOT CORRADE_TARGET_EMSCRIPTEN AND NOT CORRADE_TARGET_WINDOWS_RT) - option(MAGNUM_TARGET_GLES "Build for OpenGL ES / WebGL" OFF) -else() - set(MAGNUM_TARGET_GLES ON) -endif() - -cmake_dependent_option(MAGNUM_TARGET_GLES2 "Build for OpenGL ES 2 / WebGL 1.0" ON "MAGNUM_TARGET_GLES" OFF) -cmake_dependent_option(MAGNUM_TARGET_DESKTOP_GLES "Build for OpenGL ES on desktop" OFF "MAGNUM_TARGET_GLES" OFF) - # Magnum GL Info (currently only using GLX/CGL/EGL on *nix, WGL/EGL on Windows # and EGL on Emscripten) if(CORRADE_TARGET_UNIX OR CORRADE_TARGET_WINDOWS OR CORRADE_TARGET_EMSCRIPTEN) option(MAGNUM_WITH_GL_INFO "Build magnum-gl-info utility" OFF) endif() -# Desktop-only utilities +# Desktop-only utilities. Not guaranteed to build on GLES, but showing the +# option everywhere for simplicity. if(CORRADE_TARGET_UNIX OR CORRADE_TARGET_WINDOWS) - cmake_dependent_option(MAGNUM_WITH_FONTCONVERTER "Build magnum-fontconverter utility" OFF "NOT MAGNUM_TARGET_GLES" OFF) - cmake_dependent_option(MAGNUM_WITH_DISTANCEFIELDCONVERTER "Build magnum-distancefieldconverter utility" OFF "NOT MAGNUM_TARGET_GLES" OFF) + option(MAGNUM_WITH_FONTCONVERTER "Build magnum-fontconverter utility" OFF) + option(MAGNUM_WITH_DISTANCEFIELDCONVERTER "Build magnum-distancefieldconverter utility" OFF) endif() # API-independent utilities @@ -208,12 +198,31 @@ cmake_dependent_option(MAGNUM_WITH_TRADE "Build Trade library" ON "NOT MAGNUM_WI cmake_dependent_option(MAGNUM_WITH_GL "Build GL library" ON "NOT MAGNUM_WITH_SHADERS;NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_ANDROIDAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSIOSAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSCGLAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSGLXAPPLICATION;NOT MAGNUM_WITH_CGLCONTEXT;NOT MAGNUM_WITH_GLXAPPLICATION;NOT MAGNUM_WITH_GLXCONTEXT;NOT MAGNUM_WITH_XEGLAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSWGLAPPLICATION;NOT MAGNUM_WITH_WGLCONTEXT;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) option(MAGNUM_WITH_PRIMITIVES "Build Primitives library" ON) -cmake_dependent_option(MAGNUM_TARGET_HEADLESS "Build command-line utilities for use on a headless machines" OFF "MAGNUM_WITH_GL" OFF) cmake_dependent_option(MAGNUM_TARGET_GL "Build libraries with OpenGL interoperability" ON "MAGNUM_WITH_GL" OFF) -# EGL context and windowless EGL application, available everywhere -cmake_dependent_option(MAGNUM_WITH_WINDOWLESSEGLAPPLICATION "Build WindowlessEglApplication library" OFF "NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES OR NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_TARGET_HEADLESS" ON) -option(MAGNUM_WITH_EGLCONTEXT "Build EglContext library" OFF) +# If targeting iOS, Android, Emscripten or Windows RT, implicitly enable GLES. +# Otherwise default to desktop GL. +if(CORRADE_TARGET_IOS OR CORRADE_TARGET_ANDROID OR CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_WINDOWS_RT) + set(MAGNUM_TARGET_GLES ON) +elseif(MAGNUM_WITH_GL) + cmake_dependent_option(MAGNUM_TARGET_GLES "Build for OpenGL ES / WebGL" OFF "MAGNUM_WITH_GL" OFF) +endif() + +# If targeting Android, Emscripten or Windows RT, implicitly enable EGL. +# Otherwise enable EGL by default only if targeting GLES and not on iOS (where +# it's EAGL instead) +if(CORRADE_TARGET_ANDROID OR CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_WINDOWS_RT) + set(MAGNUM_TARGET_EGL ON) +else() + if(MAGNUM_TARGET_GLES AND NOT CORRADE_TARGET_IOS) + set(_MAGNUM_TARGET_EGL_DEFAULT ON) + else() + set(_MAGNUM_TARGET_EGL_DEFAULT OFF) + endif() + cmake_dependent_option(MAGNUM_TARGET_EGL "Build for EGL instead of EAGL / CGL / GLX / WGL" ${_MAGNUM_TARGET_EGL_DEFAULT} "MAGNUM_WITH_GL" OFF) +endif() + +cmake_dependent_option(MAGNUM_TARGET_GLES2 "Build for OpenGL ES 2 / WebGL 1.0" ON "MAGNUM_TARGET_GLES" OFF) # Vulkan, everywhere except Emscripten if(NOT CORRADE_TARGET_EMSCRIPTEN) @@ -221,6 +230,18 @@ if(NOT CORRADE_TARGET_EMSCRIPTEN) cmake_dependent_option(MAGNUM_TARGET_VK "Build libraries with Vulkan interoperability" ON "MAGNUM_WITH_VK" OFF) endif() +# EGL context and windowless EGL application, available everywhere. If +# targeting EGL and not on Windows, it's implied by the CLI tools, otherwise +# it's independent. +if(MAGNUM_TARGET_EGL AND NOT CORRADE_TARGET_WINDOWS) + cmake_dependent_option(MAGNUM_WITH_WINDOWLESSEGLAPPLICATION "Build WindowlessEglApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER;NOT MAGNUM_WITH_FONTCONVERTER" ON) +else() + # TODO when CMake 3.22 can be relied on, clean this up to use a proper + # condition instead + cmake_dependent_option(MAGNUM_WITH_WINDWLESSEGLAPPLICATION "Build WindowlessEglApplication library" OFF "ON" OFF) +endif() +option(MAGNUM_WITH_EGLCONTEXT "Build EglContext library" OFF) + # Android-specific application libraries if(CORRADE_TARGET_ANDROID) option(MAGNUM_WITH_ANDROIDAPPLICATION "Build AndroidApplication library" OFF) @@ -234,25 +255,42 @@ elseif(CORRADE_TARGET_IOS) option(MAGNUM_WITH_WINDOWLESSIOSAPPLICATION "Build WindowlessIosApplication library" OFF) # macOS-specific application libraries -elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_GLES) - cmake_dependent_option(MAGNUM_WITH_WINDOWLESSCGLAPPLICATION "Build WindowlessCglApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) +elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_EGL) + # WindowlessCglApplication implied by the CLI tools unless targeting EGL + if(NOT MAGNUM_TARGET_EGL) + cmake_dependent_option(MAGNUM_WITH_WINDOWLESSCGLAPPLICATION "Build WindowlessCglApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) + else() + # TODO when CMake 3.22 can be relied on, clean this up to use a proper + # condition instead + cmake_dependent_option(MAGNUM_WITH_WINDOWLESSCGLAPPLICATION "Build WindowlessCglApplication library" OFF "ON" OFF) + endif() option(MAGNUM_WITH_CGLCONTEXT "Build CglContext library" OFF) # X11 + GLX/EGL-specific application libraries elseif(CORRADE_TARGET_UNIX) option(MAGNUM_WITH_GLXAPPLICATION "Build GlxApplication library" OFF) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) + # WindowlessGlxApplication implied by the CLI tools unless targeting EGL + if(NOT MAGNUM_TARGET_EGL) cmake_dependent_option(MAGNUM_WITH_WINDOWLESSGLXAPPLICATION "Build WindowlessGlxApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) - option(MAGNUM_WITH_GLXCONTEXT "Build GlxContext library" OFF) + else() + # TODO when CMake 3.22 can be relied on, clean this up to use a proper + # condition instead + cmake_dependent_option(MAGNUM_WITH_WINDOWLESSGLXAPPLICATION "Build WindowlessGlxApplication library" OFF "ON" OFF) endif() + option(MAGNUM_WITH_GLXCONTEXT "Build GlxContext library" OFF) option(MAGNUM_WITH_XEGLAPPLICATION "Build XEglApplication library" OFF) # Windows-specific application libraries elseif(CORRADE_TARGET_WINDOWS) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) + # WindowlessWglApplication implied by the CLI tools unless targeting EGL + if(NOT MAGNUM_TARGET_EGL) cmake_dependent_option(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION "Build WindowlessWglApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) - option(MAGNUM_WITH_WGLCONTEXT "Build WglContext library" OFF) + else() + # TODO when CMake 3.22 can be relied on, clean this up to use a proper + # condition instead + cmake_dependent_option(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION "Build WindowlessWglApplication library" OFF "ON" OFF) endif() + option(MAGNUM_WITH_WGLCONTEXT "Build WglContext library" OFF) endif() # Platform-independent (almost) application libraries @@ -265,18 +303,6 @@ endif() option(MAGNUM_BUILD_DEPRECATED "Include deprecated API in the build" ON) -# BUILD_MULTITHREADED got moved to Corrade itself. In case we're building with -# deprecated features enabled, print a warning in case it's set but Corrade -# reports a different value. We can't print a warning in case it's set because -# that would cause false positives when both Corrade and Magnum are subprojects -# (and thus this option is visible to both). Otherwise it's silent --- for -# non-deprecated builds CMake will at most warn about "variable being unused". -if(MAGNUM_BUILD_DEPRECATED) - if(DEFINED BUILD_MULTITHREADED AND ((NOT CORRADE_BUILD_MULTITHREADED AND BUILD_MULTITHREADED) OR (CORRADE_BUILD_MULTITHREADED AND NOT BUILD_MULTITHREADED))) - message(DEPRECATION "BUILD_MULTITHREADED (set to ${BUILD_MULTITHREADED}) is now ignored — you need to set it when building Corrade instead (there it's ${CORRADE_BUILD_MULTITHREADED} now)") - endif() -endif() - set(MAGNUM_DEPLOY_PREFIX "." CACHE STRING "Prefix where to put final application executables") @@ -386,6 +412,9 @@ if(_MAGNUM_ACCEPT_DEPRECATED_UNPREFIXED_OPTIONS AND MAGNUM_BUILD_DEPRECATED) set(WITH_WINDOWLESSCGLAPPLICATION ON) endif() elseif(CORRADE_TARGET_UNIX) + # Checking the old deprecated options here, checking + # MAGNUM_TARGET_EGL wouldn't make sense as that's an option the + # old code definitely won't use. if((NOT TARGET_GLES AND NOT TARGET_HEADLESS) OR TARGET_DESKTOP_GLES) if(NOT DEFINED WITH_WINDOWLESSGLXAPPLICATION) set(WITH_WINDOWLESSGLXAPPLICATION ON) @@ -454,6 +483,45 @@ if(_MAGNUM_ACCEPT_DEPRECATED_UNPREFIXED_OPTIONS AND MAGNUM_BUILD_DEPRECATED) endif() endif() +# Handle other deprecated options. For non-deprecated builds CMake will at most +# warn about "variable being unused". Done after the MAGNUM_ prefix backwards +# compatibility above to pick up also the old names, i.e. TARGET_HEADLESS -> +# MAGNUM_TARGET_EGL. +if(MAGNUM_BUILD_DEPRECATED) + # BUILD_MULTITHREADED got moved to Corrade itself. Print a warning in case + # it's set but Corrade reports a different value. We can't print a warning + # in case it's set because that would cause false positives when both + # Corrade and Magnum are subprojects (and thus this option is visible to + # both). + if(DEFINED BUILD_MULTITHREADED AND ((NOT CORRADE_BUILD_MULTITHREADED AND BUILD_MULTITHREADED) OR (CORRADE_BUILD_MULTITHREADED AND NOT BUILD_MULTITHREADED))) + message(DEPRECATION "BUILD_MULTITHREADED (set to ${BUILD_MULTITHREADED}) is now ignored — you need to set it when building Corrade instead (there it's ${CORRADE_BUILD_MULTITHREADED} now)") + endif() + + # The following two options were desktop-only, so don't handle any + # backwards compatibility on mobile / web platforms + if(NOT CORRADE_TARGET_IOS AND NOT CORRADE_TARGET_ANDROID AND NOT CORRADE_TARGET_EMSCRIPTEN AND NOT CORRADE_TARGET_WINDOWS_RT) + # MAGNUM_TARGET_HEADLESS is now MAGNUM_TARGET_EGL. Print a warning in + # case we're on desktop GL (where it was meant to be used) and the two + # are set to a different value, and sync them. + if(NOT MAGNUM_TARGET_GLES AND DEFINED MAGNUM_TARGET_HEADLESS AND ((NOT MAGNUM_TARGET_EGL AND MAGNUM_TARGET_HEADLESS) OR (MAGNUM_TARGET_EGL AND NOT MAGNUM_TARGET_HEADLESS))) + message(DEPRECATION "MAGNUM_TARGET_HEADLESS is deprecated, use MAGNUM_TARGET_EGL instead") + set(MAGNUM_TARGET_EGL ${MAGNUM_TARGET_HEADLESS}) + endif() + + # MAGNUM_TARGET_DESKTOP_GLES is now an inverse of MAGNUM_TARGET_EGL. + # Print a warning in case we're on GLES (where it was meant to be used) + # and the two are set to a different value, and sync them. + if(MAGNUM_TARGET_GLES AND DEFINED MAGNUM_TARGET_DESKTOP_GLES AND ((MAGNUM_TARGET_EGL AND MAGNUM_TARGET_DESKTOP_GLES) OR (NOT MAGNUM_TARGET_EGL AND NOT MAGNUM_TARGET_DESKTOP_GLES))) + message(DEPRECATION "MAGNUM_TARGET_DESKTOP_GLES is deprecated, use MAGNUM_TARGET_EGL instead") + if(MAGNUM_TARGET_DESKTOP_GLES) + set(MAGNUM_TARGET_EGL OFF) + else() + set(MAGNUM_TARGET_EGL ON) + endif() + endif() + endif() +endif() + # Dynamic linking is meaningless on Emscripten and too inconvenient on Android if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(MAGNUM_BUILD_STATIC ON) @@ -465,7 +533,7 @@ endif() # Check dependencies if(MAGNUM_WITH_GL) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) + if(NOT MAGNUM_TARGET_GLES OR (MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_EGL AND NOT CORRADE_TARGET_IOS)) # OpenGL library preference. Prefer to use GLVND, since that's the # better approach nowadays, but allow the users to override it from # outside in case it is broken for some reason (Nvidia drivers in @@ -487,7 +555,7 @@ else() # consistency set(MAGNUM_TARGET_GLES OFF) set(MAGNUM_TARGET_GLES2 OFF) - set(MAGNUM_TARGET_DESKTOP_GLES OFF) + set(MAGNUM_TARGET_EGL OFF) endif() if(NOT MAGNUM_WITH_VK) @@ -523,7 +591,7 @@ if(MAGNUM_BUILD_TESTS) endif() if(MAGNUM_WITH_OPENGLTESTER) - if(MAGNUM_TARGET_HEADLESS OR CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) + if(MAGNUM_TARGET_EGL) set(MAGNUM_WITH_WINDOWLESSEGLAPPLICATION ON) set(OPENGLTESTER_APPLICATION MagnumWindowlessEglApplication) elseif(CORRADE_TARGET_IOS) @@ -533,21 +601,11 @@ if(MAGNUM_WITH_OPENGLTESTER) set(MAGNUM_WITH_WINDOWLESSCGLAPPLICATION ON) set(OPENGLTESTER_APPLICATION MagnumWindowlessCglApplication) elseif(CORRADE_TARGET_UNIX) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - set(MAGNUM_WITH_WINDOWLESSEGLAPPLICATION ON) - set(OPENGLTESTER_APPLICATION MagnumWindowlessEglApplication) - else() - set(MAGNUM_WITH_WINDOWLESSGLXAPPLICATION ON) - set(OPENGLTESTER_APPLICATION MagnumWindowlessGlxApplication) - endif() + set(MAGNUM_WITH_WINDOWLESSGLXAPPLICATION ON) + set(OPENGLTESTER_APPLICATION MagnumWindowlessGlxApplication) elseif(CORRADE_TARGET_WINDOWS) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - set(MAGNUM_WITH_WINDOWLESSEGLAPPLICATION ON) - set(OPENGLTESTER_APPLICATION MagnumWindowlessEglApplication) - else() - set(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION ON) - set(OPENGLTESTER_APPLICATION MagnumWindowlessWglApplication) - endif() + set(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION ON) + set(OPENGLTESTER_APPLICATION MagnumWindowlessWglApplication) else() # Assuming this gets hit only if MAGNUM_BUILD_GL_TESTS are enabled message(FATAL_ERROR "Cannot run tests for OpenGL code on this platform. Set MAGNUM_BUILD_GL_TESTS to OFF to skip building them.") diff --git a/doc/building.dox b/doc/building.dox index 8687d4dd6..0ae5caf89 100644 --- a/doc/building.dox +++ b/doc/building.dox @@ -461,15 +461,10 @@ available for desktop OpenGL only, see @ref requires-gl. - `MAGNUM_TARGET_GLES2` --- Target OpenGL ES 2.0 instead of 3.0 and later. Available only when `MAGNUM_WITH_GL` is enabled. Currently enabled by default when `MAGNUM_TARGET_GLES` is set. -- `MAGNUM_TARGET_DESKTOP_GLES` --- Target OpenGL ES on desktop, i.e. use - OpenGL ES emulation in desktop OpenGL drivers. Available on Linux and - Windows, though might not be supported by all drivers. Available only when - `MAGNUM_WITH_GL` is enabled. -- `MAGNUM_TARGET_HEADLESS` --- Build command-line utilities for use on a - headless machine. Basically it means that EGL with no display attachment is - being used everywhere instead of platform-specific toolkits like CGL, GLX - or WGL. Supported mainly on OpenGL ES drivers. Available only when - `MAGNUM_WITH_GL` is enabled. +- `MAGNUM_TARGET_EGL` --- Target EGL instead of a platform-specific OpenGL + support library like CGL, EAGL, GLX or WGL. Enabled implicitly on Android, + Emscripten and Windows RT, enabled by default when `MAGNUM_TARGET_GLES` is + set unless on iOS. Available only when `MAGNUM_WITH_GL` is enabled. - `MAGNUM_TARGET_VK` --- Build libraries with Vulkan interoperability enabled. Enabled by default when `MAGNUM_WITH_VK` is enabled. Disabling this will cause libraries to not depend on the @ref Vk library, but doesn't @@ -736,6 +731,9 @@ compatibility if `MAGNUM_BUILD_DEPRECATED` isn't disabled. this was used to handle that case. Nowadays please use NDK r19 and newer, with the unified sysroot layout. Defaults to ``.``. If a relative path is used, it's relative to `CMAKE_INSTALL_PREFIX`. +- `MAGNUM_TARGET_HEADLESS` --- Alias to `MAGNUM_TARGET_EGL`. +- `MAGNUM_TARGET_DESKTOP_GLES` --- Inverse of `MAGNUM_TARGET_EGL` if + `MAGNUM_TARGET_GLES` is enabled. Various plugin interfaces search for plugins in locations and order documented in @ref Corrade::PluginManager::implicitPluginSearchPaths(), diff --git a/doc/changelog.dox b/doc/changelog.dox index 1ca63c0ee..8c654968a 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -449,9 +449,9 @@ See also: - Added a @ref Platform::GlfwApplication::setWindowIcon() overload taking a @ref Corrade::Containers::ArrayView in addition to @ref std::initializer_list -- @ref Platform::GlfwApplication now defaults to EGL on - @ref MAGNUM_TARGET_DESKTOP_GLES "non-desktop" - @ref MAGNUM_TARGET_GLES "GLES builds" (see [mosra/magnum#470](https://github.com/mosra/magnum/pull/470)) +- @ref Platform::GlfwApplication now properly uses + @ref MAGNUM_TARGET_EGL "EGL" on @ref MAGNUM_TARGET_GLES "GLES builds" (see + [mosra/magnum#470](https://github.com/mosra/magnum/pull/470)) - On Emscripten, @ref Platform::EmscriptenApplication used an internal allocation function, which changed signature in 2.0.5 and caused runtime failures when `-s ASSERTIONS` was enabled. A public stable API is now used @@ -768,6 +768,13 @@ See also: @subsection changelog-latest-deprecated Deprecated APIs +- The (mutually exclusive) @ref MAGNUM_TARGET_HEADLESS and + @ref MAGNUM_TARGET_DESKTOP_GLES options, CMake variables and preprocessor + variables are deprecated in favor of @ref MAGNUM_TARGET_EGL. It's enabled + by default on GLES and disabled by default on desktop GL --- disabling it + on GLES will force creation of a GLES context using the GLX / WGL libraries + (if available); enabling it on desktop GL will allow running windowless + applications on headless machines. - All @ref building-features "CMake build options" are now prefixed with `MAGNUM_`. For backwards compatibility, unless @ref MAGNUM_BUILD_DEPRECATED is disabled and unless a prefixed option is already set during the initial @@ -2424,7 +2431,7 @@ Released 2019-10-24, tagged as @ref Platform::Sdl2Application::exitEvent() "exitEvent()" - On OpenGL ES builds, @ref Platform::Sdl2Application now tells SDL whether to use a system GL driver or a dedicated GLES driver based on whether - @ref MAGNUM_TARGET_DESKTOP_GLES is defined. This allows for a smoother + @cpp MAGNUM_TARGET_DESKTOP_GLES @ce is defined. This allows for a smoother experience for example when using ANGLE on Windows. See @ref Platform-Sdl2Application-usage-gles for more information. - Replaced uses of `Pointer_stringify()` in @ref Platform::Sdl2Application diff --git a/doc/cmake.dox b/doc/cmake.dox index 503e9bc66..e6c5b0b38 100644 --- a/doc/cmake.dox +++ b/doc/cmake.dox @@ -327,11 +327,9 @@ are also available as preprocessor variables if including - `MAGNUM_TARGET_GLES` --- Defined if compiled for OpenGL ES - `MAGNUM_TARGET_GLES2` --- Defined if compiled for OpenGL ES 2.0 - `MAGNUM_TARGET_GLES3` --- Defined if compiled for OpenGL ES 3.0 -- `MAGNUM_TARGET_DESKTOP_GLES` --- Defined if compiled with OpenGL ES - emulation on desktop OpenGL - `MAGNUM_TARGET_WEBGL` --- Defined if compiled for WebGL -- `MAGNUM_TARGET_HEADLESS` --- Defined if compiled for headless machines. See - @ref MAGNUM_TARGET_HEADLESS documentation for more information. +- `MAGNUM_TARGET_EGL` --- Defined if compiled for EGL instead of a + platform-specific OpenGL support library such as CGL, EAGL, GLX or WGL. - `MAGNUM_TARGET_VK` --- Defined if compiled with Vulkan interoperability enabled @@ -341,6 +339,10 @@ release: - `MAGNUM_BUILD_MULTITHREADED` --- Alias to `CORRADE_BUILD_MULTITHREADED`. Use @ref CORRADE_BUILD_MULTITHREADED instead. +- `MAGNUM_TARGET_HEADLESS` --- Alias to `MAGNUM_TARGET_EGL`, unless on iOS, + Android, Emscripten or Windows RT. Use @ref MAGNUM_TARGET_EGL instead. +- `MAGNUM_TARGET_DESKTOP_GLES` --- Defined if compiled for OpenGL ES but + GLX / WGL is used instead of EGL. Use @ref MAGNUM_TARGET_EGL instead. Corrade library provides also its own set of CMake macros and variables, see @ref corrade-cmake "its documentation" for more information. diff --git a/modules/FindMagnum.cmake b/modules/FindMagnum.cmake index 7012edc04..62e10d9a9 100644 --- a/modules/FindMagnum.cmake +++ b/modules/FindMagnum.cmake @@ -136,10 +136,9 @@ # MAGNUM_TARGET_GLES - Defined if compiled for OpenGL ES # MAGNUM_TARGET_GLES2 - Defined if compiled for OpenGL ES 2.0 # MAGNUM_TARGET_GLES3 - Defined if compiled for OpenGL ES 3.0 -# MAGNUM_TARGET_DESKTOP_GLES - Defined if compiled with OpenGL ES -# emulation on desktop OpenGL # MAGNUM_TARGET_WEBGL - Defined if compiled for WebGL -# MAGNUM_TARGET_HEADLESS - Defined if compiled for headless machines +# MAGNUM_TARGET_EGL - Defined if compiled for EGL instead of a +# platform-specific OpenGL support library like CGL, EAGL, GLX or WGL # MAGNUM_TARGET_VK - Defined if compiled with Vulkan interop # # The following variables are provided for backwards compatibility purposes @@ -148,6 +147,10 @@ # # MAGNUM_BUILD_MULTITHREADED - Alias to CORRADE_BUILD_MULTITHREADED. Use # CORRADE_BUILD_MULTITHREADED instead. +# MAGNUM_TARGET_HEADLESS - Alias to MAGNUM_TARGET_EGL, unless on iOS, +# Android, Emscripten or Windows RT. Use MAGNUM_TARGET_EGL instead. +# MAGNUM_TARGET_DESKTOP_GLES` - Defined if compiled for OpenGL ES but +# GLX / WGL is used instead of EGL. Use MAGNUM_TARGET_EGL instead. # # Additionally these variables are defined for internal usage: # @@ -268,9 +271,8 @@ set(_magnumFlags TARGET_GLES TARGET_GLES2 TARGET_GLES3 - TARGET_DESKTOP_GLES TARGET_WEBGL - TARGET_HEADLESS + TARGET_EGL TARGET_VK) foreach(_magnumFlag ${_magnumFlags}) list(FIND _magnumConfigure "#define MAGNUM_${_magnumFlag}" _magnum_${_magnumFlag}) @@ -279,9 +281,20 @@ foreach(_magnumFlag ${_magnumFlags}) endif() endforeach() -# For compatibility only, to be removed at some point -if(MAGNUM_BUILD_DEPRECATED AND CORRADE_BUILD_MULTITHREADED) - set(MAGNUM_BUILD_MULTITHREADED 1) +# For compatibility only, to be removed at some point. Refer to +# src/Magnum/configure.h.cmake for the decision logic here. +if(MAGNUM_BUILD_DEPRECATED) + if(CORRADE_BUILD_MULTITHREADED) + set(MAGNUM_BUILD_MULTITHREADED 1) + endif() + if(NOT CORRADE_TARGET_IOS AND NOT CORRADE_TARGET_ANDROID AND NOT CORRADE_TARGET_EMSCRIPTEN AND NOT CORRADE_TARGET_WINDOWS_RT) + if(NOT MAGNUM_TARGET_GLES AND MAGNUM_TARGET_EGL) + set(MAGNUM_TARGET_HEADLESS 1) + endif() + if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_EGL) + set(MAGNUM_TARGET_DESKTOP_GLES 1) + endif() + endif() endif() # OpenGL library preference. Prefer to use GLVND, since that's the better @@ -425,24 +438,16 @@ if(MAGNUM_TARGET_GL) endif() set(_MAGNUM_OpenGLTester_DEPENDENCIES GL) -if(MAGNUM_TARGET_HEADLESS OR CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) +if(MAGNUM_TARGET_EGL) list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessEglApplication) elseif(CORRADE_TARGET_IOS) list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessIosApplication) -elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_GLES) +elseif(CORRADE_TARGET_APPLE) list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessCglApplication) elseif(CORRADE_TARGET_UNIX) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessEglApplication) - else() - list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessGlxApplication) - endif() + list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessGlxApplication) elseif(CORRADE_TARGET_WINDOWS) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessEglApplication) - else() - list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessWglApplication) - endif() + list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessWglApplication) endif() set(_MAGNUM_Primitives_DEPENDENCIES MeshTools Trade) @@ -706,16 +711,16 @@ foreach(_component ${Magnum_FIND_COMPONENTS}) # OPENGL_opengl_LIBRARY because that's set even if # OpenGL_GL_PREFERENCE is explicitly set to LEGACY. if(MAGNUM_TARGET_GL) - if(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE AND (NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES)) + if(MAGNUM_TARGET_EGL) + find_package(EGL) + set_property(TARGET Magnum::${_component} APPEND + PROPERTY INTERFACE_LINK_LIBRARIES EGL::EGL) + elseif(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE) find_package(OpenGL) if(OPENGL_opengl_LIBRARY AND OpenGL_GL_PREFERENCE STREQUAL GLVND) set_property(TARGET Magnum::${_component} APPEND PROPERTY INTERFACE_LINK_LIBRARIES OpenGL::GLX) endif() - elseif(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES AND NOT CORRADE_TARGET_EMSCRIPTEN) - find_package(EGL) - set_property(TARGET Magnum::${_component} APPEND - PROPERTY INTERFACE_LINK_LIBRARIES EGL::EGL) endif() endif() @@ -745,16 +750,16 @@ foreach(_component ${Magnum_FIND_COMPONENTS}) # OPENGL_opengl_LIBRARY because that's set even if # OpenGL_GL_PREFERENCE is explicitly set to LEGACY. if(MAGNUM_TARGET_GL) - if(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE AND (NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES)) + if(MAGNUM_TARGET_EGL) + find_package(EGL) + set_property(TARGET Magnum::${_component} APPEND + PROPERTY INTERFACE_LINK_LIBRARIES EGL::EGL) + elseif(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE) find_package(OpenGL) if(OPENGL_opengl_LIBRARY AND OpenGL_GL_PREFERENCE STREQUAL GLVND) set_property(TARGET Magnum::${_component} APPEND PROPERTY INTERFACE_LINK_LIBRARIES OpenGL::GLX) endif() - elseif(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES AND NOT CORRADE_TARGET_EMSCRIPTEN) - find_package(EGL) - set_property(TARGET Magnum::${_component} APPEND - PROPERTY INTERFACE_LINK_LIBRARIES EGL::EGL) endif() endif() @@ -852,7 +857,7 @@ foreach(_component ${Magnum_FIND_COMPONENTS}) # GL library elseif(_component STREQUAL GL) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) + if(NOT MAGNUM_TARGET_GLES OR (MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_EGL AND NOT CORRADE_TARGET_IOS)) # If the GLVND library (CMake 3.11+) was found, link to the # imported target. Otherwise (and also on all systems except # Linux) link to the classic libGL. Can't use diff --git a/package/archlinux/PKGBUILD-es2desktop b/package/archlinux/PKGBUILD-es2desktop index eedadf5f2..a8712d86f 100644 --- a/package/archlinux/PKGBUILD-es2desktop +++ b/package/archlinux/PKGBUILD-es2desktop @@ -22,7 +22,7 @@ build() { -DCMAKE_INSTALL_PREFIX=/usr \ -DMAGNUM_TARGET_GLES=ON \ -DMAGNUM_TARGET_GLES2=ON \ - -DMAGNUM_TARGET_DESKTOP_GLES=ON \ + -DMAGNUM_TARGET_EGL=OFF \ -DMAGNUM_WITH_AUDIO=ON \ -DMAGNUM_WITH_GLFWAPPLICATION=ON \ -DMAGNUM_WITH_GLXAPPLICATION=ON \ diff --git a/package/archlinux/PKGBUILD-es3desktop b/package/archlinux/PKGBUILD-es3desktop index dd24b445a..907ee65a3 100644 --- a/package/archlinux/PKGBUILD-es3desktop +++ b/package/archlinux/PKGBUILD-es3desktop @@ -22,7 +22,7 @@ build() { -DCMAKE_INSTALL_PREFIX=/usr \ -DMAGNUM_TARGET_GLES=ON \ -DMAGNUM_TARGET_GLES2=OFF \ - -DMAGNUM_TARGET_DESKTOP_GLES=ON \ + -DMAGNUM_TARGET_EGL=OFF \ -DMAGNUM_WITH_AUDIO=ON \ -DMAGNUM_WITH_GLFWAPPLICATION=ON \ -DMAGNUM_WITH_GLXAPPLICATION=ON \ diff --git a/package/ci/appveyor-desktop-gles.bat b/package/ci/appveyor-desktop-gles.bat index 31bb5b849..48f0be498 100644 --- a/package/ci/appveyor-desktop-gles.bat +++ b/package/ci/appveyor-desktop-gles.bat @@ -27,7 +27,7 @@ cmake .. ^ -DCMAKE_PREFIX_PATH="%APPVEYOR_BUILD_FOLDER%/openal" ^ -DMAGNUM_TARGET_GLES=ON ^ -DMAGNUM_TARGET_GLES2=%TARGET_GLES2% ^ - -DMAGNUM_TARGET_DESKTOP_GLES=ON ^ + -DMAGNUM_TARGET_EGL=OFF ^ -DMAGNUM_WITH_AUDIO=OFF ^ -DMAGNUM_WITH_VK=OFF ^ -DMAGNUM_WITH_SCENETOOLS=OFF ^ diff --git a/src/Magnum/GL/CMakeLists.txt b/src/Magnum/GL/CMakeLists.txt index 0461c6068..02977172f 100644 --- a/src/Magnum/GL/CMakeLists.txt +++ b/src/Magnum/GL/CMakeLists.txt @@ -214,7 +214,7 @@ elseif(MAGNUM_BUILD_STATIC_PIC) set_target_properties(MagnumGL PROPERTIES POSITION_INDEPENDENT_CODE ON) endif() target_link_libraries(MagnumGL PUBLIC Magnum) -if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) +if(NOT MAGNUM_TARGET_GLES OR (MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_EGL AND NOT CORRADE_TARGET_IOS)) # If the GLVND library (CMake 3.11+) was found, link to the imported # target. Otherwise (and also on all systems except Linux) link to the # classic libGL. Can't use OpenGL_OpenGL_FOUND, because that one is set @@ -297,7 +297,7 @@ if(MAGNUM_BUILD_TESTS) endif() target_link_libraries(MagnumGLTestLib PUBLIC Magnum) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) + if(NOT MAGNUM_TARGET_GLES OR (MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_EGL AND NOT CORRADE_TARGET_IOS)) # If the GLVND library (CMake 3.11+) was found, link to the imported # target. Otherwise (and also on all systems except Linux) link to the # classic libGL. Can't use OpenGL_OpenGL_FOUND, because that one is set @@ -331,7 +331,7 @@ if(MAGNUM_BUILD_TESTS) # Windows only and elsewhere I just link the same way as with # MagnumOpenGLTester. if(CORRADE_TARGET_WINDOWS) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) + if(MAGNUM_TARGET_EGL) # Otherwise it complains that EGL::EGL does not exist here find_package(EGL) endif() diff --git a/src/Magnum/GL/OpenGL.h b/src/Magnum/GL/OpenGL.h index 9fecc070f..ef206e0cf 100644 --- a/src/Magnum/GL/OpenGL.h +++ b/src/Magnum/GL/OpenGL.h @@ -46,7 +46,7 @@ #endif /* Special case for desktop GLES on Windows (still links to the old opengl32.dll) */ -#elif defined(CORRADE_TARGET_WINDOWS) && defined(MAGNUM_TARGET_DESKTOP_GLES) +#elif defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_EGL) #ifdef MAGNUM_TARGET_GLES2 #include "MagnumExternal/OpenGL/GLES2/flextGLWindowsDesktop.h" #else diff --git a/src/Magnum/GL/OpenGLTester.h b/src/Magnum/GL/OpenGLTester.h index 4606655f7..1ff80c326 100644 --- a/src/Magnum/GL/OpenGLTester.h +++ b/src/Magnum/GL/OpenGLTester.h @@ -37,24 +37,16 @@ #include "Magnum/GL/Renderer.h" #include "Magnum/GL/TimeQuery.h" -#if defined(MAGNUM_TARGET_HEADLESS) || defined(CORRADE_TARGET_EMSCRIPTEN) || defined(CORRADE_TARGET_ANDROID) +#ifdef MAGNUM_TARGET_EGL #include "Magnum/Platform/WindowlessEglApplication.h" #elif defined(CORRADE_TARGET_IOS) #include "Magnum/Platform/WindowlessIosApplication.h" -#elif defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) +#elif defined(CORRADE_TARGET_APPLE) #include "Magnum/Platform/WindowlessCglApplication.h" #elif defined(CORRADE_TARGET_UNIX) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessEglApplication.h" -#else #include "Magnum/Platform/WindowlessGlxApplication.h" -#endif #elif defined(CORRADE_TARGET_WINDOWS) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessEglApplication.h" -#else #include "Magnum/Platform/WindowlessWglApplication.h" -#endif #else #error cannot run OpenGL tests on this platform #endif @@ -98,7 +90,7 @@ See @ref building, @ref cmake and @ref testsuite for more information. Implicitly, running the test executables requires presence of a GPU with OpenGL drivers. In addition, on desktop, unless Magnum is built with -`MAGNUM_TARGET_HEADLESS`, OpenGL context creation requires a graphical desktop +@ref MAGNUM_TARGET_EGL, OpenGL context creation requires a graphical desktop to be running. On embedded systems (and @ref CORRADE_TARGET_IOS "iOS", @ref CORRADE_TARGET_ANDROID "Android" in particular) running the tests has no special requirements. On Emscripten the tests have to be running in a browser, diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index 0210090c0..ef8a65ad1 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -131,7 +131,7 @@ Defined if the engine is built with OpenGL interoperability enabled --- extra APIs in various libraries interacting with the @ref Magnum::GL "GL" library. Enabled by default in case the @ref Magnum::GL "GL" library is built. @see @ref MAGNUM_TARGET_GLES2, @ref MAGNUM_TARGET_GLES3, - @ref MAGNUM_TARGET_DESKTOP_GLES, @ref building, @ref cmake + @ref MAGNUM_TARGET_EGL, @ref building, @ref cmake */ #define MAGNUM_TARGET_GL /* (enabled by default) */ @@ -141,7 +141,7 @@ Enabled by default in case the @ref Magnum::GL "GL" library is built. Defined if the engine is built for OpenGL ES 3.0 or OpenGL ES 2.0. @see @ref MAGNUM_TARGET_GLES2, @ref MAGNUM_TARGET_GLES3, - @ref MAGNUM_TARGET_DESKTOP_GLES, @ref building, @ref cmake + @ref MAGNUM_TARGET_EGL, @ref building, @ref cmake */ #define MAGNUM_TARGET_GLES #undef MAGNUM_TARGET_GLES @@ -151,7 +151,7 @@ Defined if the engine is built for OpenGL ES 3.0 or OpenGL ES 2.0. Defined if the engine is built for OpenGL ES 2.0. Implies also @ref MAGNUM_TARGET_GLES. -@see @ref MAGNUM_TARGET_GLES3, @ref MAGNUM_TARGET_DESKTOP_GLES, @ref building, +@see @ref MAGNUM_TARGET_GLES3, @ref MAGNUM_TARGET_EGL, @ref building, @ref cmake */ #define MAGNUM_TARGET_GLES2 @@ -162,23 +162,12 @@ Defined if the engine is built for OpenGL ES 2.0. Implies also Defined if the engine is built for OpenGL ES 3.0. Implies also @ref MAGNUM_TARGET_GLES. -@see @ref MAGNUM_TARGET_GLES2, @ref MAGNUM_TARGET_DESKTOP_GLES, @ref building, +@see @ref MAGNUM_TARGET_GLES2, @ref MAGNUM_TARGET_EGL, @ref building, @ref cmake */ #define MAGNUM_TARGET_GLES3 #undef MAGNUM_TARGET_GLES3 -/** -@brief Desktop emulation of OpenGL ES target - -Defined if the engine is built for OpenGL ES 3.0 or OpenGL ES 2.0 emulated -within standard desktop OpenGL. Implies also @ref MAGNUM_TARGET_GLES. -@see @ref MAGNUM_TARGET_GLES2, @ref MAGNUM_TARGET_GLES3, @ref building, - @ref cmake -*/ -#define MAGNUM_TARGET_DESKTOP_GLES -#undef MAGNUM_TARGET_DESKTOP_GLES - /** @brief WebGL target @@ -193,19 +182,48 @@ which you might want to be aware of. Implies also @ref MAGNUM_TARGET_GLES and #define MAGNUM_TARGET_WEBGL #undef MAGNUM_TARGET_WEBGL +/** +@brief EGL target +@m_since_latest + +Defined if the engine is built for EGL instead of a platform-specific OpenGL +support library like CGL, EAGL, GLX or WGL. When enabled, +@relativeref{Magnum,Platform::Sdl2Application} and +@relativeref{Magnum,Platform::GlfwApplication} +will create the context using EGL, and command-line utilities like +@ref magnum-gl-info "magnum-gl-info" or +@ref magnum-distancefieldconverter "magnum-distancefieldconverter" as well as +the @relativeref{Magnum,GL::OpenGLTester} library will use +@relativeref{Magnum,Platform::WindowlessEglApplication}. Defined implicitly on +@ref CORRADE_TARGET_IOS "iOS", @ref CORRADE_TARGET_ANDROID "Android", +@ref CORRADE_TARGET_EMSCRIPTEN "Emscripten" and +@ref CORRADE_TARGET_WINDOWS_RT "Windows RT". +*/ +#define MAGNUM_TARGET_EGL +#undef MAGNUM_TARGET_EGL + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief Headless target +@m_deprecated_since_latest Use @ref MAGNUM_TARGET_EGL instead. -Defined if the engine is built for use on a headless machine (without any -graphical desktop environment). Basically it means that EGL with no display -attachment is being used everywhere instead of platform-specific toolkits like -CGL, GLX or WGL. Note that this might not be supported on all platforms, see -@ref Magnum::Platform::WindowlessEglApplication "Platform::WindowlessEglApplication" -for more information. +Alias to @ref MAGNUM_TARGET_EGL, unless on iOS, Android, Emscripten or Windows +RT. */ #define MAGNUM_TARGET_HEADLESS #undef MAGNUM_TARGET_HEADLESS +/** +@brief OpenGL ES target on GLX / WGL +@m_deprecated_since_latest Use @ref MAGNUM_TARGET_EGL instead. + +Defined if @ref MAGNUM_TARGET_GLES is set but @ref MAGNUM_TARGET_EGL isn't, +unless on iOS, Android, Emscripten or Windows RT. +*/ +#define MAGNUM_TARGET_DESKTOP_GLES +#undef MAGNUM_TARGET_DESKTOP_GLES +#endif + /** @brief Vulkan interoperability diff --git a/src/Magnum/Platform/CMakeLists.txt b/src/Magnum/Platform/CMakeLists.txt index 3534bfebb..109bf3b6b 100644 --- a/src/Magnum/Platform/CMakeLists.txt +++ b/src/Magnum/Platform/CMakeLists.txt @@ -98,24 +98,29 @@ install(FILES ${MagnumPlatform_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR if(MAGNUM_TARGET_GL) # Decide about platform-specific context for cross-platform toolkits if(MAGNUM_WITH_GLFWAPPLICATION OR MAGNUM_WITH_SDL2APPLICATION) - if(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_GLES) + # On WebGL windowless apps don't use anything from EGL, only windowless + # do. On iOS it's EAGL, which isn't really EGL (and thus + # MAGNUM_TARGET_EGL isn't set), but we handle it inside EglContext as + # well. TODO make EaglContext for iOS to prepare for true EGL (such as + # with ANGLE or Zink) + if((MAGNUM_TARGET_EGL AND NOT MAGNUM_TARGET_WEBGL) OR CORRADE_TARGET_IOS) + set(NEED_EGLCONTEXT 1) + set(MagnumSomeContext_OBJECTS $) + # We're linking to EGL explicitly, no need to bother with GLVND there + elseif(CORRADE_TARGET_APPLE) set(NEED_CGLCONTEXT 1) set(MagnumSomeContext_OBJECTS $) - elseif(CORRADE_TARGET_WINDOWS AND (NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES)) + elseif(CORRADE_TARGET_WINDOWS) set(NEED_WGLCONTEXT 1) set(MagnumSomeContext_OBJECTS $) - elseif(CORRADE_TARGET_UNIX AND (NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES)) + elseif(CORRADE_TARGET_UNIX) set(NEED_GLXCONTEXT 1) set(MagnumSomeContext_OBJECTS $) - elseif(MAGNUM_TARGET_GLES AND NOT CORRADE_TARGET_EMSCRIPTEN) - set(NEED_EGLCONTEXT 1) - set(MagnumSomeContext_OBJECTS $) - # We're linking to EGL explicitly, no need to bother with GLVND there endif() endif() # This is needed also by [Windowless]GlxApplication - if((MAGNUM_WITH_GLXAPPLICATION OR MAGNUM_WITH_WINDOWLESSGLXAPPLICATION OR MAGNUM_WITH_GLFWAPPLICATION OR MAGNUM_WITH_SDL2APPLICATION) AND CORRADE_TARGET_UNIX AND (NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES)) + if((MAGNUM_WITH_GLXAPPLICATION OR MAGNUM_WITH_WINDOWLESSGLXAPPLICATION OR MAGNUM_WITH_GLFWAPPLICATION OR MAGNUM_WITH_SDL2APPLICATION) AND CORRADE_TARGET_UNIX AND NOT MAGNUM_TARGET_EGL) # If the GLVND library (CMake 3.11+) was found and linked to, we need # to link to GLX explicitly. Otherwise (and also on all systems except # Linux) the transitive dependency to classic GL lib from MagnumGL is @@ -255,8 +260,8 @@ if(MAGNUM_WITH_GLFWAPPLICATION) ${MagnumSomeContext_LIBRARY}) endif() - # Link also EGL library, if on ES (and not on WebGL) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES AND NOT MAGNUM_TARGET_WEBGL) + # Link also EGL library, if desired (and not on WebGL) + if(MAGNUM_TARGET_EGL AND NOT MAGNUM_TARGET_WEBGL) find_package(EGL REQUIRED) target_link_libraries(MagnumGlfwApplication PUBLIC EGL::EGL) endif() @@ -327,8 +332,8 @@ if(MAGNUM_WITH_SDL2APPLICATION) ${MagnumSomeContext_LIBRARY}) endif() - # Link also EGL library, if on ES (and not on WebGL) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES AND NOT MAGNUM_TARGET_WEBGL) + # Link also EGL library, if desired (and not on WebGL) + if(MAGNUM_TARGET_EGL AND NOT MAGNUM_TARGET_WEBGL) find_package(EGL REQUIRED) target_link_libraries(MagnumSdl2Application PUBLIC EGL::EGL) endif() @@ -731,7 +736,7 @@ endif() if(NOT MAGNUM_TARGET_GLES) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GL/flextGLPlatform.cpp) elseif(MAGNUM_TARGET_GLES AND MAGNUM_TARGET_GLES2) - if(CORRADE_TARGET_WINDOWS AND MAGNUM_TARGET_DESKTOP_GLES) + if(CORRADE_TARGET_WINDOWS AND NOT MAGNUM_TARGET_EGL) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GLES2/flextGLPlatformWindowsDesktop.cpp) elseif(CORRADE_TARGET_IOS) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GLES2/flextGLPlatformIOS.cpp) @@ -739,7 +744,7 @@ elseif(MAGNUM_TARGET_GLES AND MAGNUM_TARGET_GLES2) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GLES2/flextGLPlatform.cpp) endif() elseif(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_GLES2) - if(CORRADE_TARGET_WINDOWS AND MAGNUM_TARGET_DESKTOP_GLES) + if(CORRADE_TARGET_WINDOWS AND NOT MAGNUM_TARGET_EGL) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GLES3/flextGLPlatformWindowsDesktop.cpp) elseif(CORRADE_TARGET_IOS) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GLES3/flextGLPlatformIOS.cpp) @@ -903,24 +908,16 @@ if(MAGNUM_WITH_GL_INFO) add_executable(magnum-gl-info gl-info.cpp) target_link_libraries(magnum-gl-info PRIVATE MagnumGL) - if(MAGNUM_TARGET_HEADLESS OR CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) + if(MAGNUM_TARGET_EGL) target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessEglApplication) elseif(CORRADE_TARGET_IOS) target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessIosApplication) - elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_GLES) + elseif(CORRADE_TARGET_APPLE) target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessCglApplication) elseif(CORRADE_TARGET_UNIX) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessEglApplication) - else() - target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessGlxApplication) - endif() + target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessGlxApplication) elseif(CORRADE_TARGET_WINDOWS) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessEglApplication) - else() - target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessWglApplication) - endif() + target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessWglApplication) else() message(FATAL_ERROR "magnum-gl-info is not available on this platform. Set MAGNUM_WITH_GL_INFO to OFF to skip building it.") endif() diff --git a/src/Magnum/Platform/GlfwApplication.cpp b/src/Magnum/Platform/GlfwApplication.cpp index 1852cf8fc..55da5ad12 100644 --- a/src/Magnum/Platform/GlfwApplication.cpp +++ b/src/Magnum/Platform/GlfwApplication.cpp @@ -457,10 +457,7 @@ bool GlfwApplication::tryCreate(const Configuration& configuration, const GLConf } #else glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); - /* Force EGL on Windows and non-desktop GLES -- needed by ANGLE: - https://stackoverflow.com/a/58904181/4084782 . Might be useful on - other platforms as well (Mac?), not tested yet. */ - #if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_DESKTOP_GLES) + #ifdef MAGNUM_TARGET_EGL /* Force EGL if desired */ glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); #endif #endif @@ -487,10 +484,7 @@ bool GlfwApplication::tryCreate(const Configuration& configuration, const GLConf #endif glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); - /* Force EGL on Windows and non-desktop GLES -- needed by ANGLE: - https://stackoverflow.com/a/58904181/4084782 . Might be useful on - other platforms as well (Mac?), not tested yet. */ - #if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_DESKTOP_GLES) + #ifdef MAGNUM_TARGET_EGL /* Force EGL if desired */ glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); #endif #endif diff --git a/src/Magnum/Platform/GlxApplication.h b/src/Magnum/Platform/GlxApplication.h index 7dbdd578c..b9d478557 100644 --- a/src/Magnum/Platform/GlxApplication.h +++ b/src/Magnum/Platform/GlxApplication.h @@ -43,8 +43,7 @@ namespace Magnum { namespace Platform { @m_keywords{Application} Application using pure X11 and GLX. Supports keyboard and mouse handling. -Available on desktop OpenGL and -@ref MAGNUM_TARGET_DESKTOP_GLES "OpenGL ES emulation on desktop" on Linux. +Available on desktop OpenGL and OpenGL ES using GLX on Linux. @section Platform-GlxApplication-bootstrap Bootstrap application diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index f788803dc..c9e6f517e 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -139,10 +139,9 @@ Sdl2Application::Sdl2Application(const Arguments& arguments, NoCreateT): #ifdef SDL_HINT_NO_SIGNAL_HANDLERS SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); #endif - /* Available since 2.0.6, uses dedicated OpenGL ES drivers by default and a - desktop GLES context only if MAGNUM_TARGET_DESKTOP_GLES is defined as - well. */ - #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) && defined(SDL_HINT_OPENGL_ES_DRIVER) + /* Available since 2.0.6, uses dedicated OpenGL ES drivers if EGL is used, + and desktop GLES context otherwise. */ + #if defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_EGL) && defined(SDL_HINT_OPENGL_ES_DRIVER) SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1"); #endif /* Available since 2.0.8, disables compositor bypass on X11, which causes diff --git a/src/Magnum/Platform/WindowlessGlxApplication.h b/src/Magnum/Platform/WindowlessGlxApplication.h index a0360a44f..e8ca1c680 100644 --- a/src/Magnum/Platform/WindowlessGlxApplication.h +++ b/src/Magnum/Platform/WindowlessGlxApplication.h @@ -344,9 +344,8 @@ CORRADE_ENUMSET_OPERATORS(WindowlessGlxContext::Configuration::Flags) @m_keywords{WindowlessApplication GLX} -Application for offscreen rendering using @ref WindowlessGlxContext. This -application library is available on desktop OpenGL and -@ref MAGNUM_TARGET_DESKTOP_GLES "OpenGL ES emulation on desktop" on Linux. +Application for offscreen rendering using @ref WindowlessGlxContext. Available +on desktop OpenGL and OpenGL ES using GLX on Linux. @section Platform-WindowlessGlxApplication-bootstrap Bootstrap application diff --git a/src/Magnum/Platform/gl-info.cpp b/src/Magnum/Platform/gl-info.cpp index c3705df94..2ded27ad3 100644 --- a/src/Magnum/Platform/gl-info.cpp +++ b/src/Magnum/Platform/gl-info.cpp @@ -58,24 +58,16 @@ #include "Magnum/GL/TransformFeedback.h" #endif -#if defined(MAGNUM_TARGET_HEADLESS) || defined(CORRADE_TARGET_EMSCRIPTEN) || defined(CORRADE_TARGET_ANDROID) +#ifdef MAGNUM_TARGET_EGL #include "Magnum/Platform/WindowlessEglApplication.h" #elif defined(CORRADE_TARGET_IOS) #include "Magnum/Platform/WindowlessIosApplication.h" -#elif defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) +#elif defined(CORRADE_TARGET_APPLE) #include "Magnum/Platform/WindowlessCglApplication.h" #elif defined(CORRADE_TARGET_UNIX) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessEglApplication.h" -#else #include "Magnum/Platform/WindowlessGlxApplication.h" -#endif #elif defined(CORRADE_TARGET_WINDOWS) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessEglApplication.h" -#else #include "Magnum/Platform/WindowlessWglApplication.h" -#endif #else #error no windowless application available on this platform #endif @@ -347,14 +339,11 @@ MagnumInfo::MagnumInfo(const Arguments& arguments): Platform::WindowlessApplicat #ifdef MAGNUM_TARGET_GLES2 Debug{} << " MAGNUM_TARGET_GLES2"; #endif - #ifdef MAGNUM_TARGET_DESKTOP_GLES - Debug{} << " MAGNUM_TARGET_DESKTOP_GLES"; - #endif #ifdef MAGNUM_TARGET_WEBGL Debug{} << " MAGNUM_TARGET_WEBGL"; #endif - #ifdef MAGNUM_TARGET_HEADLESS - Debug{} << " MAGNUM_TARGET_HEADLESS"; + #ifdef MAGNUM_TARGET_EGL + Debug{} << " MAGNUM_TARGET_EGL"; #endif Debug{} << "Compiled CPU features:"; Debug{} << " " << Debug::packed << Cpu::compiledFeatures(); diff --git a/src/Magnum/Text/CMakeLists.txt b/src/Magnum/Text/CMakeLists.txt index d5ac2d0dc..4fd075698 100644 --- a/src/Magnum/Text/CMakeLists.txt +++ b/src/Magnum/Text/CMakeLists.txt @@ -117,24 +117,16 @@ if(MAGNUM_WITH_FONTCONVERTER) Magnum MagnumText MagnumTrade) - if(MAGNUM_TARGET_HEADLESS) + if(MAGNUM_TARGET_EGL) target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessEglApplication) elseif(CORRADE_TARGET_IOS) target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessIosApplication) - elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_GLES) + elseif(CORRADE_TARGET_APPLE) target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessCglApplication) elseif(CORRADE_TARGET_UNIX) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessEglApplication) - else() - target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessGlxApplication) - endif() + target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessGlxApplication) elseif(CORRADE_TARGET_WINDOWS) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessEglApplication) - else() - target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessWglApplication) - endif() + target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessWglApplication) else() message(FATAL_ERROR "magnum-fontconverter is not available on this platform. Set MAGNUM_WITH_FONTCONVERTER to OFF to suppress this warning.") endif() diff --git a/src/Magnum/Text/fontconverter.cpp b/src/Magnum/Text/fontconverter.cpp index 5180df6d4..ab27ed2ba 100644 --- a/src/Magnum/Text/fontconverter.cpp +++ b/src/Magnum/Text/fontconverter.cpp @@ -34,24 +34,16 @@ #include "Magnum/Text/DistanceFieldGlyphCache.h" #include "Magnum/Trade/AbstractImageConverter.h" -#ifdef MAGNUM_TARGET_HEADLESS +#ifdef MAGNUM_TARGET_EGL #include "Magnum/Platform/WindowlessEglApplication.h" #elif defined(CORRADE_TARGET_IOS) #include "Magnum/Platform/WindowlessIosApplication.h" #elif defined(CORRADE_TARGET_APPLE) #include "Magnum/Platform/WindowlessCglApplication.h" #elif defined(CORRADE_TARGET_UNIX) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessEglApplication.h" -#else #include "Magnum/Platform/WindowlessGlxApplication.h" -#endif #elif defined(CORRADE_TARGET_WINDOWS) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessEglApplication.h" -#else #include "Magnum/Platform/WindowlessWglApplication.h" -#endif #else #error no windowless application available on this platform #endif diff --git a/src/Magnum/TextureTools/CMakeLists.txt b/src/Magnum/TextureTools/CMakeLists.txt index 8fc9eafc8..625b71a25 100644 --- a/src/Magnum/TextureTools/CMakeLists.txt +++ b/src/Magnum/TextureTools/CMakeLists.txt @@ -85,24 +85,16 @@ if(MAGNUM_WITH_DISTANCEFIELDCONVERTER) Magnum MagnumTextureTools MagnumTrade) - if(MAGNUM_TARGET_HEADLESS) + if(MAGNUM_TARGET_EGL) target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessEglApplication) elseif(CORRADE_TARGET_IOS) target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessIosApplication) - elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_GLES) + elseif(CORRADE_TARGET_APPLE) target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessCglApplication) elseif(CORRADE_TARGET_UNIX) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessEglApplication) - else() - target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessGlxApplication) - endif() + target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessGlxApplication) elseif(CORRADE_TARGET_WINDOWS) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessEglApplication) - else() - target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessWglApplication) - endif() + target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessWglApplication) else() message(FATAL_ERROR "magnum-distancefieldconverter is not available on this platform. Set MAGNUM_WITH_DISTANCEFIELDCONVERTER to OFF to suppress this warning.") endif() diff --git a/src/Magnum/TextureTools/distancefieldconverter.cpp b/src/Magnum/TextureTools/distancefieldconverter.cpp index 226c01325..8c3d3a181 100644 --- a/src/Magnum/TextureTools/distancefieldconverter.cpp +++ b/src/Magnum/TextureTools/distancefieldconverter.cpp @@ -42,24 +42,16 @@ #include "Magnum/Trade/AbstractImageConverter.h" #include "Magnum/Trade/ImageData.h" -#ifdef MAGNUM_TARGET_HEADLESS +#ifdef MAGNUM_TARGET_EGL #include "Magnum/Platform/WindowlessEglApplication.h" #elif defined(CORRADE_TARGET_IOS) #include "Magnum/Platform/WindowlessIosApplication.h" -#elif defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) +#elif defined(CORRADE_TARGET_APPLE) #include "Magnum/Platform/WindowlessCglApplication.h" #elif defined(CORRADE_TARGET_UNIX) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessEglApplication.h" -#else #include "Magnum/Platform/WindowlessGlxApplication.h" -#endif #elif defined(CORRADE_TARGET_WINDOWS) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessEglApplication.h" -#else #include "Magnum/Platform/WindowlessWglApplication.h" -#endif #else #error no windowless application available on this platform #endif diff --git a/src/Magnum/configure.h.cmake b/src/Magnum/configure.h.cmake index 407bbfd8b..f697971ee 100644 --- a/src/Magnum/configure.h.cmake +++ b/src/Magnum/configure.h.cmake @@ -32,16 +32,34 @@ #cmakedefine MAGNUM_TARGET_GLES #cmakedefine MAGNUM_TARGET_GLES2 #cmakedefine MAGNUM_TARGET_GLES3 -#cmakedefine MAGNUM_TARGET_DESKTOP_GLES #cmakedefine MAGNUM_TARGET_WEBGL -#cmakedefine MAGNUM_TARGET_HEADLESS +#cmakedefine MAGNUM_TARGET_EGL #cmakedefine MAGNUM_TARGET_VK #ifdef MAGNUM_BUILD_DEPRECATED #include "Corrade/configure.h" #ifdef CORRADE_BUILD_MULTITHREADED -/* For compatibility only, to be removed at some point */ -#define MAGNUM_BUILD_MULTITHREADED +/* For compatibility only, to be removed at some point. Deliberate double space + after the #define to avoid being unconditionally matched by older FindMagnum + modules. */ +#define MAGNUM_BUILD_MULTITHREADED +#endif +/* The following applies only to desktop platforms */ +#if !defined(CORRADE_TARGET_IOS) && !defined(CORRADE_TARGET_ANDROID) && !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_WINDOWS_RT) +/* MAGNUM_TARGET_HEADLESS used to be an option defined on desktop GL only, not + on ES; MAGNUM_TARGET_EGL is defined implicitly also on all platforms that + use EGL exclusively. Deliberate double space after the #define to avoid + being unconditionally matched by older FindMagnum modules. */ +#if !defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_EGL) +#define MAGNUM_TARGET_HEADLESS +#endif +/* MAGNUM_TARGET_DESKTOP_GLES used to do the opposite of + MAGNUM_TARGET_HEADLESS, i.e. force GLX/WGL instead of EGL on GLES builds. + Deliberate double space after the #define to avoid being unconditionally + matched by older FindMagnum modules. */ +#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_EGL) +#define MAGNUM_TARGET_DESKTOP_GLES +#endif #endif #endif diff --git a/src/MagnumExternal/OpenGL/GLES2/CMakeLists.txt b/src/MagnumExternal/OpenGL/GLES2/CMakeLists.txt index 843f8aec0..9e6b89b59 100644 --- a/src/MagnumExternal/OpenGL/GLES2/CMakeLists.txt +++ b/src/MagnumExternal/OpenGL/GLES2/CMakeLists.txt @@ -27,9 +27,9 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "MagnumExternal/OpenGL") -# Desktop GLES on Windows still links to opengl32.dll so we have a special +# Non-EGL GLES on Windows still links to opengl32.dll so we have a special # function loading code that queries everything above OpenGL 1.1 -if(CORRADE_TARGET_WINDOWS AND MAGNUM_TARGET_DESKTOP_GLES) +if(CORRADE_TARGET_WINDOWS AND NOT MAGNUM_TARGET_EGL) set(MagnumOpenGL_HEADERS flextGLWindowsDesktop.h) set(MagnumOpenGL_SRCS flextGLWindowsDesktop.cpp) diff --git a/src/MagnumExternal/OpenGL/GLES3/CMakeLists.txt b/src/MagnumExternal/OpenGL/GLES3/CMakeLists.txt index bff885743..96b7023a9 100644 --- a/src/MagnumExternal/OpenGL/GLES3/CMakeLists.txt +++ b/src/MagnumExternal/OpenGL/GLES3/CMakeLists.txt @@ -27,9 +27,9 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "MagnumExternal/OpenGL") -# Desktop GLES on Windows still links to opengl32.dll so we have a special +# Non-EGL GLES on Windows still links to opengl32.dll so we have a special # function loading code that queries everything above OpenGL 1.1 -if(CORRADE_TARGET_WINDOWS AND MAGNUM_TARGET_DESKTOP_GLES) +if(CORRADE_TARGET_WINDOWS AND NOT MAGNUM_TARGET_EGL) set(MagnumOpenGL_HEADERS flextGLWindowsDesktop.h) set(MagnumOpenGL_SRCS flextGLWindowsDesktop.cpp) From eb89b5cbf9d0ea898d5d0528805f05c85c35004a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 9 Sep 2022 20:19:20 +0200 Subject: [PATCH 84/93] Platform: make Glfw and Sdl2App work with EGL on desktop GL as well. A large portion of the needed changes was in the previous commit already, this does just the remaining part, in particular ensuring EGL is linked and SDL is told to use EGL as well -- GLFW was told so in the previous cleanup commit already. --- doc/changelog.dox | 2 ++ src/Magnum/Platform/CMakeLists.txt | 15 ++------------- src/Magnum/Platform/Sdl2Application.cpp | 4 ++++ 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 8c654968a..d596ee711 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -452,6 +452,8 @@ See also: - @ref Platform::GlfwApplication now properly uses @ref MAGNUM_TARGET_EGL "EGL" on @ref MAGNUM_TARGET_GLES "GLES builds" (see [mosra/magnum#470](https://github.com/mosra/magnum/pull/470)) +- @ref Platform::GlfwApplication and @ref Platform::Sdl2Application can now + work with EGL on desktop GL as well if @ref MAGNUM_TARGET_EGL is enabled - On Emscripten, @ref Platform::EmscriptenApplication used an internal allocation function, which changed signature in 2.0.5 and caused runtime failures when `-s ASSERTIONS` was enabled. A public stable API is now used diff --git a/src/Magnum/Platform/CMakeLists.txt b/src/Magnum/Platform/CMakeLists.txt index 109bf3b6b..fbe01944b 100644 --- a/src/Magnum/Platform/CMakeLists.txt +++ b/src/Magnum/Platform/CMakeLists.txt @@ -106,7 +106,8 @@ if(MAGNUM_TARGET_GL) if((MAGNUM_TARGET_EGL AND NOT MAGNUM_TARGET_WEBGL) OR CORRADE_TARGET_IOS) set(NEED_EGLCONTEXT 1) set(MagnumSomeContext_OBJECTS $) - # We're linking to EGL explicitly, no need to bother with GLVND there + find_package(EGL REQUIRED) + set(MagnumSomeContext_LIBRARY EGL::EGL) elseif(CORRADE_TARGET_APPLE) set(NEED_CGLCONTEXT 1) set(MagnumSomeContext_OBJECTS $) @@ -260,12 +261,6 @@ if(MAGNUM_WITH_GLFWAPPLICATION) ${MagnumSomeContext_LIBRARY}) endif() - # Link also EGL library, if desired (and not on WebGL) - if(MAGNUM_TARGET_EGL AND NOT MAGNUM_TARGET_WEBGL) - find_package(EGL REQUIRED) - target_link_libraries(MagnumGlfwApplication PUBLIC EGL::EGL) - endif() - install(FILES ${MagnumGlfwApplication_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Platform) install(TARGETS MagnumGlfwApplication RUNTIME DESTINATION ${MAGNUM_BINARY_INSTALL_DIR} @@ -332,12 +327,6 @@ if(MAGNUM_WITH_SDL2APPLICATION) ${MagnumSomeContext_LIBRARY}) endif() - # Link also EGL library, if desired (and not on WebGL) - if(MAGNUM_TARGET_EGL AND NOT MAGNUM_TARGET_WEBGL) - find_package(EGL REQUIRED) - target_link_libraries(MagnumSdl2Application PUBLIC EGL::EGL) - endif() - install(FILES ${MagnumSdl2Application_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Platform) install(TARGETS MagnumSdl2Application RUNTIME DESTINATION ${MAGNUM_BINARY_INSTALL_DIR} diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index c9e6f517e..9888fb6a7 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -149,6 +149,10 @@ Sdl2Application::Sdl2Application(const Arguments& arguments, NoCreateT): #ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); #endif + /* Available since 2.0.12, use EGL if MAGNUM_TARGET_HEADLESS is enabled */ + #if defined(MAGNUM_TARGET_HEADLESS) && defined(SDL_HINT_VIDEO_X11_FORCE_EGL) + SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1"); + #endif if(SDL_Init(SDL_INIT_VIDEO) < 0) { Error() << "Cannot initialize SDL:" << SDL_GetError(); From 9297cd74ab2558754c390465069dd4cb6e16680b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 9 Sep 2022 20:25:02 +0200 Subject: [PATCH 85/93] Platform: add helpful error messages to application test build files. --- src/Magnum/Platform/Test/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Magnum/Platform/Test/CMakeLists.txt b/src/Magnum/Platform/Test/CMakeLists.txt index 0bb325f49..8e86d26ed 100644 --- a/src/Magnum/Platform/Test/CMakeLists.txt +++ b/src/Magnum/Platform/Test/CMakeLists.txt @@ -85,6 +85,9 @@ if(MAGNUM_WITH_GLFWAPPLICATION) # HiDPi.manifest not needed, as GLFW sets that on its own target_link_libraries(PlatformGlfwApplicationTest PRIVATE MagnumGlfwApplication Corrade::Main) # Window icon loading + if(NOT MAGNUM_WITH_TRADE) + message(FATAL_ERROR "GlfwApplication tests need the Trade library enabled") + endif() target_sources(PlatformGlfwApplicationTest PRIVATE ${Platform_RESOURCES}) target_link_libraries(PlatformGlfwApplicationTest PRIVATE MagnumTrade) if(CORRADE_TARGET_APPLE) @@ -112,6 +115,9 @@ if(MAGNUM_WITH_SDL2APPLICATION) endif() target_link_libraries(PlatformSdl2ApplicationTest PRIVATE MagnumSdl2Application Corrade::Main) # Window icon loading + if(NOT MAGNUM_WITH_TRADE) + message(FATAL_ERROR "GlfwApplication tests need the Trade library enabled") + endif() if(NOT CORRADE_TARGET_EMSCRIPTEN) target_sources(PlatformSdl2ApplicationTest PRIVATE ${Platform_RESOURCES}) target_link_libraries(PlatformSdl2ApplicationTest PRIVATE MagnumTrade) From 8fd821c88b156eff6f02cb143b03290cf62a6d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 10 Sep 2022 17:22:19 +0200 Subject: [PATCH 86/93] *converter: make --plugin-dir accept the actual root plugin directory. For some reason, it was adding also the magnum / magnum-d directory, which isn't really useful, especially in cases where the directory is not at all or it's desired to pick a debug plugin from a release executable and vice versa. Also the distancefieldconverter was still attempting to join with an absolute path -- somehow 7fb63a9434b72c1e9da74eeb331676ebbcae0430 missed this one. I really need to write regression tests for all this, sigh. --- src/Magnum/SceneTools/sceneconverter.cpp | 4 ++-- src/Magnum/ShaderTools/shaderconverter.cpp | 2 +- src/Magnum/Text/fontconverter.cpp | 7 ++++--- src/Magnum/TextureTools/distancefieldconverter.cpp | 5 +++-- src/Magnum/Trade/imageconverter.cpp | 5 +++-- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Magnum/SceneTools/sceneconverter.cpp b/src/Magnum/SceneTools/sceneconverter.cpp index b7c015fc0..1f7867943 100644 --- a/src/Magnum/SceneTools/sceneconverter.cpp +++ b/src/Magnum/SceneTools/sceneconverter.cpp @@ -337,12 +337,12 @@ is specified as well, the IDs reference attributes of the first mesh.)") /* Importer manager */ PluginManager::Manager importerManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImporter::pluginSearchPaths().back()).second())}; /* Scene converter manager */ PluginManager::Manager converterManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractSceneConverter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractSceneConverter::pluginSearchPaths().back()).second())}; Containers::Pointer importer = importerManager.loadAndInstantiate(args.value("importer")); if(!importer) { diff --git a/src/Magnum/ShaderTools/shaderconverter.cpp b/src/Magnum/ShaderTools/shaderconverter.cpp index 6c4b1256a..b35d3552a 100644 --- a/src/Magnum/ShaderTools/shaderconverter.cpp +++ b/src/Magnum/ShaderTools/shaderconverter.cpp @@ -359,7 +359,7 @@ see documentation of a particular converter for more information.)") /* Set up a converter manager */ PluginManager::Manager converterManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), ShaderTools::AbstractConverter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(ShaderTools::AbstractConverter::pluginSearchPaths().back()).second())}; /* Data passed from one converter to another in case there's more than one */ Containers::Array data; diff --git a/src/Magnum/Text/fontconverter.cpp b/src/Magnum/Text/fontconverter.cpp index ab27ed2ba..419fa21fa 100644 --- a/src/Magnum/Text/fontconverter.cpp +++ b/src/Magnum/Text/fontconverter.cpp @@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -158,12 +159,12 @@ int FontConverter::exec() { /* Font converter dependencies */ PluginManager::Manager imageConverterManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImageConverter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImageConverter::pluginSearchPaths().back()).second())}; /* Load font */ PluginManager::Manager fontManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Text::AbstractFont::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Text::AbstractFont::pluginSearchPaths().back()).second())}; Containers::Pointer font = fontManager.loadAndInstantiate(args.value("font")); if(!font) return 1; @@ -171,7 +172,7 @@ int FontConverter::exec() { (MagnumFontConverter needs TgaImageConverter, for example) */ PluginManager::Manager converterManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Text::AbstractFontConverter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Text::AbstractFontConverter::pluginSearchPaths().back()).second())}; converterManager.registerExternalManager(imageConverterManager); /* Load font converter */ diff --git a/src/Magnum/TextureTools/distancefieldconverter.cpp b/src/Magnum/TextureTools/distancefieldconverter.cpp index 8c3d3a181..222c0fc4f 100644 --- a/src/Magnum/TextureTools/distancefieldconverter.cpp +++ b/src/Magnum/TextureTools/distancefieldconverter.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include /** @todo remove once Arguments is std::string-free */ #include @@ -156,14 +157,14 @@ int DistanceFieldConverter::exec() { /* Load importer plugin */ PluginManager::Manager importerManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths()[0])}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImporter::pluginSearchPaths().back()).second())}; Containers::Pointer importer = importerManager.loadAndInstantiate(args.value("importer")); if(!importer) return 1; /* Load converter plugin */ PluginManager::Manager converterManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImageConverter::pluginSearchPaths()[0])}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImageConverter::pluginSearchPaths().back()).second())}; Containers::Pointer converter = converterManager.loadAndInstantiate(args.value("converter")); if(!converter) return 2; diff --git a/src/Magnum/Trade/imageconverter.cpp b/src/Magnum/Trade/imageconverter.cpp index 95657e8ee..440238313 100644 --- a/src/Magnum/Trade/imageconverter.cpp +++ b/src/Magnum/Trade/imageconverter.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -447,7 +448,7 @@ no -C / --converter is specified, AnyImageConverter is used.)") PluginManager::Manager importerManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImporter::pluginSearchPaths().back()).second())}; const Int dimensions = args.value("dimensions"); /** @todo make them array options as well? */ @@ -921,7 +922,7 @@ no -C / --converter is specified, AnyImageConverter is used.)") PluginManager::Manager converterManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImageConverter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImageConverter::pluginSearchPaths().back()).second())}; /* Assume there's always one passed --converter option less, and the last is implicitly AnyImageConverter. All converters except the last one are From 443a05f91af78dd8361d341cf7afdc9c8a5ede72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 11 Sep 2022 11:34:20 +0200 Subject: [PATCH 87/93] sceneconverter: fully cover with tests. I'm going to add quite a few features to this one, and doing that without any regression tests whatsoever would be a misery. Same needs to eventually be done for the imageconverter and other utils. No bugs found here, fortunately, except for one message update -- otherwise the verbose output would contain (1/2) but never (2/2) which may be confusing. At the moment the testing is done only on Unix -- originally I wanted to postpone this until something like Utility::System::execute() is implemented, with proper argument escaping and output redirection, but I simply DO NOT HAVE TIME to do that properly now. So instead it's calling into std::system(), assumes there is no whitespace in the arguments, and assumes a Unix shell with stdout/stderr redirection to a file. Ugly and probably way slower than necessary, but works. --- .editorconfig | 2 +- src/Magnum/SceneTools/Test/CMakeLists.txt | 33 +- .../SceneTools/Test/SceneConverterTest.cpp | 457 ++++++++++++++++++ .../SceneConverterTestFiles/broken-mesh.obj | 3 + .../SceneConverterTestFiles/broken-scene.gltf | 22 + .../Test/SceneConverterTestFiles/empty.gltf | 5 + .../info-ignored-output.txt | 6 + .../Test/SceneConverterTestFiles/info.txt | 5 + .../Test/SceneConverterTestFiles/point.obj | 2 + .../quad-duplicates-fuzzy.obj | 12 + .../quad-duplicates.obj | 12 + .../quad-duplicates.ply | Bin 0 -> 268 bytes .../quad-normals-texcoords.obj | 12 + .../Test/SceneConverterTestFiles/quad.obj | 10 + .../Test/SceneConverterTestFiles/quad.ply | Bin 0 -> 244 bytes .../two-triangles-transformed.bin | Bin 0 -> 72 bytes .../two-triangles-transformed.bin.in | 15 + .../two-triangles-transformed.gltf | 75 +++ .../SceneConverterTestFiles/two-triangles.obj | 14 + src/Magnum/SceneTools/Test/configure.h.cmake | 24 + src/Magnum/SceneTools/sceneconverter.cpp | 2 +- 21 files changed, 706 insertions(+), 5 deletions(-) create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-mesh.obj create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-scene.gltf create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/empty.gltf create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-ignored-output.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/point.obj create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates-fuzzy.obj create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.obj create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.ply create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-normals-texcoords.obj create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.obj create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.ply create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin.in create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.gltf create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles.obj diff --git a/.editorconfig b/.editorconfig index 4c86f3ff1..d0764f768 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,6 +5,6 @@ indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true -[*.{css,html,yml,rb}] +[*.{css,html,yml,rb,gltf}] indent_style = space indent_size = 2 diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index 0ea8253b3..c1d7b5f60 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -29,12 +29,23 @@ set(CMAKE_FOLDER "Magnum/SceneTools/Test") if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(SCENETOOLS_TEST_DIR ".") + set(SCENETOOLS_TEST_OUTPUT_DIR "write") else() set(SCENETOOLS_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + set(SCENETOOLS_TEST_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) endif() +# Executable testing is implemented on Unix platforms only at the moment, so +# don't even provide the filename elsewhere. +if(MAGNUM_WITH_SCENECONVERTER AND CORRADE_TARGET_UNIX) + set(SCENECONVERTER_EXECUTABLE_FILENAME $) +endif() + +# First replace ${} variables, then $<> generator expressions configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake - ${CMAKE_CURRENT_BINARY_DIR}/configure.h) + ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) +file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h + INPUT ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) corrade_add_test(SceneToolsCombineTest CombineTest.cpp LIBRARIES MagnumTrade) corrade_add_test(SceneToolsConvertToSingleFun___Test ConvertToSingleFunctionObjectsTest.cpp LIBRARIES MagnumTrade) @@ -44,6 +55,9 @@ corrade_add_test(SceneToolsOrderClusterParentsTest OrderClusterParentsTest.cpp L corrade_add_test(SceneToolsSceneConverterTest SceneConverterTest.cpp LIBRARIES MagnumSceneTools FILES + SceneConverterTestFiles/broken-mesh.obj + SceneConverterTestFiles/broken-scene.gltf + SceneConverterTestFiles/empty.gltf SceneConverterTestFiles/info-animations.txt SceneConverterTestFiles/info-cameras.txt SceneConverterTestFiles/info-images.txt @@ -56,5 +70,18 @@ corrade_add_test(SceneToolsSceneConverterTest SceneConverterTest.cpp SceneConverterTestFiles/info-scenes-objects.txt SceneConverterTestFiles/info-scenes.txt SceneConverterTestFiles/info-skins.txt - SceneConverterTestFiles/info-textures.txt) -target_include_directories(SceneToolsSceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + SceneConverterTestFiles/info-textures.txt + SceneConverterTestFiles/point.obj + SceneConverterTestFiles/quad-duplicates-fuzzy.obj + SceneConverterTestFiles/quad-duplicates.obj + SceneConverterTestFiles/quad-duplicates.ply + SceneConverterTestFiles/quad-normals-texcoords.obj + SceneConverterTestFiles/quad.obj + SceneConverterTestFiles/quad.ply + SceneConverterTestFiles/two-triangles-transformed.bin + SceneConverterTestFiles/two-triangles-transformed.gltf + SceneConverterTestFiles/two-triangles.obj) +target_include_directories(SceneToolsSceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) +if(MAGNUM_WITH_SCENECONVERTER AND CORRADE_TARGET_UNIX) + add_dependencies(SceneToolsSceneConverterTest magnum-sceneconverter) +endif() diff --git a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp index abf4589e0..05d528d67 100644 --- a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp +++ b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -34,6 +36,7 @@ #include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#include "Magnum/Trade/AbstractSceneConverter.h" #include "Magnum/SceneTools/Implementation/sceneConverterUtilities.h" @@ -59,9 +62,16 @@ struct SceneConverterTest: TestSuite::Tester { void infoImplementationReferenceCount(); void infoImplementationError(); + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + void info(); + void convert(); + void error(); + #endif + Utility::Arguments _infoArgs; }; +using namespace Containers::Literals; using namespace Math::Literals; const struct { @@ -84,6 +94,299 @@ const struct { {"--info", false, false}, }; +#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT +const struct { + const char* name; + Containers::Array args; + const char* expected; +} InfoData[]{ + {"", Containers::array({}), + "info.txt"}, + {"map", Containers::array({ + "--map"}), + /** @todo change to something else once we have a plugin that can + zero-copy pass the imported data */ + "info.txt"}, + {"ignored output file", Containers::array({ + "whatever.ply"}), + "info-ignored-output.txt"}, +}; + +const struct { + const char* name; + Containers::Array args; + const char* requiresImporter; + const char* requiresConverter; + const char* expected; + Containers::String message; +} ConvertData[]{ + {"one mesh", Containers::array({ + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, explicit importer and converter", Containers::array({ + "-I", "ObjImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, map", Containers::array({ + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, options", Containers::array({ + /* It's silly, but since we have option propagation tested in + AnySceneImporter / AnySceneConverter .cpp already, it's enough to + just verify the (nonexistent) options arrive there */ + "-i", "nonexistentOption=13", "-c", "nonexistentConverterOption=26", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Trade::AnySceneImporter::openFile(): option nonexistentOption not recognized by ObjImporter\n" + "Trade::AnySceneConverter::convertToFile(): option nonexistentConverterOption not recognized by StanfordSceneConverter\n"}, + {"one mesh, options, explicit importer and converter", Containers::array({ + /* Same here, since we have option propagation tested in + Magnum/Test/ConverterUtilitiesTest.cpp already, to verify it's + getting called we can just supply nonexistent options */ + "-i", "nonexistentOption=13", "-c", "nonexistentConverterOption=26", + "-I", "ObjImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentOption not recognized by ObjImporter\n" + "Option nonexistentConverterOption not recognized by StanfordSceneConverter\n"}, + {"concatenate meshes without a scene", Containers::array({ + "--concatenate-meshes", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/two-triangles.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad-duplicates.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad-duplicates.ply", + {}}, + {"concatenate meshes with a scene", Containers::array({ + "--concatenate-meshes", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/two-triangles-transformed.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad-duplicates.ply")}), + "GltfImporter", "StanfordSceneConverter", + "quad-duplicates.ply", + {}}, + {"filter attributes", Containers::array({ + /* Only 0 gets picked from here, others ignored */ + "--only-attributes", "17,0,25-36", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-normals-texcoords.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"remove duplicates", Containers::array({ + "--remove-duplicates", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"remove duplicates, verbose", Containers::array({ + /* Forcing the importer and converter to avoid AnySceneImporter / + AnySceneConverter delegation messages */ + "--remove-duplicates", "-v", "-I", "ObjImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Duplicate removal: 6 -> 4 vertices\n"}, + {"remove duplicates fuzzy", Containers::array({ + "--remove-duplicates-fuzzy 1.0e-1", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates-fuzzy.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"remove duplicates fuzzy, verbose", Containers::array({ + /* Forcing the importer and converter to avoid AnySceneImporter / + AnySceneConverter delegation messages */ + "--remove-duplicates-fuzzy 1.0e-1", "-v", "-I", "ObjImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates-fuzzy.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Fuzzy duplicate removal: 6 -> 4 vertices\n"}, + {"one mesh, two converters", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, two converters, explicit last", Containers::array({ + "-C", "MeshOptimizerSceneConverter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, two converters, verbose", Containers::array({ + "-C", "MeshOptimizerSceneConverter", "-v", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + /** @todo this is a no-op, use some other converter that tests also + that the resulting mesh is actually passed further */ + "Trade::AnySceneImporter::openFile(): using ObjImporter\n" + "Trade::MeshOptimizerSceneConverter::convert(): processing stats:\n" + " vertex cache:\n" + " 4 -> 4 transformed vertices\n" + " 1 -> 1 executed warps\n" + " ACMR 2 -> 2\n" + " ATVR 1 -> 1\n" + " vertex fetch:\n" + " 64 -> 64 bytes fetched\n" + " overfetch 1.33333 -> 1.33333\n" + " overdraw:\n" + " 65536 -> 65536 shaded pixels\n" + " 65536 -> 65536 covered pixels\n" + " overdraw 1 -> 1\n" + "Trade::AnySceneConverter::convertToFile(): using StanfordSceneConverter\n"}, + {"one mesh, two converters, explicit last, verbose", Containers::array({ + "-C", "MeshOptimizerSceneConverter", "-C", "StanfordSceneConverter", "-v", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + /* As the importers and converters are specified explicitly, there's + no messages from AnySceneConverter, OTOH as we have more than one -C + option the verbose output includes a progress info */ + "Trade::AnySceneImporter::openFile(): using ObjImporter\n" + "Processing (1/2) with MeshOptimizerSceneConverter...\n" + "Trade::MeshOptimizerSceneConverter::convert(): processing stats:\n" + " vertex cache:\n" + " 4 -> 4 transformed vertices\n" + " 1 -> 1 executed warps\n" + " ACMR 2 -> 2\n" + " ATVR 1 -> 1\n" + " vertex fetch:\n" + " 64 -> 64 bytes fetched\n" + " overfetch 1.33333 -> 1.33333\n" + " overdraw:\n" + " 65536 -> 65536 shaded pixels\n" + " 65536 -> 65536 covered pixels\n" + " overdraw 1 -> 1\n" + "Saving output (2/2) with StanfordSceneConverter...\n"}, + {"one mesh, two converters, options for the first only", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + "-c", "nonexistentMeshOptimizerOption=yes", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n"}, + {"one mesh, two converters, explicit last, options for the first only", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + "-c", "nonexistentMeshOptimizerOption=yes", + "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n"}, + {"one mesh, two converters, options for both", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + "-c", "nonexistentMeshOptimizerOption=yes", + "-c", "nonexistentAnyConverterOption=no", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n" + "Trade::AnySceneConverter::convertToFile(): option nonexistentAnyConverterOption not recognized by StanfordSceneConverter\n"}, + {"one mesh, two converters, explicit last, options for both", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + "-c", "nonexistentMeshOptimizerOption=yes", + "-C", "StanfordSceneConverter", + "-c", "nonexistentStanfordConverterOption=no", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n" + "Option nonexistentStanfordConverterOption not recognized by StanfordSceneConverter\n"}, +}; + +const struct { + const char* name; + Containers::Array args; + const char* requiresImporter; + const char* requiresConverter; + Containers::String message; +} ErrorData[]{ + {"missing output argument", Containers::array({ + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj")}), + nullptr, nullptr, + /* The output should be optional only for --info, required otherwise. + No need to test anything else as that's handled by Utility::Arguments + already. Testing just a prefix of the message. */ + "Missing command-line argument output\nUsage:\n "}, + {"can't load importer plugin", Containers::array({ + /* Override also the plugin directory for consistent output */ + "--plugin-dir", "nonexistent", "-I", "NonexistentImporter", "whatever.obj", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + nullptr, nullptr, + "PluginManager::Manager::load(): plugin NonexistentImporter is not static and was not found in nonexistent/importers\n" + "Available importer plugins: "}, + {"can't open a file", Containers::array({ + "noexistent.ffs", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "AnySceneImporter", nullptr, + "Trade::AnySceneImporter::openFile(): cannot determine the format of noexistent.ffs\n" + "Cannot open file noexistent.ffs\n"}, + {"can't map a file", Containers::array({ + "noexistent.ffs", "--map", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "AnySceneImporter", nullptr, + "Utility::Path::mapRead(): can't open noexistent.ffs: error 2 (No such file or directory)\n" + "Cannot memory-map file noexistent.ffs\n"}, + {"no meshes found", Containers::array({ + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/empty.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "GltfImporter", nullptr, + Utility::format("No meshes found in {}\n", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/empty.gltf"))}, + {"can't import a mesh", Containers::array({ + "-I", "ObjImporter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/broken-mesh.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", nullptr, + "Trade::ObjImporter::mesh(): wrong index count for point\n" + "Cannot import the mesh\n"}, + {"can't import a mesh for concatenation", Containers::array({ + "-I", "ObjImporter", "--concatenate-meshes", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/broken-mesh.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", nullptr, + "Trade::ObjImporter::mesh(): wrong index count for point\n" + "Cannot import mesh 0\n"}, + {"can't import a scene for concatenation", Containers::array({ + /** @todo change to an OBJ once ObjImporter imports materials (and thus + scenes) */ + "--concatenate-meshes", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/broken-scene.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "GltfImporter", nullptr, + "Trade::GltfImporter::scene(): mesh index 1 in node 0 out of range for 1 meshes\n" + "Cannot import scene 0 for mesh concatenation\n"}, + {"invalid attribute filter", Containers::array({ + "-I", "ObjImporter", "--only-attributes", "LOLNEIN", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", nullptr, + "Utility::parseNumberSequence(): unrecognized character L in LOLNEIN\n"}, + {"can't load converter plugin", Containers::array({ + /* Override also the plugin directory for consistent output, however + then the importer plugin has to be loaded through an absolute file + path (unless using static plugins) */ + "--plugin-dir", "nonexistent", "-I", + #ifndef MAGNUM_BUILD_STATIC + Utility::Path::join(MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR, "ObjImporter" + Trade::AbstractImporter::pluginSuffix()), + #else + "ObjImporter", + #endif + "-C", "NonexistentSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", nullptr, + /* Just a prefix */ + "PluginManager::Manager::load(): plugin NonexistentSceneConverter is not static and was not found in nonexistent/sceneconverters\n" + "Available converter plugins: "}, + {"file coversion failed", Containers::array({ + "-I", "ObjImporter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.fbx")}), + "ObjImporter", "AnySceneConverter", + Utility::format("Trade::AnySceneConverter::convertToFile(): cannot determine the format of {0}\n" + "Cannot save file {0}\n", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.fbx"))}, + {"mesh coversion failed", Containers::array({ + "-I", "ObjImporter", "-C", "MeshOptimizerSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", "MeshOptimizerSceneConverter", + "Trade::MeshOptimizerSceneConverter::convert(): expected a triangle mesh, got MeshPrimitive::Points\n" + "MeshOptimizerSceneConverter cannot convert the mesh\n"}, + {"plugin doesn't support mesh conversion", Containers::array({ + /* Pass the same plugin twice, which means the first instance should + get used for a mesh-to-mesh conversion */ + "-I", "ObjImporter", "-C", "StanfordSceneConverter", "-C", "StanfordSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", "StanfordSceneConverter", + "StanfordSceneConverter doesn't support mesh conversion, only Trade::SceneConverterFeature::ConvertMeshToData\n"}, +}; +#endif + SceneConverterTest::SceneConverterTest() { addTests({&SceneConverterTest::infoImplementationEmpty}); @@ -107,6 +410,17 @@ SceneConverterTest::SceneConverterTest() { addTests({&SceneConverterTest::infoImplementationReferenceCount, &SceneConverterTest::infoImplementationError}); + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + addInstancedTests({&SceneConverterTest::info}, + Containers::arraySize(InfoData)); + + addInstancedTests({&SceneConverterTest::convert}, + Containers::arraySize(ConvertData)); + + addInstancedTests({&SceneConverterTest::error}, + Containers::arraySize(ErrorData)); + #endif + /* A subset of arguments needed by the info printing code */ _infoArgs.addBooleanOption("info") .addBooleanOption("info-scenes") @@ -120,6 +434,9 @@ SceneConverterTest::SceneConverterTest() { .addBooleanOption("info-textures") .addBooleanOption("info-images") .addBooleanOption("bounds"); + + /* Create output dir, if doesn't already exist */ + Utility::Path::make(Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles")); } void SceneConverterTest::infoImplementationEmpty() { @@ -1224,6 +1541,146 @@ void SceneConverterTest::infoImplementationError() { "Object 0: A name\n"); } +#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT +namespace { + +#ifdef SCENECONVERTER_EXECUTABLE_FILENAME +/** @todo take a StringIterable once it exists */ +Containers::Pair call(Containers::ArrayView arguments) { + /* Create a string view array for the arguments, implicitly pass the + application name and plugin directory override */ + /** @todo drop once StringIterable exists */ + Containers::Array argumentViews{ValueInit, arguments.size() + 3}; + argumentViews[0] = ""_s; + argumentViews[1] = "--plugin-dir"_s; + argumentViews[2] = MAGNUM_PLUGINS_INSTALL_DIR; + for(std::size_t i = 0; i != arguments.size(); ++i) + argumentViews[i + 3] = arguments[i]; + + const Containers::String outputFilename = Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/output.txt"); + /** @todo clean up once Utility::System::execute() with output redirection + exists */ + const bool success = std::system(Utility::format("{} {} > {} 2>&1", + SCENECONVERTER_EXECUTABLE_FILENAME, + " "_s.join(argumentViews), /** @todo handle space escaping here? */ + outputFilename + ).data()) == 0; + + const Containers::Optional output = Utility::Path::readString(outputFilename); + CORRADE_VERIFY(output); + + return {success, std::move(*output)}; +} +#endif + +} + +void SceneConverterTest::info() { + auto&& data = InfoData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef SCENECONVERTER_EXECUTABLE_FILENAME + #ifdef CORRADE_TARGET_UNIX + CORRADE_SKIP("magnum-sceneconverter not built, can't test"); + #else + CORRADE_SKIP("Executable testing implemented only on Unix platforms"); + #endif + #else + /* Check if required plugins can be loaded. Catches also ABI and interface + mismatch errors. */ + PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + if(!(importerManager.load("ObjImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("ObjImporter plugin can't be loaded."); + + Containers::Array args{InPlaceInit, + {"-I", "ObjImporter", "--info", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj")}}; + arrayAppend(args, arrayView(data.args)); /** @todo FFS fix the casts */ + + CORRADE_VERIFY(true); /* capture correct function name */ + + Containers::Pair output = call(args); + CORRADE_COMPARE_AS(output.second(), + Utility::Path::join({SCENETOOLS_TEST_DIR, "SceneConverterTestFiles", data.expected}), + TestSuite::Compare::StringToFile); + CORRADE_VERIFY(output.first()); + #endif +} + +void SceneConverterTest::convert() { + auto&& data = ConvertData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef SCENECONVERTER_EXECUTABLE_FILENAME + #ifdef CORRADE_TARGET_UNIX + CORRADE_SKIP("magnum-sceneconverter not built, can't test"); + #else + CORRADE_SKIP("Executable testing implemented only on Unix platforms"); + #endif + #else + /* Check if required plugins can be loaded. Catches also ABI and interface + mismatch errors. */ + PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + PluginManager::Manager converterManager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + if(data.requiresImporter && !(importerManager.load(data.requiresImporter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresImporter << "plugin can't be loaded."); + if(data.requiresConverter && !(converterManager.load(data.requiresConverter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresConverter << "plugin can't be loaded."); + /* AnySceneImporter & AnySceneConverter are required implicitly for + simplicity */ + if(!(importerManager.load("AnySceneImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnySceneImporter plugin can't be loaded."); + if(!(converterManager.load("AnySceneConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnySceneConverter plugin can't be loaded."); + + CORRADE_VERIFY(true); /* capture correct function name */ + + Containers::Pair output = call(data.args); + CORRADE_COMPARE(output.second(), data.message); + CORRADE_VERIFY(output.first()); + + CORRADE_COMPARE_AS(Utility::Path::join({SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles", data.expected}), + Utility::Path::join({SCENETOOLS_TEST_DIR, "SceneConverterTestFiles", data.expected}), + TestSuite::Compare::File); + #endif +} + +void SceneConverterTest::error() { + auto&& data = ErrorData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef SCENECONVERTER_EXECUTABLE_FILENAME + #ifdef CORRADE_TARGET_UNIX + CORRADE_SKIP("magnum-sceneconverter not built, can't test"); + #else + CORRADE_SKIP("Executable testing implemented only on Unix platforms"); + #endif + #else + /* Check if required plugins can be loaded. Catches also ABI and interface + mismatch errors. */ + PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + PluginManager::Manager converterManager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + if(data.requiresImporter && !(importerManager.load(data.requiresImporter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresImporter << "plugin can't be loaded."); + if(data.requiresConverter && !(converterManager.load(data.requiresConverter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresConverter << "plugin can't be loaded."); + + CORRADE_VERIFY(true); /* capture correct function name */ + + Containers::Pair output = call(data.args); + /* If the message ends with a \n, assume it's the whole message. Otherwise + it's just a prefix. */ + if(data.message.hasSuffix('\n')) + CORRADE_COMPARE(output.second(), data.message); + else + CORRADE_COMPARE_AS(output.second(), + data.message, + TestSuite::Compare::StringHasPrefix); + /* It should return a non-zero code */ + CORRADE_VERIFY(!output.first()); + #endif +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::SceneTools::Test::SceneConverterTest) diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-mesh.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-mesh.obj new file mode 100644 index 000000000..0d41da794 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-mesh.obj @@ -0,0 +1,3 @@ +# A point with two indices +v 1 2 3 +p 5 5 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-scene.gltf b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-scene.gltf new file mode 100644 index 000000000..c2e5f1095 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-scene.gltf @@ -0,0 +1,22 @@ +{ + "asset": { + "version": "2.0" + }, + "scenes": [ + { + "nodes": [0] + } + ], + "meshes": [ + { + "primitives": [ + {} + ] + } + ], + "nodes": [ + { + "mesh": 1 + } + ] +} diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/empty.gltf b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/empty.gltf new file mode 100644 index 000000000..8b778ccfb --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/empty.gltf @@ -0,0 +1,5 @@ +{ + "asset": { + "version": "2.0" + } +} diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-ignored-output.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-ignored-output.txt new file mode 100644 index 000000000..ed3609a7e --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-ignored-output.txt @@ -0,0 +1,6 @@ +Ignoring output file for --info: whatever.ply +Mesh 0: + Level 0: 1 vertices @ Points (0.0 kB) + Position @ Vector3, offset 0, stride 12 + 1 indices @ UnsignedInt, offset 0, stride 4 (0.0 kB) +Total mesh data size: 0.0 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info.txt new file mode 100644 index 000000000..1f10cb3cc --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info.txt @@ -0,0 +1,5 @@ +Mesh 0: + Level 0: 1 vertices @ Points (0.0 kB) + Position @ Vector3, offset 0, stride 12 + 1 indices @ UnsignedInt, offset 0, stride 4 (0.0 kB) +Total mesh data size: 0.0 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/point.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/point.obj new file mode 100644 index 000000000..d90424cae --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/point.obj @@ -0,0 +1,2 @@ +v 1 2 3 +p 1 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates-fuzzy.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates-fuzzy.obj new file mode 100644 index 000000000..f3f2acd4c --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates-fuzzy.obj @@ -0,0 +1,12 @@ +# 1 4--6 +# |\ \ | +# | \ \| +# 2--3 5 +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +f 1 2 3 +v -0.9 0.9 0 +v 0.9 -0.9 0 +v 1 1 0 +f 4 5 6 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.obj new file mode 100644 index 000000000..9c6d005ff --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.obj @@ -0,0 +1,12 @@ +# 1 4--6 +# |\ \ | +# | \ \| +# 2--3 5 +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +f 1 2 3 +v -1 1 0 +v 1 -1 0 +v 1 1 0 +f 4 5 6 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.ply b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.ply new file mode 100644 index 0000000000000000000000000000000000000000..aa319b3447f2ad881b65a2d76822387377ab21b0 GIT binary patch literal 268 zcmZ9GO%B2!5QXt?!ztzjMqRk|C7=4j8ra@5ztO+3wyzGcQ8+J$HM0^4D C*h-}U literal 0 HcmV?d00001 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-normals-texcoords.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-normals-texcoords.obj new file mode 100644 index 000000000..e96d09630 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-normals-texcoords.obj @@ -0,0 +1,12 @@ +# 1 1--4 +# |\ \ | +# | \ \| +# 2--3 3 +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +v 1 1 0 +vn 0 0 1 +vt 0 0 +f 1/1/1 2/1/1 3/1/1 +f 1/1/1 3/1/1 4/1/1 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.obj new file mode 100644 index 000000000..a552bb87c --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.obj @@ -0,0 +1,10 @@ +# 1 1--4 +# |\ \ | +# | \ \| +# 2--3 3 +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +v 1 1 0 +f 1 2 3 +f 1 3 4 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.ply b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.ply new file mode 100644 index 0000000000000000000000000000000000000000..e36655e079aa069fade0b05851a29fa77223c94f GIT binary patch literal 244 zcmZ8bTMoh?5Jdgs6ng@r#($4OkS;0776_%L^vj!hH5U>w=_ZrR%syr#6=_47bcUz% zIu%7OXF`R3aN5K5e33{vq0jKblyQZ-j44Fs1uO+#T=z!V8(Y6&wL!T3`{d>fiy3u- r#q|v3INjhZ`3p6o9xzEE-XF$8v}&Ant?E_Xd7C*^d0-qFP5j6gtZhc1 literal 0 HcmV?d00001 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin new file mode 100644 index 0000000000000000000000000000000000000000..84f5a85a4db3467d795063e48ff34050affb498a GIT binary patch literal 72 ocmZQzXxPud&|uF124FUj4+0=D5FgA2(gu!DHc(sui47440KH)fYXATM literal 0 HcmV?d00001 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin.in b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin.in new file mode 100644 index 000000000..5a64016f2 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin.in @@ -0,0 +1,15 @@ +# Like two-triangles.obj, but the second triangle moved 10 units on Y. Convert +# with magnum-plugins/src/MagnumPlugins/GltfImporter/Test/in2bin.py. + +type = "3f3f3f 3f3f3f" +input = [ + -1, 1, 0, + -1, -1, 0, + 1, -1, 0, + + -1, 11, 0, + 1, 9, 0, + 1, 11, 0 +] + +# kate: hl python diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.gltf b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.gltf new file mode 100644 index 000000000..b9baca103 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.gltf @@ -0,0 +1,75 @@ +{ + "asset": { + "version": "2.0" + }, + "buffers": [ + { + "uri": "two-triangles-transformed.bin", + "byteLength": 72 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 36 + }, + { + "buffer": 0, + "byteOffset": 36, + "byteLength": 36 + } + ], + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": 3, + "type": "VEC3" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 3, + "type": "VEC3" + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "POSITION": 0 + } + } + ] + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 1 + } + } + ] + } + ], + "nodes": [ + { + "mesh": 0 + }, + { + "name": "Explicit intermediate node to test proper traversal", + "translation": [0, -5, 0], + "children": [2] + }, + { + "mesh": 1, + "translation": [0, -5, 0] + } + ], + "scenes": [ + { + "nodes": [0, 1] + } + ] +} diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles.obj new file mode 100644 index 000000000..66a190eb5 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles.obj @@ -0,0 +1,14 @@ +# 1 4--6 +# |\ \ | +# | \ \| +# 2--3 5 +o First +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +f 1 2 3 +o Second +v -1 1 0 +v 1 -1 0 +v 1 1 0 +f 4 5 6 diff --git a/src/Magnum/SceneTools/Test/configure.h.cmake b/src/Magnum/SceneTools/Test/configure.h.cmake index dcb0c8113..01fccabe8 100644 --- a/src/Magnum/SceneTools/Test/configure.h.cmake +++ b/src/Magnum/SceneTools/Test/configure.h.cmake @@ -24,3 +24,27 @@ */ #define SCENETOOLS_TEST_DIR "${SCENETOOLS_TEST_DIR}" +#define SCENETOOLS_TEST_OUTPUT_DIR "${SCENETOOLS_TEST_OUTPUT_DIR}" +#cmakedefine SCENECONVERTER_EXECUTABLE_FILENAME "${SCENECONVERTER_EXECUTABLE_FILENAME}" + +#ifdef CORRADE_TARGET_WINDOWS +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_BINARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_BINARY_INSTALL_DIR}" +#endif +#else +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_LIBRARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_LIBRARY_INSTALL_DIR}" +#endif +#endif diff --git a/src/Magnum/SceneTools/sceneconverter.cpp b/src/Magnum/SceneTools/sceneconverter.cpp index 1f7867943..87c5f2410 100644 --- a/src/Magnum/SceneTools/sceneconverter.cpp +++ b/src/Magnum/SceneTools/sceneconverter.cpp @@ -510,7 +510,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") if(i + 1 >= converterCount && (converter->features() & Trade::SceneConverterFeature::ConvertMeshToFile)) { /* No verbose output for just one converter */ if(converterCount > 1 && args.isSet("verbose")) - Debug{} << "Saving output with" << converterName << Debug::nospace << "..."; + Debug{} << "Saving output (" << Debug::nospace << (i+1) << Debug::nospace << "/" << Debug::nospace << converterCount << Debug::nospace << ") with" << converterName << Debug::nospace << "..."; Trade::Implementation::Duration d{conversionTime}; if(!converter->convertToFile(*mesh, args.value("output"))) { From 52d51b8cbdb6cb4f730ac0e9ddd275521fb82dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 11 Sep 2022 11:55:11 +0200 Subject: [PATCH 88/93] doc: mention that the Corrade::Cpu-related options affect Magnum as well. Or, they eventually will. Nothing published uses CPU dispatch here at the moment. --- doc/building.dox | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/building.dox b/doc/building.dox index 0ae5caf89..5abf9d80d 100644 --- a/doc/building.dox +++ b/doc/building.dox @@ -674,7 +674,8 @@ Options controlling the build: update your code whenever there's a breaking API change. It's however recommended to have this option disabled when deploying a final application as it can result in smaller binaries. -- Additional options are inherited from the @ref CORRADE_BUILD_MULTITHREADED +- Additional options are inherited from the @ref CORRADE_BUILD_MULTITHREADED, + @ref CORRADE_BUILD_CPU_RUNTIME_DISPATCH and @ref CORRADE_CPU_USE_IFUNC options specified when building Corrade. The features used can be conveniently detected in depending projects both in From 92052e7a11441e75e6a1792b88cb37fa14c0e96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 11 Sep 2022 13:01:07 +0200 Subject: [PATCH 89/93] CMake: properly prefix an option dependency variable. Caused magnum-sceneconverter to not be built if was enabled but MAGNUM_WITH_SCENETOOLS was disabled. Sigh. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c04f11d2..87ba2f365 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,7 +189,7 @@ cmake_dependent_option(MAGNUM_WITH_AUDIO "Build Audio library" OFF "NOT MAGNUM_W option(MAGNUM_WITH_DEBUGTOOLS "Build DebugTools library" ON) cmake_dependent_option(MAGNUM_WITH_MESHTOOLS "Build MeshTools library" ON "NOT MAGNUM_WITH_OBJIMPORTER;NOT MAGNUM_WITH_SCENECONVERTER" ON) option(MAGNUM_WITH_SCENEGRAPH "Build SceneGraph library" ON) -cmake_dependent_option(MAGNUM_WITH_SCENETOOLS "Build SceneTools library" ON "NOT WITH_SCENECONVERTER" ON) +cmake_dependent_option(MAGNUM_WITH_SCENETOOLS "Build SceneTools library" ON "NOT MAGNUM_WITH_SCENECONVERTER" ON) option(MAGNUM_WITH_SHADERS "Build Shaders library" ON) cmake_dependent_option(MAGNUM_WITH_SHADERTOOLS "Build ShaderTools library" ON "NOT MAGNUM_WITH_SHADERCONVERTER" ON) cmake_dependent_option(MAGNUM_WITH_TEXT "Build Text library" ON "NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_MAGNUMFONT;NOT MAGNUM_WITH_MAGNUMFONTCONVERTER" ON) From 8d4d854d9c711682b439fdc7163d7a97c46bd086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 11 Sep 2022 13:03:00 +0200 Subject: [PATCH 90/93] MagnumFont: peroperly link the TgaImporter dependency on static build. It's correct in MagnumFontConverter (and probably all other plugins as well), nut sure why not here. --- src/MagnumPlugins/MagnumFont/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MagnumPlugins/MagnumFont/CMakeLists.txt b/src/MagnumPlugins/MagnumFont/CMakeLists.txt index a26164f59..77b1f8ef0 100644 --- a/src/MagnumPlugins/MagnumFont/CMakeLists.txt +++ b/src/MagnumPlugins/MagnumFont/CMakeLists.txt @@ -52,6 +52,8 @@ endif() target_link_libraries(MagnumFont PUBLIC Magnum MagnumText MagnumTrade) if(CORRADE_TARGET_WINDOWS) target_link_libraries(MagnumFont PUBLIC TgaImporter) +elseif(MAGNUM_MAGNUMFONT_BUILD_STATIC) + target_link_libraries(MagnumFont INTERFACE TgaImporter) endif() install(FILES MagnumFont.h ${CMAKE_CURRENT_BINARY_DIR}/configure.h From 5c36b39ba2724a270135bd454fc9e79348e40628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 11 Sep 2022 13:03:58 +0200 Subject: [PATCH 91/93] CMake: add a possibility to link static plugins to utility executables. Funny/sad that this possibility took me so long to realize. Until now it was "you can have command-line utilities or static plugins but never both", and only with CMake 3.13+ it was possible to link static plugins to these executables from outside. Now it's a builtin and supported option. --- CMakeLists.txt | 7 +++++++ doc/building.dox | 16 ++++++++++++++++ doc/changelog.dox | 7 +++++++ src/Magnum/SceneTools/CMakeLists.txt | 14 +++++++++++++- src/Magnum/ShaderTools/CMakeLists.txt | 3 ++- src/Magnum/Text/CMakeLists.txt | 3 ++- src/Magnum/TextureTools/CMakeLists.txt | 15 ++++++++++++++- src/Magnum/Trade/CMakeLists.txt | 3 ++- 8 files changed, 63 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 87ba2f365..f5433172b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,6 +157,9 @@ endif() if(CORRADE_TARGET_UNIX OR CORRADE_TARGET_WINDOWS) option(MAGNUM_WITH_FONTCONVERTER "Build magnum-fontconverter utility" OFF) option(MAGNUM_WITH_DISTANCEFIELDCONVERTER "Build magnum-distancefieldconverter utility" OFF) + + set(MAGNUM_FONTCONVERTER_STATIC_PLUGINS "" CACHE STRING "Static plugins to link to the magnum-fontconverter utility") + set(MAGNUM_DISTANCEFIELDCONVERTER_STATIC_PLUGINS "" CACHE STRING "Static plugins to link to the magnum-distancefieldconverter utility") endif() # API-independent utilities @@ -164,6 +167,10 @@ option(MAGNUM_WITH_IMAGECONVERTER "Build magnum-imageconverter utility" OFF) option(MAGNUM_WITH_SCENECONVERTER "Build magnum-sceneconverter utility" OFF) option(MAGNUM_WITH_SHADERCONVERTER "Build magnum-shaderconverter utility" OFF) +set(MAGNUM_IMAGECONVERTER_STATIC_PLUGINS "" CACHE STRING "Static plugins to link to the magnum-imageconverter utility") +set(MAGNUM_SCENECONVERTER_STATIC_PLUGINS "" CACHE STRING "Static plugins to link to the magnum-sceneconverter utility") +set(MAGNUM_SHADERCONVERTER_STATIC_PLUGINS "" CACHE STRING "Static plugins to link to the magnum-shaderconverter utility") + # Magnum AL Info option(MAGNUM_WITH_AL_INFO "Build magnum-al-info utility" OFF) diff --git a/doc/building.dox b/doc/building.dox index 5abf9d80d..783177e4e 100644 --- a/doc/building.dox +++ b/doc/building.dox @@ -674,6 +674,22 @@ Options controlling the build: update your code whenever there's a breaking API change. It's however recommended to have this option disabled when deploying a final application as it can result in smaller binaries. +- `MAGNUM_DISTANCEFIELDCONVERTER_STATIC_PLUGINS`, + `MAGNUM_FONTCONVERTER_STATIC_PLUGINS`, + `MAGNUM_IMAGECONVERTER_STATIC_PLUGINS`, + `MAGNUM_SCENECONVERTER_STATIC_PLUGINS` and + `MAGNUM_SHADERCONVERTER_STATIC_PLUGINS` --- Static plugins to link to the + @ref magnum-distancefieldconverter "magnum-distancefieldconverter", + @ref magnum-fontconverter "magnum-fontconverter", + @ref magnum-imageconverter "magnum-imageconverter", + @ref magnum-sceneconverter "magnum-sceneconverter" and + @ref magnum-shaderconverter "magnum-shaderconverter" utilities, + respectively. Intended for use in scenarios where both + `MAGNUM_BUILD_STATIC` and `MAGNUM_BUILD_PLUGINS_STATIC` is enabled, in + which case these executables don't have a possibility to load dynamic + plugins from a filesystem. Plugins from the Magnum Plugins repository + (and elsewhere) can be linked if it's added as a CMake subproject. Expects + a semicolon-separated list of existing CMake targets, for example `Magnum::AnyImageImporter;MagnumPlugins::StbImageImporter`. - Additional options are inherited from the @ref CORRADE_BUILD_MULTITHREADED, @ref CORRADE_BUILD_CPU_RUNTIME_DISPATCH and @ref CORRADE_CPU_USE_IFUNC options specified when building Corrade. diff --git a/doc/changelog.dox b/doc/changelog.dox index d596ee711..7e6e319c7 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -672,6 +672,13 @@ See also: [mosra/magnum#570](https://github.com/mosra/magnum/pull/570). - Fixed wrong `.gitattributes` option for LF line endings in MSYS PKGBUILDs (see [mosra/magnum#574](https://github.com/mosra/magnum/issues/574)) +- Added `MAGNUM_DISTANCEFIELDCONVERTER_STATIC_PLUGINS`, + `MAGNUM_FONTCONVERTER_STATIC_PLUGINS`, + `MAGNUM_IMAGECONVERTER_STATIC_PLUGINS`, + `MAGNUM_SCENECONVERTER_STATIC_PLUGINS` and + `MAGNUM_SHADERCONVERTER_STATIC_PLUGINS` CMake options for linking static + plugins to the command-line utilities. See @ref building-features for more + information. @subsection changelog-latest-bugfixes Bug fixes diff --git a/src/Magnum/SceneTools/CMakeLists.txt b/src/Magnum/SceneTools/CMakeLists.txt index ab7e8fcc2..f82451f7b 100644 --- a/src/Magnum/SceneTools/CMakeLists.txt +++ b/src/Magnum/SceneTools/CMakeLists.txt @@ -27,6 +27,17 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "Magnum/SceneTools") +# Somehow, due to MagnumTradeObjects having target_include_directories() with +# $, +# if MAGNUM_SCENECONVERTER_STATIC_PLUGINS is non-empty then CMake fails with +# +# Target "Corrade::PluginManager" not found. +# +# unless the find_package() is here. Not sure why, probably some bug in CMake +# dependency handling? Changing target_include_directories() to PRIVATE doesn't +# help, removing it altogether helps. +find_package(Corrade REQUIRED PluginManager) + # Files shared between main library and unit test library set(MagnumSceneTools_SRCS ) @@ -88,7 +99,8 @@ if(MAGNUM_WITH_SCENECONVERTER) Magnum MagnumMeshTools MagnumSceneTools - MagnumTrade) + MagnumTrade + ${MAGNUM_SCENECONVERTER_STATIC_PLUGINS}) install(TARGETS magnum-sceneconverter DESTINATION ${MAGNUM_BINARY_INSTALL_DIR}) diff --git a/src/Magnum/ShaderTools/CMakeLists.txt b/src/Magnum/ShaderTools/CMakeLists.txt index 131daa46a..400af9302 100644 --- a/src/Magnum/ShaderTools/CMakeLists.txt +++ b/src/Magnum/ShaderTools/CMakeLists.txt @@ -91,7 +91,8 @@ if(MAGNUM_WITH_SHADERCONVERTER) add_executable(magnum-shaderconverter shaderconverter.cpp) target_link_libraries(magnum-shaderconverter PRIVATE Magnum - MagnumShaderTools) + MagnumShaderTools + ${MAGNUM_SHADERCONVERTER_STATIC_PLUGINS}) install(TARGETS magnum-shaderconverter DESTINATION ${MAGNUM_BINARY_INSTALL_DIR}) diff --git a/src/Magnum/Text/CMakeLists.txt b/src/Magnum/Text/CMakeLists.txt index 4fd075698..8dd3b71b0 100644 --- a/src/Magnum/Text/CMakeLists.txt +++ b/src/Magnum/Text/CMakeLists.txt @@ -116,7 +116,8 @@ if(MAGNUM_WITH_FONTCONVERTER) target_link_libraries(magnum-fontconverter PRIVATE Magnum MagnumText - MagnumTrade) + MagnumTrade + ${MAGNUM_FONTCONVERTER_STATIC_PLUGINS}) if(MAGNUM_TARGET_EGL) target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessEglApplication) elseif(CORRADE_TARGET_IOS) diff --git a/src/Magnum/TextureTools/CMakeLists.txt b/src/Magnum/TextureTools/CMakeLists.txt index 625b71a25..f701a6396 100644 --- a/src/Magnum/TextureTools/CMakeLists.txt +++ b/src/Magnum/TextureTools/CMakeLists.txt @@ -27,6 +27,18 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "Magnum/TextureTools") +# Somehow, due to MagnumTradeObjects having target_include_directories() with +# $, +# if MAGNUM_DISTANCEFIELDCONVERTER_STATIC_PLUGINS is non-empty then CMake fails +# with +# +# Target "Corrade::PluginManager" not found. +# +# unless the find_package() is here. Not sure why, probably some bug in CMake +# dependency handling? Changing target_include_directories() to PRIVATE doesn't +# help, removing it altogether helps. +find_package(Corrade REQUIRED PluginManager) + set(MagnumTextureTools_GracefulAssert_SRCS Atlas.cpp) @@ -84,7 +96,8 @@ if(MAGNUM_WITH_DISTANCEFIELDCONVERTER) target_link_libraries(magnum-distancefieldconverter PRIVATE Magnum MagnumTextureTools - MagnumTrade) + MagnumTrade + ${MAGNUM_DISTANCEFIELDCONVERTER_STATIC_PLUGINS}) if(MAGNUM_TARGET_EGL) target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessEglApplication) elseif(CORRADE_TARGET_IOS) diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index 6c981bc54..547bed61a 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -160,7 +160,8 @@ if(MAGNUM_WITH_IMAGECONVERTER) MagnumTrade # BasisImageConverter uses these, and linking pthread to just the # plugin doesn't work. See its documentation for details. - Threads::Threads) + Threads::Threads + ${MAGNUM_IMAGECONVERTER_STATIC_PLUGINS}) install(TARGETS magnum-imageconverter DESTINATION ${MAGNUM_BINARY_INSTALL_DIR}) From 31cf9e848cf35bacb0aa59cd20fae176cc9e9f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 11 Sep 2022 21:46:54 +0200 Subject: [PATCH 92/93] sceneconverter: use MAGNUM_SCENECONVERTER_STATIC_PLUGINS for the test too. So both have consistent results from plugin existence queries. --- src/Magnum/SceneTools/Test/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index c1d7b5f60..51f448cbb 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -53,7 +53,11 @@ corrade_add_test(SceneToolsFlattenMeshHierarchyTest FlattenMeshHierarchyTest.cpp corrade_add_test(SceneToolsOrderClusterParentsTest OrderClusterParentsTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsSceneConverterTest SceneConverterTest.cpp - LIBRARIES MagnumSceneTools + LIBRARIES + MagnumSceneTools + # Link the same static plugins as for the magnum-sceneconverter + # executable so plugin existence checks are consistent between the two + ${MAGNUM_SCENECONVERTER_STATIC_PLUGINS} FILES SceneConverterTestFiles/broken-mesh.obj SceneConverterTestFiles/broken-scene.gltf From e5d3ec8d831771385421c156cf99b5b55d5aadbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 11 Sep 2022 19:51:21 +0200 Subject: [PATCH 93/93] package/ci: link plugins to the sceneconverter in static builds. So the executables get tested at least somewhat. --- package/ci/circleci.yml | 8 ++++++++ package/ci/unix-desktop.sh | 1 + 2 files changed, 9 insertions(+) diff --git a/package/ci/circleci.yml b/package/ci/circleci.yml index 3eb08bacf..bf31bda38 100644 --- a/package/ci/circleci.yml +++ b/package/ci/circleci.yml @@ -381,6 +381,11 @@ jobs: # STUPID yml interprets unquoted ON as a boolean # https://stackoverflow.com/questions/53648244/specifying-the-string-value-yes-in-a-yaml-property BUILD_STATIC: "ON" + # Testing magnum-sceneconverter and other utilities requires the plugins + # to be either installed or static. Tests are however deliberately run + # before install, so the static builds are the only case where the + # utilities get thoroughly tested. + EXTRA_OPTS: -DMAGNUM_SCENECONVERTER_STATIC_PLUGINS=Magnum::AnySceneImporter;Magnum::ObjImporter CMAKE_CXX_FLAGS: --coverage LCOV_EXTRA_OPTS: --gcov-tool /usr/bin/gcov-4.8 CONFIGURATION: Debug @@ -514,6 +519,9 @@ jobs: environment: # STUPID yml interprets unquoted ON as a boolean BUILD_STATIC: "ON" + # Same comment as with the linux-static build -- these are the only jobs + # that test command-line tools thoroughly + EXTRA_OPTS: -DMAGNUM_SCENECONVERTER_STATIC_PLUGINS=Magnum::AnySceneImporter;Magnum::ObjImporter CMAKE_CXX_FLAGS: --coverage CONFIGURATION: Debug PLATFORM_GL_API: CGL diff --git a/package/ci/unix-desktop.sh b/package/ci/unix-desktop.sh index 49369492b..ebcd06608 100755 --- a/package/ci/unix-desktop.sh +++ b/package/ci/unix-desktop.sh @@ -65,6 +65,7 @@ cmake .. \ -DMAGNUM_BUILD_DEPRECATED=$BUILD_DEPRECATED \ -DMAGNUM_BUILD_STATIC=$BUILD_STATIC \ -DMAGNUM_BUILD_PLUGINS_STATIC=$BUILD_STATIC \ + $EXTRA_OPTS \ -G Ninja ninja $NINJA_JOBS ASAN_OPTIONS="color=always" LSAN_OPTIONS="color=always suppressions=$(pwd)/../package/ci/leaksanitizer.conf" TSAN_OPTIONS="color=always" CORRADE_TEST_COLOR=ON ctest -V -E "GLTest|GLBenchmark|VkTest"