From 8ea86b05f936f0f8534f120ca18a30f5dbfc3d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 11 Nov 2019 16:42:48 +0100 Subject: [PATCH] Trade: implement mutable data access in MeshData. Turns out the design wasn't so simple after all. AnimationData and ImageData classes will follow with similar changes. --- doc/snippets/MagnumTrade.cpp | 15 + src/Magnum/Trade/Data.cpp | 4 + src/Magnum/Trade/Data.h | 10 +- .../Trade/Implementation/arrayUtilities.h | 5 +- src/Magnum/Trade/MeshData.cpp | 62 ++- src/Magnum/Trade/MeshData.h | 287 ++++++++++++- src/Magnum/Trade/Test/MeshDataTest.cpp | 403 +++++++++++++++++- 7 files changed, 766 insertions(+), 20 deletions(-) diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index d9600728e..82296fae2 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -266,6 +266,21 @@ if(data.isIndexed()) { } #endif +{ +Trade::MeshData data{MeshPrimitive::Points, 0}; +/* [MeshData-usage-mutable] */ +/* Check prerequisites */ +if(!(data.vertexDataFlags() & Trade::DataFlag::Mutable) || + !data.hasAttribute(Trade::MeshAttribute::Position) || + data.attributeFormat(Trade::MeshAttribute::Position) != VertexFormat::Vector3) + Fatal{} << "Oh well"; + +/* Scale the mesh two times */ +MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{2.0f}), + data.mutableAttribute(Trade::MeshAttribute::Position)); +/* [MeshData-usage-mutable] */ +} + { Trade::MeshData2D& foo(); Trade::MeshData2D& data = foo(); diff --git a/src/Magnum/Trade/Data.cpp b/src/Magnum/Trade/Data.cpp index a0bc474e2..b7efc41c8 100644 --- a/src/Magnum/Trade/Data.cpp +++ b/src/Magnum/Trade/Data.cpp @@ -50,4 +50,8 @@ Debug& operator<<(Debug& debug, const DataFlags value) { DataFlag::Mutable}); } +namespace Implementation { + void nonOwnedArrayDeleter(char*, std::size_t) { /* does nothing */ } +} + }} diff --git a/src/Magnum/Trade/Data.h b/src/Magnum/Trade/Data.h index 9f2abcaf5..bc74cce2f 100644 --- a/src/Magnum/Trade/Data.h +++ b/src/Magnum/Trade/Data.h @@ -41,7 +41,8 @@ namespace Magnum { namespace Trade { @brief Data flag @m_since_latest -@see @ref DataFlags, @ref MeshData::dataFlags() +@see @ref DataFlags, @ref MeshData::indexDataFlags(), + @ref MeshData::vertexDataFlags() */ enum class DataFlag: UnsignedByte { /** @@ -70,7 +71,7 @@ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlag value); @brief Data flags @m_since_latest -@see @ref MeshData::dataFlags() +@see @ref MeshData::indexDataFlags(), @ref MeshData::vertexDataFlags() */ typedef Containers::EnumSet DataFlags; @@ -82,6 +83,11 @@ CORRADE_ENUMSET_OPERATORS(DataFlags) */ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlags value); +namespace Implementation { + /* Used internally by MeshData */ + MAGNUM_TRADE_EXPORT void nonOwnedArrayDeleter(char*, std::size_t); +} + }} #endif diff --git a/src/Magnum/Trade/Implementation/arrayUtilities.h b/src/Magnum/Trade/Implementation/arrayUtilities.h index fc1420fde..4ae2efd2a 100644 --- a/src/Magnum/Trade/Implementation/arrayUtilities.h +++ b/src/Magnum/Trade/Implementation/arrayUtilities.h @@ -32,9 +32,8 @@ namespace Magnum { namespace Trade { namespace Implementation { -/* Can't use InPlaceInit as that uses a custom deleters. Compared to - InPlaceInit it does an an unnecessary default-initialization of all - elements */ +/* Can't use InPlaceInit as that uses custom deleters. Compared to InPlaceInit + it does an an unnecessary default-initialization of all elements. */ /** @todo isn't there some C++56 feature that would allow me to allocate without calling constructors? */ template Containers::Array initializerListToArrayWithDefaultDeleter(const std::initializer_list list) { diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 67c67cd01..79c693610 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -58,7 +58,12 @@ MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexForma "Trade::MeshAttributeData:" << format << "is not a valid format for" << name, ); } -MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: _indexType{indices.type}, _primitive{primitive}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{indices.data} { +Containers::Array meshAttributeDataNonOwningArray(const Containers::ArrayView view) { + /* Ugly, eh? */ + return Containers::Array{const_cast(view.data()), view.size(), reinterpret_cast(Trade::Implementation::nonOwnedArrayDeleter)}; +} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: _indexType{indices.type}, _primitive{primitive}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{indices.data} { /* Save vertex count. It's a strided array view, so the size is not depending on type. */ if(_attributes.empty()) { @@ -92,13 +97,54 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* const importerState): MeshData{primitive, std::move(indexData), indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), importerState} { + CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); + CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); + _indexDataFlags = indexDataFlags; + _vertexDataFlags = vertexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, indexDataFlags, indexData, indices, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, std::move(vertexData), std::move(attributes), importerState} { + CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); + _indexDataFlags = indexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, indexDataFlags, indexData, indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), importerState} { + CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); + _vertexDataFlags = vertexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, std::move(indexData), indices, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} + MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, std::move(vertexData), std::move(attributes), importerState} {} MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), importerState} { + CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); + _vertexDataFlags = vertexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const void* const importerState): MeshData{primitive, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} + MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, {}, {}, importerState} {} -MeshData::MeshData(const MeshPrimitive primitive, const UnsignedInt vertexCount, const void* const importerState) noexcept: _vertexCount{vertexCount}, _indexType{}, _primitive{primitive}, _importerState{importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, importerState} { + CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); + _indexDataFlags = indexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, const UnsignedInt vertexCount, const void* const importerState) noexcept: _vertexCount{vertexCount}, _indexType{}, _primitive{primitive}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState} {} MeshData::~MeshData() = default; @@ -106,6 +152,18 @@ MeshData::MeshData(MeshData&&) noexcept = default; MeshData& MeshData::operator=(MeshData&&) noexcept = default; +Containers::ArrayView MeshData::mutableIndexData() & { + CORRADE_ASSERT(_indexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableIndexData(): index data not mutable", {}); + return _indexData; +} + +Containers::ArrayView MeshData::mutableVertexData() & { + CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableVertexData(): vertex data not mutable", {}); + return _vertexData; +} + UnsignedInt MeshData::indexCount() const { CORRADE_ASSERT(isIndexed(), "Trade::MeshData::indexCount(): the mesh is not indexed", {}); diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index de41b4d3b..e5ac1865b 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -35,8 +35,8 @@ #include "Magnum/Mesh.h" #include "Magnum/VertexFormat.h" +#include "Magnum/Trade/Data.h" #include "Magnum/Trade/Trade.h" -#include "Magnum/Trade/visibility.h" namespace Magnum { namespace Trade { @@ -243,6 +243,16 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { Containers::StridedArrayView1D data; }; +/** @relatesalso MeshAttributeData +@brief Create a non-owning array of @ref MeshAttributeData items +@m_since_latest + +Useful when you have the attribute definitions statically defined (for example +when the vertex data themselves are already defined at compile time) and don't +want to allocate just to pass those to @ref MeshData. +*/ +Containers::Array MAGNUM_TRADE_EXPORT meshAttributeDataNonOwningArray(Containers::ArrayView view); + /** @brief Mesh data @m_since_latest @@ -275,6 +285,20 @@ the GPU know of the format and layout: @snippet MagnumTrade.cpp MeshData-usage-advanced +@section Trade-MeshData-usage-mutable Mutable data access + +The interfaces implicitly provide @cpp const @ce views on the contained index +and vertex data through the @ref indexData(), @ref vertexData(), +@ref indices() and @ref attribute() accessors. This is done because in general +case the data can also refer to a memory-mapped file or constant memory. In +cases when it's desirable to modify the data in-place, there's the +@ref mutableIndexData(), @ref mutableVertexData(), @ref mutableIndices() and +@ref mutableAttribute() set of functions. To use these, you need to check that +the data are mutable using @ref indexDataFlags() or @ref vertexDataFlags() +first. The following snippet applies a transformation to the mesh data: + +@snippet MagnumTrade.cpp MeshData-usage-mutable + @see @ref AbstractImporter::mesh() */ class MAGNUM_TRADE_EXPORT MeshData { @@ -295,6 +319,12 @@ class MAGNUM_TRADE_EXPORT MeshData { * index-less attribute-less mesh, use * @ref MeshData(MeshPrimitive, UnsignedInt, const void*) to specify * desired vertex count. + * + * The @ref indexDataFlags() / @ref vertexDataFlags() are implicitly + * set to a combination of @ref DataFlag::Owned and + * @ref DataFlag::Mutable. For non-owned data use the + * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, const MeshIndexData&, DataFlags, Containers::ArrayView, Containers::Array&&, const void*) + * constructor or its variants instead. */ explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; @@ -302,6 +332,77 @@ class MAGNUM_TRADE_EXPORT MeshData { /* Not noexcept because allocation happens inside */ explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + /** + * @brief Construct indexed mesh data with non-owned index and vertex data + * @param primitive Primitive + * @param indexDataFlags Index data flags + * @param indexData View on index data + * @param indices Index data description + * @param vertexDataFlags Vertex data flags + * @param vertexData View on vertex data + * @param attributes Description of all vertex attribute data + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed vertex and index + * data. The @p indexDataFlags / @p vertexDataFlags parameters can + * contain @ref DataFlag::Mutable to indicate the external data can be + * modified, and is expected to *not* have @ref DataFlag::Owned set. + */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const void* importerState = nullptr); + + /** + * @brief Construct indexed mesh data with non-owned index data + * @param primitive Primitive + * @param indexDataFlags Index data flags + * @param indexData View on index data + * @param indices Index data description + * @param vertexData Vertex data + * @param attributes Description of all vertex attribute data + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed index data. The + * @p indexDataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. The @ref vertexDataFlags() are + * implicitly set to a combination of @ref DataFlag::Owned and + * @ref DataFlag::Mutable. + */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + + /** + * @brief Construct indexed mesh data with non-owned vertex data + * @param primitive Primitive + * @param indexData Index data + * @param indices Index data description + * @param vertexDataFlags Vertex data flags + * @param vertexData View on vertex data + * @param attributes Description of all vertex attribute data + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed vertex data. The + * @p vertexDataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. The @ref indexDataFlags() are + * implicitly set to a combination of @ref DataFlag::Owned and + * @ref DataFlag::Mutable. + */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const void* importerState = nullptr); + /** * @brief Construct a non-indexed mesh data * @param primitive Primitive @@ -311,6 +412,14 @@ class MAGNUM_TRADE_EXPORT MeshData { * * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) * with default-constructed @p indexData and @p indices arguments. + * + * The @ref vertexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For consistency, + * the @ref indexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there + * isn't any data to own or to mutate. For non-owned data use the + * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, Containers::Array&&, const void*) + * constructor instead. */ explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; @@ -318,6 +427,29 @@ class MAGNUM_TRADE_EXPORT MeshData { /* Not noexcept because allocation happens inside */ explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + /** + * @brief Construct a non-owned non-indexed mesh data + * @param primitive Primitive + * @param vertexDataFlags Vertex data flags + * @param vertexData View on vertex data + * @param attributes Description of all vertex attribute data + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p vertexDataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. For consistency, the + * @ref indexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there + * isn't any data to own or to mutate. + */ + explicit MeshData(MeshPrimitive primitive, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const void* importerState = nullptr); + /** * @brief Construct an attribute-less indexed mesh data * @param primitive Primitive @@ -331,16 +463,47 @@ class MAGNUM_TRADE_EXPORT MeshData { * to create an index-less attribute-less mesh, use * @ref MeshData(MeshPrimitive, UnsignedInt, const void*) to specify * desired vertex count. + * + * The @ref indexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For consistency, + * the @ref vertexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there + * isn't any data to own or to mutate. For non-owned data use the + * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, const MeshIndexData&, const void*) + * constructor instead. */ explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const void* importerState = nullptr) noexcept; + /** + * @brief Construct a non-owned attribute-less indexed mesh data + * @param primitive Primitive + * @param indexDataFlags Index data flags + * @param indexData View on index data + * @param indices Index data description + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, const void*) + * creates an instance that doesn't own the passed data. The + * @p indexDataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. For consistency, the + * @ref vertexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there + * isn't any data to own or to mutate. + */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, const void* importerState = nullptr) noexcept; + /** * @brief Construct an index-less attribute-less mesh data * @param primitive Primitive * @param vertexCount Desired count of vertices to draw * @param importerState Importer-specific state * - * Useful in case the drawing is fully driven by a shader. + * Useful in case the drawing is fully driven by a shader. For + * consistency, the @ref indexDataFlags() / @ref vertexDataFlags() are + * implicitly set to a combination of @ref DataFlag::Owned and + * @ref DataFlag::Mutable, even though there isn't any data to own or + * to mutate. */ explicit MeshData(MeshPrimitive primitive, UnsignedInt vertexCount, const void* importerState = nullptr) noexcept; @@ -358,6 +521,22 @@ class MAGNUM_TRADE_EXPORT MeshData { /** @brief Move assignment */ MeshData& operator=(MeshData&&) noexcept; + /** + * @brief Index data flags + * + * @see @ref releaseIndexData(), @ref mutableIndexData(), + * @ref mutableIndices() + */ + DataFlags indexDataFlags() const { return _indexDataFlags; } + + /** + * @brief Vertex data flags + * + * @see @ref releaseVertexData(), @ref mutableVertexData(), + * @ref mutableAttribute() + */ + DataFlags vertexDataFlags() const { return _vertexDataFlags; } + /** @brief Primitive */ MeshPrimitive primitive() const { return _primitive; } @@ -366,13 +545,25 @@ class MAGNUM_TRADE_EXPORT MeshData { * * Returns @cpp nullptr @ce if the mesh is not indexed. * @see @ref isIndexed(), @ref indexCount(), @ref indexType(), - * @ref indices(), @ref releaseIndexData() + * @ref indices(), @ref mutableIndexData(), @ref releaseIndexData() */ Containers::ArrayView indexData() const & { return _indexData; } /** @brief Taking a view to a r-value instance is not allowed */ Containers::ArrayView indexData() const && = delete; + /** + * @brief Mutable raw index data + * + * Like @ref indexData(), but returns a non-const view. Expects that + * the mesh is mutable. + * @see @ref indexDataFlags() + */ + Containers::ArrayView mutableIndexData() &; + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView mutableIndexData() && = delete; + /** * @brief Raw vertex data * @@ -380,13 +571,25 @@ class MAGNUM_TRADE_EXPORT MeshData { * the mesh has no attributes. * @see @ref attributeCount(), @ref attributeName(), * @ref attributeFormat(), @ref attribute(), - * @ref releaseVertexData() + * @ref mutableVertexData(), @ref releaseVertexData() */ Containers::ArrayView vertexData() const & { return _vertexData; } /** @brief Taking a view to a r-value instance is not allowed */ Containers::ArrayView vertexData() const && = delete; + /** + * @brief Mutable raw vertex data + * + * Like @ref vertexData(), but returns a non-const view. Expects that + * the mesh is mutable. + * @see @ref vertexDataFlags() + */ + Containers::ArrayView mutableVertexData() &; + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView mutableVertexData() && = delete; + /** @brief Whether the mesh is indexed */ bool isIndexed() const { return _indexType != MeshIndexType{}; } @@ -418,10 +621,19 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref indicesAsArray() accessor to get indices converted to 32-bit, * but note that such operation involves extra allocation and data * conversion. - * @see @ref isIndexed(), @ref attribute() + * @see @ref isIndexed(), @ref attribute(), @ref mutableIndices() */ template Containers::ArrayView indices() const; + /** + * @brief Mutable mesh indices + * + * Like @ref indices() const, but returns a mutable view. Expects that + * the mesh is mutable. + * @see @ref indexDataFlags() + */ + template Containers::ArrayView mutableIndices(); + /** * @brief Mesh vertex count * @@ -548,10 +760,20 @@ class MAGNUM_TRADE_EXPORT MeshData { * @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 + * @see @ref attribute(MeshAttribute, UnsignedInt) const, + * @ref mutableAttribute(MeshAttribute, UnsignedInt) */ template Containers::StridedArrayView1D attribute(UnsignedInt id) const; + /** + * @brief Mutable data for given attribute array + * + * 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); + /** * @brief Data for given named attribute array * @@ -564,10 +786,20 @@ class MAGNUM_TRADE_EXPORT MeshData { * 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 + * @see @ref attribute(UnsignedInt) const, + * @ref mutableAttribute(MeshAttribute, UnsignedInt) */ template Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0) const; + /** + * @brief Mutable data for given named attribute array + * + * 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); + /** * @brief Indices as 32-bit integers * @@ -701,8 +933,10 @@ class MAGNUM_TRADE_EXPORT MeshData { * * Releases the ownership of the index data array and resets internal * index-related state to default. The mesh then behaves like - * non-indexed. - * @see @ref indexData() + * non-indexed. Note that the returned array has a custom no-op deleter + * when the data are not owned by the mesh, and while the returned + * array type is mutable, the actual memory might be not. + * @see @ref indexData(), @ref indexDataFlags() */ Containers::Array releaseIndexData(); @@ -711,8 +945,10 @@ class MAGNUM_TRADE_EXPORT MeshData { * * Releases the ownership of the index data array and resets internal * attribute-related state to default. The mesh then behaves like if - * it has no attributes. - * @see @ref vertexData() + * it has no attributes. Note that the returned array has a custom + * no-op deleter when the data are not owned by the mesh, and while the + * returned array type is mutable, the actual memory might be not. + * @see @ref vertexData(), @ref vertexDataFlags() */ Containers::Array releaseVertexData(); @@ -734,6 +970,7 @@ class MAGNUM_TRADE_EXPORT MeshData { UnsignedInt _vertexCount; MeshIndexType _indexType; MeshPrimitive _primitive; + DataFlags _indexDataFlags, _vertexDataFlags; const void* _importerState; Containers::Array _indexData, _vertexData; Containers::Array _attributes; @@ -789,6 +1026,16 @@ template Containers::ArrayView MeshData::indices() const { return Containers::arrayCast(_indices); } +template Containers::ArrayView MeshData::mutableIndices() { + CORRADE_ASSERT(_indexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableIndices(): index data not mutable", {}); + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::mutableIndices(): the mesh is not indexed", {}); + CORRADE_ASSERT(Implementation::meshIndexTypeFor() == _indexType, + "Trade::MeshData::mutableIndices(): improper type requested for" << _indexType, nullptr); + return Containers::arrayCast(reinterpret_cast&>(_indices)); +} + template Containers::StridedArrayView1D MeshData::attribute(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); @@ -797,12 +1044,30 @@ template Containers::StridedArrayView1D MeshData::attribute(Un return Containers::arrayCast(_attributes[id].data); } +template Containers::StridedArrayView1D MeshData::mutableAttribute(UnsignedInt id) { + CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, + "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); + CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[id].format, + "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[id].name << "of format" << _attributes[id].format, nullptr); + return Containers::arrayCast(reinterpret_cast&>(_attributes[id].data)); +} + template Containers::StridedArrayView1D MeshData::attribute(MeshAttribute name, UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(name, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); return attribute(attributeId); } +template Containers::StridedArrayView1D MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { + CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return mutableAttribute(attributeId); +} + }} #endif diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index c3cd6a6c5..7f6a0caac 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -51,6 +51,7 @@ struct MeshDataTest: TestSuite::Tester { void constructAttributeWrongFormat(); void constructAttributeTypeErased(); void constructAttributeTypeErasedWrongStride(); + void constructAttributeNonOwningArray(); void construct(); void constructIndexless(); @@ -59,6 +60,12 @@ struct MeshDataTest: TestSuite::Tester { void constructIndexlessAttributeless(); void constructIndexlessAttributelessZeroVertices(); + void constructNotOwned(); + void constructIndicesNotOwned(); + void constructVerticesNotOwned(); + void constructIndexlessNotOwned(); + void constructAttributelessNotOwned(); + void constructIndexDataButNotIndexed(); void constructVertexDataButNoAttributes(); void constructVertexDataButNoVertices(); @@ -66,6 +73,12 @@ struct MeshDataTest: TestSuite::Tester { void constructIndicesNotContained(); void constructAttributeNotContained(); void constructInconsitentVertexCount(); + void constructNotOwnedIndexFlagOwned(); + void constructNotOwnedVertexFlagOwned(); + void constructIndicesNotOwnedFlagOwned(); + void constructVerticesNotOwnedFlagOwned(); + void constructIndexlessNotOwnedFlagOwned(); + void constructAttributelessNotOwnedFlagOwned(); void constructCopy(); void constructMove(); @@ -83,6 +96,8 @@ struct MeshDataTest: TestSuite::Tester { template void colorsAsArray(); void colorsIntoArrayInvalidSize(); + void mutableAccessNotAllowed(); + void indicesNotIndexed(); void indicesWrongType(); @@ -93,6 +108,24 @@ struct MeshDataTest: TestSuite::Tester { void releaseVertexData(); }; +struct { + const char* name; + DataFlags indexDataFlags, vertexDataFlags; +} NotOwnedData[] { + {"", {}, {}}, + {"indices mutable", DataFlag::Mutable, {}}, + {"vertices mutable", {}, DataFlag::Mutable}, + {"both mutable", DataFlag::Mutable, DataFlag::Mutable} +}; + +struct { + const char* name; + DataFlags dataFlags; +} SingleNotOwnedData[] { + {"", {}}, + {"mutable", DataFlag::Mutable} +}; + MeshDataTest::MeshDataTest() { addTests({&MeshDataTest::customAttributeName, &MeshDataTest::customAttributeNameTooLarge, @@ -109,21 +142,36 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttributeWrongFormat, &MeshDataTest::constructAttributeTypeErased, &MeshDataTest::constructAttributeTypeErasedWrongStride, + &MeshDataTest::constructAttributeNonOwningArray, &MeshDataTest::construct, &MeshDataTest::constructIndexless, &MeshDataTest::constructIndexlessZeroVertices, &MeshDataTest::constructAttributeless, &MeshDataTest::constructIndexlessAttributeless, - &MeshDataTest::constructIndexlessAttributelessZeroVertices, + &MeshDataTest::constructIndexlessAttributelessZeroVertices}); - &MeshDataTest::constructIndexDataButNotIndexed, + addInstancedTests({&MeshDataTest::constructNotOwned}, + Containers::arraySize(NotOwnedData)); + addInstancedTests({&MeshDataTest::constructIndicesNotOwned, + &MeshDataTest::constructVerticesNotOwned, + &MeshDataTest::constructIndexlessNotOwned, + &MeshDataTest::constructAttributelessNotOwned}, + Containers::arraySize(SingleNotOwnedData)); + + addTests({&MeshDataTest::constructIndexDataButNotIndexed, &MeshDataTest::constructVertexDataButNoAttributes, &MeshDataTest::constructVertexDataButNoVertices, &MeshDataTest::constructAttributelessInvalidIndices, &MeshDataTest::constructIndicesNotContained, &MeshDataTest::constructAttributeNotContained, &MeshDataTest::constructInconsitentVertexCount, + &MeshDataTest::constructNotOwnedIndexFlagOwned, + &MeshDataTest::constructNotOwnedVertexFlagOwned, + &MeshDataTest::constructIndicesNotOwnedFlagOwned, + &MeshDataTest::constructVerticesNotOwnedFlagOwned, + &MeshDataTest::constructIndexlessNotOwnedFlagOwned, + &MeshDataTest::constructAttributelessNotOwnedFlagOwned, &MeshDataTest::constructCopy, &MeshDataTest::constructMove, @@ -146,6 +194,8 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::colorsAsArray, &MeshDataTest::colorsIntoArrayInvalidSize, + &MeshDataTest::mutableAccessNotAllowed, + &MeshDataTest::indicesNotIndexed, &MeshDataTest::indicesWrongType, @@ -312,6 +362,13 @@ void MeshDataTest::constructAttributeTypeErasedWrongStride() { CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: view stride 1 is not large enough to contain VertexFormat::Vector3\n"); } +void MeshDataTest::constructAttributeNonOwningArray() { + const MeshAttributeData data[3]; + Containers::Array array = meshAttributeDataNonOwningArray(data); + CORRADE_COMPARE(array.size(), 3); + CORRADE_COMPARE(static_cast(array.data()), data); +} + void MeshDataTest::construct() { struct Vertex { Vector3 position; @@ -360,9 +417,13 @@ void MeshDataTest::construct() { std::move(vertexData), {positions, textureCoordinates, normals, textureCoordinates, ids}, &importerState}; /* Basics */ + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); CORRADE_COMPARE(static_cast(data.indexData()), indexView.data()); CORRADE_COMPARE(static_cast(data.vertexData()), vertexView.data()); + CORRADE_COMPARE(static_cast(data.mutableIndexData()), indexView.data()); + CORRADE_COMPARE(static_cast(data.mutableVertexData()), vertexView.data()); CORRADE_COMPARE(data.importerState(), &importerState); /* Index access */ @@ -400,6 +461,11 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attribute(2)[2], Vector3::zAxis()); CORRADE_COMPARE(data.attribute(3)[1], (Vector2{0.250f, 0.375f})); CORRADE_COMPARE(data.attribute(4)[1], -374); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.mutableAttribute(1)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.mutableAttribute(2)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.mutableAttribute(3)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.mutableAttribute(4)[1], -374); /* Attribute access by name */ CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Position)); @@ -438,6 +504,11 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f})); CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2], 22); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Normal)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2], 22); } void MeshDataTest::constructIndexless() { @@ -450,6 +521,10 @@ void MeshDataTest::constructIndexless() { int importerState; MeshAttributeData positions{MeshAttribute::Position, vertexView}; MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions}, &importerState}; + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + /* These are empty so it doesn't matter, but this is a nice non-restrictive + default */ + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::LineLoop); CORRADE_COMPARE(data.indexData(), nullptr); CORRADE_COMPARE(data.importerState(), &importerState); @@ -487,6 +562,10 @@ void MeshDataTest::constructAttributeless() { int importerState; MeshIndexData indices{indexView}; MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), indices, &importerState}; + /* These are empty so it doesn't matter, but this is a nice non-restrictive + default */ + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); CORRADE_COMPARE(data.vertexData(), nullptr); CORRADE_COMPARE(data.importerState(), &importerState); @@ -502,9 +581,215 @@ void MeshDataTest::constructAttributeless() { CORRADE_COMPARE(data.attributeCount(), 0); } +void MeshDataTest::constructNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + UnsignedShort indexData[]{0, 1, 0}; + Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + int importerState; + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + MeshData data{MeshPrimitive::Triangles, instanceData.indexDataFlags, Containers::arrayView(indexData), indices, instanceData.vertexDataFlags, Containers::arrayView(vertexData), {positions}, &importerState}; + + CORRADE_COMPARE(data.indexDataFlags(), instanceData.indexDataFlags); + CORRADE_COMPARE(data.vertexDataFlags(), instanceData.vertexDataFlags); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(data.indexData()), +indexData); + CORRADE_COMPARE(static_cast(data.vertexData()), +vertexData); + if(instanceData.indexDataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableIndexData()), +indexData); + if(instanceData.vertexDataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableVertexData()), +vertexData); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[1], 1); + CORRADE_COMPARE(data.indices()[2], 0); + if(instanceData.indexDataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableIndices()[1], 1); + CORRADE_COMPARE(data.mutableIndices()[2], 0); + } + + CORRADE_COMPARE(data.vertexCount(), 2); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(data.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.attribute(0)[1], (Vector2{0.4f, 0.5f})); + if(instanceData.vertexDataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableAttribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector2{0.4f, 0.5f})); + } +} + +void MeshDataTest::constructIndicesNotOwned() { + auto&& instanceData = SingleNotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + UnsignedShort indexData[]{0, 1, 0}; + Containers::Array vertexData{2*sizeof(Vector2)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0] = {0.1f, 0.2f}; + vertexView[1] = {0.4f, 0.5f}; + + int importerState; + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + MeshData data{MeshPrimitive::Triangles, instanceData.dataFlags, Containers::arrayView(indexData), indices, std::move(vertexData), {positions}, &importerState}; + + CORRADE_COMPARE(data.indexDataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(data.indexData()), +indexData); + CORRADE_COMPARE(static_cast(data.vertexData()), vertexView.data()); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableIndexData()), +indexData); + CORRADE_COMPARE(static_cast(data.mutableVertexData()), vertexView.data()); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[1], 1); + CORRADE_COMPARE(data.indices()[2], 0); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableIndices()[1], 1); + CORRADE_COMPARE(data.mutableIndices()[2], 0); + } + + CORRADE_COMPARE(data.vertexCount(), 2); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(data.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.attribute(0)[1], (Vector2{0.4f, 0.5f})); + CORRADE_COMPARE(data.mutableAttribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector2{0.4f, 0.5f})); +} + +void MeshDataTest::constructVerticesNotOwned() { + auto&& instanceData = SingleNotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + Containers::Array indexData{3*sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 0; + Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + int importerState; + MeshIndexData indices{indexView}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + MeshData data{MeshPrimitive::Triangles, std::move(indexData), indices, instanceData.dataFlags, Containers::arrayView(vertexData), {positions}, &importerState}; + + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(data.indexData()), indexView.data()); + CORRADE_COMPARE(static_cast(data.vertexData()), +vertexData); + CORRADE_COMPARE(static_cast(data.mutableIndexData()), indexView.data()); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableVertexData()), +vertexData); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[1], 1); + CORRADE_COMPARE(data.indices()[2], 0); + CORRADE_COMPARE(data.mutableIndices()[1], 1); + CORRADE_COMPARE(data.mutableIndices()[2], 0); + + CORRADE_COMPARE(data.vertexCount(), 2); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(data.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.attribute(0)[1], (Vector2{0.4f, 0.5f})); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableAttribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector2{0.4f, 0.5f})); + } +} + +void MeshDataTest::constructIndexlessNotOwned() { + auto&& instanceData = SingleNotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + int importerState; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + MeshData data{MeshPrimitive::LineLoop, instanceData.dataFlags, vertexData, {positions}, &importerState}; + + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::LineLoop); + CORRADE_COMPARE(data.indexData(), nullptr); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(data.mutableIndexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 2); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position), VertexFormat::Vector2); + CORRADE_COMPARE(data.attribute(MeshAttribute::Position)[1], (Vector2{0.4f, 0.5f})); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position)[1], (Vector2{0.4f, 0.5f})); +} + +void MeshDataTest::constructAttributelessNotOwned() { + auto&& instanceData = SingleNotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + UnsignedShort indexData[]{0, 1, 0}; + + int importerState; + MeshIndexData indices{indexData}; + MeshData data{MeshPrimitive::TriangleStrip, instanceData.dataFlags, indexData, indices, &importerState}; + CORRADE_COMPARE(data.indexDataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_COMPARE(data.vertexData(), nullptr); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(data.mutableVertexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[0], 0); + CORRADE_COMPARE(data.indices()[1], 1); + CORRADE_COMPARE(data.indices()[2], 0); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableIndices()[0], 0); + CORRADE_COMPARE(data.mutableIndices()[1], 1); + CORRADE_COMPARE(data.mutableIndices()[2], 0); + } + + CORRADE_COMPARE(data.vertexCount(), 0); /** @todo what to return here? */ + CORRADE_COMPARE(data.attributeCount(), 0); +} + void MeshDataTest::constructIndexlessAttributeless() { int importerState; MeshData data{MeshPrimitive::TriangleStrip, 37, &importerState}; + /* These are both empty so it doesn't matter, but this is a nice + non-restrictive default */ + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); CORRADE_COMPARE(data.indexData(), nullptr); CORRADE_COMPARE(data.vertexData(), nullptr); @@ -606,6 +891,91 @@ void MeshDataTest::constructInconsitentVertexCount() { "Trade::MeshData: attribute 1 has 2 vertices but 3 expected\n"); } +void MeshDataTest::constructNotOwnedIndexFlagOwned() { + const UnsignedShort indexData[]{0, 1, 0}; + const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, indexData, indices, {}, vertexData, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned index data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructNotOwnedVertexFlagOwned() { + const UnsignedShort indexData[]{0, 1, 0}; + const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, {}, indexData, indices, DataFlag::Owned, vertexData, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned vertex data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructIndicesNotOwnedFlagOwned() { + UnsignedShort indexData[]{0, 1, 0}; + Containers::Array vertexData{2*sizeof(Vector2)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0] = {0.1f, 0.2f}; + vertexView[1] = {0.4f, 0.5f}; + + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, indexData, indices, std::move(vertexData), {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned index data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructVerticesNotOwnedFlagOwned() { + Containers::Array indexData{3*sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 0; + Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + MeshIndexData indices{indexView}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, std::move(indexData), indices, DataFlag::Owned, vertexData, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned vertex data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructIndexlessNotOwnedFlagOwned() { + const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, vertexData, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned vertex data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructAttributelessNotOwnedFlagOwned() { + const UnsignedShort indexData[]{0, 1, 0}; + MeshIndexData indices{indexData}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, indexData, indices}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned index data but Trade::DataFlag::Owned\n"); +} + void MeshDataTest::constructCopy() { CORRADE_VERIFY(!(std::is_constructible{})); CORRADE_VERIFY(!(std::is_assignable{})); @@ -630,6 +1000,8 @@ void MeshDataTest::constructMove() { MeshData b{std::move(a)}; + CORRADE_COMPARE(b.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(b.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(b.primitive(), MeshPrimitive::Triangles); CORRADE_COMPARE(static_cast(b.indexData()), indexView.data()); CORRADE_COMPARE(static_cast(b.vertexData()), vertexView.data()); @@ -653,6 +1025,8 @@ void MeshDataTest::constructMove() { MeshData c{MeshPrimitive::LineLoop, 37}; c = std::move(b); + CORRADE_COMPARE(c.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(c.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(c.primitive(), MeshPrimitive::Triangles); CORRADE_COMPARE(static_cast(c.indexData()), indexView.data()); CORRADE_COMPARE(static_cast(c.vertexData()), vertexView.data()); @@ -851,6 +1225,31 @@ void MeshDataTest::colorsIntoArrayInvalidSize() { "Trade::MeshData::colorsInto(): expected a view with 3 elements but got 2\n"); } +void MeshDataTest::mutableAccessNotAllowed() { + const UnsignedShort indexData[]{0, 1, 0}; + const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + MeshData data{MeshPrimitive::Triangles, {}, indexData, indices, {}, vertexData, {positions}}; + CORRADE_COMPARE(data.indexDataFlags(), DataFlags{}); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlags{}); + + std::ostringstream out; + Error redirectError{&out}; + data.mutableIndexData(); + data.mutableVertexData(); + data.mutableIndices(); + data.mutableAttribute(0); + data.mutableAttribute(MeshAttribute::Position); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::mutableIndexData(): index data not mutable\n" + "Trade::MeshData::mutableVertexData(): vertex data not mutable\n" + "Trade::MeshData::mutableIndices(): index data not mutable\n" + "Trade::MeshData::mutableAttribute(): vertex data not mutable\n" + "Trade::MeshData::mutableAttribute(): vertex data not mutable\n"); +} + void MeshDataTest::indicesNotIndexed() { MeshData data{MeshPrimitive::Triangles, 37};