diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 938fb8301..adaa1db16 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -51,7 +51,7 @@ MeshIndexData::MeshIndexData(const Containers::StridedArrayView2D& d _data = data.asContiguous(); } -MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, format, data, nullptr} { +MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, format, arraySize, data, nullptr} { /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ @@ -60,12 +60,16 @@ MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexForma "Trade::MeshAttributeData: expected stride to be positive and enough to fit" << format << Debug::nospace << ", got" << data.stride(), ); } -MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, format, Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, nullptr} { +MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, format, arraySize, Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, nullptr} { /* 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] || isVertexFormatImplementationSpecific(format) || vertexFormatSize(format) == data.size()[1], + #ifndef CORRADE_NO_ASSERT + if(arraySize) CORRADE_ASSERT(data.empty()[0] || isVertexFormatImplementationSpecific(format) || data.size()[1] == vertexFormatSize(format)*arraySize, + "Trade::MeshAttributeData: second view dimension size" << data.size()[1] << "doesn't match" << format << "and array size" << arraySize, ); + else CORRADE_ASSERT(data.empty()[0] || isVertexFormatImplementationSpecific(format) || data.size()[1] == vertexFormatSize(format), "Trade::MeshAttributeData: second view dimension size" << data.size()[1] << "doesn't match" << format, ); + #endif CORRADE_ASSERT(data.isContiguous<1>(), "Trade::MeshAttributeData: second view dimension is not contiguous", ); } @@ -263,6 +267,12 @@ UnsignedInt MeshData::attributeStride(UnsignedInt id) const { return _attributes[id]._stride; } +UnsignedShort MeshData::attributeArraySize(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeArraySize(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id]._arraySize; +} + UnsignedInt MeshData::attributeCount(const MeshAttribute name) const { UnsignedInt count = 0; for(const MeshAttributeData& attribute: _attributes) @@ -307,6 +317,12 @@ UnsignedInt MeshData::attributeStride(MeshAttribute name, UnsignedInt id) const return attributeStride(attributeId); } +UnsignedShort MeshData::attributeArraySize(MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeArraySize(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeArraySize(attributeId); +} + Containers::StridedArrayView1D MeshData::attributeDataViewInternal(const MeshAttributeData& attribute) const { return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ @@ -327,7 +343,8 @@ Containers::StridedArrayView2D MeshData::attribute(UnsignedInt id) c return Containers::arrayCast<2, const char>( attributeDataViewInternal(attribute), isVertexFormatImplementationSpecific(attribute._format) ? - attribute._stride : vertexFormatSize(attribute._format)); + attribute._stride : vertexFormatSize(attribute._format)* + (attribute._arraySize ? attribute._arraySize : 1)); } Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) { @@ -340,7 +357,8 @@ Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) auto out = Containers::arrayCast<2, const char>( attributeDataViewInternal(attribute), isVertexFormatImplementationSpecific(attribute._format) ? - attribute._stride : vertexFormatSize(attribute._format)); + attribute._stride : vertexFormatSize(attribute._format)* + (attribute._arraySize ? attribute._arraySize : 1)); /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 34c5b54fe..efee5571f 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{}, _isOffsetOnly{false} {} + constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{}, _arraySize{}, _isOffsetOnly{false} {} /** * @brief Type-erased constructor @@ -255,7 +255,21 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * Expects that @p data stride is large enough to fit @p type and that * @p type corresponds to @p name. */ - explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data) noexcept; + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, format, 0, data} {} + + /** + * @brief Type-erased constructor for an array attribute + * @param name Attribute name + * @param format Vertex format + * @param arraySize Array size + * @param data Attribute data + * + * Expects that @p data stride is large enough to fit @p type, @p type + * corresponds to @p name and @p arraySize is zero for builtin + * attributes. Passing @cpp 0 @ce to @p arraySize is equivalent to + * calling the above overload. + */ + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView1D& data) noexcept; /** * @brief Constructor @@ -266,10 +280,25 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * Expects that the second dimension of @p data is contiguous and its * size matches @p type; and that @p type corresponds to @p name. */ - explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView2D& data) noexcept; + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, format, 0, data} {} /** @overload */ - explicit MeshAttributeData(MeshAttribute name, VertexFormat format, std::nullptr_t) noexcept: MeshAttributeData{name, format, nullptr, nullptr} {} + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, std::nullptr_t) noexcept: MeshAttributeData{name, format, 0, nullptr, nullptr} {} + + /** + * @brief Construct an array attribute + * @param name Attribute name + * @param format Vertex format + * @param arraySize Array size + * @param data Attribute data + * + * Expects that the second dimension of @p data is contiguous and its + * size matches @p type and @p arraSize, that @p type corresponds to + * @p name and @p arraySize is zero for builtin attributes. Passing + * @cpp 0 @ce to @p arraySize is equivalent to calling the above + * overload. + */ + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView2D& data) noexcept; /** * @brief Constructor @@ -305,6 +334,21 @@ 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 array attribute + * @param name Attribute name + * @param data Attribute data + * + * Detects @ref VertexFormat based on @p T and calls + * @ref MeshAttributeData(MeshAttribute, VertexFormat, UnsignedShort, const Containers::StridedArrayView1D&) + * with the second dimension size passed to @p arraySize. Expects that + * the second dimension is contiguous. At the moment only custom + * attributes can be arrays, which means this function can't be used + * with a builtin @p name. See @ref MeshAttributeData(MeshAttribute, const Containers::StridedArrayView1D&) + * for details about @ref VertexFormat detection. + */ + template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView2D& data) noexcept; + /** * @brief Construct an offset-only attribute * @param name Attribute name @@ -312,16 +356,19 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * @param offset Attribute data offset * @param vertexCount Attribute vertex count * @param stride Attribute stride + * @param arraySize Array size. Use @cpp 0 @ce for non-array + * attributes. * * 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(), + * attribute construction time. Expects that @p arraySize is zero for + * builtin attributes. Note that instances created this way can't be + * used in most @ref MeshTools algorithms. + * @see @ref isOffsetOnly(), @ref arraySize(), * @ref data(Containers::ArrayView) const */ - explicit constexpr MeshAttributeData(MeshAttribute name, VertexFormat format, std::size_t offset, UnsignedInt vertexCount, std::ptrdiff_t stride) noexcept; + explicit constexpr MeshAttributeData(MeshAttribute name, VertexFormat format, std::size_t offset, UnsignedInt vertexCount, std::ptrdiff_t stride, UnsignedShort arraySize = 0) noexcept; /** * @brief Construct a pad value @@ -334,7 +381,7 @@ 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{}, _isOffsetOnly{false} {} + }, _name{}, _arraySize{}, _isOffsetOnly{false} {} /** * @brief If the attribute is offset-only @@ -342,7 +389,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * 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) + * @ref MeshAttributeData(MeshAttribute, VertexFormat, std::size_t, UnsignedInt, std::ptrdiff_t, UnsignedShort) */ constexpr bool isOffsetOnly() const { return _isOffsetOnly; } @@ -352,6 +399,9 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /** @brief Attribute format */ constexpr VertexFormat format() const { return _format; } + /** @brief Attribute array size */ + constexpr UnsignedShort arraySize() const { return _arraySize; } + /** * @brief Type-erased attribute data * @@ -383,7 +433,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { } private: - constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept; + constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept; friend MeshData; union Data { @@ -402,9 +452,10 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { current largest reported stride is 4k so 32k should be enough */ Short _stride; MeshAttribute _name; + UnsignedShort _arraySize; bool _isOffsetOnly; - /* 3 bytes free for more stuff on 64b (21, aligned to 24) and on 32b - (17 used, aligned to 20) */ + /* 1 byte free for more stuff on 64b (23, aligned to 24) and on 32b + (19, aligned to 20) */ }; /** @relatesalso MeshAttributeData @@ -940,6 +991,26 @@ class MAGNUM_TRADE_EXPORT MeshData { */ UnsignedInt attributeStride(UnsignedInt id) const; + /** + * @brief Attribute array size + * + * In case given attribute is an array (the equivalent of e.g. + * @cpp int[30] @ce), returns array size, otherwise returns @cpp 0 @ce. + * At the moment only custom attributes can be arrays, no builtin + * @ref MeshAttribute is an array attribute. You can also use + * @ref attributeArraySize(MeshAttribute, UnsignedInt) const to + * directly get array size of given named attribute. + * + * Note that this is different from vertex count, which is exposed + * through @ref vertexCount(), and is an orthogonal concept to having + * multiple attributes of the same name (for example two sets of + * texture coordinates), which is exposed through + * @ref attributeCount(MeshAttribute) const. See + * @ref Trade-MeshData-populating-custom for an example. + * @see @ref isMeshAttributeCustom() + */ + UnsignedShort attributeArraySize(UnsignedInt id) const; + /** * @brief Whether the mesh has given attribute * @@ -997,7 +1068,18 @@ class MAGNUM_TRADE_EXPORT MeshData { UnsignedInt attributeStride(MeshAttribute name, UnsignedInt id = 0) const; /** - * @brief Data for given attribute array + * @brief Array size of a named attribute + * + * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const. Note that this is + * different from vertex count, and is an orthogonal concept to having + * multiple attributes of the same name --- see + * @ref attributeArraySize(UnsignedInt) const for more information. + */ + UnsignedShort attributeArraySize(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Data for given attribute * * The @p id is expected to be smaller than @ref attributeCount() const. * The second dimension represents the actual data type (its size is @@ -1011,7 +1093,7 @@ class MAGNUM_TRADE_EXPORT MeshData { Containers::StridedArrayView2D attribute(UnsignedInt id) const; /** - * @brief Mutable data for given attribute array + * @brief Mutable data for given attribute * * Like @ref attribute(UnsignedInt) const, but returns a mutable view. * Expects that the mesh is mutable. @@ -1020,36 +1102,61 @@ class MAGNUM_TRADE_EXPORT MeshData { Containers::StridedArrayView2D mutableAttribute(UnsignedInt id); /** - * @brief Data for given attribute array in a concrete type + * @brief Data for given attribute in a concrete type * * The @p id is expected to be smaller than @ref attributeCount() const * and @p T is expected to correspond to * @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. + * above. The attribute is also expected to not be an array, in that + * case you need to use the overload below by using @cpp T[] @ce + * instead of @cpp T @ce. 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 isVertexFormatImplementationSpecific() + * @ref isVertexFormatImplementationSpecific(), + * @ref attributeArraySize() + */ + template::value>::type> Containers::StridedArrayView1D attribute(UnsignedInt id) const; + + /** + * @brief Data for given array attribute in a concrete type + * + * Same as above, except that it works with array attributes instead + * --- you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref attributeArraySize() for given attribute. */ - template Containers::StridedArrayView1D attribute(UnsignedInt id) const; + template::value>::type> Containers::StridedArrayView2D::type> attribute(UnsignedInt id) const; /** - * @brief Mutable data for given attribute array in a concrete type + * @brief Mutable data for given attribute in a concrete type * * Like @ref attribute(UnsignedInt) const, but returns a mutable view. * Expects that the mesh is mutable. * @see @ref vertexDataFlags() */ - template Containers::StridedArrayView1D mutableAttribute(UnsignedInt id); + template::value>::type> Containers::StridedArrayView1D mutableAttribute(UnsignedInt id); /** - * @brief Data for given named attribute array + * @brief Mutable data for given array attribute in a concrete type + * + * Same as above, except that it works with array attributes instead + * --- you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref attributeArraySize() for given attribute. + */ + template::value>::type> Containers::StridedArrayView2D::type> mutableAttribute(UnsignedInt id); + + /** + * @brief Data for given named attribute * * The @p id is expected to be smaller than * @ref attributeCount(MeshAttribute) const. The second dimension @@ -1066,7 +1173,7 @@ class MAGNUM_TRADE_EXPORT MeshData { Containers::StridedArrayView2D attribute(MeshAttribute name, UnsignedInt id = 0) const; /** - * @brief Mutable data for given named attribute array + * @brief Mutable data for given named attribute * * Like @ref attribute(MeshAttribute, UnsignedInt) const, but returns a * mutable view. Expects that the mesh is mutable. @@ -1075,7 +1182,7 @@ class MAGNUM_TRADE_EXPORT MeshData { Containers::StridedArrayView2D mutableAttribute(MeshAttribute name, UnsignedInt id = 0); /** - * @brief Data for given named attribute array in a concrete type + * @brief Data for given named attribute in a concrete type * * The @p id is expected to be smaller than * @ref attributeCount(MeshAttribute) const and @p T is expected to @@ -1093,16 +1200,38 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref mutableAttribute(MeshAttribute, UnsignedInt), * @ref isVertexFormatImplementationSpecific() */ - template Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0) const; + template::value>::type> Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0) const; /** - * @brief Mutable data for given named attribute array in a concrete type + * @brief Data for given named array attribute in a concrete type + * + * Same as above, except that it works with array attributes instead + * --- you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref attributeArraySize() for given attribute. + */ + template::value>::type> Containers::StridedArrayView2D::type> attribute(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Mutable data for given named attribute in a concrete type * * Like @ref attribute(MeshAttribute, UnsignedInt) const, but returns a * mutable view. Expects that the mesh is mutable. * @see @ref vertexDataFlags() */ - template Containers::StridedArrayView1D mutableAttribute(MeshAttribute name, UnsignedInt id = 0); + template::value>::type> Containers::StridedArrayView1D mutableAttribute(MeshAttribute name, UnsignedInt id = 0); + + /** + * @brief Mutable data for given named array attribute in a concrete type + * + * Same as above, except that it works with array attributes instead + * --- you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref attributeArraySize() for given attribute. + */ + template::value>::type> Containers::StridedArrayView2D::type> mutableAttribute(MeshAttribute name, UnsignedInt id = 0); /** * @brief Indices as 32-bit integers @@ -1318,6 +1447,10 @@ class MAGNUM_TRADE_EXPORT MeshData { /* Like attribute(), but returning just a 1D view */ Containers::StridedArrayView1D attributeDataViewInternal(const MeshAttributeData& attribute) const; + #ifndef CORRADE_NO_ASSERT + template bool checkAttributeTypeCompatibility(const MeshAttributeData& attribute, const char* prefix) const; + #endif + /* GPUs don't currently support more than 32-bit index types / vertex counts so this should be enough. Sanity check: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkIndexType.html */ @@ -1490,10 +1623,14 @@ namespace Implementation { /* Custom attributes can be anything */ isMeshAttributeCustom(name); } + + constexpr bool isAttributeArrayAllowed(MeshAttribute name) { + return isMeshAttributeCustom(name); + } } #endif -constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept: +constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const UnsignedShort arraySize, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept: _data{data.data()}, _vertexCount{UnsignedInt(data.size())}, _format{format}, /** @todo support zero / negative stride? would be hard to transfer to GL */ _stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(data.stride()) & 0xffff8000), @@ -1501,9 +1638,12 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V Short(data.stride())) }, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format), "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name) - }, _isOffsetOnly{false} {} + }, _arraySize{(CORRADE_CONSTEXPR_ASSERT(!arraySize || Implementation::isAttributeArrayAllowed(name), + "Trade::MeshAttributeData:" << name << "can't be an array attribute"), arraySize) + }, _isOffsetOnly{(CORRADE_CONSTEXPR_ASSERT(!arraySize || !isVertexFormatImplementationSpecific(format), + "Trade::MeshAttributeData: array attributes can't have an implementation-specific format"), false)} {} -constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const std::size_t offset, const UnsignedInt vertexCount, const std::ptrdiff_t stride) noexcept: +constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const std::size_t offset, const UnsignedInt vertexCount, const std::ptrdiff_t stride, UnsignedShort arraySize) 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), @@ -1511,9 +1651,14 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V Short(stride)) }, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format), "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name) - }, _isOffsetOnly{true} {} + }, _arraySize{(CORRADE_CONSTEXPR_ASSERT(!arraySize || Implementation::isAttributeArrayAllowed(name), + "Trade::MeshAttributeData:" << name << "can't be an array attribute"), arraySize) + }, _isOffsetOnly{(CORRADE_CONSTEXPR_ASSERT(!arraySize || !isVertexFormatImplementationSpecific(format), + "Trade::MeshAttributeData: array attributes can't have an implementation-specific format"), true)} {} -template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), data, nullptr} {} +template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), 0, data, nullptr} {} + +template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), UnsignedShort(data.size()[1]), Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, (CORRADE_CONSTEXPR_ASSERT(data.stride()[1] == sizeof(T), "Trade::MeshAttributeData: second view dimension is not contiguous"), nullptr)} {} template Containers::ArrayView MeshData::indices() const { Containers::StridedArrayView2D data = indices(); @@ -1535,66 +1680,110 @@ template Containers::ArrayView MeshData::mutableIndices() { return Containers::arrayCast<1, T>(data).asContiguous(); } -template Containers::StridedArrayView1D MeshData::attribute(UnsignedInt id) const { +#ifndef CORRADE_NO_ASSERT +template bool MeshData::checkAttributeTypeCompatibility(const MeshAttributeData& attribute, const char* const prefix) const { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + prefix << "can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), false); + CORRADE_ASSERT(Implementation::isVertexFormatCompatible::type>(attribute._format), + prefix << "improper type requested for" << attribute._name << "of format" << attribute._format, false); + CORRADE_ASSERT(std::is_array::value == !!attribute._arraySize, + prefix << "use T[] to access an array attribute", false); + return true; +} +#endif + +template Containers::StridedArrayView1D MeshData::attribute(const UnsignedInt id) const { Containers::StridedArrayView2D data = attribute(id); #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - const MeshAttributeData& attribute = _attributes[id]; + if(!checkAttributeTypeCompatibility(_attributes[id], "Trade::MeshData::attribute():")) return {}; #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); } -template Containers::StridedArrayView1D MeshData::mutableAttribute(UnsignedInt id) { +template Containers::StridedArrayView2D::type> MeshData::attribute(const UnsignedInt id) const { + Containers::StridedArrayView2D data = attribute(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + const MeshAttributeData& attribute = _attributes[id]; + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(attribute, "Trade::MeshData::attribute():")) return {}; + #endif + return Containers::arrayCast<2, const typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D MeshData::mutableAttribute(const UnsignedInt id) { Containers::StridedArrayView2D data = mutableAttribute(id); #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - const MeshAttributeData& attribute = _attributes[id]; + if(!checkAttributeTypeCompatibility(_attributes[id], "Trade::MeshData::mutableAttribute():")) return {}; #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); } -template Containers::StridedArrayView1D MeshData::attribute(MeshAttribute name, UnsignedInt id) const { +template Containers::StridedArrayView2D::type> MeshData::mutableAttribute(const UnsignedInt id) { + Containers::StridedArrayView2D data = mutableAttribute(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + const MeshAttributeData& attribute = _attributes[id]; + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(attribute, "Trade::MeshData::mutableAttribute():")) return {}; + #endif + return Containers::arrayCast<2, typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D MeshData::attribute(MeshAttribute name, UnsignedInt id) const { Containers::StridedArrayView2D data = attribute(name, id); #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; + if(!checkAttributeTypeCompatibility(_attributes[attributeFor(name, id)], "Trade::MeshData::attribute():")) return {}; #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); } -template Containers::StridedArrayView1D MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { +template Containers::StridedArrayView2D::type> MeshData::attribute(MeshAttribute name, UnsignedInt id) const { + Containers::StridedArrayView2D data = attribute(name, id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(attribute, "Trade::MeshData::attribute():")) return {}; + #endif + return Containers::arrayCast<2, const typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { Containers::StridedArrayView2D data = mutableAttribute(name, id); #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; + if(!checkAttributeTypeCompatibility(_attributes[attributeFor(name, id)], "Trade::MeshData::mutableAttribute():")) return {}; #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); } +template Containers::StridedArrayView2D::type> MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { + Containers::StridedArrayView2D data = mutableAttribute(name, id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(attribute, "Trade::MeshData::mutableAttribute():")) return {}; + #endif + return Containers::arrayCast<2, typename std::remove_extent::type>(data); +} + }} #endif diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 0657703f3..a72aaee30 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -65,6 +65,15 @@ struct MeshDataTest: TestSuite::Tester { void constructAttributeWrongStride(); void constructAttributeWrongDataAccess(); + void constructArrayAttribute(); + void constructArrayAttributeNonContiguous(); + void constructArrayAttribute2D(); + void constructArrayAttribute2DWrongSize(); + void constructArrayAttribute2DNonContiguous(); + void constructArrayAttributeTypeErased(); + void constructArrayAttributeOffsetOnly(); + void constructArrayAttributeNotAllowed(); + void construct(); void constructZeroIndices(); void constructZeroAttributes(); @@ -128,6 +137,9 @@ struct MeshDataTest: TestSuite::Tester { void implementationSpecificVertexFormatWrongAccess(); void implementationSpecificVertexFormatNotContained(); + void arrayAttribute(); + void arrayAttributeWrongAccess(); + void mutableAccessNotAllowed(); void indicesNotIndexed(); @@ -188,6 +200,15 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttributeWrongStride, &MeshDataTest::constructAttributeWrongDataAccess, + &MeshDataTest::constructArrayAttribute, + &MeshDataTest::constructArrayAttributeNonContiguous, + &MeshDataTest::constructArrayAttribute2D, + &MeshDataTest::constructArrayAttribute2DWrongSize, + &MeshDataTest::constructArrayAttribute2DNonContiguous, + &MeshDataTest::constructArrayAttributeTypeErased, + &MeshDataTest::constructArrayAttributeOffsetOnly, + &MeshDataTest::constructArrayAttributeNotAllowed, + &MeshDataTest::construct, &MeshDataTest::constructZeroIndices, &MeshDataTest::constructZeroAttributes, @@ -298,6 +319,9 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::implementationSpecificVertexFormatWrongAccess, &MeshDataTest::implementationSpecificVertexFormatNotContained, + &MeshDataTest::arrayAttribute, + &MeshDataTest::arrayAttributeWrongAccess, + &MeshDataTest::mutableAccessNotAllowed, &MeshDataTest::indicesNotIndexed, @@ -465,6 +489,7 @@ void MeshDataTest::constructAttribute() { const Vector2 positionData[3]; MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(positionData)}; CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_VERIFY(positions.data().data() == positionData); @@ -473,10 +498,12 @@ void MeshDataTest::constructAttribute() { constexpr MeshAttributeData cpositions{MeshAttribute::Position, Containers::arrayView(Positions)}; constexpr bool isOffsetOnly = cpositions.isOffsetOnly(); + constexpr UnsignedShort arraySize = cpositions.arraySize(); constexpr MeshAttribute name = cpositions.name(); constexpr VertexFormat format = cpositions.format(); constexpr Containers::StridedArrayView1D data = cpositions.data(); CORRADE_VERIFY(!isOffsetOnly); + CORRADE_COMPARE(arraySize, 0); CORRADE_COMPARE(name, MeshAttribute::Position); CORRADE_COMPARE(format, VertexFormat::Vector2); CORRADE_COMPARE(data.data(), Positions); @@ -497,6 +524,7 @@ void MeshDataTest::constructAttribute2D() { MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, positionView}; CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_COMPARE(positions.data().data(), positionView.data()); @@ -528,6 +556,7 @@ void MeshDataTest::constructAttributeTypeErased() { const Vector3 positionData[3]{}; MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(Containers::stridedArrayView(positionData))}; CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector3); CORRADE_VERIFY(positions.data().data() == positionData); @@ -536,6 +565,7 @@ void MeshDataTest::constructAttributeTypeErased() { void MeshDataTest::constructAttributeNullptr() { MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_VERIFY(!positions.data().data()); @@ -544,6 +574,7 @@ void MeshDataTest::constructAttributeNullptr() { void MeshDataTest::constructAttributePadding() { MeshAttributeData padding{-35}; CORRADE_VERIFY(!padding.isOffsetOnly()); + CORRADE_COMPARE(padding.arraySize(), 0); CORRADE_COMPARE(padding.name(), MeshAttribute{}); CORRADE_COMPARE(padding.format(), VertexFormat{}); CORRADE_COMPARE(padding.data().size(), 0); @@ -569,6 +600,7 @@ void MeshDataTest::constructAttributeOffsetOnly() { MeshAttributeData a{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)}; CORRADE_VERIFY(a.isOffsetOnly()); + CORRADE_COMPARE(a.arraySize(), 0); CORRADE_COMPARE(a.name(), MeshAttribute::TextureCoordinates); CORRADE_COMPARE(a.format(), VertexFormat::Vector2); CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), @@ -577,6 +609,7 @@ void MeshDataTest::constructAttributeOffsetOnly() { constexpr MeshAttributeData ca{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)}; CORRADE_VERIFY(ca.isOffsetOnly()); + CORRADE_COMPARE(ca.arraySize(), 0); CORRADE_COMPARE(ca.name(), MeshAttribute::TextureCoordinates); CORRADE_COMPARE(ca.format(), VertexFormat::Vector2); CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), @@ -642,6 +675,140 @@ void MeshDataTest::constructAttributeWrongDataAccess() { "Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array\n"); } +constexpr Vector2 ArrayVertexData[3*4]; + +void MeshDataTest::constructArrayAttribute() { + Vector2 vertexData[3*4]; + Containers::StridedArrayView2D attribute{vertexData, {3, 4}}; + MeshAttributeData data{meshAttributeCustom(35), attribute}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_VERIFY(data.data().data() == vertexData); + CORRADE_COMPARE(data.data().size(), 3); + CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); + + constexpr Containers::StridedArrayView2D cattribute{ArrayVertexData, {3, 4}}; + constexpr MeshAttributeData cdata{meshAttributeCustom(35), cattribute}; + CORRADE_VERIFY(!cdata.isOffsetOnly()); + CORRADE_COMPARE(cdata.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(cdata.format(), VertexFormat::Vector2); + CORRADE_COMPARE(cdata.arraySize(), 4); + CORRADE_VERIFY(cdata.data().data() == ArrayVertexData); + CORRADE_COMPARE(cdata.data().size(), 3); + CORRADE_COMPARE(cdata.data().stride(), sizeof(Vector2)*4); +} + +void MeshDataTest::constructArrayAttributeNonContiguous() { + Vector2 vertexData[4*3]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{meshAttributeCustom(35), + Containers::StridedArrayView2D{vertexData, + {4, 3}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension is not contiguous\n"); +} + +void MeshDataTest::constructArrayAttribute2D() { + char vertexData[3*4*sizeof(Vector2)]; + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, 4, Containers::StridedArrayView2D{vertexData, {3, 4*sizeof(Vector2)}}}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_VERIFY(data.data().data() == vertexData); + CORRADE_COMPARE(data.data().size(), 3); + CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); +} + +void MeshDataTest::constructArrayAttribute2DWrongSize() { + char vertexData[3*4*sizeof(Vector2)]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{meshAttributeCustom(35), VertexFormat::Vector2, 3, + Containers::StridedArrayView2D{vertexData, + {3, 4*sizeof(Vector2)}}}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension size 32 doesn't match VertexFormat::Vector2 and array size 3\n"); +} + +void MeshDataTest::constructArrayAttribute2DNonContiguous() { + char vertexData[4*3*sizeof(Vector2)]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{meshAttributeCustom(35), VertexFormat::Vector2, 2, + Containers::StridedArrayView2D{vertexData, + {3, sizeof(Vector2)*4}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension is not contiguous\n"); +} + +void MeshDataTest::constructArrayAttributeTypeErased() { + Vector2 vertexData[3*4]; + Containers::StridedArrayView1D attribute{vertexData, 3, 4*sizeof(Vector2)}; + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, 4, attribute}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_VERIFY(data.data().data() == vertexData); + CORRADE_COMPARE(data.data().size(), 3); + CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); +} + +void MeshDataTest::constructArrayAttributeOffsetOnly() { + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, sizeof(Vector2), 3, sizeof(Vector2), 4}; + CORRADE_VERIFY(data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + + Vector2 vertexData[1 + 3*4]; + CORRADE_VERIFY(data.data(vertexData).data() == vertexData + 1); + CORRADE_COMPARE(data.data(vertexData).size(), 3); + CORRADE_COMPARE(data.data(vertexData).stride(), sizeof(Vector2)); + + constexpr MeshAttributeData cdata{meshAttributeCustom(35), VertexFormat::Vector2, sizeof(Vector2), 3, sizeof(Vector2), 4}; + CORRADE_VERIFY(cdata.isOffsetOnly()); + CORRADE_COMPARE(cdata.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(cdata.format(), VertexFormat::Vector2); + CORRADE_COMPARE(cdata.arraySize(), 4); +} + +void MeshDataTest::constructArrayAttributeNotAllowed() { + Vector2 positionData[3*3]; + Containers::ArrayView positions{positionData}; + Containers::StridedArrayView2D positions2D{positionData, {3, 3}}; + auto positions2Dchar = Containers::arrayCast<2, const char>(positions2D); + + /* This is all fine */ + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, positions}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, 3, 6*sizeof(Vector2), 0}; + MeshAttributeData{meshAttributeCustom(35), vertexFormatWrap(0xdead), 0, positions}; + MeshAttributeData{meshAttributeCustom(35), positions2D}; + MeshAttributeData{meshAttributeCustom(35), VertexFormat::Vector2, 3, positions2Dchar}; + MeshAttributeData{meshAttributeCustom(35), VertexFormat::Vector2, 0, 3, 6*sizeof(Vector2), 3}; + + /* This is not */ + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 3, Containers::arrayView(positionData)}; + MeshAttributeData{meshAttributeCustom(35), vertexFormatWrap(0xdead), 3, Containers::arrayView(positionData)}; + MeshAttributeData{MeshAttribute::Position, positions2D}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 3, positions2Dchar}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, 3, 6*sizeof(Vector2), 3}; + MeshAttributeData{meshAttributeCustom(35), vertexFormatWrap(0xdead), 0, 3, 6*sizeof(Vector2), 3}; + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData: Trade::MeshAttribute::Position can't be an array attribute\n" + "Trade::MeshAttributeData: array attributes can't have an implementation-specific format\n" + "Trade::MeshAttributeData: Trade::MeshAttribute::Position can't be an array attribute\n" + "Trade::MeshAttributeData: Trade::MeshAttribute::Position can't be an array attribute\n" + "Trade::MeshAttributeData: Trade::MeshAttribute::Position can't be an array attribute\n" + "Trade::MeshAttributeData: array attributes can't have an implementation-specific format\n"); +} + void MeshDataTest::construct() { struct Vertex { Vector3 position; @@ -740,6 +907,11 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attributeStride(1), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(2), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(3), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeArraySize(0), 0); + CORRADE_COMPARE(data.attributeArraySize(1), 0); + CORRADE_COMPARE(data.attributeArraySize(2), 0); + CORRADE_COMPARE(data.attributeArraySize(3), 0); + CORRADE_COMPARE(data.attributeArraySize(4), 0); /* Typeless access by ID with a cast later */ CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( @@ -817,6 +989,11 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 0), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 1), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(meshAttributeCustom(13)), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::Position), 0); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::Normal), 0); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::TextureCoordinates, 0), 0); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::TextureCoordinates, 1), 0); + CORRADE_COMPARE(data.attributeArraySize(meshAttributeCustom(13)), 0); /* Typeless access by name with a cast later */ CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( @@ -1996,6 +2173,99 @@ void MeshDataTest::implementationSpecificVertexFormatNotContained() { "Trade::MeshData: attribute 1 [0xdead:0xdeaf] is not contained in passed vertexData array [0xbadda9:0xbaddac]\n"); } +void MeshDataTest::arrayAttribute() { + Vector2 vertexData[3*4]{ + {1.0f, 2.0f}, {3.0f, 4.0f}, {5.0f, 6.0f}, {7.0f, 8.0f}, + {1.1f, 2.2f}, {3.3f, 4.4f}, {5.5f, 6.6f}, {7.7f, 8.8f}, + {0.1f, 0.2f}, {0.3f, 0.4f}, {0.5f, 0.6f}, {0.7f, 0.8f}, + }; + Containers::StridedArrayView2D positions2D{vertexData, {3, 4}}; + + MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { + MeshAttributeData{meshAttributeCustom(35), positions2D} + }}; + + CORRADE_COMPARE(data.vertexCount(), 3); + CORRADE_COMPARE(data.attributeArraySize(meshAttributeCustom(35)), 4); + + /* Raw access is "as usual" */ + auto attribute = Containers::arrayCast<2, const Vector2>(data.attribute(0)); + auto attributeByName = Containers::arrayCast<2, const Vector2>(data.attribute(meshAttributeCustom(35))); + auto mutableAttribute = Containers::arrayCast<2, Vector2>(data.mutableAttribute(0)); + auto mutableAttributeByName = Containers::arrayCast<2, Vector2>(data.mutableAttribute(meshAttributeCustom(35))); + CORRADE_COMPARE(attribute.size()[0], 3); + CORRADE_COMPARE(attributeByName.size()[0], 3); + CORRADE_COMPARE(mutableAttribute.size()[0], 3); + CORRADE_COMPARE(mutableAttributeByName.size()[0], 3); + for(std::size_t i = 0; i != 3; ++i) { + CORRADE_ITERATION(i); + CORRADE_COMPARE_AS(attribute[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(attributeByName[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mutableAttribute[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mutableAttributeByName[i], positions2D[i], + TestSuite::Compare::Container); + } + + /* Typed access */ + attribute = data.attribute(0); + attributeByName = data.attribute(meshAttributeCustom(35)); + mutableAttribute = data.mutableAttribute(0); + mutableAttributeByName = data.mutableAttribute(meshAttributeCustom(35)); + CORRADE_COMPARE(attribute.size()[0], 3); + CORRADE_COMPARE(attributeByName.size()[0], 3); + CORRADE_COMPARE(mutableAttribute.size()[0], 3); + CORRADE_COMPARE(mutableAttributeByName.size()[0], 3); + for(std::size_t i = 0; i != 3; ++i) { + CORRADE_ITERATION(i); + CORRADE_COMPARE_AS(attribute[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(attributeByName[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mutableAttribute[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mutableAttributeByName[i], positions2D[i], + TestSuite::Compare::Container); + } +} + +void MeshDataTest::arrayAttributeWrongAccess() { + Vector2 vertexData[3*4]{ + {1.0f, 2.0f}, {3.0f, 4.0f}, {5.0f, 6.0f}, {7.0f, 8.0f}, + {1.1f, 2.2f}, {3.3f, 4.4f}, {5.5f, 6.6f}, {7.7f, 8.8f}, + {0.1f, 0.2f}, {0.3f, 0.4f}, {0.5f, 0.6f}, {0.7f, 0.8f}, + }; + Containers::StridedArrayView1D positions{vertexData, 3, 4*sizeof(Vector2)}; + Containers::StridedArrayView2D positions2D{vertexData, {3, 4}}; + + MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { + MeshAttributeData{MeshAttribute::Position, positions}, + MeshAttributeData{meshAttributeCustom(35), positions2D} + }}; + + std::ostringstream out; + Error redirectError{&out}; + data.attribute(0); + data.attribute(1); + data.mutableAttribute(0); + data.mutableAttribute(1); + data.attribute(MeshAttribute::Position); + data.attribute(meshAttributeCustom(35)); + data.mutableAttribute(MeshAttribute::Position); + data.mutableAttribute(meshAttributeCustom(35)); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::attribute(): use T[] to access an array attribute\n" + "Trade::MeshData::attribute(): use T[] to access an array attribute\n" + "Trade::MeshData::mutableAttribute(): use T[] to access an array attribute\n" + "Trade::MeshData::mutableAttribute(): use T[] to access an array attribute\n" + "Trade::MeshData::attribute(): use T[] to access an array attribute\n" + "Trade::MeshData::attribute(): use T[] to access an array attribute\n" + "Trade::MeshData::mutableAttribute(): use T[] to access an array attribute\n" + "Trade::MeshData::mutableAttribute(): use T[] to access an array attribute\n"); +} + void MeshDataTest::mutableAccessNotAllowed() { const UnsignedShort indexData[]{0, 1, 0}; const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; @@ -2073,6 +2343,7 @@ void MeshDataTest::attributeNotFound() { data.attributeFormat(2); data.attributeOffset(2); data.attributeStride(2); + data.attributeArraySize(2); data.attribute(2); data.attribute(2); data.attributeId(MeshAttribute::Position); @@ -2083,6 +2354,8 @@ void MeshDataTest::attributeNotFound() { data.attributeOffset(MeshAttribute::Color, 2); data.attributeStride(MeshAttribute::Position); data.attributeStride(MeshAttribute::Color, 2); + data.attributeArraySize(MeshAttribute::Position); + data.attributeArraySize(MeshAttribute::Color, 2); data.attribute(MeshAttribute::Position); data.attribute(MeshAttribute::Color, 2); data.attribute(MeshAttribute::Position); @@ -2097,6 +2370,7 @@ void MeshDataTest::attributeNotFound() { "Trade::MeshData::attributeFormat(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attributeOffset(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attributeStride(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeArraySize(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attributeId(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" @@ -2107,6 +2381,8 @@ void MeshDataTest::attributeNotFound() { "Trade::MeshData::attributeOffset(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" "Trade::MeshData::attributeStride(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attributeStride(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeArraySize(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attributeArraySize(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n"