From a29e9dc009166c8c694ffa2dee5a985c6b5e1317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 21 Feb 2020 17:09:42 +0100 Subject: [PATCH] Trade: handle implementation-specific vertex formats in MeshData. --- src/Magnum/Trade/MeshData.cpp | 33 ++++-- src/Magnum/Trade/MeshData.h | 123 ++++++++++++++++------- src/Magnum/Trade/Test/MeshDataTest.cpp | 134 +++++++++++++++++++++++++ 3 files changed, 246 insertions(+), 44 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index e8982ee70..938fb8301 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -56,7 +56,7 @@ MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexForma because I feel that makes more sense than duplicating the full assert logic */ /** @todo support zero / negative stride? would be hard to transfer to GL */ - CORRADE_ASSERT(data.empty() || std::ptrdiff_t(vertexFormatSize(format)) <= data.stride(), + CORRADE_ASSERT(data.empty() || isVertexFormatImplementationSpecific(format) || std::ptrdiff_t(vertexFormatSize(format)) <= data.stride(), "Trade::MeshAttributeData: expected stride to be positive and enough to fit" << format << Debug::nospace << ", got" << data.stride(), ); } @@ -64,7 +64,7 @@ MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexForma /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ - CORRADE_ASSERT(data.empty()[0] || vertexFormatSize(format) == data.size()[1], + CORRADE_ASSERT(data.empty()[0] || isVertexFormatImplementationSpecific(format) || vertexFormatSize(format) == data.size()[1], "Trade::MeshAttributeData: second view dimension size" << data.size()[1] << "doesn't match" << format, ); CORRADE_ASSERT(data.isContiguous<1>(), "Trade::MeshAttributeData: second view dimension is not contiguous", ); @@ -99,7 +99,12 @@ 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 UnsignedInt typeSize = vertexFormatSize(attribute._format); + /* Check that the view fits into the provided vertex data array. For + implementation-specific formats we don't know the size so use 0 to + check at least partially. */ + const UnsignedInt typeSize = + isVertexFormatImplementationSpecific(attribute._format) ? 0 : + 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(), @@ -317,10 +322,12 @@ Containers::StridedArrayView1D MeshData::attributeDataViewInternal(c Containers::StridedArrayView2D MeshData::attribute(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + const MeshAttributeData& attribute = _attributes[id]; /* Build a 2D view using information about attribute type size */ return Containers::arrayCast<2, const char>( - attributeDataViewInternal(_attributes[id]), - vertexFormatSize(_attributes[id]._format)); + attributeDataViewInternal(attribute), + isVertexFormatImplementationSpecific(attribute._format) ? + attribute._stride : vertexFormatSize(attribute._format)); } Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) { @@ -328,10 +335,12 @@ Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + const MeshAttributeData& attribute = _attributes[id]; /* Build a 2D view using information about attribute type size */ auto out = Containers::arrayCast<2, const char>( - attributeDataViewInternal(_attributes[id]), - vertexFormatSize(_attributes[id]._format)); + attributeDataViewInternal(attribute), + isVertexFormatImplementationSpecific(attribute._format) ? + attribute._stride : vertexFormatSize(attribute._format)); /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the @@ -391,6 +400,8 @@ void MeshData::positions2DInto(const Containers::StridedArrayView1D des CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::positions2DInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination2f = Containers::arrayCast<2, Float>(destination); @@ -439,6 +450,8 @@ void MeshData::positions3DInto(const Containers::StridedArrayView1D des CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions3DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions3DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::positions3DInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const Containers::StridedArrayView2D destination2f = Containers::arrayCast<2, Float>(Containers::arrayCast(destination)); const Containers::StridedArrayView2D destination3f = Containers::arrayCast<2, Float>(destination); @@ -518,6 +531,8 @@ void MeshData::normalsInto(const Containers::StridedArrayView1D destina CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal) << "normal attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::normalsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::normalsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination3f = Containers::arrayCast<2, Float>(destination); @@ -543,6 +558,8 @@ void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination2f = Containers::arrayCast<2, Float>(destination); @@ -580,6 +597,8 @@ void MeshData::colorsInto(const Containers::StridedArrayView1D destinati CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::colorsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Color) << "color attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::colorsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::colorsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const Containers::StridedArrayView2D destination3f = Containers::arrayCast<2, Float>(Containers::arrayCast(destination)); const Containers::StridedArrayView2D destination4f = Containers::arrayCast<2, Float>(destination); diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index fe9bdf642..34c5b54fe 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -1001,9 +1001,12 @@ class MAGNUM_TRADE_EXPORT MeshData { * * The @p id is expected to be smaller than @ref attributeCount() const. * The second dimension represents the actual data type (its size is - * equal to type size) and is guaranteed to be contiguous. Use the - * templated overload below to get the attribute in a concrete type. - * @see @ref Corrade::Containers::StridedArrayView::isContiguous() + * equal to format size for known @ref VertexFormat values and to + * attribute stride for implementation-specific values) and is + * guaranteed to be contiguous. Use the templated overload below to get + * the attribute in a concrete type. + * @see @ref Corrade::Containers::StridedArrayView::isContiguous(), + * @ref isVertexFormatImplementationSpecific() */ Containers::StridedArrayView2D attribute(UnsignedInt id) const; @@ -1021,14 +1024,18 @@ class MAGNUM_TRADE_EXPORT MeshData { * * The @p id is expected to be smaller than @ref attributeCount() const * and @p T is expected to correspond to - * @ref attributeFormat(UnsignedInt) const. You can also use the - * non-templated @ref positions2DAsArray(), @ref positions3DAsArray(), - * @ref normalsAsArray(), @ref textureCoordinates2DAsArray() and - * @ref colorsAsArray() accessors to get common attributes converted to - * usual types, but note that these operations involve extra allocation - * and data conversion. + * @ref attributeFormat(UnsignedInt) const. Expects that the vertex + * format is *not* implementation-specific, in that case you can only + * access the attribute via the typeless @ref attribute(UnsignedInt) const + * above. You can also use the non-templated @ref positions2DAsArray(), + * @ref positions3DAsArray(), @ref normalsAsArray(), + * @ref textureCoordinates2DAsArray() and @ref colorsAsArray() + * accessors to get common attributes converted to usual types, but + * note that these operations involve extra allocation and data + * conversion. * @see @ref attribute(MeshAttribute, UnsignedInt) const, - * @ref mutableAttribute(MeshAttribute, UnsignedInt) + * @ref mutableAttribute(MeshAttribute, UnsignedInt), + * @ref isVertexFormatImplementationSpecific() */ template Containers::StridedArrayView1D attribute(UnsignedInt id) const; @@ -1046,12 +1053,15 @@ class MAGNUM_TRADE_EXPORT MeshData { * * The @p id is expected to be smaller than * @ref attributeCount(MeshAttribute) const. The second dimension - * represents the actual data type (its size is equal to type size) and - * is guaranteed to be contiguous. Use the templated overload below to - * get the attribute in a concrete type. + * represents the actual data type (its size is equal to format size + * for known @ref VertexFormat values and to attribute stride for + * implementation-specific values) and is guaranteed to be contiguous. + * Use the templated overload below to get the attribute in a concrete + * type. * @see @ref attribute(UnsignedInt) const, * @ref mutableAttribute(MeshAttribute, UnsignedInt), - * @ref Corrade::Containers::StridedArrayView::isContiguous() + * @ref Corrade::Containers::StridedArrayView::isContiguous(), + * @ref isVertexFormatImplementationSpecific() */ Containers::StridedArrayView2D attribute(MeshAttribute name, UnsignedInt id = 0) const; @@ -1070,14 +1080,18 @@ class MAGNUM_TRADE_EXPORT MeshData { * The @p id is expected to be smaller than * @ref attributeCount(MeshAttribute) const and @p T is expected to * correspond to @ref attributeFormat(MeshAttribute, UnsignedInt) const. - * You can also use the non-templated @ref positions2DAsArray(), + * Expects that the vertex format is *not* implementation-specific, in + * that case you can only access the attribute via the typeless + * @ref attribute(MeshAttribute, UnsignedInt) const above. You can also + * use the non-templated @ref positions2DAsArray(), * @ref positions3DAsArray(), @ref normalsAsArray(), * @ref textureCoordinates2DAsArray() and @ref colorsAsArray() * accessors to get common attributes converted to usual types, but * note that these operations involve extra data conversion and an * allocation. * @see @ref attribute(UnsignedInt) const, - * @ref mutableAttribute(MeshAttribute, UnsignedInt) + * @ref mutableAttribute(MeshAttribute, UnsignedInt), + * @ref isVertexFormatImplementationSpecific() */ template Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0) const; @@ -1117,8 +1131,11 @@ class MAGNUM_TRADE_EXPORT MeshData { * with @ref MeshAttribute::Position as the first argument. Converts * the position array from an arbitrary underlying type and returns it * in a newly-allocated array. If the underlying type is - * three-component, the last component is dropped. - * @see @ref positions2DInto() + * three-component, the last component is dropped. Expects that the + * vertex format is *not* implementation-specific, in that case you can + * only access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref positions2DInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() */ Containers::Array positions2DAsArray(UnsignedInt id = 0) const; @@ -1139,8 +1156,11 @@ class MAGNUM_TRADE_EXPORT MeshData { * with @ref MeshAttribute::Position as the first argument. Converts * the position array from an arbitrary underlying type and returns it * in a newly-allocated array. If the underlying type is two-component, - * the Z component is set to @cpp 0.0f @ce. - * @see @ref positions3DInto() + * the Z component is set to @cpp 0.0f @ce. Expects that the vertex + * format is *not* implementation-specific, in that case you can only + * access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref positions3DInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() */ Containers::Array positions3DAsArray(UnsignedInt id = 0) const; @@ -1160,8 +1180,11 @@ class MAGNUM_TRADE_EXPORT MeshData { * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const * with @ref MeshAttribute::Normal as the first argument. Converts the * normal array from an arbitrary underlying type and returns it in a - * newly-allocated array. - * @see @ref normalsInto() + * newly-allocated array. Expects that the vertex format is *not* + * implementation-specific, in that case you can only access the + * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref normalsInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() */ Containers::Array normalsAsArray(UnsignedInt id = 0) const; @@ -1181,8 +1204,12 @@ class MAGNUM_TRADE_EXPORT MeshData { * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const * with @ref MeshAttribute::TextureCoordinates as the first argument. * Converts the texture coordinate array from an arbitrary underlying - * type and returns it in a newly-allocated array. - * @see @ref textureCoordinates2DInto() + * type and returns it in a newly-allocated array. Expects that the + * vertex format is *not* implementation-specific, in that case you can + * only access the attribute via the typeless + * @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref textureCoordinates2DInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() */ Containers::Array textureCoordinates2DAsArray(UnsignedInt id = 0) const; @@ -1203,8 +1230,11 @@ class MAGNUM_TRADE_EXPORT MeshData { * with @ref MeshAttribute::Color as the first argument. Converts the * color array from an arbitrary underlying type and returns it in a * newly-allocated array. If the underlying type is three-component, - * the alpha component is set to @cpp 1.0f @ce. - * @see @ref colorsInto() + * the alpha component is set to @cpp 1.0f @ce. Expects that the vertex + * format is *not* implementation-specific, in that case you can only + * access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref colorsInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() */ Containers::Array colorsAsArray(UnsignedInt id = 0) const; @@ -1407,6 +1437,10 @@ namespace Implementation { /* Double types intentionally not supported for any builtin attributes right now -- only for custom types */ return + /* Implementation-specific formats can be used for any attribute + (tho the access capabilities will be reduced) */ + isVertexFormatImplementationSpecific(format) || + /* Named attributes are restricted so we can decode them */ (name == MeshAttribute::Position && (format == VertexFormat::Vector2 || format == VertexFormat::Vector2h || @@ -1453,7 +1487,8 @@ namespace Implementation { format == VertexFormat::Vector2usNormalized || format == VertexFormat::Vector2s || format == VertexFormat::Vector2sNormalized)) || - isMeshAttributeCustom(name); /* can be any format */ + /* Custom attributes can be anything */ + isMeshAttributeCustom(name); } } #endif @@ -1505,8 +1540,13 @@ template Containers::StridedArrayView1D MeshData::attribute(Un #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif - CORRADE_ASSERT(Implementation::isVertexFormatCompatible(_attributes[id]._format), - "Trade::MeshData::attribute(): improper type requested for" << _attributes[id]._name << "of format" << _attributes[id]._format, nullptr); + #ifndef CORRADE_NO_ASSERT + const MeshAttributeData& attribute = _attributes[id]; + #endif + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), {}); + CORRADE_ASSERT(Implementation::isVertexFormatCompatible(attribute._format), + "Trade::MeshData::attribute(): improper type requested for" << attribute._name << "of format" << attribute._format, nullptr); return Containers::arrayCast<1, const T>(data); } @@ -1515,8 +1555,13 @@ template Containers::StridedArrayView1D MeshData::mutableAttribute(U #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif - CORRADE_ASSERT(Implementation::isVertexFormatCompatible(_attributes[id]._format), - "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[id]._name << "of format" << _attributes[id]._format, nullptr); + #ifndef CORRADE_NO_ASSERT + const MeshAttributeData& attribute = _attributes[id]; + #endif + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), {}); + CORRADE_ASSERT(Implementation::isVertexFormatCompatible(attribute._format), + "Trade::MeshData::mutableAttribute(): improper type requested for" << attribute._name << "of format" << attribute._format, nullptr); return Containers::arrayCast<1, T>(data); } @@ -1526,10 +1571,12 @@ template Containers::StridedArrayView1D MeshData::attribute(Me if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - const UnsignedInt attributeId = attributeFor(name, id); + const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; #endif - CORRADE_ASSERT(Implementation::isVertexFormatCompatible(_attributes[attributeId]._format), - "Trade::MeshData::attribute(): improper type requested for" << _attributes[attributeId]._name << "of format" << _attributes[attributeId]._format, nullptr); + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), {}); + CORRADE_ASSERT(Implementation::isVertexFormatCompatible(attribute._format), + "Trade::MeshData::attribute(): improper type requested for" << attribute._name << "of format" << attribute._format, nullptr); return Containers::arrayCast<1, const T>(data); } @@ -1539,10 +1586,12 @@ template Containers::StridedArrayView1D MeshData::mutableAttribute(M if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - const UnsignedInt attributeId = attributeFor(name, id); + const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; #endif - CORRADE_ASSERT(Implementation::isVertexFormatCompatible(_attributes[attributeId]._format), - "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[attributeId]._name << "of format" << _attributes[attributeId]._format, nullptr); + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), {}); + CORRADE_ASSERT(Implementation::isVertexFormatCompatible(attribute._format), + "Trade::MeshData::mutableAttribute(): improper type requested for" << attribute._name << "of format" << attribute._format, nullptr); return Containers::arrayCast<1, T>(data); } diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 972f2d254..0657703f3 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -60,6 +60,7 @@ struct MeshDataTest: TestSuite::Tester { void constructAttributePadding(); void constructAttributeNonOwningArray(); void constructAttributeOffsetOnly(); + void constructAttributeImplementationSpecificFormat(); void constructAttributeWrongFormat(); void constructAttributeWrongStride(); void constructAttributeWrongDataAccess(); @@ -123,6 +124,10 @@ struct MeshDataTest: TestSuite::Tester { template void colorsAsArrayPackedUnsignedNormalized(); void colorsIntoArrayInvalidSize(); + void implementationSpecificVertexFormat(); + void implementationSpecificVertexFormatWrongAccess(); + void implementationSpecificVertexFormatNotContained(); + void mutableAccessNotAllowed(); void indicesNotIndexed(); @@ -178,6 +183,7 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttributePadding, &MeshDataTest::constructAttributeNonOwningArray, &MeshDataTest::constructAttributeOffsetOnly, + &MeshDataTest::constructAttributeImplementationSpecificFormat, &MeshDataTest::constructAttributeWrongFormat, &MeshDataTest::constructAttributeWrongStride, &MeshDataTest::constructAttributeWrongDataAccess, @@ -288,6 +294,10 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, &MeshDataTest::colorsIntoArrayInvalidSize, + &MeshDataTest::implementationSpecificVertexFormat, + &MeshDataTest::implementationSpecificVertexFormatWrongAccess, + &MeshDataTest::implementationSpecificVertexFormatNotContained, + &MeshDataTest::mutableAccessNotAllowed, &MeshDataTest::indicesNotIndexed, @@ -574,6 +584,18 @@ void MeshDataTest::constructAttributeOffsetOnly() { TestSuite::Compare::Container); } +void MeshDataTest::constructAttributeImplementationSpecificFormat() { + Vector2 positions[]{{1.0f, 0.3f}, {0.5f, 0.7f}}; + + /* This should not fire any asserts */ + MeshAttributeData a{MeshAttribute::TextureCoordinates, vertexFormatWrap(0x3a), positions}; + CORRADE_COMPARE(a.name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(a.format(), vertexFormatWrap(0x3a)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.data()), + Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), + TestSuite::Compare::Container); +} + void MeshDataTest::constructAttributeWrongFormat() { Vector2 positionData[3]; @@ -1234,6 +1256,8 @@ void MeshDataTest::constructAttributeNotContained() { MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast(vertexData)}; MeshAttributeData positions2{MeshAttribute::Position, Containers::arrayView(vertexData2)}; MeshAttributeData positions3{MeshAttribute::Position, VertexFormat::Vector2, 1, 3, 8}; + /* See implementationSpecificVertexFormatNotContained() below for + implementation-specific formats */ std::ostringstream out; Error redirectError{&out}; @@ -1862,6 +1886,116 @@ void MeshDataTest::colorsIntoArrayInvalidSize() { "Trade::MeshData::colorsInto(): expected a view with 3 elements but got 2\n"); } +void MeshDataTest::implementationSpecificVertexFormat() { + struct Vertex { + Long:64; + long double thing; + } vertexData[] { + {456.0l}, + {456.0l} + }; + + /* Constructing should work w/o asserts */ + Containers::StridedArrayView1D attribute{vertexData, + &vertexData[0].thing, 2, sizeof(Vertex)}; + MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { + MeshAttributeData{MeshAttribute::Position, + vertexFormatWrap(0xdead1), attribute}, + MeshAttributeData{MeshAttribute::Normal, + vertexFormatWrap(0xdead2), attribute}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + vertexFormatWrap(0xdead3), attribute}, + MeshAttributeData{MeshAttribute::Color, + vertexFormatWrap(0xdead4), attribute}}}; + + /* Getting typeless attribute should work also */ + UnsignedInt format = 0xdead1; + for(MeshAttribute name: {MeshAttribute::Position, + MeshAttribute::Normal, + MeshAttribute::TextureCoordinates, + MeshAttribute::Color}) { + CORRADE_ITERATION(name); + CORRADE_COMPARE(data.attributeFormat(name), vertexFormatWrap(format++)); + + /* The actual type size is unknown, so this will use the full stride */ + CORRADE_COMPARE(data.attribute(name).size()[1], sizeof(Vertex)); + + CORRADE_COMPARE_AS((Containers::arrayCast<1, const long double>( + data.attribute(name).prefix({2, sizeof(long double)}))), + attribute, TestSuite::Compare::Container); + CORRADE_COMPARE_AS((Containers::arrayCast<1, const long double>( + data.mutableAttribute(name).prefix({2, sizeof(long double)}))), + attribute, TestSuite::Compare::Container); + } +} + +void MeshDataTest::implementationSpecificVertexFormatWrongAccess() { + struct Vertex { + Long:64; + long double thing; + } vertexData[] { + {456.0l}, + {456.0l} + }; + + Containers::StridedArrayView1D attribute{vertexData, + &vertexData[0].thing, 2, sizeof(Vertex)}; + MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { + MeshAttributeData{MeshAttribute::Position, + vertexFormatWrap(0xdead1), attribute}, + MeshAttributeData{MeshAttribute::Normal, + vertexFormatWrap(0xdead2), attribute}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + vertexFormatWrap(0xdead3), attribute}, + MeshAttributeData{MeshAttribute::Color, + vertexFormatWrap(0xdead4), attribute}}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attribute(MeshAttribute::Position); + data.attribute(MeshAttribute::Normal); + data.attribute(MeshAttribute::TextureCoordinates); + data.attribute(MeshAttribute::Color); + data.mutableAttribute(MeshAttribute::Position); + data.mutableAttribute(MeshAttribute::Normal); + data.mutableAttribute(MeshAttribute::TextureCoordinates); + data.mutableAttribute(MeshAttribute::Color); + data.positions2DAsArray(); + data.positions3DAsArray(); + data.normalsAsArray(); + data.textureCoordinates2DAsArray(); + data.colorsAsArray(); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead2\n" + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead3\n" + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead4\n" + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead2\n" + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead3\n" + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead4\n" + "Trade::MeshData::positions2DInto(): can't extract data out of an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::positions3DInto(): can't extract data out of an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::normalsInto(): can't extract data out of an implementation-specific vertex format 0xdead2\n" + "Trade::MeshData::textureCoordinatesInto(): can't extract data out of an implementation-specific vertex format 0xdead3\n" + "Trade::MeshData::colorsInto(): can't extract data out of an implementation-specific vertex format 0xdead4\n"); +} + +void MeshDataTest::implementationSpecificVertexFormatNotContained() { + Containers::Array vertexData{reinterpret_cast(0xbadda9), 3, [](char*, std::size_t){}}; + Containers::ArrayView vertexData2{reinterpret_cast(0xdead), 3}; + MeshAttributeData positions{MeshAttribute::Position, vertexFormatWrap(0x3a), vertexData}; + MeshAttributeData positions2{MeshAttribute::Position, vertexFormatWrap(0x3a), vertexData2}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}}; + CORRADE_COMPARE(out.str(), + /* Assumes size of the type is 0, so the diagnostic is different from + constructAttributeNotContained() */ + "Trade::MeshData: attribute 1 [0xdead:0xdeaf] is not contained in passed vertexData array [0xbadda9:0xbaddac]\n"); +} + void MeshDataTest::mutableAccessNotAllowed() { const UnsignedShort indexData[]{0, 1, 0}; const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}};