diff --git a/src/Magnum/MeshTools/Duplicate.cpp b/src/Magnum/MeshTools/Duplicate.cpp index 4d6b2cb45..3d6b49ed6 100644 --- a/src/Magnum/MeshTools/Duplicate.cpp +++ b/src/Magnum/MeshTools/Duplicate.cpp @@ -93,6 +93,12 @@ Trade::MeshData duplicate(const Trade::MeshData& data, const Containers::ArrayVi /* Padding, ignore */ if(extra[i].format() == VertexFormat{}) continue; + /* Asserting here even though data() has another assert since that one + would be too confusing in this context */ + CORRADE_ASSERT(!extra[i].isOffsetOnly(), + "MeshTools::duplicate(): extra attribute" << i << "is offset-only, which is not supported", + (Trade::MeshData{MeshPrimitive::Triangles, 0})); + /* Copy the attribute in, if it is non-empty, otherwise keep the memory uninitialized */ if(extra[i].data()) { diff --git a/src/Magnum/MeshTools/Duplicate.h b/src/Magnum/MeshTools/Duplicate.h index a1bf32b8a..e5458dfab 100644 --- a/src/Magnum/MeshTools/Duplicate.h +++ b/src/Magnum/MeshTools/Duplicate.h @@ -137,7 +137,8 @@ any, are duplicated and interleaved together with existing attributes (or, in case the attribute view is empty, only the corresponding space for given attribute type is reserved, with memory left uninitialized). The data layouting is done by @ref interleavedLayout(), see its documentation for detailed -behavior description. +behavior description. Note that offset-only @ref Trade::MeshAttributeData +instances are not supported in the @p extra array. Expects that @p data is indexed and each attribute in @p extra has either the same amount of elements as @p data vertex count (*not* index count) or has diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp index 525c5cabe..8f89b6095 100644 --- a/src/Magnum/MeshTools/Interleave.cpp +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -185,6 +185,12 @@ Trade::MeshData interleave(Trade::MeshData&& data, const Containers::ArrayView indexData{4}; auto indexView = Containers::arrayCast(indexData); diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 7e597334d..a28ff3a86 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -99,10 +99,17 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde "Trade::MeshData: attribute" << i << "doesn't specify anything", ); CORRADE_ASSERT(attribute._vertexCount == _vertexCount, "Trade::MeshData: attribute" << i << "has" << attribute._vertexCount << "vertices but" << _vertexCount << "expected", ); - const void* const begin = static_cast(attribute._data); - const void* const end = static_cast(attribute._data) + (_vertexCount - 1)*attribute._stride + vertexFormatSize(attribute._format); - CORRADE_ASSERT(!_vertexCount || (begin >= _vertexData.begin() && end <= _vertexData.end()), - "Trade::MeshData: attribute" << i << "[" << Debug::nospace << begin << Debug::nospace << ":" << Debug::nospace << end << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); + const UnsignedInt typeSize = vertexFormatSize(attribute._format); + if(attribute._isOffsetOnly) { + const std::size_t size = attribute._data.offset + (_vertexCount - 1)*attribute._stride + typeSize; + CORRADE_ASSERT(!_vertexCount || size <= _vertexData.size(), + "Trade::MeshData: offset attribute" << i << "spans" << size << "bytes but passed vertexData array has only" << _vertexData.size(), ); + } else { + const void* const begin = static_cast(attribute._data.pointer); + const void* const end = static_cast(attribute._data.pointer) + (_vertexCount - 1)*attribute._stride + typeSize; + CORRADE_ASSERT(!_vertexCount || (begin >= _vertexData.begin() && end <= _vertexData.end()), + "Trade::MeshData: attribute" << i << "[" << Debug::nospace << begin << Debug::nospace << ":" << Debug::nospace << end << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); + } } #endif } @@ -218,6 +225,14 @@ Containers::StridedArrayView2D MeshData::mutableIndices() { out.size(), out.stride()}; } +MeshAttributeData MeshData::attributeData(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeData(): index" << id << "out of range for" << _attributes.size() << "attributes", MeshAttributeData{}); + const MeshAttributeData& attribute = _attributes[id]; + return attribute._isOffsetOnly ? MeshAttributeData{attribute._name, + attribute._format, attributeDataViewInternal(attribute)} : attribute; +} + MeshAttribute MeshData::attributeName(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeName(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); @@ -233,7 +248,8 @@ VertexFormat MeshData::attributeFormat(UnsignedInt id) const { std::size_t MeshData::attributeOffset(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); - return static_cast(_attributes[id]._data) - _vertexData.data(); + return _attributes[id]._isOffsetOnly ? _attributes[id]._data.offset : + static_cast(_attributes[id]._data.pointer) - _vertexData.data(); } UnsignedInt MeshData::attributeStride(UnsignedInt id) const { @@ -366,7 +382,8 @@ Containers::StridedArrayView1D MeshData::attributeDataViewInternal(c return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ - {attribute._data, ~std::size_t{}}, + {attribute._isOffsetOnly ? _vertexData.data() + attribute._data.offset : + attribute._data.pointer, ~std::size_t{}}, /* Not using attribute._vertexCount because that gets stale after releaseVertexData() gets called, and then we would need to slice the result inside attribute() and elsewhere */ diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 95e79ddc0..fe9bdf642 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -244,7 +244,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * initialization of the attribute array for @ref MeshData, expected to * be replaced with concrete values later. */ - constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{} {} + constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{}, _isOffsetOnly{false} {} /** * @brief Type-erased constructor @@ -305,6 +305,24 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /** @overload */ template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {} + /** + * @brief Construct an offset-only attribute + * @param name Attribute name + * @param format Attribute format + * @param offset Attribute data offset + * @param vertexCount Attribute vertex count + * @param stride Attribute stride + * + * Instances created this way refer to an offset in unspecified + * external vertex data instead of containing the data view directly. + * Useful when the location of the vertex data array is not known at + * attribute construction time. Note that instances created this way + * can't be used in most @ref MeshTools algorithms. + * @see @ref isOffsetOnly(), + * @ref data(Containers::ArrayView) const + */ + explicit constexpr MeshAttributeData(MeshAttribute name, VertexFormat format, std::size_t offset, UnsignedInt vertexCount, std::ptrdiff_t stride) noexcept; + /** * @brief Construct a pad value * @@ -316,7 +334,17 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { constexpr explicit MeshAttributeData(Int padding): _data{nullptr}, _vertexCount{0}, _format{}, _stride{ (CORRADE_CONSTEXPR_ASSERT(padding >= -32768 && padding <= 32767, "Trade::MeshAttributeData: at most 32k padding supported, got" << padding), Short(padding)) - }, _name{} {} + }, _name{}, _isOffsetOnly{false} {} + + /** + * @brief If the attribute is offset-only + * + * Returns @cpp true @ce if the attribute doesn't contain the data view + * directly, but instead refers to unspecified external vertex data. + * @see @ref data(Containers::ArrayView) const, + * @ref MeshAttributeData(MeshAttribute, VertexFormat, std::size_t, UnsignedInt, std::ptrdiff_t) + */ + constexpr bool isOffsetOnly() const { return _isOffsetOnly; } /** @brief Attribute name */ constexpr MeshAttribute name() const { return _name; } @@ -324,20 +352,48 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /** @brief Attribute format */ constexpr VertexFormat format() const { return _format; } - /** @brief Type-erased attribute data */ + /** + * @brief Type-erased attribute data + * + * Expects that the attribute is not offset-only, in that case use the + * @ref data(Containers::ArrayView) const overload + * instead. + * @see @ref isOffsetOnly() + */ constexpr Containers::StridedArrayView1D data() const { return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ - {_data, ~std::size_t{}}, - _vertexCount, _stride}; + {_data.pointer, ~std::size_t{}}, _vertexCount, + (CORRADE_CONSTEXPR_ASSERT(!_isOffsetOnly, "Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array"), _stride)}; + } + + /** + * @brief Type-erased attribute data for an offset-only attribute + * + * If the attribute is not offset-only, the @ref vertexData parameter + * is ignored. + * @see @ref isOffsetOnly(), @ref data() const + */ + Containers::StridedArrayView1D data(Containers::ArrayView vertexData) const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + vertexData, _isOffsetOnly ? reinterpret_cast(vertexData.data()) + _data.offset : _data.pointer, _vertexCount, _stride}; } private: constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept; friend MeshData; - const void* _data; + union Data { + /* FFS C++ why this doesn't JUST WORK goddamit?! */ + constexpr Data(const void* pointer = nullptr): pointer{pointer} {} + constexpr Data(std::size_t offset): offset{offset} {} + + const void* pointer; + std::size_t offset; + } _data; /* Vertex count in MeshData is currently 32-bit, so this doesn't need to be 64-bit either */ UnsignedInt _vertexCount; @@ -346,8 +402,9 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { current largest reported stride is 4k so 32k should be enough */ Short _stride; MeshAttribute _name; - /* 4 bytes free for more stuff on 64b (20, aligned to 24); nothing on - 32b */ + bool _isOffsetOnly; + /* 3 bytes free for more stuff on 64b (21, aligned to 24) and on 32b + (17 used, aligned to 20) */ }; /** @relatesalso MeshAttributeData @@ -679,14 +736,21 @@ class MAGNUM_TRADE_EXPORT MeshData { /** * @brief Raw attribute metadata * - * Useful mainly for passing particular attributes to @ref MeshTools - * algorithms, everything is otherwise exposed directly through various - * `attribute*()` getters. Returns @cpp nullptr @ce if the mesh has no - * attributes. - * @see @ref attributeCount(), @ref attributeName(), - * @ref attributeFormat(), @ref attribute() + * Returns the raw data that are used as a base for all `attribute*()` + * accessors, or @cpp nullptr @ce if the mesh has no attributes. In + * most cases you don't want to access those directly, but rather use + * the @ref attribute(), @ref attributeName(), @ref attributeFormat(), + * @ref attributeOffset(), @ref attributeStride() etc. accessors. + * Compared to those and to @ref attributeData(UnsignedInt) const, the + * @ref MeshAttributeData instances returned by this function may have + * different data pointers, and some of them might be offset-only --- + * use this function only if you *really* know what are you doing. + * @see @ref MeshAttributeData::isOffsetOnly() */ - Containers::ArrayView attributeData() const { return _attributes; } + Containers::ArrayView attributeData() const & { return _attributes; } + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView attributeData() && = delete; /** * @brief Raw vertex data @@ -812,6 +876,25 @@ class MAGNUM_TRADE_EXPORT MeshData { */ UnsignedInt attributeCount() const { return _attributes.size(); } + /** + * @brief Raw attribute data + * + * Returns the raw data that are used as a base for all `attribute*()` + * accessors. In most cases you don't want to access those directly, + * but rather use the @ref attribute(), @ref attributeName(), + * @ref attributeFormat(), @ref attributeOffset(), + * @ref attributeStride() etc. accessors. + * + * Useful mainly for passing particular attributes unchanged directly + * to @ref MeshTools algorithms --- unlike with @ref attributeData() + * and @ref releaseAttributeData(), returned instances are guaranteed + * to always have an absolute data pointer (i.e., + * @ref MeshAttributeData::isOffsetOnly() always returning + * @cpp false @ce). The @p id is expected to be smaller than + * @ref attributeCount() const. + */ + MeshAttributeData attributeData(UnsignedInt id) const; + /** * @brief Attribute name * @@ -1159,10 +1242,13 @@ class MAGNUM_TRADE_EXPORT MeshData { * like if it has no attributes (but it can still have a non-zero * vertex count). Note that the returned array has a custom no-op * deleter when the data are not owned by the mesh, and while the - * returned array type is mutable, the actual memory might be not --- - * use this function only if you are sure about the origin of the - * array. - * @see @ref attributeData() + * returned array type is mutable, the actual memory might be not. + * Additionally, the returned @ref MeshAttributeData instances + * may have different data pointers and sizes than what's returned by + * the @ref attribute() and @ref attributeData(UnsignedInt) const + * accessors as some of them might be offset-only --- use this function + * only if you *really* know what are you doing. + * @see @ref attributeData(), @ref MeshAttributeData::isOffsetOnly() */ Containers::Array releaseAttributeData(); @@ -1316,6 +1402,59 @@ namespace Implementation { #undef _c #endif /* LCOV_EXCL_STOP */ + + constexpr bool isVertexFormatCompatibleWithAttribute(MeshAttribute name, VertexFormat format) { + /* Double types intentionally not supported for any builtin attributes + right now -- only for custom types */ + return + (name == MeshAttribute::Position && + (format == VertexFormat::Vector2 || + format == VertexFormat::Vector2h || + format == VertexFormat::Vector2ub || + format == VertexFormat::Vector2ubNormalized || + format == VertexFormat::Vector2b || + format == VertexFormat::Vector2bNormalized || + format == VertexFormat::Vector2us || + format == VertexFormat::Vector2usNormalized || + format == VertexFormat::Vector2s || + format == VertexFormat::Vector2sNormalized || + format == VertexFormat::Vector3 || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3ub || + format == VertexFormat::Vector3ubNormalized || + format == VertexFormat::Vector3b || + format == VertexFormat::Vector3bNormalized || + format == VertexFormat::Vector3us || + format == VertexFormat::Vector3usNormalized || + format == VertexFormat::Vector3s || + format == VertexFormat::Vector3sNormalized)) || + (name == MeshAttribute::Normal && + (format == VertexFormat::Vector3 || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3bNormalized || + format == VertexFormat::Vector3sNormalized)) || + (name == MeshAttribute::Color && + (format == VertexFormat::Vector3 || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3ubNormalized || + format == VertexFormat::Vector3usNormalized || + format == VertexFormat::Vector4 || + format == VertexFormat::Vector4h || + format == VertexFormat::Vector4ubNormalized || + format == VertexFormat::Vector4usNormalized)) || + (name == MeshAttribute::TextureCoordinates && + (format == VertexFormat::Vector2 || + format == VertexFormat::Vector2h || + format == VertexFormat::Vector2ub || + format == VertexFormat::Vector2ubNormalized || + format == VertexFormat::Vector2b || + format == VertexFormat::Vector2bNormalized || + format == VertexFormat::Vector2us || + format == VertexFormat::Vector2usNormalized || + format == VertexFormat::Vector2s || + format == VertexFormat::Vector2sNormalized)) || + isMeshAttributeCustom(name); /* can be any format */ + } } #endif @@ -1325,58 +1464,19 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V _stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(data.stride()) & 0xffff8000), "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got" << data.stride()), Short(data.stride())) - }, _name{(CORRADE_CONSTEXPR_ASSERT( - /* Double types intentionally not supported for any builtin attributes - right now -- only for custom types */ - (name == MeshAttribute::Position && - (format == VertexFormat::Vector2 || - format == VertexFormat::Vector2h || - format == VertexFormat::Vector2ub || - format == VertexFormat::Vector2ubNormalized || - format == VertexFormat::Vector2b || - format == VertexFormat::Vector2bNormalized || - format == VertexFormat::Vector2us || - format == VertexFormat::Vector2usNormalized || - format == VertexFormat::Vector2s || - format == VertexFormat::Vector2sNormalized || - format == VertexFormat::Vector3 || - format == VertexFormat::Vector3h || - format == VertexFormat::Vector3ub || - format == VertexFormat::Vector3ubNormalized || - format == VertexFormat::Vector3b || - format == VertexFormat::Vector3bNormalized || - format == VertexFormat::Vector3us || - format == VertexFormat::Vector3usNormalized || - format == VertexFormat::Vector3s || - format == VertexFormat::Vector3sNormalized)) || - (name == MeshAttribute::Normal && - (format == VertexFormat::Vector3 || - format == VertexFormat::Vector3h || - format == VertexFormat::Vector3bNormalized || - format == VertexFormat::Vector3sNormalized)) || - (name == MeshAttribute::Color && - (format == VertexFormat::Vector3 || - format == VertexFormat::Vector3h || - format == VertexFormat::Vector3ubNormalized || - format == VertexFormat::Vector3usNormalized || - format == VertexFormat::Vector4 || - format == VertexFormat::Vector4h || - format == VertexFormat::Vector4ubNormalized || - format == VertexFormat::Vector4usNormalized)) || - (name == MeshAttribute::TextureCoordinates && - (format == VertexFormat::Vector2 || - format == VertexFormat::Vector2h || - format == VertexFormat::Vector2ub || - format == VertexFormat::Vector2ubNormalized || - format == VertexFormat::Vector2b || - format == VertexFormat::Vector2bNormalized || - format == VertexFormat::Vector2us || - format == VertexFormat::Vector2usNormalized || - format == VertexFormat::Vector2s || - format == VertexFormat::Vector2sNormalized)) || - isMeshAttributeCustom(name) /* can be any format */, + }, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format), + "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name) + }, _isOffsetOnly{false} {} + +constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const std::size_t offset, const UnsignedInt vertexCount, const std::ptrdiff_t stride) noexcept: + _data{offset}, _vertexCount{vertexCount}, _format{format}, + /** @todo support zero / negative stride? would be hard to transfer to GL */ + _stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(stride) & 0xffff8000), + "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got" << stride), + Short(stride)) + }, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format), "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name) - } {} + }, _isOffsetOnly{true} {} template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), data, nullptr} {} diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index b865b6b4e..7da715e43 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -52,7 +52,6 @@ struct MeshDataTest: TestSuite::Tester { void constructAttribute(); void constructAttributeCustom(); - void constructAttributeWrongFormat(); void constructAttribute2D(); void constructAttribute2DWrongSize(); void constructAttribute2DNonContiguous(); @@ -60,7 +59,10 @@ struct MeshDataTest: TestSuite::Tester { void constructAttributeNullptr(); void constructAttributePadding(); void constructAttributeNonOwningArray(); + void constructAttributeOffsetOnly(); + void constructAttributeWrongFormat(); void constructAttributeWrongStride(); + void constructAttributeWrongDataAccess(); void construct(); void constructZeroIndices(); @@ -168,7 +170,6 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttribute, &MeshDataTest::constructAttributeCustom, - &MeshDataTest::constructAttributeWrongFormat, &MeshDataTest::constructAttribute2D, &MeshDataTest::constructAttribute2DWrongSize, &MeshDataTest::constructAttribute2DNonContiguous, @@ -176,7 +177,10 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttributeNullptr, &MeshDataTest::constructAttributePadding, &MeshDataTest::constructAttributeNonOwningArray, + &MeshDataTest::constructAttributeOffsetOnly, + &MeshDataTest::constructAttributeWrongFormat, &MeshDataTest::constructAttributeWrongStride, + &MeshDataTest::constructAttributeWrongDataAccess, &MeshDataTest::construct, &MeshDataTest::constructZeroIndices, @@ -450,14 +454,19 @@ constexpr Vector2 Positions[] { void MeshDataTest::constructAttribute() { const Vector2 positionData[3]; MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(positionData)}; + CORRADE_VERIFY(!positions.isOffsetOnly()); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_VERIFY(positions.data().data() == positionData); + /* This is allowed too for simplicity, it just ignores the parameter */ + CORRADE_VERIFY(positions.data(positionData).data() == positionData); constexpr MeshAttributeData cpositions{MeshAttribute::Position, Containers::arrayView(Positions)}; + constexpr bool isOffsetOnly = cpositions.isOffsetOnly(); constexpr MeshAttribute name = cpositions.name(); constexpr VertexFormat format = cpositions.format(); constexpr Containers::StridedArrayView1D data = cpositions.data(); + CORRADE_VERIFY(!isOffsetOnly); CORRADE_COMPARE(name, MeshAttribute::Position); CORRADE_COMPARE(format, VertexFormat::Vector2); CORRADE_COMPARE(data.data(), Positions); @@ -471,15 +480,6 @@ void MeshDataTest::constructAttributeCustom() { CORRADE_VERIFY(ids.data().data() == idData); } -void MeshDataTest::constructAttributeWrongFormat() { - Vector2 positionData[3]; - - std::ostringstream out; - Error redirectError{&out}; - MeshAttributeData{MeshAttribute::Color, Containers::arrayView(positionData)}; - CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n"); -} - void MeshDataTest::constructAttribute2D() { char positionData[4*sizeof(Vector2)]{}; auto positionView = Containers::StridedArrayView2D{positionData, @@ -544,12 +544,53 @@ void MeshDataTest::constructAttributeNonOwningArray() { CORRADE_COMPARE(static_cast(array.data()), data); } +void MeshDataTest::constructAttributeOffsetOnly() { + struct { + Vector2 position; + Vector2 textureCoordinates; + } vertexData[] { + {{}, {1.0f, 0.3f}}, + {{}, {0.5f, 0.7f}}, + }; + + MeshAttributeData a{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)}; + CORRADE_VERIFY(a.isOffsetOnly()); + CORRADE_COMPARE(a.name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(a.format(), VertexFormat::Vector2); + CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), + Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), + TestSuite::Compare::Container); + + constexpr MeshAttributeData ca{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)}; + CORRADE_VERIFY(ca.isOffsetOnly()); + CORRADE_COMPARE(ca.name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(ca.format(), VertexFormat::Vector2); + CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), + Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), + TestSuite::Compare::Container); +} + +void MeshDataTest::constructAttributeWrongFormat() { + Vector2 positionData[3]; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Color, Containers::arrayView(positionData)}; + MeshAttributeData{MeshAttribute::Color, VertexFormat::Vector2, 0, 3, sizeof(Vector2)}; + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n" + "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n"); +} + void MeshDataTest::constructAttributeWrongStride() { char positionData[3*sizeof(Vector3)]{}; std::ostringstream out; Error redirectError{&out}; MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(positionData)}; + /* We need this one to be constexpr, which means there can't be a warning + about stride not matching the size */ + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, 0, 3*sizeof(Vector3), 1}; MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::StridedArrayView1D{positionData, 0, -16}}; MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::StridedArrayView1D{positionData, 0, 65000}}; MeshAttributeData{65000}; @@ -561,6 +602,20 @@ void MeshDataTest::constructAttributeWrongStride() { ); } +void MeshDataTest::constructAttributeWrongDataAccess() { + Vector2 positionData[3]; + MeshAttributeData a{MeshAttribute::Position, Containers::arrayView(positionData)}; + MeshAttributeData b{MeshAttribute::Position, VertexFormat::Vector2, 0, 3, sizeof(Vector2)}; + CORRADE_VERIFY(!a.isOffsetOnly()); + CORRADE_VERIFY(b.isOffsetOnly()); + + std::ostringstream out; + Error redirectError{&out}; + b.data(); + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array\n"); +} + void MeshDataTest::construct() { struct Vertex { Vector3 position; @@ -597,8 +652,10 @@ void MeshDataTest::construct() { MeshIndexData indices{indexView}; MeshAttributeData positions{MeshAttribute::Position, Containers::StridedArrayView1D{vertexData, &vertexView[0].position, vertexView.size(), sizeof(Vertex)}}; + /* Using a relative offset */ MeshAttributeData normals{MeshAttribute::Normal, - Containers::StridedArrayView1D{vertexData, &vertexView[0].normal, vertexView.size(), sizeof(Vertex)}}; + VertexFormat::Vector3, offsetof(Vertex, normal), + UnsignedInt(vertexView.size()), sizeof(Vertex)}; MeshAttributeData textureCoordinates{MeshAttribute::TextureCoordinates, Containers::StridedArrayView1D{vertexData, &vertexView[0].textureCoordinate, vertexView.size(), sizeof(Vertex)}}; MeshAttributeData ids{meshAttributeCustom(13), @@ -692,6 +749,11 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.mutableAttribute(3)[1], (Vector2{0.250f, 0.375f})); CORRADE_COMPARE(data.mutableAttribute(4)[1], -374); + /* Raw attribute data access by ID */ + CORRADE_COMPARE(data.attributeData(3).name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(data.attributeData(3).format(), VertexFormat::Vector2); + CORRADE_COMPARE(Containers::arrayCast(data.attributeData(3).data())[1], (Vector2{0.250f, 0.375f})); + /* Attribute access by name */ CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Position)); CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Normal)); @@ -1167,14 +1229,17 @@ void MeshDataTest::constructAttributeNotContained() { Containers::ArrayView vertexData2{reinterpret_cast(0xdead), 3}; MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast(vertexData)}; MeshAttributeData positions2{MeshAttribute::Position, Containers::arrayView(vertexData2)}; + MeshAttributeData positions3{MeshAttribute::Position, VertexFormat::Vector2, 1, 3, 8}; std::ostringstream out; Error redirectError{&out}; MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}}; MeshData{MeshPrimitive::Triangles, nullptr, {positions}}; + MeshData{MeshPrimitive::Triangles, Containers::Array{24}, {positions3}}; CORRADE_COMPARE(out.str(), "Trade::MeshData: attribute 1 [0xdead:0xdec5] is not contained in passed vertexData array [0xbadda9:0xbaddc1]\n" - "Trade::MeshData: attribute 0 [0xbadda9:0xbaddc1] is not contained in passed vertexData array [0x0:0x0]\n"); + "Trade::MeshData: attribute 0 [0xbadda9:0xbaddc1] is not contained in passed vertexData array [0x0:0x0]\n" + "Trade::MeshData: offset attribute 0 spans 25 bytes but passed vertexData array has only 24\n"); } void MeshDataTest::constructInconsitentVertexCount() {