diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index cb930b30d..7e597334d 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -57,7 +57,7 @@ MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexForma logic */ /** @todo support zero / negative stride? would be hard to transfer to GL */ CORRADE_ASSERT(data.empty() || std::ptrdiff_t(vertexFormatSize(format)) <= data.stride(), - "Trade::MeshAttributeData: view stride" << data.stride() << "is not large enough to contain" << format, ); + "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} { @@ -83,7 +83,7 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde "Trade::MeshData: indices are expected to be valid if there are no attributes and vertex count isn't passed explicitly", ); /** @todo some better value? attributeless indexed with defined vertex count? */ _vertexCount = 0; - } else _vertexCount = _attributes[0]._data.size(); + } else _vertexCount = _attributes[0]._vertexCount; CORRADE_ASSERT(!_indices.empty() || _indexData.empty(), "Trade::MeshData: indexData passed for a non-indexed mesh", ); @@ -97,12 +97,12 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde const MeshAttributeData& attribute = _attributes[i]; CORRADE_ASSERT(attribute._format != VertexFormat{}, "Trade::MeshData: attribute" << i << "doesn't specify anything", ); - - const Containers::StridedArrayView1D data = Containers::arrayCast(attribute._data); - CORRADE_ASSERT(data.size() == _vertexCount, - "Trade::MeshData: attribute" << i << "has" << data.size() << "vertices but" << _vertexCount << "expected", ); - CORRADE_ASSERT(data.empty() || (&data.front() >= _vertexData.begin() && &data.back() + vertexFormatSize(attribute._format) <= _vertexData.end()), - "Trade::MeshData: attribute" << i << "[" << Debug::nospace << static_cast(&data.front()) << Debug::nospace << ":" << Debug::nospace << static_cast(&data.back() + vertexFormatSize(attribute._format)) << 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 << "]", ); + 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 << "]", ); } #endif } @@ -233,13 +233,13 @@ 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.data()) - _vertexData.data(); + return static_cast(_attributes[id]._data) - _vertexData.data(); } UnsignedInt MeshData::attributeStride(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeStride(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); - return _attributes[id]._data.stride(); + return _attributes[id]._stride; } UnsignedInt MeshData::attributeCount(const MeshAttribute name) const { @@ -292,8 +292,9 @@ Containers::StridedArrayView2D MeshData::attribute(UnsignedInt id) c /* Build a 2D view using information about attribute type size, return only a prefix of the actual vertex count (which is zero in case vertex data is released) */ - return Containers::arrayCast<2, const char>(_attributes[id]._data, - vertexFormatSize(_attributes[id]._format)).prefix(_vertexCount); + return Containers::arrayCast<2, const char>( + attributeDataViewInternal(_attributes[id]), + vertexFormatSize(_attributes[id]._format)); } Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) { @@ -304,8 +305,9 @@ Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) /* Build a 2D view using information about attribute type size, return only a prefix of the actual vertex count (which is zero in case vertex data is released) */ - auto out = Containers::arrayCast<2, const char>(_attributes[id]._data, - vertexFormatSize(_attributes[id]._format)).prefix(_vertexCount); + auto out = Containers::arrayCast<2, const char>( + attributeDataViewInternal(_attributes[id]), + vertexFormatSize(_attributes[id]._format)); /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the @@ -360,45 +362,56 @@ Containers::Array MeshData::indicesAsArray() const { return output; } +Containers::StridedArrayView1D MeshData::attributeDataViewInternal(const MeshAttributeData& attribute) const { + 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{}}, + /* 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 */ + _vertexCount, attribute._stride}; +} + void MeshData::positions2DInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(MeshAttribute::Position, id); 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]; - + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination2f = Containers::arrayCast<2, Float>(destination); /* Copy 2D positions as-is, for 3D positions ignore Z */ if(attribute._format == VertexFormat::Vector2 || attribute._format == VertexFormat::Vector3) - Utility::copy(Containers::arrayCast(attribute._data), destination); + Utility::copy(Containers::arrayCast(attributeData), destination); else if(attribute._format == VertexFormat::Vector2h || attribute._format == VertexFormat::Vector3h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ub || attribute._format == VertexFormat::Vector3ub) - Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2b || attribute._format == VertexFormat::Vector3b) - Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2us || attribute._format == VertexFormat::Vector3us) - Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2s || attribute._format == VertexFormat::Vector3s) - Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ubNormalized || attribute._format == VertexFormat::Vector3ubNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2bNormalized || attribute._format == VertexFormat::Vector3bNormalized) - Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2usNormalized || attribute._format == VertexFormat::Vector3usNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2sNormalized || attribute._format == VertexFormat::Vector3sNormalized) - Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -413,54 +426,54 @@ 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]; - + 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); /* For 2D positions copy the XY part to the first two components */ if(attribute._format == VertexFormat::Vector2) - Utility::copy(Containers::arrayCast(attribute._data), + Utility::copy(Containers::arrayCast(attributeData), Containers::arrayCast(destination)); else if(attribute._format == VertexFormat::Vector2h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ub) - Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2b) - Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2us) - Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2s) - Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ubNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2bNormalized) - Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2usNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2sNormalized) - Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); /* Copy 3D positions as-is */ else if(attribute._format == VertexFormat::Vector3) - Utility::copy(Containers::arrayCast(attribute._data), destination); + Utility::copy(Containers::arrayCast(attributeData), destination); else if(attribute._format == VertexFormat::Vector3h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3ub) - Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 3), destination3f); + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3b) - Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 3), destination3f); + Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3us) - Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3s) - Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 3), destination3f); + Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3ubNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3bNormalized) - Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3usNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3sNormalized) - Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ /* For 2D positions finally fill the Z with a single value */ @@ -492,17 +505,17 @@ 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]; - + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination3f = Containers::arrayCast<2, Float>(destination); if(attribute._format == VertexFormat::Vector3) - Utility::copy(Containers::arrayCast(attribute._data), destination); + Utility::copy(Containers::arrayCast(attributeData), destination); else if(attribute._format == VertexFormat::Vector3h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3bNormalized) - Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3sNormalized) - Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -517,29 +530,29 @@ void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination2f = Containers::arrayCast<2, Float>(destination); if(attribute._format == VertexFormat::Vector2) - Utility::copy(Containers::arrayCast(attribute._data), destination); + Utility::copy(Containers::arrayCast(attributeData), destination); else if(attribute._format == VertexFormat::Vector2h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ub) - Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2b) - Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2us) - Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2s) - Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ubNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2bNormalized) - Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2usNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2sNormalized) - Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -554,32 +567,32 @@ 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]; - + 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); /* For three-component colors copy the RGB part to the first three components */ if(attribute._format == VertexFormat::Vector3) - Utility::copy(Containers::arrayCast(attribute._data), + Utility::copy(Containers::arrayCast(attributeData), Containers::arrayCast(destination)); else if(attribute._format == VertexFormat::Vector3h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3ubNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3usNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); /* Copy four-component colors as-is */ else if(attribute._format == VertexFormat::Vector4) - Utility::copy(Containers::arrayCast(attribute._data), + Utility::copy(Containers::arrayCast(attributeData), Containers::arrayCast(destination)); else if(attribute._format == VertexFormat::Vector4h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 4), destination4f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4f); else if(attribute._format == VertexFormat::Vector4ubNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 4), destination4f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 4), destination4f); else if(attribute._format == VertexFormat::Vector4usNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 4), destination4f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ /* For three-component colors finally fill the alpha with a single value */ diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 825deed27..95e79ddc0 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: _name{}, _format{}, _data{} {} + constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{} {} /** * @brief Type-erased constructor @@ -313,7 +313,10 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * multiple different attributes onto each other. Not meant to be * passed to @ref MeshData. */ - constexpr explicit MeshAttributeData(Int padding): _name{}, _format{}, _data{nullptr, 0, padding} {} + 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{} {} /** @brief Attribute name */ constexpr MeshAttribute name() const { return _name; } @@ -322,16 +325,29 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { constexpr VertexFormat format() const { return _format; } /** @brief Type-erased attribute data */ - constexpr Containers::StridedArrayView1D data() const { return _data; } + 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}; + } private: constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept; friend MeshData; - MeshAttribute _name; - /* Here's some room for flags */ + const void* _data; + /* Vertex count in MeshData is currently 32-bit, so this doesn't need + to be 64-bit either */ + UnsignedInt _vertexCount; VertexFormat _format; - Containers::StridedArrayView1D _data; + /* According to https://opengl.gpuinfo.org/displaycapability.php?name=GL_MAX_VERTEX_ATTRIB_STRIDE, + 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 */ }; /** @relatesalso MeshAttributeData @@ -1183,6 +1199,12 @@ class MAGNUM_TRADE_EXPORT MeshData { /* Internal helper that doesn't assert, unlike attributeId() */ UnsignedInt attributeFor(MeshAttribute name, UnsignedInt id) const; + /* Like attribute(), but returning just a 1D view */ + Containers::StridedArrayView1D attributeDataViewInternal(const MeshAttributeData& attribute) const; + + /* 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 */ UnsignedInt _vertexCount; MeshIndexType _indexType; MeshPrimitive _primitive; @@ -1298,9 +1320,14 @@ namespace Implementation { #endif constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept: - _name{name}, _format{format}, _data{(CORRADE_CONSTEXPR_ASSERT( - /* Double formats intentionally not supported for any builtin attributes - right now -- only for custom formats */ + _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), + "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 || @@ -1348,8 +1375,8 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V format == VertexFormat::Vector2s || format == VertexFormat::Vector2sNormalized)) || isMeshAttributeCustom(name) /* can be any format */, - "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), data)} - {} + "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name) + } {} 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 cde87476e..b865b6b4e 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -57,10 +57,10 @@ struct MeshDataTest: TestSuite::Tester { void constructAttribute2DWrongSize(); void constructAttribute2DNonContiguous(); void constructAttributeTypeErased(); - void constructAttributeTypeErasedWrongStride(); void constructAttributeNullptr(); void constructAttributePadding(); void constructAttributeNonOwningArray(); + void constructAttributeWrongStride(); void construct(); void constructZeroIndices(); @@ -173,10 +173,10 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttribute2DWrongSize, &MeshDataTest::constructAttribute2DNonContiguous, &MeshDataTest::constructAttributeTypeErased, - &MeshDataTest::constructAttributeTypeErasedWrongStride, &MeshDataTest::constructAttributeNullptr, &MeshDataTest::constructAttributePadding, &MeshDataTest::constructAttributeNonOwningArray, + &MeshDataTest::constructAttributeWrongStride, &MeshDataTest::construct, &MeshDataTest::constructZeroIndices, @@ -521,15 +521,6 @@ void MeshDataTest::constructAttributeTypeErased() { CORRADE_VERIFY(positions.data().data() == positionData); } -void MeshDataTest::constructAttributeTypeErasedWrongStride() { - char positionData[3*sizeof(Vector3)]{}; - - std::ostringstream out; - Error redirectError{&out}; - MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(positionData)}; - CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: view stride 1 is not large enough to contain VertexFormat::Vector3\n"); -} - void MeshDataTest::constructAttributeNullptr() { MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; CORRADE_COMPARE(positions.name(), MeshAttribute::Position); @@ -553,6 +544,23 @@ void MeshDataTest::constructAttributeNonOwningArray() { CORRADE_COMPARE(static_cast(array.data()), data); } +void MeshDataTest::constructAttributeWrongStride() { + char positionData[3*sizeof(Vector3)]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(positionData)}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::StridedArrayView1D{positionData, 0, -16}}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::StridedArrayView1D{positionData, 0, 65000}}; + MeshAttributeData{65000}; + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData: expected stride to be positive and enough to fit VertexFormat::Vector3, got 1\n" + "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got -16\n" + "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got 65000\n" + "Trade::MeshAttributeData: at most 32k padding supported, got 65000\n" + ); +} + void MeshDataTest::construct() { struct Vertex { Vector3 position;