From 2d9cedfdcabc02df63fe4f338e0eefa64fbcb0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 17 Jan 2022 17:44:11 +0100 Subject: [PATCH] Trade: support strided index buffers in MeshData. Also not something the classic GPU vertex pipeline can handle, but useful for other scenarios. Subsequently a support for array indices will be added, allowing to directly represent for example OBJ files, where each attribute has its own index buffer. --- doc/changelog.dox | 21 +- doc/snippets/MagnumTrade.cpp | 6 +- src/Magnum/Trade/MeshData.cpp | 73 ++++-- src/Magnum/Trade/MeshData.h | 165 +++++++++--- src/Magnum/Trade/Test/MeshDataTest.cpp | 342 +++++++++++++++++++++++-- 5 files changed, 517 insertions(+), 90 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 428575820..3d19325dc 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -452,9 +452,10 @@ See also: @ref Trade::PhongMaterialData::commonTextureCoordinates() exposing a common texture coordinate set as a complement to a per-texture property added in 2020.06 -- @ref Trade::MeshData now allows zero and negative strides for better - data layout flexibility. This is however not commonly supported by GPU APIs - and tools like @ref MeshTools::compile() assert in that case. +- @ref Trade::MeshData now allows strided index buffers and zero and negative + attribute strides for better data layout flexibility. This is however not + commonly supported by GPU APIs and tools like @ref MeshTools::compile() + assert in that case. - Added @ref Trade::MeshData::findAttributeId() for an ability to check that an attribute exists and retrieve its ID in a single step, avoiding a double lookup compared to @relativeref{Trade::MeshData,hasAttribute()} + @@ -878,10 +879,16 @@ See also: @cpp Trade::AbstractMaterialData @ce aliases to, doesn't have a @cpp virtual @ce destructor as subclasses with extra data members aren't a desired use case anymore. -- @ref Trade::MeshData now allows zero and negative strides for better - data layout flexibility, however as this is not commonly supported by GPU - APIs, it implies the user is now expected to validate this value when - passing it there. +- @ref Trade::MeshData now allows strided index buffers and zero and negative + attribute strides for better data layout flexibility, however as this is + not commonly supported by GPU APIs, it implies the user is now expected to + validate the data layout when passing it there. Due to this change, the + @ref Trade::MeshData::indices() and + @relativeref{Trade::MeshData,mutableIndices()} accessors now return a + @relativeref{Corrade,Containers::StridedArrayView} instead of an + @relativeref{Corrade,Containers::ArrayView} --- either change your code to + accept a strided view instead or use @relativeref{Corrade,Containers::StridedArrayView::asContiguous()} to get a + contiguous @relativeref{Corrade,Containers::ArrayView} again. - @ref Trade::SceneData constructor taking a @ref std::vector of 2D and 3D children that got deprecated in favor of @ref Trade::SceneData::SceneData(SceneMappingType, UnsignedLong, Containers::Array&&, Containers::Array&&, const void*) diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index b508baad1..957e57a62 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -736,10 +736,12 @@ MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{2.0f}), Trade::MeshData data{MeshPrimitive::Points, 0}; /* [MeshData-usage-special-layouts] */ if(data.attributeStride(Trade::MeshAttribute::Position) <= 0 || - data.attributeStride(Trade::MeshAttribute::Normal) <= 0) + data.attributeStride(Trade::MeshAttribute::Normal) <= 0 || + (data.isIndexed() && !data.indices().isContiguous())) Fatal{} << "Uh oh"; -// Now it's safe to use the Position and Normal attributes in a GPU mesh +// Now it's safe to use the Position and Normal attributes and the index buffer +// in a GPU mesh /* [MeshData-usage-special-layouts] */ } diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 343d79df4..0ff363ff7 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -37,12 +37,12 @@ namespace Magnum { namespace Trade { -MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayView data) noexcept: _type{type}, _data{data} { - CORRADE_ASSERT(data.size()%meshIndexTypeSize(type) == 0, - "Trade::MeshIndexData: view size" << data.size() << "does not correspond to" << type, ); -} - -MeshIndexData::MeshIndexData(const Containers::StridedArrayView2D& data) noexcept { +MeshIndexData::MeshIndexData(const Containers::StridedArrayView2D& data) noexcept: + /* Delegating to the constexpr function in order to reuse the stride size + assert. The index type is not checked there so we can set it to nothing + and then overwrite below. */ + MeshIndexData{MeshIndexType{}, {{nullptr, ~std::size_t{}}, data.data(), data.size()[0], data.stride()[0]}} +{ /* Second dimension being zero indicates a non-indexed mesh */ if(data.size()[1] == 0) { _type = MeshIndexType{}; @@ -54,8 +54,8 @@ MeshIndexData::MeshIndexData(const Containers::StridedArrayView2D& d else if(data.size()[1] == 1) _type = MeshIndexType::UnsignedByte; else CORRADE_ASSERT_UNREACHABLE("Trade::MeshIndexData: expected index type size 1, 2 or 4 but got" << data.size()[1], ); - CORRADE_ASSERT(data.isContiguous(), "Trade::MeshIndexData: view is not contiguous", ); - _data = data.asContiguous(); + CORRADE_ASSERT(data.isContiguous<1>(), + "Trade::MeshIndexData: second view dimension is not contiguous", ); } MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, UnsignedShort arraySize) noexcept: MeshAttributeData{nullptr, name, format, data, arraySize} { @@ -83,10 +83,22 @@ Containers::Array meshAttributeDataNonOwningArray(const Conta return Containers::Array{const_cast(view.data()), view.size(), reinterpret_cast(Implementation::nonOwnedArrayDeleter)}; } -MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: _primitive{primitive}, _indexType{indices._type}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState}, _indices{static_cast(indices._data.data())}, _attributes{std::move(attributes)}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)} { +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: + _primitive{primitive}, _indexType{indices._type}, + /* Bounds of index stride are checked in MeshIndexData already, so the + cast alone is fine */ + _indexStride{Short(indices._data.stride())}, + _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, + _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, + _importerState{importerState}, + _indices{static_cast(indices._data.data())}, + _attributes{std::move(attributes)}, + _indexData{std::move(indexData)}, + _vertexData{std::move(vertexData)} +{ /* Save index count, only if the indices are actually specified */ if(_indexType != MeshIndexType{}) - _indexCount = UnsignedInt(indices._data.size()/meshIndexTypeSize(indices._type)); + _indexCount = indices._data.size(); else _indexCount = 0; /* Save vertex count. If it's passed explicitly, use that (but still check @@ -112,12 +124,23 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde #endif } + #ifndef CORRADE_NO_ASSERT CORRADE_ASSERT(_indexCount || _indexData.empty(), "Trade::MeshData: indexData passed for a non-indexed mesh", ); - CORRADE_ASSERT(!_indices || (_indices >= _indexData.begin() && _indices + indices._data.size() <= _indexData.end()), - "Trade::MeshData: indices [" << Debug::nospace << static_cast(_indices) << Debug::nospace << ":" << Debug::nospace << static_cast(_indices + indices._data.size()) << Debug::nospace << "] are not contained in passed indexData array [" << Debug::nospace << static_cast(_indexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_indexData.end()) << Debug::nospace << "]", ); + if(_indexCount) { + const void* begin = _indices; + /* C integer promotion rules are weird, without the Int the result is + an unsigned 32-bit value that messes things up on 64bit */ + const void* end = _indices + Int(_indexCount - 1)*_indexStride; + /* Flip for negative stride */ + if(begin > end) std::swap(begin, end); + /* Add the last element size to the higher address */ + end = static_cast(end) + meshIndexTypeSize(_indexType); + + CORRADE_ASSERT(begin >= _indexData.begin() && end <= _indexData.end(), + "Trade::MeshData: indices [" << Debug::nospace << begin << Debug::nospace << ":" << Debug::nospace << end << Debug::nospace << "] are not contained in passed indexData array [" << Debug::nospace << static_cast(_indexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_indexData.end()) << Debug::nospace << "]", ); + } - #ifndef CORRADE_NO_ASSERT /* Not checking what's already checked in MeshIndexData / MeshAttributeData constructors */ for(std::size_t i = 0; i != _attributes.size(); ++i) { @@ -250,13 +273,20 @@ std::size_t MeshData::indexOffset() const { return _indices - _indexData.data(); } +Short MeshData::indexStride() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indexStride(): the mesh is not indexed", {}); + return _indexStride; +} + Containers::StridedArrayView2D MeshData::indices() const { /* For a non-indexed mesh returning zero size in both dimensions, indexed mesh with zero indices still has the second dimension non-zero */ if(!isIndexed()) return {}; const std::size_t indexTypeSize = meshIndexTypeSize(_indexType); - /* Build a 2D view using information about attribute type size */ - return {{_indices, _indexCount*indexTypeSize}, {_indexCount, indexTypeSize}}; + /* Build a 2D view using information about index type size and stride. + We're *sure* the view is correct, so faking the view size */ + return {{_indices, ~std::size_t{}}, {_indexCount, indexTypeSize}, {_indexStride, 1}}; } Containers::StridedArrayView2D MeshData::mutableIndices() { @@ -266,8 +296,9 @@ Containers::StridedArrayView2D MeshData::mutableIndices() { mesh with zero indices still has the second dimension non-zero */ if(!isIndexed()) return {}; const std::size_t indexTypeSize = meshIndexTypeSize(_indexType); - /* Build a 2D view using information about index type size */ - Containers::StridedArrayView2D out{{_indices, _indexCount*indexTypeSize}, {_indexCount, indexTypeSize}}; + /* Build a 2D view using information about index type size and stride. + We're *sure* the view is correct, so faking the view size */ + Containers::StridedArrayView2D out{{_indices, ~std::size_t{}}, {_indexCount, indexTypeSize}, {_indexStride, 1}}; /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the @@ -429,12 +460,14 @@ void MeshData::indicesInto(const Containers::StridedArrayView1D& de CORRADE_ASSERT(destination.size() == indexCount(), "Trade::MeshData::indicesInto(): expected a view with" << indexCount() << "elements but got" << destination.size(), ); const auto destination1ui = Containers::arrayCast<2, UnsignedInt>(destination); + const Containers::StridedArrayView2D indexData = indices(); + if(_indexType == MeshIndexType::UnsignedInt) - return Utility::copy(Containers::arrayView(reinterpret_cast(_indices), _indexCount), destination); + return Utility::copy(Containers::arrayCast<1, const UnsignedInt>(indexData), destination); else if(_indexType == MeshIndexType::UnsignedShort) - return Math::castInto(Containers::arrayCast<2, const UnsignedShort>(Containers::arrayView(reinterpret_cast(_indices), _indexCount)), destination1ui); + return Math::castInto(Containers::arrayCast<2, const UnsignedShort>(indexData), destination1ui); else if(_indexType == MeshIndexType::UnsignedByte) - return Math::castInto(Containers::arrayCast<2, const UnsignedByte>(Containers::arrayView(reinterpret_cast(_indices), _indexCount)), destination1ui); + return Math::castInto(Containers::arrayCast<2, const UnsignedByte>(indexData), destination1ui); else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index c66600311..a6b2ac489 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -233,54 +233,108 @@ class MAGNUM_TRADE_EXPORT MeshIndexData { explicit MeshIndexData(std::nullptr_t = nullptr) noexcept: _type{} {} /** - * @brief Construct with a runtime-specified index type + * @brief Construct a contiguous index array with a runtime-specified index type * @param type Index type * @param data Index data * - * The @p data size is expected to correspond to given @p type (e.g., - * for @ref MeshIndexType::UnsignedInt the @p data array size should - * be divisible by 4). If you know the @p type at compile time, you can - * use one of the @ref MeshIndexData(Containers::ArrayView), - * @ref MeshIndexData(Containers::ArrayView) or - * @ref MeshIndexData(Containers::ArrayView) + * This overload is picked over @ref MeshIndexData(MeshIndexType, Containers::StridedArrayView1D) + * if @p data is convertible to a contiguous view. The @p data size is + * then expected to correspond to given @p type (e.g., for + * @ref MeshIndexType::UnsignedInt the @p data array size should be + * divisible by 4). If you know the @p type at compile time, you can + * use one of the @ref MeshIndexData(Containers::StridedArrayView1D), + * @ref MeshIndexData(Containers::StridedArrayView1D) or + * @ref MeshIndexData(Containers::StridedArrayView1D) * constructors, which infer the index type automatically. * * If @p data is empty, the mesh will be treated as indexed but with * zero indices. To create a non-indexed mesh, use the * @ref MeshIndexData(std::nullptr_t) constructor. */ + #ifndef DOXYGEN_GENERATING_OUTPUT + template>::value>::type> explicit MeshIndexData(MeshIndexType type, T&& data) noexcept; + #else explicit MeshIndexData(MeshIndexType type, Containers::ArrayView data) noexcept; + #endif - /** @brief Construct with unsigned byte indices */ - constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: _type{MeshIndexType::UnsignedByte}, _data{data} {} + /** + * @brief Construct a strided index array with a runtime-specified index type + * @param type Index type + * @param data Index data + * @m_since_latest + * + * If you know the @p type at compile time, you can use one of the + * @ref MeshIndexData(Containers::StridedArrayView1D), + * @ref MeshIndexData(Containers::StridedArrayView1D) or + * @ref MeshIndexData(Containers::StridedArrayView1D) + * constructors, which infer the index type automatically. The view + * doesn't need to be contiguous and the stride can be even zero or + * negative, but note that such data layout is not commonly supported + * by GPU APIs. + * + * If @p data is empty, the mesh will be treated as indexed but with + * zero indices. To create a non-indexed mesh, use the + * @ref MeshIndexData(std::nullptr_t) constructor. + */ + constexpr explicit MeshIndexData(MeshIndexType type, Containers::StridedArrayView1D data) noexcept; - /** @brief Construct with unsigned short indices */ - constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: _type{MeshIndexType::UnsignedShort}, _data{data} {} + /** + * @brief Construct with unsigned byte indices + * + * The view doesn't need to be contiguous and the stride can be even + * zero or negative, but note that such data layout is not commonly + * supported by GPU APIs. + */ + constexpr explicit MeshIndexData(Containers::StridedArrayView1D data) noexcept: MeshIndexData{MeshIndexType::UnsignedByte, data} {} - /** @brief Construct with unsigned int indices */ - constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: _type{MeshIndexType::UnsignedInt}, _data{data} {} + /** + * @brief Construct with unsigned short indices + * + * The view doesn't need to be contiguous and the stride can be even + * zero or negative, but note that such data layout is not commonly + * supported by GPU APIs. + */ + constexpr explicit MeshIndexData(Containers::StridedArrayView1D data) noexcept: MeshIndexData{MeshIndexType::UnsignedShort, data} {} + + /** + * @brief Construct with unsigned int indices + * + * The view doesn't need to be contiguous and the stride can be even + * zero or negative, but note that such data layout is not commonly + * supported by GPU APIs. + */ + constexpr explicit MeshIndexData(Containers::StridedArrayView1D data) noexcept: MeshIndexData{MeshIndexType::UnsignedInt, data} {} /** * @brief Constructor * - * Expects that @p data is contiguous and size of the second dimension - * is either 1, 2 or 4, corresponding to one of the @ref MeshIndexType - * values. As a special case, if second dimension size is 0, the - * constructor is equivalent to @ref MeshIndexData(std::nullptr_t). + * Expects that the second dimension of @p data is contiguous and its + * size is either 1, 2 or 4, corresponding to one of the + * @ref MeshIndexType values. The first dimension doesn't need to be + * contiguous and its stride can be even zero or negative, but note + * that such data layout is not commonly supported by GPU APIs. As a + * special case, if second dimension size is 0, the constructor is + * equivalent to @ref MeshIndexData(std::nullptr_t). */ explicit MeshIndexData(const Containers::StridedArrayView2D& data) noexcept; /** @brief Index type */ constexpr MeshIndexType type() const { return _type; } - /** @brief Type-erased index data */ - constexpr Containers::ArrayView data() const { return _data; } + /** + * @brief Type-erased index data + * + * In rare cases the stride may be different from the index type size + * and even be zero or negative, such data layouts are however not + * commonly supported by GPU APIs. + */ + constexpr Containers::StridedArrayView1D data() const { return _data; } private: friend MeshData; MeshIndexType _type; /* Void so the constructors can be constexpr */ - Containers::ArrayView _data; + Containers::StridedArrayView1D _data; }; /** @@ -1111,13 +1165,28 @@ class MAGNUM_TRADE_EXPORT MeshData { */ std::size_t indexOffset() const; + /** + * @brief Index stride + * @m_since_latest + * + * Stride between consecutive elements in the @ref indexData() array. + * In rare cases the stride may be different from the index type size + * and even be zero or negative, such data layouts are however not + * commonly supported by GPU APIs. + * @see @ref MeshTools::isInterleaved() + */ + Short indexStride() const; + /** * @brief Mesh indices * - * For an indexed mesh, the view is guaranteed to be contiguous and its - * second dimension represents the actual data type (its size is equal - * to type size, even in case there's zero indices). For a non-indexed - * mesh, the returned view has a zero size in both dimensions. + * For an indexed mesh, the second dimension of the view is guaranteed + * to be contiguous and its size is equal to type size, even in case + * there's zero indices. For a non-indexed mesh, the returned view has + * a zero size in both dimensions. In rare cases the first dimension + * stride may be different from the index type size and even be zero or + * negative, such data layouts are however not commonly supported by + * GPU APIs. * * Use the templated overload below to get the indices in a concrete * type. @@ -1138,13 +1207,16 @@ class MAGNUM_TRADE_EXPORT MeshData { * @brief Mesh indices in a concrete type * * Expects that the mesh is indexed and that @p T corresponds to - * @ref indexType(). You can also use the non-templated - * @ref indicesAsArray() accessor to get indices converted to 32-bit, - * but note that such operation involves extra allocation and data - * conversion. - * @see @ref isIndexed(), @ref attribute(), @ref mutableIndices() + * @ref indexType(). In rare cases the first dimension stride may be + * different from the index type size and even be zero or negative, + * such data layouts are however not commonly supported by GPU APIs. + * You can also use the non-templated @ref indicesAsArray() accessor to + * get indices converted to a contiguous 32-bit array, but note that + * such operation involves extra allocation and data conversion. + * @see @ref isIndexed(), @ref attribute(), @ref mutableIndices(), + * @relativeref{Corrade,Containers::StridedArrayView::isContiguous()} */ - template Containers::ArrayView indices() const; + template Containers::StridedArrayView1D indices() const; /** * @brief Mutable mesh indices in a concrete type @@ -1153,7 +1225,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * the mesh is mutable. * @see @ref indexDataFlags() */ - template Containers::ArrayView mutableIndices(); + template Containers::StridedArrayView1D mutableIndices(); /** * @brief Mesh vertex count @@ -1860,7 +1932,10 @@ class MAGNUM_TRADE_EXPORT MeshData { UnsignedInt _indexCount, _vertexCount; MeshPrimitive _primitive; MeshIndexType _indexType; + /* 3 byte padding, reserved for 4-byte MeshIndexType */ + Short _indexStride; DataFlags _indexDataFlags, _vertexDataFlags; + /* 4 byte padding on 64bit */ const void* _importerState; const char* _indices; Containers::Array _attributes; @@ -1877,7 +1952,27 @@ namespace Implementation { template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedByte; } template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedShort; } template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedInt; } +} +#ifndef DOXYGEN_GENERATING_OUTPUT +template MeshIndexData::MeshIndexData(const MeshIndexType type, T&& data) noexcept: _type{type} { + const Containers::ArrayView erased = data; + const std::size_t typeSize = meshIndexTypeSize(type); + CORRADE_ASSERT(erased.size()%typeSize == 0, + "Trade::MeshIndexData: view size" << erased.size() << "does not correspond to" << type, ); + _data = Containers::StridedArrayView1D{erased, erased.size()/typeSize, std::ptrdiff_t(typeSize)}; +} +#endif + +constexpr MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::StridedArrayView1D data) noexcept: + _type{type}, + /* The actual limitation is in MeshData, but better to have it caught here + already */ + _data{(CORRADE_CONSTEXPR_ASSERT(data.stride() >= -32768 && data.stride() <= 32767, + "Trade::MeshIndexData: expected stride to fit into 16 bits but got" << data.stride()), data)} + {} + +namespace Implementation { /* Implicit mapping from a format to enum (1:1) */ template constexpr VertexFormat vertexFormatFor() { /* C++ why there isn't an obvious way to do such a thing?! */ @@ -2173,7 +2268,7 @@ template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute n UnsignedShort(data.size()[1]) } {} -template Containers::ArrayView MeshData::indices() const { +template Containers::StridedArrayView1D MeshData::indices() const { CORRADE_ASSERT(isIndexed(), "Trade::MeshData::indices(): the mesh is not indexed", {}); Containers::StridedArrayView2D data = indices(); @@ -2182,10 +2277,10 @@ template Containers::ArrayView MeshData::indices() const { #endif CORRADE_ASSERT(Implementation::meshIndexTypeFor() == _indexType, "Trade::MeshData::indices(): indices are" << _indexType << "but requested" << Implementation::meshIndexTypeFor(), {}); - return Containers::arrayCast<1, const T>(data).asContiguous(); + return Containers::arrayCast<1, const T>(data); } -template Containers::ArrayView MeshData::mutableIndices() { +template Containers::StridedArrayView1D MeshData::mutableIndices() { CORRADE_ASSERT(isIndexed(), "Trade::MeshData::mutableIndices(): the mesh is not indexed", {}); Containers::StridedArrayView2D data = mutableIndices(); @@ -2194,7 +2289,7 @@ template Containers::ArrayView MeshData::mutableIndices() { #endif CORRADE_ASSERT(Implementation::meshIndexTypeFor() == _indexType, "Trade::MeshData::mutableIndices(): indices are" << _indexType << "but requested" << Implementation::meshIndexTypeFor(), {}); - return Containers::arrayCast<1, T>(data).asContiguous(); + return Containers::arrayCast<1, T>(data); } #ifndef CORRADE_NO_ASSERT diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 25fc09bf6..4ad9ce1dd 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -44,12 +44,17 @@ struct MeshDataTest: TestSuite::Tester { void customAttributeNameNotCustom(); void debugAttributeName(); - void constructIndex(); - void constructIndexTypeErased(); - void constructIndexTypeErasedWrongSize(); + void constructIndexContiguous(); + void constructIndexStrided(); + void constructIndexStridedWrongStride(); + void constructIndexTypeErasedContiguous(); + void constructIndexTypeErasedContiguousWrongSize(); + void constructIndexTypeErasedStrided(); + void constructIndexTypeErasedStridedWrongStride(); void constructIndex2D(); void constructIndex2DNotIndexed(); void constructIndex2DWrongSize(); + void constructIndex2DWrongStride(); void constructIndex2DNonContiguous(); void constructIndexNullptr(); @@ -90,6 +95,7 @@ struct MeshDataTest: TestSuite::Tester { void constructIndexlessAttributeless(); void constructIndexlessAttributelessZeroVertices(); + void constructSpecialIndexStrides(); void constructSpecialAttributeStrides(); void constructSpecialAttributeStridesImplementationSpecificVertexFormat(); @@ -206,12 +212,17 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::customAttributeNameNotCustom, &MeshDataTest::debugAttributeName, - &MeshDataTest::constructIndex, - &MeshDataTest::constructIndexTypeErased, - &MeshDataTest::constructIndexTypeErasedWrongSize, + &MeshDataTest::constructIndexContiguous, + &MeshDataTest::constructIndexStrided, + &MeshDataTest::constructIndexStridedWrongStride, + &MeshDataTest::constructIndexTypeErasedContiguous, + &MeshDataTest::constructIndexTypeErasedContiguousWrongSize, + &MeshDataTest::constructIndexTypeErasedStrided, + &MeshDataTest::constructIndexTypeErasedStridedWrongStride, &MeshDataTest::constructIndex2D, &MeshDataTest::constructIndex2DNotIndexed, &MeshDataTest::constructIndex2DWrongSize, + &MeshDataTest::constructIndex2DWrongStride, &MeshDataTest::constructIndex2DNonContiguous, &MeshDataTest::constructIndexNullptr, @@ -253,6 +264,7 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructIndexlessAttributeless, &MeshDataTest::constructIndexlessAttributelessZeroVertices, + &MeshDataTest::constructSpecialIndexStrides, &MeshDataTest::constructSpecialAttributeStrides, &MeshDataTest::constructSpecialAttributeStridesImplementationSpecificVertexFormat}); @@ -450,51 +462,148 @@ constexpr UnsignedByte IndexBytes[]{25, 132, 3}; constexpr UnsignedShort IndexShorts[]{2575, 13224, 3}; constexpr UnsignedInt IndexInts[]{2110122, 132257, 3}; -void MeshDataTest::constructIndex() { +void MeshDataTest::constructIndexContiguous() { { const UnsignedByte indexData[]{25, 132, 3}; MeshIndexData indices{indexData}; CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedByte); CORRADE_COMPARE(indices.data().data(), indexData); + CORRADE_COMPARE(indices.data().size(), 3); + CORRADE_COMPARE(indices.data().stride(), 1); constexpr MeshIndexData cindices{IndexBytes}; constexpr MeshIndexType type = cindices.type(); - constexpr Containers::ArrayView data = cindices.data(); + constexpr Containers::StridedArrayView1D data = cindices.data(); CORRADE_COMPARE(type, MeshIndexType::UnsignedByte); CORRADE_COMPARE(data.data(), IndexBytes); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.stride(), 1); } { const UnsignedShort indexData[]{2575, 13224, 3}; MeshIndexData indices{indexData}; CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedShort); CORRADE_COMPARE(indices.data().data(), indexData); + CORRADE_COMPARE(indices.data().size(), 3); + CORRADE_COMPARE(indices.data().stride(), 2); constexpr MeshIndexData cindices{IndexShorts}; constexpr MeshIndexType type = cindices.type(); - constexpr Containers::ArrayView data = cindices.data(); + constexpr Containers::StridedArrayView1D data = cindices.data(); CORRADE_COMPARE(type, MeshIndexType::UnsignedShort); CORRADE_COMPARE(data.data(), IndexShorts); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.stride(), 2); } { const UnsignedInt indexData[]{2110122, 132257, 3}; MeshIndexData indices{indexData}; CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedInt); CORRADE_COMPARE(indices.data().data(), indexData); + CORRADE_COMPARE(indices.data().size(), 3); + CORRADE_COMPARE(indices.data().stride(), 4); constexpr MeshIndexData cindices{IndexInts}; constexpr MeshIndexType type = cindices.type(); - constexpr Containers::ArrayView data = cindices.data(); + constexpr Containers::StridedArrayView1D data = cindices.data(); CORRADE_COMPARE(type, MeshIndexType::UnsignedInt); CORRADE_COMPARE(data.data(), IndexInts); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.stride(), 4); } } -void MeshDataTest::constructIndexTypeErased() { +constexpr struct IndexStruct { + UnsignedByte byteIndex; + UnsignedShort shortIndex; + UnsignedInt intIndex; +} IndexStructData[3]{ + {25, 2575, 2110122}, + {132, 13224, 132257}, + {3, 3, 3} +}; + +void MeshDataTest::constructIndexStrided() { + const IndexStruct data[3]{ + {25, 2575, 2110122}, + {132, 13224, 132257}, + {3, 3, 3} + }; + Containers::StridedArrayView1D view = data; + + { + MeshIndexData indices{view.slice(&IndexStruct::byteIndex)}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedByte); + CORRADE_COMPARE(indices.data().data(), &data[0].byteIndex); + CORRADE_COMPARE(indices.data().size(), 3); + CORRADE_COMPARE(indices.data().stride(), sizeof(IndexStruct)); + + constexpr MeshIndexData cindices{Containers::stridedArrayView(IndexStructData, &IndexStructData[0].byteIndex, 3, sizeof(IndexStruct))}; + constexpr MeshIndexType type = cindices.type(); + constexpr Containers::StridedArrayView1D data = cindices.data(); + CORRADE_COMPARE(type, MeshIndexType::UnsignedByte); + CORRADE_COMPARE(data.data(), &IndexStructData[0].byteIndex); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.stride(), sizeof(IndexStruct)); + } { + MeshIndexData indices{view.slice(&IndexStruct::shortIndex)}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(indices.data().data(), &data[0].shortIndex); + CORRADE_COMPARE(indices.data().size(), 3); + CORRADE_COMPARE(indices.data().stride(), sizeof(IndexStruct)); + + constexpr MeshIndexData cindices{Containers::stridedArrayView(IndexStructData, &IndexStructData[0].shortIndex, 3, sizeof(IndexStruct))}; + constexpr MeshIndexType type = cindices.type(); + constexpr Containers::StridedArrayView1D data = cindices.data(); + CORRADE_COMPARE(type, MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.data(), &IndexStructData[0].shortIndex); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.stride(), sizeof(IndexStruct)); + } { + MeshIndexData indices{view.slice(&IndexStruct::intIndex)}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(indices.data().data(), &data[0].intIndex); + CORRADE_COMPARE(indices.data().size(), 3); + CORRADE_COMPARE(indices.data().stride(), sizeof(IndexStruct)); + + constexpr MeshIndexData cindices{Containers::stridedArrayView(IndexStructData, &IndexStructData[0].intIndex, 3, sizeof(IndexStruct))}; + constexpr MeshIndexType type = cindices.type(); + constexpr Containers::StridedArrayView1D data = cindices.data(); + CORRADE_COMPARE(type, MeshIndexType::UnsignedInt); + CORRADE_COMPARE(data.data(), &IndexStructData[0].intIndex); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.stride(), sizeof(IndexStruct)); + } +} + +void MeshDataTest::constructIndexStridedWrongStride() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + char toomuch[2*(32768 + 1)]; + + /* These should be fine */ + MeshIndexData{Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32767}}; + MeshIndexData{Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}.flipped<0>()}; + + std::ostringstream out; + Error redirectError{&out}; + MeshIndexData{Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}}; + MeshIndexData{Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32769}.flipped<0>()}; + CORRADE_COMPARE(out.str(), + "Trade::MeshIndexData: expected stride to fit into 16 bits but got 32768\n" + "Trade::MeshIndexData: expected stride to fit into 16 bits but got -32769\n"); +} + +void MeshDataTest::constructIndexTypeErasedContiguous() { const char indexData[3*2]{}; MeshIndexData indices{MeshIndexType::UnsignedShort, indexData}; CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedShort); - CORRADE_VERIFY(indices.data().data() == indexData); + CORRADE_COMPARE(indices.data().data(), indexData); + CORRADE_COMPARE(indices.data().size(), 3); + CORRADE_COMPARE(indices.data().stride(), 2); } -void MeshDataTest::constructIndexTypeErasedWrongSize() { +void MeshDataTest::constructIndexTypeErasedContiguousWrongSize() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -507,22 +616,71 @@ void MeshDataTest::constructIndexTypeErasedWrongSize() { CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: view size 6 does not correspond to MeshIndexType::UnsignedInt\n"); } +constexpr const char IndexData[3*4]{}; + +void MeshDataTest::constructIndexTypeErasedStrided() { + const char indexData[3*4]{}; + MeshIndexData indices{MeshIndexType::UnsignedShort, {indexData, 3, 4}}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedShort); + CORRADE_VERIFY(indices.data().data() == indexData); + CORRADE_COMPARE(indices.data().size(), 3); + CORRADE_COMPARE(indices.data().stride(), 4); + + constexpr MeshIndexData cindices{MeshIndexType::UnsignedShort, {IndexData, 3, 4}}; + constexpr MeshIndexType type = cindices.type(); + constexpr Containers::StridedArrayView1D data = cindices.data(); + CORRADE_COMPARE(type, MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.data(), IndexData); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.stride(), 4); +} + +void MeshDataTest::constructIndexTypeErasedStridedWrongStride() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + char toomuch[2*(32768 + 1)]{}; + + /* These should be fine */ + MeshIndexData{MeshIndexType::UnsignedByte, Containers::StridedArrayView1D{toomuch, 2, 32767}}; + MeshIndexData{MeshIndexType::UnsignedByte, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}.flipped<0>()}; + + std::ostringstream out; + Error redirectError{&out}; + MeshIndexData{MeshIndexType::UnsignedByte, Containers::StridedArrayView1D{toomuch, 2, 32768}}; + MeshIndexData{MeshIndexType::UnsignedByte, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32769}.flipped<0>()}; + CORRADE_COMPARE(out.str(), + "Trade::MeshIndexData: expected stride to fit into 16 bits but got 32768\n" + "Trade::MeshIndexData: expected stride to fit into 16 bits but got -32769\n"); +} + void MeshDataTest::constructIndex2D() { + const IndexStruct data[3]{ + {25, 2575, 2110122}, + {132, 13224, 132257}, + {3, 3, 3} + }; + Containers::StridedArrayView1D view = data; + { - const UnsignedByte indexData[]{25, 132, 3}; - MeshIndexData indices{Containers::arrayCast<2, const char>(Containers::stridedArrayView(indexData))}; + MeshIndexData indices{Containers::arrayCast<2, const char>(view.slice(&IndexStruct::byteIndex))}; CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedByte); - CORRADE_COMPARE(indices.data().data(), indexData); + CORRADE_COMPARE(indices.data().data(), &data[0].byteIndex); + CORRADE_COMPARE(indices.data().size(), 3); + CORRADE_COMPARE(indices.data().stride(), sizeof(IndexStruct)); } { - const UnsignedShort indexData[]{2575, 13224, 3}; - MeshIndexData indices{Containers::arrayCast<2, const char>(Containers::stridedArrayView(indexData))}; + MeshIndexData indices{Containers::arrayCast<2, const char>(view.slice(&IndexStruct::shortIndex))}; CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedShort); - CORRADE_COMPARE(indices.data().data(), indexData); + CORRADE_COMPARE(indices.data().data(), &data[0].shortIndex); + CORRADE_COMPARE(indices.data().size(), 3); + CORRADE_COMPARE(indices.data().stride(), sizeof(IndexStruct)); } { - const UnsignedInt indexData[]{2110122, 132257, 3}; - MeshIndexData indices{Containers::arrayCast<2, const char>(Containers::stridedArrayView(indexData))}; + MeshIndexData indices{Containers::arrayCast<2, const char>(view.slice(&IndexStruct::intIndex))}; CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedInt); - CORRADE_COMPARE(indices.data().data(), indexData); + CORRADE_COMPARE(indices.data().data(), &data[0].intIndex); + CORRADE_COMPARE(indices.data().size(), 3); + CORRADE_COMPARE(indices.data().stride(), sizeof(IndexStruct)); } } @@ -545,6 +703,26 @@ void MeshDataTest::constructIndex2DWrongSize() { CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: expected index type size 1, 2 or 4 but got 3\n"); } +void MeshDataTest::constructIndex2DWrongStride() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + char toomuch[2*(32768 + 1)]; + + /* These should be fine */ + MeshIndexData{Containers::StridedArrayView2D{toomuch, {2, 1}, {32767, 1}}}; + MeshIndexData{Containers::StridedArrayView2D{toomuch, {2, 1}, {32768, 1}}.flipped<0>()}; + + std::ostringstream out; + Error redirectError{&out}; + MeshIndexData{Containers::StridedArrayView2D{toomuch, {2, 1}, {32768, 1}}}; + MeshIndexData{Containers::StridedArrayView2D{toomuch, {2, 1}, {32769, 1}}.flipped<0>()}; + CORRADE_COMPARE(out.str(), + "Trade::MeshIndexData: expected stride to fit into 16 bits but got 32768\n" + "Trade::MeshIndexData: expected stride to fit into 16 bits but got -32769\n"); +} + void MeshDataTest::constructIndex2DNonContiguous() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -552,10 +730,13 @@ void MeshDataTest::constructIndex2DNonContiguous() { const char data[3*4]{}; + /* This should be fine */ + MeshIndexData{Containers::StridedArrayView2D{data, {3, 2}, {4, 1}}}; + std::ostringstream out; Error redirectError{&out}; MeshIndexData{Containers::StridedArrayView2D{data, {3, 2}, {4, 2}}}; - CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: view is not contiguous\n"); + CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: second view dimension is not contiguous\n"); } void MeshDataTest::constructIndexNullptr() { @@ -1649,6 +1830,102 @@ void MeshDataTest::constructIndexlessAttributelessZeroVertices() { CORRADE_COMPARE(data.attributeCount(), 0); } +void MeshDataTest::constructSpecialIndexStrides() { + /* Every second index */ + { + Containers::Array indexData{sizeof(UnsignedShort)*8}; + Containers::StridedArrayView1D indices = Containers::arrayCast(indexData); + Utility::copy({1, 0, 2, 0, 3, 0, 4, 0}, indices); + MeshData mesh{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indices.every(2)}, 1}; + + CORRADE_COMPARE(mesh.indexStride(), 4); + + /* Type-erased access with a cast later */ + CORRADE_COMPARE_AS((Containers::arrayCast<1, const UnsignedShort>(mesh.indices())), + Containers::arrayView({1, 2, 3, 4}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS((Containers::arrayCast<1, UnsignedShort>(mesh.mutableIndices())), + Containers::stridedArrayView({1, 2, 3, 4}), + TestSuite::Compare::Container); + + /* Typed access */ + CORRADE_COMPARE_AS(mesh.indices(), + Containers::arrayView({1, 2, 3, 4}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.mutableIndices(), + Containers::stridedArrayView({1, 2, 3, 4}), + TestSuite::Compare::Container); + + /* Convenience accessor. This uses the indicesInto() internally so it + verifies both. */ + CORRADE_COMPARE_AS(mesh.indicesAsArray(), + Containers::arrayView({1, 2, 3, 4}), + TestSuite::Compare::Container); + + /* Zero stride. Not sure how useful like this. */ + } { + Containers::Array indexData{sizeof(UnsignedShort)}; + Containers::StridedArrayView1D indices = Containers::arrayCast(indexData); + indices[0] = 15; + MeshData mesh{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indices.broadcasted<0>(4)}, 1}; + + CORRADE_COMPARE(mesh.indexStride(), 0); + + /* Type-erased access with a cast later */ + CORRADE_COMPARE_AS((Containers::arrayCast<1, const UnsignedShort>(mesh.indices())), + Containers::arrayView({15, 15, 15, 15}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS((Containers::arrayCast<1, UnsignedShort>(mesh.mutableIndices())), + Containers::stridedArrayView({15, 15, 15, 15}), + TestSuite::Compare::Container); + + /* Typed access */ + CORRADE_COMPARE_AS(mesh.indices(), + Containers::arrayView({15, 15, 15, 15}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.mutableIndices(), + Containers::stridedArrayView({15, 15, 15, 15}), + TestSuite::Compare::Container); + + /* The convenience accessor should work as well, as it consumes output + of the type-erased one */ + CORRADE_COMPARE_AS(mesh.indicesAsArray(), + Containers::arrayView({15, 15, 15, 15}), + TestSuite::Compare::Container); + + /* Negative stride */ + } { + Containers::Array indexData{sizeof(UnsignedShort)*4}; + Containers::StridedArrayView1D indices = Containers::arrayCast(indexData); + Utility::copy({1, 2, 3, 4}, indices); + MeshData mesh{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indices.flipped<0>()}, 1}; + + CORRADE_COMPARE(mesh.indexStride(), -2); + + /* Type-erased access with a cast later */ + CORRADE_COMPARE_AS((Containers::arrayCast<1, const UnsignedShort>(mesh.indices())), + Containers::arrayView({4, 3, 2, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS((Containers::arrayCast<1, UnsignedShort>(mesh.mutableIndices())), + Containers::stridedArrayView({4, 3, 2, 1}), + TestSuite::Compare::Container); + + /* Typed access */ + CORRADE_COMPARE_AS(mesh.indices(), + Containers::arrayView({4, 3, 2, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mesh.mutableIndices(), + Containers::stridedArrayView({4, 3, 2, 1}), + TestSuite::Compare::Container); + + /* The convenience accessor should work as well, as it consumes output + of the type-erased one */ + CORRADE_COMPARE_AS(mesh.indicesAsArray(), + Containers::arrayView({4, 3, 2, 1}), + TestSuite::Compare::Container); + } +} + void MeshDataTest::constructSpecialAttributeStrides() { Containers::Array vertexData{sizeof(UnsignedShort)*5}; Containers::StridedArrayView1D vertices = Containers::arrayCast(vertexData); @@ -1769,9 +2046,11 @@ void MeshDataTest::constructIndicesNotContained() { CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif - Containers::Array indexData{reinterpret_cast(0xbadda9), 6, [](char*, std::size_t){}}; + const Containers::Array indexData{reinterpret_cast(0xbadda9), 6, [](char*, std::size_t){}}; + Containers::Array sameIndexDataButMovable{reinterpret_cast(0xbadda9), 6, [](char*, std::size_t){}}; Containers::ArrayView indexDataSlightlyOut{reinterpret_cast(0xbaddaa), 3}; Containers::ArrayView indexDataOut{reinterpret_cast(0xdead), 3}; + Containers::StridedArrayView1D indexDataStridedOut{{reinterpret_cast(0xbadda9), 6}, 3, 4}; std::ostringstream out; Error redirectError{&out}; @@ -1779,20 +2058,29 @@ void MeshDataTest::constructIndicesNotContained() { MeshData{MeshPrimitive::Triangles, {}, indexData, MeshIndexData{indexDataSlightlyOut}, 1}; /* Second a view that's in a completely different location */ MeshData{MeshPrimitive::Triangles, {}, indexData, MeshIndexData{indexDataOut}, 1}; + /* A strided index array which would pass if stride wasn't taken into + account */ + MeshData{MeshPrimitive::Triangles, {}, indexData, MeshIndexData{indexDataStridedOut}, 1}; /* Empty view which however begins outside */ MeshData{MeshPrimitive::Triangles, {}, indexData, MeshIndexData{indexDataSlightlyOut.slice(3, 3)}, 1}; /* Verify the owning constructor does the checks as well */ - MeshData{MeshPrimitive::Triangles, std::move(indexData), MeshIndexData{indexDataOut}, 1}; + MeshData{MeshPrimitive::Triangles, std::move(sameIndexDataButMovable), MeshIndexData{indexDataOut}, 1}; /* If we have no data at all, it doesn't try to dereference them but still checks properly */ MeshData{MeshPrimitive::Triangles, nullptr, MeshIndexData{indexDataOut}, 1}; + /* And the final boss, negative strides. Only caught if the element size + gets properly added to the larger offset, not just the "end". */ + MeshData{MeshPrimitive::Triangles, {}, indexData, MeshIndexData{stridedArrayView(indexDataSlightlyOut).flipped<0>()}, 1}; CORRADE_COMPARE(out.str(), "Trade::MeshData: indices [0xbaddaa:0xbaddb0] are not contained in passed indexData array [0xbadda9:0xbaddaf]\n" "Trade::MeshData: indices [0xdead:0xdeb3] are not contained in passed indexData array [0xbadda9:0xbaddaf]\n" + "Trade::MeshData: indices [0xbadda9:0xbaddb3] are not contained in passed indexData array [0xbadda9:0xbaddaf]\n" /* This scenario is invalid, just have it here for the record */ "Trade::MeshData: indexData passed for a non-indexed mesh\n" "Trade::MeshData: indices [0xdead:0xdeb3] are not contained in passed indexData array [0xbadda9:0xbaddaf]\n" - "Trade::MeshData: indices [0xdead:0xdeb3] are not contained in passed indexData array [0x0:0x0]\n"); + "Trade::MeshData: indices [0xdead:0xdeb3] are not contained in passed indexData array [0x0:0x0]\n" + + "Trade::MeshData: indices [0xbaddaa:0xbaddb0] are not contained in passed indexData array [0xbadda9:0xbaddaf]\n"); } void MeshDataTest::constructAttributeNotContained() { @@ -2936,6 +3224,7 @@ void MeshDataTest::indicesNotIndexed() { data.indexCount(); data.indexType(); data.indexOffset(); + data.indexStride(); data.indices(); data.mutableIndices(); data.indicesAsArray(); @@ -2945,6 +3234,7 @@ void MeshDataTest::indicesNotIndexed() { "Trade::MeshData::indexCount(): the mesh is not indexed\n" "Trade::MeshData::indexType(): the mesh is not indexed\n" "Trade::MeshData::indexOffset(): the mesh is not indexed\n" + "Trade::MeshData::indexStride(): the mesh is not indexed\n" "Trade::MeshData::indices(): the mesh is not indexed\n" "Trade::MeshData::mutableIndices(): the mesh is not indexed\n" "Trade::MeshData::indicesAsArray(): the mesh is not indexed\n"