Browse Source

Trade: support offset-only MeshAttributeData.

Originally this was done in order to make handling of deserialized data
much simpler (as for those attributes also need to only contain an
offset into some unknown data array), but seems this could be very
useful elsewhere as well -- for example when the layout is known
beforehand but the actual data not yet -- such as in the Line and
Gradient primitives (going to switch them to this in the next commit).

What still unfortunately has to be known in advance is the actual vertex
count (as supplying it directly to MeshData would mean adding 6 new
constructor overloads, and there's enough of those already). Might
revisit later.
pull/371/head
Vladimír Vondruš 6 years ago
parent
commit
47695f0978
  1. 6
      src/Magnum/MeshTools/Duplicate.cpp
  2. 3
      src/Magnum/MeshTools/Duplicate.h
  3. 6
      src/Magnum/MeshTools/Interleave.cpp
  4. 13
      src/Magnum/MeshTools/Interleave.h
  5. 16
      src/Magnum/MeshTools/Test/DuplicateTest.cpp
  6. 14
      src/Magnum/MeshTools/Test/InterleaveTest.cpp
  7. 29
      src/Magnum/Trade/MeshData.cpp
  8. 240
      src/Magnum/Trade/MeshData.h
  9. 91
      src/Magnum/Trade/Test/MeshDataTest.cpp

6
src/Magnum/MeshTools/Duplicate.cpp

@ -93,6 +93,12 @@ Trade::MeshData duplicate(const Trade::MeshData& data, const Containers::ArrayVi
/* Padding, ignore */
if(extra[i].format() == VertexFormat{}) continue;
/* Asserting here even though data() has another assert since that one
would be too confusing in this context */
CORRADE_ASSERT(!extra[i].isOffsetOnly(),
"MeshTools::duplicate(): extra attribute" << i << "is offset-only, which is not supported",
(Trade::MeshData{MeshPrimitive::Triangles, 0}));
/* Copy the attribute in, if it is non-empty, otherwise keep the
memory uninitialized */
if(extra[i].data()) {

3
src/Magnum/MeshTools/Duplicate.h

@ -137,7 +137,8 @@ any, are duplicated and interleaved together with existing attributes (or, in
case the attribute view is empty, only the corresponding space for given
attribute type is reserved, with memory left uninitialized). The data layouting
is done by @ref interleavedLayout(), see its documentation for detailed
behavior description.
behavior description. Note that offset-only @ref Trade::MeshAttributeData
instances are not supported in the @p extra array.
Expects that @p data is indexed and each attribute in @p extra has either the
same amount of elements as @p data vertex count (*not* index count) or has

6
src/Magnum/MeshTools/Interleave.cpp

@ -185,6 +185,12 @@ Trade::MeshData interleave(Trade::MeshData&& data, const Containers::ArrayView<c
/* Padding, ignore */
if(extra[i].format() == VertexFormat{}) continue;
/* Asserting here even though data() has another assert since that
one would be too confusing in this context */
CORRADE_ASSERT(!extra[i].isOffsetOnly(),
"MeshTools::interleave(): extra attribute" << i << "is offset-only, which is not supported",
(Trade::MeshData{MeshPrimitive::Triangles, 0}));
/* Copy the attribute in, if it is non-empty, otherwise keep the
memory uninitialized */
if(extra[i].data()) {

13
src/Magnum/MeshTools/Interleave.h

@ -217,11 +217,11 @@ any, are interleaved together with existing attributes. Returned instance
vertex data flags have both @ref Trade::DataFlag::Mutable and @ref Trade::DataFlag::Owned, so mutable attribute access is guaranteed.
For greater control you can also pass an empty @ref Trade::MeshData instance
and fill @p extra with attributes cherry-picked from
@ref Trade::MeshData::attributeData() of an existing instance. By default the
attributes are tightly packed, you can add arbitrary padding using instances
constructed via @ref Trade::MeshAttributeData::MeshAttributeData(Int).
Example:
and fill @p extra with attributes cherry-picked using
@ref Trade::MeshData::attributeData(UnsignedInt) const on an existing instance.
By default the attributes are tightly packed, you can add arbitrary padding
using instances constructed via
@ref Trade::MeshAttributeData::MeshAttributeData(Int). Example:
@snippet MagnumMeshTools.cpp interleavedLayout-extra
@ -249,7 +249,8 @@ interleaved together with existing attributes (or, in case the attribute view
is empty, only the corresponding space for given attribute type is reserved,
with memory left uninitialized). The data layouting is done by
@ref interleavedLayout(), see its documentation for detailed behavior
description.
description. Note that offset-only @ref Trade::MeshAttributeData instances are
not supported in the @p extra array.
Expects that each attribute in @p extra has either the same amount of elements
as @p data vertex count or has none.

16
src/Magnum/MeshTools/Test/DuplicateTest.cpp

@ -60,6 +60,7 @@ struct DuplicateTest: TestSuite::Tester {
void duplicateMeshDataExtra();
void duplicateMeshDataExtraEmpty();
void duplicateMeshDataExtraWrongCount();
void duplicateMeshDataExtraOffsetOnly();
void duplicateMeshDataNoAttributes();
};
@ -90,6 +91,7 @@ DuplicateTest::DuplicateTest() {
&DuplicateTest::duplicateMeshDataExtra,
&DuplicateTest::duplicateMeshDataExtraEmpty,
&DuplicateTest::duplicateMeshDataExtraWrongCount,
&DuplicateTest::duplicateMeshDataExtraOffsetOnly,
&DuplicateTest::duplicateMeshDataNoAttributes});
}
@ -359,6 +361,20 @@ void DuplicateTest::duplicateMeshDataExtraWrongCount() {
CORRADE_COMPARE(out.str(), "MeshTools::duplicate(): extra attribute 1 expected to have 3 items but got 2\n");
}
void DuplicateTest::duplicateMeshDataExtraOffsetOnly() {
UnsignedByte indices[]{0, 1, 2, 2, 1, 0};
Trade::MeshData data{MeshPrimitive::TriangleFan,
{}, indices, Trade::MeshIndexData{indices}};
std::ostringstream out;
Error redirectError{&out};
MeshTools::duplicate(data, {
Trade::MeshAttributeData{10},
Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, 3, 5, 14}
});
CORRADE_COMPARE(out.str(), "MeshTools::duplicate(): extra attribute 1 is offset-only, which is not supported\n");
}
void DuplicateTest::duplicateMeshDataNoAttributes() {
UnsignedByte indices[]{0, 1, 2, 2, 1, 0};
Trade::MeshData data{MeshPrimitive::Lines,

14
src/Magnum/MeshTools/Test/InterleaveTest.cpp

@ -73,6 +73,7 @@ struct InterleaveTest: Corrade::TestSuite::Tester {
void interleaveMeshDataExtraEmpty();
void interleaveMeshDataExtraOriginalEmpty();
void interleaveMeshDataExtraWrongCount();
void interleaveMeshDataExtraOffsetOnly();
void interleaveMeshDataAlreadyInterleavedMove();
void interleaveMeshDataAlreadyInterleavedMoveNonOwned();
void interleaveMeshDataNothing();
@ -112,6 +113,7 @@ InterleaveTest::InterleaveTest() {
&InterleaveTest::interleaveMeshDataExtraEmpty,
&InterleaveTest::interleaveMeshDataExtraOriginalEmpty,
&InterleaveTest::interleaveMeshDataExtraWrongCount,
&InterleaveTest::interleaveMeshDataExtraOffsetOnly,
&InterleaveTest::interleaveMeshDataAlreadyInterleavedMove,
&InterleaveTest::interleaveMeshDataAlreadyInterleavedMoveNonOwned,
&InterleaveTest::interleaveMeshDataNothing});
@ -732,6 +734,18 @@ void InterleaveTest::interleaveMeshDataExtraWrongCount() {
CORRADE_COMPARE(out.str(), "MeshTools::interleave(): extra attribute 1 expected to have 3 items but got 2\n");
}
void InterleaveTest::interleaveMeshDataExtraOffsetOnly() {
Trade::MeshData data{MeshPrimitive::TriangleFan, 5};
std::ostringstream out;
Error redirectError{&out};
MeshTools::interleave(data, {
Trade::MeshAttributeData{10},
Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, 3, 5, 14}
});
CORRADE_COMPARE(out.str(), "MeshTools::interleave(): extra attribute 1 is offset-only, which is not supported\n");
}
void InterleaveTest::interleaveMeshDataAlreadyInterleavedMove() {
Containers::Array<char> indexData{4};
auto indexView = Containers::arrayCast<UnsignedShort>(indexData);

29
src/Magnum/Trade/MeshData.cpp

@ -99,10 +99,17 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array<char>&& inde
"Trade::MeshData: attribute" << i << "doesn't specify anything", );
CORRADE_ASSERT(attribute._vertexCount == _vertexCount,
"Trade::MeshData: attribute" << i << "has" << attribute._vertexCount << "vertices but" << _vertexCount << "expected", );
const void* const begin = static_cast<const char*>(attribute._data);
const void* const end = static_cast<const char*>(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<const void*>(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast<const void*>(_vertexData.end()) << Debug::nospace << "]", );
const UnsignedInt typeSize = vertexFormatSize(attribute._format);
if(attribute._isOffsetOnly) {
const std::size_t size = attribute._data.offset + (_vertexCount - 1)*attribute._stride + typeSize;
CORRADE_ASSERT(!_vertexCount || size <= _vertexData.size(),
"Trade::MeshData: offset attribute" << i << "spans" << size << "bytes but passed vertexData array has only" << _vertexData.size(), );
} else {
const void* const begin = static_cast<const char*>(attribute._data.pointer);
const void* const end = static_cast<const char*>(attribute._data.pointer) + (_vertexCount - 1)*attribute._stride + typeSize;
CORRADE_ASSERT(!_vertexCount || (begin >= _vertexData.begin() && end <= _vertexData.end()),
"Trade::MeshData: attribute" << i << "[" << Debug::nospace << begin << Debug::nospace << ":" << Debug::nospace << end << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast<const void*>(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast<const void*>(_vertexData.end()) << Debug::nospace << "]", );
}
}
#endif
}
@ -218,6 +225,14 @@ Containers::StridedArrayView2D<char> MeshData::mutableIndices() {
out.size(), out.stride()};
}
MeshAttributeData MeshData::attributeData(UnsignedInt id) const {
CORRADE_ASSERT(id < _attributes.size(),
"Trade::MeshData::attributeData(): index" << id << "out of range for" << _attributes.size() << "attributes", MeshAttributeData{});
const MeshAttributeData& attribute = _attributes[id];
return attribute._isOffsetOnly ? MeshAttributeData{attribute._name,
attribute._format, attributeDataViewInternal(attribute)} : attribute;
}
MeshAttribute MeshData::attributeName(UnsignedInt id) const {
CORRADE_ASSERT(id < _attributes.size(),
"Trade::MeshData::attributeName(): index" << id << "out of range for" << _attributes.size() << "attributes", {});
@ -233,7 +248,8 @@ VertexFormat MeshData::attributeFormat(UnsignedInt id) const {
std::size_t MeshData::attributeOffset(UnsignedInt id) const {
CORRADE_ASSERT(id < _attributes.size(),
"Trade::MeshData::attributeOffset(): index" << id << "out of range for" << _attributes.size() << "attributes", {});
return static_cast<const char*>(_attributes[id]._data) - _vertexData.data();
return _attributes[id]._isOffsetOnly ? _attributes[id]._data.offset :
static_cast<const char*>(_attributes[id]._data.pointer) - _vertexData.data();
}
UnsignedInt MeshData::attributeStride(UnsignedInt id) const {
@ -366,7 +382,8 @@ Containers::StridedArrayView1D<const void> MeshData::attributeDataViewInternal(c
return Containers::StridedArrayView1D<const void>{
/* We're *sure* the view is correct, so faking the view size */
/** @todo better ideas for the StridedArrayView API? */
{attribute._data, ~std::size_t{}},
{attribute._isOffsetOnly ? _vertexData.data() + attribute._data.offset :
attribute._data.pointer, ~std::size_t{}},
/* Not using attribute._vertexCount because that gets stale after
releaseVertexData() gets called, and then we would need to slice the
result inside attribute() and elsewhere */

240
src/Magnum/Trade/MeshData.h

@ -244,7 +244,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData {
* initialization of the attribute array for @ref MeshData, expected to
* be replaced with concrete values later.
*/
constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{} {}
constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{}, _isOffsetOnly{false} {}
/**
* @brief Type-erased constructor
@ -305,6 +305,24 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData {
/** @overload */
template<class T> constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView<T>& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {}
/**
* @brief Construct an offset-only attribute
* @param name Attribute name
* @param format Attribute format
* @param offset Attribute data offset
* @param vertexCount Attribute vertex count
* @param stride Attribute stride
*
* Instances created this way refer to an offset in unspecified
* external vertex data instead of containing the data view directly.
* Useful when the location of the vertex data array is not known at
* attribute construction time. Note that instances created this way
* can't be used in most @ref MeshTools algorithms.
* @see @ref isOffsetOnly(),
* @ref data(Containers::ArrayView<const void>) const
*/
explicit constexpr MeshAttributeData(MeshAttribute name, VertexFormat format, std::size_t offset, UnsignedInt vertexCount, std::ptrdiff_t stride) noexcept;
/**
* @brief Construct a pad value
*
@ -316,7 +334,17 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData {
constexpr explicit MeshAttributeData(Int padding): _data{nullptr}, _vertexCount{0}, _format{}, _stride{
(CORRADE_CONSTEXPR_ASSERT(padding >= -32768 && padding <= 32767,
"Trade::MeshAttributeData: at most 32k padding supported, got" << padding), Short(padding))
}, _name{} {}
}, _name{}, _isOffsetOnly{false} {}
/**
* @brief If the attribute is offset-only
*
* Returns @cpp true @ce if the attribute doesn't contain the data view
* directly, but instead refers to unspecified external vertex data.
* @see @ref data(Containers::ArrayView<const void>) const,
* @ref MeshAttributeData(MeshAttribute, VertexFormat, std::size_t, UnsignedInt, std::ptrdiff_t)
*/
constexpr bool isOffsetOnly() const { return _isOffsetOnly; }
/** @brief Attribute name */
constexpr MeshAttribute name() const { return _name; }
@ -324,20 +352,48 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData {
/** @brief Attribute format */
constexpr VertexFormat format() const { return _format; }
/** @brief Type-erased attribute data */
/**
* @brief Type-erased attribute data
*
* Expects that the attribute is not offset-only, in that case use the
* @ref data(Containers::ArrayView<const void>) const overload
* instead.
* @see @ref isOffsetOnly()
*/
constexpr Containers::StridedArrayView1D<const void> data() const {
return Containers::StridedArrayView1D<const void>{
/* We're *sure* the view is correct, so faking the view size */
/** @todo better ideas for the StridedArrayView API? */
{_data, ~std::size_t{}},
_vertexCount, _stride};
{_data.pointer, ~std::size_t{}}, _vertexCount,
(CORRADE_CONSTEXPR_ASSERT(!_isOffsetOnly, "Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array"), _stride)};
}
/**
* @brief Type-erased attribute data for an offset-only attribute
*
* If the attribute is not offset-only, the @ref vertexData parameter
* is ignored.
* @see @ref isOffsetOnly(), @ref data() const
*/
Containers::StridedArrayView1D<const void> data(Containers::ArrayView<const void> vertexData) const {
return Containers::StridedArrayView1D<const void>{
/* We're *sure* the view is correct, so faking the view size */
/** @todo better ideas for the StridedArrayView API? */
vertexData, _isOffsetOnly ? reinterpret_cast<const char*>(vertexData.data()) + _data.offset : _data.pointer, _vertexCount, _stride};
}
private:
constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D<const void>& data, std::nullptr_t) noexcept;
friend MeshData;
const void* _data;
union Data {
/* FFS C++ why this doesn't JUST WORK goddamit?! */
constexpr Data(const void* pointer = nullptr): pointer{pointer} {}
constexpr Data(std::size_t offset): offset{offset} {}
const void* pointer;
std::size_t offset;
} _data;
/* Vertex count in MeshData is currently 32-bit, so this doesn't need
to be 64-bit either */
UnsignedInt _vertexCount;
@ -346,8 +402,9 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData {
current largest reported stride is 4k so 32k should be enough */
Short _stride;
MeshAttribute _name;
/* 4 bytes free for more stuff on 64b (20, aligned to 24); nothing on
32b */
bool _isOffsetOnly;
/* 3 bytes free for more stuff on 64b (21, aligned to 24) and on 32b
(17 used, aligned to 20) */
};
/** @relatesalso MeshAttributeData
@ -679,14 +736,21 @@ class MAGNUM_TRADE_EXPORT MeshData {
/**
* @brief Raw attribute metadata
*
* Useful mainly for passing particular attributes to @ref MeshTools
* algorithms, everything is otherwise exposed directly through various
* `attribute*()` getters. Returns @cpp nullptr @ce if the mesh has no
* attributes.
* @see @ref attributeCount(), @ref attributeName(),
* @ref attributeFormat(), @ref attribute()
* Returns the raw data that are used as a base for all `attribute*()`
* accessors, or @cpp nullptr @ce if the mesh has no attributes. In
* most cases you don't want to access those directly, but rather use
* the @ref attribute(), @ref attributeName(), @ref attributeFormat(),
* @ref attributeOffset(), @ref attributeStride() etc. accessors.
* Compared to those and to @ref attributeData(UnsignedInt) const, the
* @ref MeshAttributeData instances returned by this function may have
* different data pointers, and some of them might be offset-only ---
* use this function only if you *really* know what are you doing.
* @see @ref MeshAttributeData::isOffsetOnly()
*/
Containers::ArrayView<const MeshAttributeData> attributeData() const { return _attributes; }
Containers::ArrayView<const MeshAttributeData> attributeData() const & { return _attributes; }
/** @brief Taking a view to a r-value instance is not allowed */
Containers::ArrayView<const MeshAttributeData> attributeData() && = delete;
/**
* @brief Raw vertex data
@ -812,6 +876,25 @@ class MAGNUM_TRADE_EXPORT MeshData {
*/
UnsignedInt attributeCount() const { return _attributes.size(); }
/**
* @brief Raw attribute data
*
* Returns the raw data that are used as a base for all `attribute*()`
* accessors. In most cases you don't want to access those directly,
* but rather use the @ref attribute(), @ref attributeName(),
* @ref attributeFormat(), @ref attributeOffset(),
* @ref attributeStride() etc. accessors.
*
* Useful mainly for passing particular attributes unchanged directly
* to @ref MeshTools algorithms --- unlike with @ref attributeData()
* and @ref releaseAttributeData(), returned instances are guaranteed
* to always have an absolute data pointer (i.e.,
* @ref MeshAttributeData::isOffsetOnly() always returning
* @cpp false @ce). The @p id is expected to be smaller than
* @ref attributeCount() const.
*/
MeshAttributeData attributeData(UnsignedInt id) const;
/**
* @brief Attribute name
*
@ -1159,10 +1242,13 @@ class MAGNUM_TRADE_EXPORT MeshData {
* like if it has no attributes (but it can still have a non-zero
* vertex count). Note that the returned array has a custom no-op
* deleter when the data are not owned by the mesh, and while the
* returned array type is mutable, the actual memory might be not ---
* use this function only if you are sure about the origin of the
* array.
* @see @ref attributeData()
* returned array type is mutable, the actual memory might be not.
* Additionally, the returned @ref MeshAttributeData instances
* may have different data pointers and sizes than what's returned by
* the @ref attribute() and @ref attributeData(UnsignedInt) const
* accessors as some of them might be offset-only --- use this function
* only if you *really* know what are you doing.
* @see @ref attributeData(), @ref MeshAttributeData::isOffsetOnly()
*/
Containers::Array<MeshAttributeData> releaseAttributeData();
@ -1316,6 +1402,59 @@ namespace Implementation {
#undef _c
#endif
/* LCOV_EXCL_STOP */
constexpr bool isVertexFormatCompatibleWithAttribute(MeshAttribute name, VertexFormat format) {
/* Double types intentionally not supported for any builtin attributes
right now -- only for custom types */
return
(name == MeshAttribute::Position &&
(format == VertexFormat::Vector2 ||
format == VertexFormat::Vector2h ||
format == VertexFormat::Vector2ub ||
format == VertexFormat::Vector2ubNormalized ||
format == VertexFormat::Vector2b ||
format == VertexFormat::Vector2bNormalized ||
format == VertexFormat::Vector2us ||
format == VertexFormat::Vector2usNormalized ||
format == VertexFormat::Vector2s ||
format == VertexFormat::Vector2sNormalized ||
format == VertexFormat::Vector3 ||
format == VertexFormat::Vector3h ||
format == VertexFormat::Vector3ub ||
format == VertexFormat::Vector3ubNormalized ||
format == VertexFormat::Vector3b ||
format == VertexFormat::Vector3bNormalized ||
format == VertexFormat::Vector3us ||
format == VertexFormat::Vector3usNormalized ||
format == VertexFormat::Vector3s ||
format == VertexFormat::Vector3sNormalized)) ||
(name == MeshAttribute::Normal &&
(format == VertexFormat::Vector3 ||
format == VertexFormat::Vector3h ||
format == VertexFormat::Vector3bNormalized ||
format == VertexFormat::Vector3sNormalized)) ||
(name == MeshAttribute::Color &&
(format == VertexFormat::Vector3 ||
format == VertexFormat::Vector3h ||
format == VertexFormat::Vector3ubNormalized ||
format == VertexFormat::Vector3usNormalized ||
format == VertexFormat::Vector4 ||
format == VertexFormat::Vector4h ||
format == VertexFormat::Vector4ubNormalized ||
format == VertexFormat::Vector4usNormalized)) ||
(name == MeshAttribute::TextureCoordinates &&
(format == VertexFormat::Vector2 ||
format == VertexFormat::Vector2h ||
format == VertexFormat::Vector2ub ||
format == VertexFormat::Vector2ubNormalized ||
format == VertexFormat::Vector2b ||
format == VertexFormat::Vector2bNormalized ||
format == VertexFormat::Vector2us ||
format == VertexFormat::Vector2usNormalized ||
format == VertexFormat::Vector2s ||
format == VertexFormat::Vector2sNormalized)) ||
isMeshAttributeCustom(name); /* can be any format */
}
}
#endif
@ -1325,58 +1464,19 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V
_stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(data.stride()) & 0xffff8000),
"Trade::MeshAttributeData: expected stride to be positive and at most 32k, got" << data.stride()),
Short(data.stride()))
}, _name{(CORRADE_CONSTEXPR_ASSERT(
/* Double types intentionally not supported for any builtin attributes
right now -- only for custom types */
(name == MeshAttribute::Position &&
(format == VertexFormat::Vector2 ||
format == VertexFormat::Vector2h ||
format == VertexFormat::Vector2ub ||
format == VertexFormat::Vector2ubNormalized ||
format == VertexFormat::Vector2b ||
format == VertexFormat::Vector2bNormalized ||
format == VertexFormat::Vector2us ||
format == VertexFormat::Vector2usNormalized ||
format == VertexFormat::Vector2s ||
format == VertexFormat::Vector2sNormalized ||
format == VertexFormat::Vector3 ||
format == VertexFormat::Vector3h ||
format == VertexFormat::Vector3ub ||
format == VertexFormat::Vector3ubNormalized ||
format == VertexFormat::Vector3b ||
format == VertexFormat::Vector3bNormalized ||
format == VertexFormat::Vector3us ||
format == VertexFormat::Vector3usNormalized ||
format == VertexFormat::Vector3s ||
format == VertexFormat::Vector3sNormalized)) ||
(name == MeshAttribute::Normal &&
(format == VertexFormat::Vector3 ||
format == VertexFormat::Vector3h ||
format == VertexFormat::Vector3bNormalized ||
format == VertexFormat::Vector3sNormalized)) ||
(name == MeshAttribute::Color &&
(format == VertexFormat::Vector3 ||
format == VertexFormat::Vector3h ||
format == VertexFormat::Vector3ubNormalized ||
format == VertexFormat::Vector3usNormalized ||
format == VertexFormat::Vector4 ||
format == VertexFormat::Vector4h ||
format == VertexFormat::Vector4ubNormalized ||
format == VertexFormat::Vector4usNormalized)) ||
(name == MeshAttribute::TextureCoordinates &&
(format == VertexFormat::Vector2 ||
format == VertexFormat::Vector2h ||
format == VertexFormat::Vector2ub ||
format == VertexFormat::Vector2ubNormalized ||
format == VertexFormat::Vector2b ||
format == VertexFormat::Vector2bNormalized ||
format == VertexFormat::Vector2us ||
format == VertexFormat::Vector2usNormalized ||
format == VertexFormat::Vector2s ||
format == VertexFormat::Vector2sNormalized)) ||
isMeshAttributeCustom(name) /* can be any format */,
}, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format),
"Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name)
}, _isOffsetOnly{false} {}
constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const std::size_t offset, const UnsignedInt vertexCount, const std::ptrdiff_t stride) noexcept:
_data{offset}, _vertexCount{vertexCount}, _format{format},
/** @todo support zero / negative stride? would be hard to transfer to GL */
_stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(stride) & 0xffff8000),
"Trade::MeshAttributeData: expected stride to be positive and at most 32k, got" << stride),
Short(stride))
}, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format),
"Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name)
} {}
}, _isOffsetOnly{true} {}
template<class T> constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D<T>& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor<typename std::remove_const<T>::type>(), data, nullptr} {}

91
src/Magnum/Trade/Test/MeshDataTest.cpp

@ -52,7 +52,6 @@ struct MeshDataTest: TestSuite::Tester {
void constructAttribute();
void constructAttributeCustom();
void constructAttributeWrongFormat();
void constructAttribute2D();
void constructAttribute2DWrongSize();
void constructAttribute2DNonContiguous();
@ -60,7 +59,10 @@ struct MeshDataTest: TestSuite::Tester {
void constructAttributeNullptr();
void constructAttributePadding();
void constructAttributeNonOwningArray();
void constructAttributeOffsetOnly();
void constructAttributeWrongFormat();
void constructAttributeWrongStride();
void constructAttributeWrongDataAccess();
void construct();
void constructZeroIndices();
@ -168,7 +170,6 @@ MeshDataTest::MeshDataTest() {
&MeshDataTest::constructAttribute,
&MeshDataTest::constructAttributeCustom,
&MeshDataTest::constructAttributeWrongFormat,
&MeshDataTest::constructAttribute2D,
&MeshDataTest::constructAttribute2DWrongSize,
&MeshDataTest::constructAttribute2DNonContiguous,
@ -176,7 +177,10 @@ MeshDataTest::MeshDataTest() {
&MeshDataTest::constructAttributeNullptr,
&MeshDataTest::constructAttributePadding,
&MeshDataTest::constructAttributeNonOwningArray,
&MeshDataTest::constructAttributeOffsetOnly,
&MeshDataTest::constructAttributeWrongFormat,
&MeshDataTest::constructAttributeWrongStride,
&MeshDataTest::constructAttributeWrongDataAccess,
&MeshDataTest::construct,
&MeshDataTest::constructZeroIndices,
@ -450,14 +454,19 @@ constexpr Vector2 Positions[] {
void MeshDataTest::constructAttribute() {
const Vector2 positionData[3];
MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(positionData)};
CORRADE_VERIFY(!positions.isOffsetOnly());
CORRADE_COMPARE(positions.name(), MeshAttribute::Position);
CORRADE_COMPARE(positions.format(), VertexFormat::Vector2);
CORRADE_VERIFY(positions.data().data() == positionData);
/* This is allowed too for simplicity, it just ignores the parameter */
CORRADE_VERIFY(positions.data(positionData).data() == positionData);
constexpr MeshAttributeData cpositions{MeshAttribute::Position, Containers::arrayView(Positions)};
constexpr bool isOffsetOnly = cpositions.isOffsetOnly();
constexpr MeshAttribute name = cpositions.name();
constexpr VertexFormat format = cpositions.format();
constexpr Containers::StridedArrayView1D<const void> data = cpositions.data();
CORRADE_VERIFY(!isOffsetOnly);
CORRADE_COMPARE(name, MeshAttribute::Position);
CORRADE_COMPARE(format, VertexFormat::Vector2);
CORRADE_COMPARE(data.data(), Positions);
@ -471,15 +480,6 @@ void MeshDataTest::constructAttributeCustom() {
CORRADE_VERIFY(ids.data().data() == idData);
}
void MeshDataTest::constructAttributeWrongFormat() {
Vector2 positionData[3];
std::ostringstream out;
Error redirectError{&out};
MeshAttributeData{MeshAttribute::Color, Containers::arrayView(positionData)};
CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n");
}
void MeshDataTest::constructAttribute2D() {
char positionData[4*sizeof(Vector2)]{};
auto positionView = Containers::StridedArrayView2D<char>{positionData,
@ -544,12 +544,53 @@ void MeshDataTest::constructAttributeNonOwningArray() {
CORRADE_COMPARE(static_cast<const void*>(array.data()), data);
}
void MeshDataTest::constructAttributeOffsetOnly() {
struct {
Vector2 position;
Vector2 textureCoordinates;
} vertexData[] {
{{}, {1.0f, 0.3f}},
{{}, {0.5f, 0.7f}},
};
MeshAttributeData a{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)};
CORRADE_VERIFY(a.isOffsetOnly());
CORRADE_COMPARE(a.name(), MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(a.format(), VertexFormat::Vector2);
CORRADE_COMPARE_AS(Containers::arrayCast<const Vector2>(a.data(vertexData)),
Containers::arrayView<Vector2>({{1.0f, 0.3f}, {0.5f, 0.7f}}),
TestSuite::Compare::Container);
constexpr MeshAttributeData ca{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)};
CORRADE_VERIFY(ca.isOffsetOnly());
CORRADE_COMPARE(ca.name(), MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(ca.format(), VertexFormat::Vector2);
CORRADE_COMPARE_AS(Containers::arrayCast<const Vector2>(a.data(vertexData)),
Containers::arrayView<Vector2>({{1.0f, 0.3f}, {0.5f, 0.7f}}),
TestSuite::Compare::Container);
}
void MeshDataTest::constructAttributeWrongFormat() {
Vector2 positionData[3];
std::ostringstream out;
Error redirectError{&out};
MeshAttributeData{MeshAttribute::Color, Containers::arrayView(positionData)};
MeshAttributeData{MeshAttribute::Color, VertexFormat::Vector2, 0, 3, sizeof(Vector2)};
CORRADE_COMPARE(out.str(),
"Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n"
"Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n");
}
void MeshDataTest::constructAttributeWrongStride() {
char positionData[3*sizeof(Vector3)]{};
std::ostringstream out;
Error redirectError{&out};
MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast<const char>(positionData)};
/* We need this one to be constexpr, which means there can't be a warning
about stride not matching the size */
MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, 0, 3*sizeof(Vector3), 1};
MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::StridedArrayView1D<const void>{positionData, 0, -16}};
MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::StridedArrayView1D<const void>{positionData, 0, 65000}};
MeshAttributeData{65000};
@ -561,6 +602,20 @@ void MeshDataTest::constructAttributeWrongStride() {
);
}
void MeshDataTest::constructAttributeWrongDataAccess() {
Vector2 positionData[3];
MeshAttributeData a{MeshAttribute::Position, Containers::arrayView(positionData)};
MeshAttributeData b{MeshAttribute::Position, VertexFormat::Vector2, 0, 3, sizeof(Vector2)};
CORRADE_VERIFY(!a.isOffsetOnly());
CORRADE_VERIFY(b.isOffsetOnly());
std::ostringstream out;
Error redirectError{&out};
b.data();
CORRADE_COMPARE(out.str(),
"Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array\n");
}
void MeshDataTest::construct() {
struct Vertex {
Vector3 position;
@ -597,8 +652,10 @@ void MeshDataTest::construct() {
MeshIndexData indices{indexView};
MeshAttributeData positions{MeshAttribute::Position,
Containers::StridedArrayView1D<Vector3>{vertexData, &vertexView[0].position, vertexView.size(), sizeof(Vertex)}};
/* Using a relative offset */
MeshAttributeData normals{MeshAttribute::Normal,
Containers::StridedArrayView1D<Vector3>{vertexData, &vertexView[0].normal, vertexView.size(), sizeof(Vertex)}};
VertexFormat::Vector3, offsetof(Vertex, normal),
UnsignedInt(vertexView.size()), sizeof(Vertex)};
MeshAttributeData textureCoordinates{MeshAttribute::TextureCoordinates,
Containers::StridedArrayView1D<Vector2>{vertexData, &vertexView[0].textureCoordinate, vertexView.size(), sizeof(Vertex)}};
MeshAttributeData ids{meshAttributeCustom(13),
@ -692,6 +749,11 @@ void MeshDataTest::construct() {
CORRADE_COMPARE(data.mutableAttribute<Vector2>(3)[1], (Vector2{0.250f, 0.375f}));
CORRADE_COMPARE(data.mutableAttribute<Short>(4)[1], -374);
/* Raw attribute data access by ID */
CORRADE_COMPARE(data.attributeData(3).name(), MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(data.attributeData(3).format(), VertexFormat::Vector2);
CORRADE_COMPARE(Containers::arrayCast<const Vector2>(data.attributeData(3).data())[1], (Vector2{0.250f, 0.375f}));
/* Attribute access by name */
CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Position));
CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Normal));
@ -1167,14 +1229,17 @@ void MeshDataTest::constructAttributeNotContained() {
Containers::ArrayView<Vector2> vertexData2{reinterpret_cast<Vector2*>(0xdead), 3};
MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast<Vector2>(vertexData)};
MeshAttributeData positions2{MeshAttribute::Position, Containers::arrayView(vertexData2)};
MeshAttributeData positions3{MeshAttribute::Position, VertexFormat::Vector2, 1, 3, 8};
std::ostringstream out;
Error redirectError{&out};
MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}};
MeshData{MeshPrimitive::Triangles, nullptr, {positions}};
MeshData{MeshPrimitive::Triangles, Containers::Array<char>{24}, {positions3}};
CORRADE_COMPARE(out.str(),
"Trade::MeshData: attribute 1 [0xdead:0xdec5] is not contained in passed vertexData array [0xbadda9:0xbaddc1]\n"
"Trade::MeshData: attribute 0 [0xbadda9:0xbaddc1] is not contained in passed vertexData array [0x0:0x0]\n");
"Trade::MeshData: attribute 0 [0xbadda9:0xbaddc1] is not contained in passed vertexData array [0x0:0x0]\n"
"Trade::MeshData: offset attribute 0 spans 25 bytes but passed vertexData array has only 24\n");
}
void MeshDataTest::constructInconsitentVertexCount() {

Loading…
Cancel
Save