Browse Source

Trade: pack MeshAttributeData internals better.

Before it was a 32-byte structure with 3 bytes free (or a 20-byte
structure with 3 bytes free), now it's a 24-byte structure with 5 bytes
free. Exploiting the fact that strides can't be too high for a GPU
anyway (so 2 bytes is enough instead of 8), and vertex count is capped
to 32bit by MeshData anyway (so no need for 8 also), saving 10 bytes on
a 64-bit build.
pull/371/head
Vladimír Vondruš 6 years ago
parent
commit
5e81d10e13
  1. 155
      src/Magnum/Trade/MeshData.cpp
  2. 49
      src/Magnum/Trade/MeshData.h
  3. 30
      src/Magnum/Trade/Test/MeshDataTest.cpp

155
src/Magnum/Trade/MeshData.cpp

@ -57,7 +57,7 @@ MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexForma
logic */
/** @todo support zero / negative stride? would be hard to transfer to GL */
CORRADE_ASSERT(data.empty() || std::ptrdiff_t(vertexFormatSize(format)) <= data.stride(),
"Trade::MeshAttributeData: view stride" << data.stride() << "is not large enough to contain" << format, );
"Trade::MeshAttributeData: expected stride to be positive and enough to fit" << format << Debug::nospace << ", got" << data.stride(), );
}
MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView2D<const char>& data) noexcept: MeshAttributeData{name, format, Containers::StridedArrayView1D<const void>{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, nullptr} {
@ -83,7 +83,7 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array<char>&& inde
"Trade::MeshData: indices are expected to be valid if there are no attributes and vertex count isn't passed explicitly", );
/** @todo some better value? attributeless indexed with defined vertex count? */
_vertexCount = 0;
} else _vertexCount = _attributes[0]._data.size();
} else _vertexCount = _attributes[0]._vertexCount;
CORRADE_ASSERT(!_indices.empty() || _indexData.empty(),
"Trade::MeshData: indexData passed for a non-indexed mesh", );
@ -97,12 +97,12 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array<char>&& inde
const MeshAttributeData& attribute = _attributes[i];
CORRADE_ASSERT(attribute._format != VertexFormat{},
"Trade::MeshData: attribute" << i << "doesn't specify anything", );
const Containers::StridedArrayView1D<const char> data = Containers::arrayCast<const char>(attribute._data);
CORRADE_ASSERT(data.size() == _vertexCount,
"Trade::MeshData: attribute" << i << "has" << data.size() << "vertices but" << _vertexCount << "expected", );
CORRADE_ASSERT(data.empty() || (&data.front() >= _vertexData.begin() && &data.back() + vertexFormatSize(attribute._format) <= _vertexData.end()),
"Trade::MeshData: attribute" << i << "[" << Debug::nospace << static_cast<const void*>(&data.front()) << Debug::nospace << ":" << Debug::nospace << static_cast<const void*>(&data.back() + vertexFormatSize(attribute._format)) << 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 << "]", );
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 << "]", );
}
#endif
}
@ -233,13 +233,13 @@ VertexFormat MeshData::attributeFormat(UnsignedInt id) const {
std::size_t MeshData::attributeOffset(UnsignedInt id) const {
CORRADE_ASSERT(id < _attributes.size(),
"Trade::MeshData::attributeOffset(): index" << id << "out of range for" << _attributes.size() << "attributes", {});
return static_cast<const char*>(_attributes[id]._data.data()) - _vertexData.data();
return static_cast<const char*>(_attributes[id]._data) - _vertexData.data();
}
UnsignedInt MeshData::attributeStride(UnsignedInt id) const {
CORRADE_ASSERT(id < _attributes.size(),
"Trade::MeshData::attributeStride(): index" << id << "out of range for" << _attributes.size() << "attributes", {});
return _attributes[id]._data.stride();
return _attributes[id]._stride;
}
UnsignedInt MeshData::attributeCount(const MeshAttribute name) const {
@ -292,8 +292,9 @@ Containers::StridedArrayView2D<const char> MeshData::attribute(UnsignedInt id) c
/* Build a 2D view using information about attribute type size, return only
a prefix of the actual vertex count (which is zero in case vertex data
is released) */
return Containers::arrayCast<2, const char>(_attributes[id]._data,
vertexFormatSize(_attributes[id]._format)).prefix(_vertexCount);
return Containers::arrayCast<2, const char>(
attributeDataViewInternal(_attributes[id]),
vertexFormatSize(_attributes[id]._format));
}
Containers::StridedArrayView2D<char> MeshData::mutableAttribute(UnsignedInt id) {
@ -304,8 +305,9 @@ Containers::StridedArrayView2D<char> MeshData::mutableAttribute(UnsignedInt id)
/* Build a 2D view using information about attribute type size, return only
a prefix of the actual vertex count (which is zero in case vertex data
is released) */
auto out = Containers::arrayCast<2, const char>(_attributes[id]._data,
vertexFormatSize(_attributes[id]._format)).prefix(_vertexCount);
auto out = Containers::arrayCast<2, const char>(
attributeDataViewInternal(_attributes[id]),
vertexFormatSize(_attributes[id]._format));
/** @todo some arrayConstCast? UGH */
return Containers::StridedArrayView2D<char>{
/* The view size is there only for a size assert, we're pretty sure the
@ -360,45 +362,56 @@ Containers::Array<UnsignedInt> MeshData::indicesAsArray() const {
return output;
}
Containers::StridedArrayView1D<const void> MeshData::attributeDataViewInternal(const MeshAttributeData& attribute) 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? */
{attribute._data, ~std::size_t{}},
/* Not using attribute._vertexCount because that gets stale after
releaseVertexData() gets called, and then we would need to slice the
result inside attribute() and elsewhere */
_vertexCount, attribute._stride};
}
void MeshData::positions2DInto(const Containers::StridedArrayView1D<Vector2> destination, const UnsignedInt id) const {
const UnsignedInt attributeId = attributeFor(MeshAttribute::Position, id);
CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", );
CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), );
const MeshAttributeData& attribute = _attributes[attributeId];
const Containers::StridedArrayView1D<const void> attributeData = attributeDataViewInternal(attribute);
const auto destination2f = Containers::arrayCast<2, Float>(destination);
/* Copy 2D positions as-is, for 3D positions ignore Z */
if(attribute._format == VertexFormat::Vector2 ||
attribute._format == VertexFormat::Vector3)
Utility::copy(Containers::arrayCast<const Vector2>(attribute._data), destination);
Utility::copy(Containers::arrayCast<const Vector2>(attributeData), destination);
else if(attribute._format == VertexFormat::Vector2h ||
attribute._format == VertexFormat::Vector3h)
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f);
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2ub ||
attribute._format == VertexFormat::Vector3ub)
Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f);
Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2b ||
attribute._format == VertexFormat::Vector3b)
Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f);
Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2us ||
attribute._format == VertexFormat::Vector3us)
Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f);
Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2s ||
attribute._format == VertexFormat::Vector3s)
Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f);
Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2ubNormalized ||
attribute._format == VertexFormat::Vector3ubNormalized)
Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f);
Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2bNormalized ||
attribute._format == VertexFormat::Vector3bNormalized)
Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f);
Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2usNormalized ||
attribute._format == VertexFormat::Vector3usNormalized)
Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f);
Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2sNormalized ||
attribute._format == VertexFormat::Vector3sNormalized)
Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f);
Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f);
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
@ -413,54 +426,54 @@ void MeshData::positions3DInto(const Containers::StridedArrayView1D<Vector3> des
CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions3DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", );
CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions3DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), );
const MeshAttributeData& attribute = _attributes[attributeId];
const Containers::StridedArrayView1D<const void> attributeData = attributeDataViewInternal(attribute);
const Containers::StridedArrayView2D<Float> destination2f = Containers::arrayCast<2, Float>(Containers::arrayCast<Vector2>(destination));
const Containers::StridedArrayView2D<Float> destination3f = Containers::arrayCast<2, Float>(destination);
/* For 2D positions copy the XY part to the first two components */
if(attribute._format == VertexFormat::Vector2)
Utility::copy(Containers::arrayCast<const Vector2>(attribute._data),
Utility::copy(Containers::arrayCast<const Vector2>(attributeData),
Containers::arrayCast<Vector2>(destination));
else if(attribute._format == VertexFormat::Vector2h)
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f);
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2ub)
Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f);
Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2b)
Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f);
Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2us)
Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f);
Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2s)
Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f);
Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2ubNormalized)
Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f);
Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2bNormalized)
Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f);
Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2usNormalized)
Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f);
Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2sNormalized)
Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f);
Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f);
/* Copy 3D positions as-is */
else if(attribute._format == VertexFormat::Vector3)
Utility::copy(Containers::arrayCast<const Vector3>(attribute._data), destination);
Utility::copy(Containers::arrayCast<const Vector3>(attributeData), destination);
else if(attribute._format == VertexFormat::Vector3h)
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f);
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f);
else if(attribute._format == VertexFormat::Vector3ub)
Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 3), destination3f);
Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f);
else if(attribute._format == VertexFormat::Vector3b)
Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 3), destination3f);
Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f);
else if(attribute._format == VertexFormat::Vector3us)
Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f);
Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f);
else if(attribute._format == VertexFormat::Vector3s)
Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 3), destination3f);
Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f);
else if(attribute._format == VertexFormat::Vector3ubNormalized)
Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 3), destination3f);
Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f);
else if(attribute._format == VertexFormat::Vector3bNormalized)
Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 3), destination3f);
Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f);
else if(attribute._format == VertexFormat::Vector3usNormalized)
Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f);
Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f);
else if(attribute._format == VertexFormat::Vector3sNormalized)
Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 3), destination3f);
Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f);
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
/* For 2D positions finally fill the Z with a single value */
@ -492,17 +505,17 @@ void MeshData::normalsInto(const Containers::StridedArrayView1D<Vector3> destina
CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal) << "normal attributes", );
CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::normalsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), );
const MeshAttributeData& attribute = _attributes[attributeId];
const Containers::StridedArrayView1D<const void> attributeData = attributeDataViewInternal(attribute);
const auto destination3f = Containers::arrayCast<2, Float>(destination);
if(attribute._format == VertexFormat::Vector3)
Utility::copy(Containers::arrayCast<const Vector3>(attribute._data), destination);
Utility::copy(Containers::arrayCast<const Vector3>(attributeData), destination);
else if(attribute._format == VertexFormat::Vector3h)
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f);
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f);
else if(attribute._format == VertexFormat::Vector3bNormalized)
Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 3), destination3f);
Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f);
else if(attribute._format == VertexFormat::Vector3sNormalized)
Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 3), destination3f);
Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f);
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
@ -517,29 +530,29 @@ void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D<Vec
CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::textureCoordinates2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::TextureCoordinates) << "texture coordinate attributes", );
CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::textureCoordinates2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), );
const MeshAttributeData& attribute = _attributes[attributeId];
const Containers::StridedArrayView1D<const void> attributeData = attributeDataViewInternal(attribute);
const auto destination2f = Containers::arrayCast<2, Float>(destination);
if(attribute._format == VertexFormat::Vector2)
Utility::copy(Containers::arrayCast<const Vector2>(attribute._data), destination);
Utility::copy(Containers::arrayCast<const Vector2>(attributeData), destination);
else if(attribute._format == VertexFormat::Vector2h)
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f);
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2ub)
Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f);
Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2b)
Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f);
Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2us)
Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f);
Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2s)
Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f);
Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2ubNormalized)
Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f);
Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2bNormalized)
Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f);
Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2usNormalized)
Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f);
Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f);
else if(attribute._format == VertexFormat::Vector2sNormalized)
Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f);
Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f);
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
@ -554,32 +567,32 @@ void MeshData::colorsInto(const Containers::StridedArrayView1D<Color4> destinati
CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::colorsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Color) << "color attributes", );
CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::colorsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), );
const MeshAttributeData& attribute = _attributes[attributeId];
const Containers::StridedArrayView1D<const void> attributeData = attributeDataViewInternal(attribute);
const Containers::StridedArrayView2D<Float> destination3f = Containers::arrayCast<2, Float>(Containers::arrayCast<Vector3>(destination));
const Containers::StridedArrayView2D<Float> destination4f = Containers::arrayCast<2, Float>(destination);
/* For three-component colors copy the RGB part to the first three
components */
if(attribute._format == VertexFormat::Vector3)
Utility::copy(Containers::arrayCast<const Vector3>(attribute._data),
Utility::copy(Containers::arrayCast<const Vector3>(attributeData),
Containers::arrayCast<Vector3>(destination));
else if(attribute._format == VertexFormat::Vector3h)
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f);
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f);
else if(attribute._format == VertexFormat::Vector3ubNormalized)
Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 3), destination3f);
Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f);
else if(attribute._format == VertexFormat::Vector3usNormalized)
Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f);
Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f);
/* Copy four-component colors as-is */
else if(attribute._format == VertexFormat::Vector4)
Utility::copy(Containers::arrayCast<const Vector4>(attribute._data),
Utility::copy(Containers::arrayCast<const Vector4>(attributeData),
Containers::arrayCast<Vector4>(destination));
else if(attribute._format == VertexFormat::Vector4h)
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 4), destination4f);
Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4f);
else if(attribute._format == VertexFormat::Vector4ubNormalized)
Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 4), destination4f);
Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 4), destination4f);
else if(attribute._format == VertexFormat::Vector4usNormalized)
Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 4), destination4f);
Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4f);
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
/* For three-component colors finally fill the alpha with a single value */

49
src/Magnum/Trade/MeshData.h

@ -244,7 +244,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData {
* initialization of the attribute array for @ref MeshData, expected to
* be replaced with concrete values later.
*/
constexpr explicit MeshAttributeData() noexcept: _name{}, _format{}, _data{} {}
constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{} {}
/**
* @brief Type-erased constructor
@ -313,7 +313,10 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData {
* multiple different attributes onto each other. Not meant to be
* passed to @ref MeshData.
*/
constexpr explicit MeshAttributeData(Int padding): _name{}, _format{}, _data{nullptr, 0, padding} {}
constexpr explicit MeshAttributeData(Int padding): _data{nullptr}, _vertexCount{0}, _format{}, _stride{
(CORRADE_CONSTEXPR_ASSERT(padding >= -32768 && padding <= 32767,
"Trade::MeshAttributeData: at most 32k padding supported, got" << padding), Short(padding))
}, _name{} {}
/** @brief Attribute name */
constexpr MeshAttribute name() const { return _name; }
@ -322,16 +325,29 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData {
constexpr VertexFormat format() const { return _format; }
/** @brief Type-erased attribute data */
constexpr Containers::StridedArrayView1D<const void> data() const { return _data; }
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};
}
private:
constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D<const void>& data, std::nullptr_t) noexcept;
friend MeshData;
MeshAttribute _name;
/* Here's some room for flags */
const void* _data;
/* Vertex count in MeshData is currently 32-bit, so this doesn't need
to be 64-bit either */
UnsignedInt _vertexCount;
VertexFormat _format;
Containers::StridedArrayView1D<const void> _data;
/* According to https://opengl.gpuinfo.org/displaycapability.php?name=GL_MAX_VERTEX_ATTRIB_STRIDE,
current largest reported stride is 4k so 32k should be enough */
Short _stride;
MeshAttribute _name;
/* 4 bytes free for more stuff on 64b (20, aligned to 24); nothing on
32b */
};
/** @relatesalso MeshAttributeData
@ -1183,6 +1199,12 @@ class MAGNUM_TRADE_EXPORT MeshData {
/* Internal helper that doesn't assert, unlike attributeId() */
UnsignedInt attributeFor(MeshAttribute name, UnsignedInt id) const;
/* Like attribute(), but returning just a 1D view */
Containers::StridedArrayView1D<const void> attributeDataViewInternal(const MeshAttributeData& attribute) const;
/* GPUs don't currently support more than 32-bit index types / vertex
counts so this should be enough. Sanity check:
https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkIndexType.html */
UnsignedInt _vertexCount;
MeshIndexType _indexType;
MeshPrimitive _primitive;
@ -1298,9 +1320,14 @@ namespace Implementation {
#endif
constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D<const void>& data, std::nullptr_t) noexcept:
_name{name}, _format{format}, _data{(CORRADE_CONSTEXPR_ASSERT(
/* Double formats intentionally not supported for any builtin attributes
right now -- only for custom formats */
_data{data.data()}, _vertexCount{UnsignedInt(data.size())}, _format{format},
/** @todo support zero / negative stride? would be hard to transfer to GL */
_stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(data.stride()) & 0xffff8000),
"Trade::MeshAttributeData: expected stride to be positive and at most 32k, got" << data.stride()),
Short(data.stride()))
}, _name{(CORRADE_CONSTEXPR_ASSERT(
/* Double types intentionally not supported for any builtin attributes
right now -- only for custom types */
(name == MeshAttribute::Position &&
(format == VertexFormat::Vector2 ||
format == VertexFormat::Vector2h ||
@ -1348,8 +1375,8 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V
format == VertexFormat::Vector2s ||
format == VertexFormat::Vector2sNormalized)) ||
isMeshAttributeCustom(name) /* can be any format */,
"Trade::MeshAttributeData:" << format << "is not a valid format for" << name), data)}
{}
"Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name)
} {}
template<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} {}

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

@ -57,10 +57,10 @@ struct MeshDataTest: TestSuite::Tester {
void constructAttribute2DWrongSize();
void constructAttribute2DNonContiguous();
void constructAttributeTypeErased();
void constructAttributeTypeErasedWrongStride();
void constructAttributeNullptr();
void constructAttributePadding();
void constructAttributeNonOwningArray();
void constructAttributeWrongStride();
void construct();
void constructZeroIndices();
@ -173,10 +173,10 @@ MeshDataTest::MeshDataTest() {
&MeshDataTest::constructAttribute2DWrongSize,
&MeshDataTest::constructAttribute2DNonContiguous,
&MeshDataTest::constructAttributeTypeErased,
&MeshDataTest::constructAttributeTypeErasedWrongStride,
&MeshDataTest::constructAttributeNullptr,
&MeshDataTest::constructAttributePadding,
&MeshDataTest::constructAttributeNonOwningArray,
&MeshDataTest::constructAttributeWrongStride,
&MeshDataTest::construct,
&MeshDataTest::constructZeroIndices,
@ -521,15 +521,6 @@ void MeshDataTest::constructAttributeTypeErased() {
CORRADE_VERIFY(positions.data().data() == positionData);
}
void MeshDataTest::constructAttributeTypeErasedWrongStride() {
char positionData[3*sizeof(Vector3)]{};
std::ostringstream out;
Error redirectError{&out};
MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast<const char>(positionData)};
CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: view stride 1 is not large enough to contain VertexFormat::Vector3\n");
}
void MeshDataTest::constructAttributeNullptr() {
MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr};
CORRADE_COMPARE(positions.name(), MeshAttribute::Position);
@ -553,6 +544,23 @@ void MeshDataTest::constructAttributeNonOwningArray() {
CORRADE_COMPARE(static_cast<const void*>(array.data()), data);
}
void MeshDataTest::constructAttributeWrongStride() {
char positionData[3*sizeof(Vector3)]{};
std::ostringstream out;
Error redirectError{&out};
MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast<const char>(positionData)};
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};
CORRADE_COMPARE(out.str(),
"Trade::MeshAttributeData: expected stride to be positive and enough to fit VertexFormat::Vector3, got 1\n"
"Trade::MeshAttributeData: expected stride to be positive and at most 32k, got -16\n"
"Trade::MeshAttributeData: expected stride to be positive and at most 32k, got 65000\n"
"Trade::MeshAttributeData: at most 32k padding supported, got 65000\n"
);
}
void MeshDataTest::construct() {
struct Vertex {
Vector3 position;

Loading…
Cancel
Save