From 0510449b36395ba337cc7606f7bf4dbf10c155c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 28 Jul 2020 23:46:47 +0200 Subject: [PATCH] Trade: material layer support in the new MaterialData. --- .../materialAttributeProperties.hpp | 1 + src/Magnum/Trade/MaterialData.cpp | 380 ++++++-- src/Magnum/Trade/MaterialData.h | 763 ++++++++++++--- src/Magnum/Trade/Test/MaterialDataTest.cpp | 907 ++++++++++++++++-- 4 files changed, 1783 insertions(+), 268 deletions(-) diff --git a/src/Magnum/Trade/Implementation/materialAttributeProperties.hpp b/src/Magnum/Trade/Implementation/materialAttributeProperties.hpp index 037219da0..9b78c0a50 100644 --- a/src/Magnum/Trade/Implementation/materialAttributeProperties.hpp +++ b/src/Magnum/Trade/Implementation/materialAttributeProperties.hpp @@ -25,6 +25,7 @@ /* See Magnum/Trade/MaterialData.cpp and Magnum/Trade/Test/MaterialDataTest.cpp */ #ifdef _c +_cnt(LayerName,"$LayerName",String,Containers::StringView) _c(AlphaMask,Float) _ct(AlphaBlend,Bool,bool) _ct(DoubleSided,Bool,bool) diff --git a/src/Magnum/Trade/MaterialData.cpp b/src/Magnum/Trade/MaterialData.cpp index b0e1ea061..2106b1acd 100644 --- a/src/Magnum/Trade/MaterialData.cpp +++ b/src/Magnum/Trade/MaterialData.cpp @@ -48,9 +48,11 @@ constexpr struct { } AttributeMap[]{ #define _c(name, type) {#name ## _s, MaterialAttributeType::type, sizeof(type)}, #define _ct(name, typeName, type) {#name ## _s, MaterialAttributeType::typeName, sizeof(type)}, + #define _cnt(name, string, typeName, type) {string ## _s, MaterialAttributeType::typeName, 255}, #include "Magnum/Trade/Implementation/materialAttributeProperties.hpp" #undef _c #undef _ct + #undef _cnt }; #endif @@ -143,12 +145,25 @@ MaterialAttributeData::MaterialAttributeData(const MaterialAttribute name, const CORRADE_ASSERT(AttributeMap[UnsignedInt(name) - 1].type == type, "Trade::MaterialAttributeData: expected" << AttributeMap[UnsignedInt(name) - 1].type << "for" << name << "but got" << type, ); - /* No builtin string attributes yet */ - CORRADE_INTERNAL_ASSERT(type != MaterialAttributeType::String); - _data.type = type; - std::memcpy(_data.data + 1, AttributeMap[UnsignedInt(name) - 1].name.data(), AttributeMap[UnsignedInt(name) - 1].name.size()); - std::memcpy(_data.data + Implementation::MaterialAttributeDataSize - AttributeMap[UnsignedInt(name) - 1].size, value, AttributeMap[UnsignedInt(name) - 1].size); + const Containers::StringView stringName = AttributeMap[UnsignedInt(name) - 1].name; + std::memcpy(_data.data + 1, stringName.data(), stringName.size()); + + /* Special handling for strings, in that case it's sot known in advance + that we fit and has to be checked */ + if(type == MaterialAttributeType::String) { + const auto& stringValue = *static_cast(value); + /* The 4 extra bytes are for a null byte after both the name and value, + a type and a string size */ + CORRADE_ASSERT(stringName.size() + stringValue.size() + 4 <= Implementation::MaterialAttributeDataSize, + "Trade::MaterialAttributeData: name" << stringName << "and value" << stringValue << "too long, expected at most" << Implementation::MaterialAttributeDataSize - 4 << "bytes in total but got" << stringName.size() + stringValue.size(), ); + _data.type = MaterialAttributeType::String; + std::memcpy(_data.data + Implementation::MaterialAttributeDataSize - stringValue.size() - 2, stringValue.data(), stringValue.size()); + _data.data[Implementation::MaterialAttributeDataSize - 1] = stringValue.size(); + return; + } else { + std::memcpy(_data.data + Implementation::MaterialAttributeDataSize - AttributeMap[UnsignedInt(name) - 1].size, value, AttributeMap[UnsignedInt(name) - 1].size); + } } const void* MaterialAttributeData::value() const { @@ -167,7 +182,7 @@ template<> MAGNUM_TRADE_EXPORT Containers::StringView MaterialAttributeData::val } #endif -MaterialData::MaterialData(const MaterialTypes types, Containers::Array&& data, const void* const importerState) noexcept: _data{std::move(data)}, _types{types}, _importerState{importerState} { +MaterialData::MaterialData(const MaterialTypes types, Containers::Array&& attributeData, Containers::Array&& layerData, const void* const importerState) noexcept: _data{std::move(attributeData)}, _layerOffsets{std::move(layerData)}, _types{types}, _importerState{importerState} { #ifndef CORRADE_NO_ASSERT /* Not checking what's already done in MaterialAttributeData constructor. Done before sorting so the index refers to the actual input index. */ @@ -176,40 +191,66 @@ MaterialData::MaterialData(const MaterialTypes types, Containers::Array 1) for(std::size_t i = 1; i != _data.size(); ++i) { - if(_data[i - 1].name() < _data[i].name()) continue; - - std::sort(_data.begin(), _data.end(), [](const MaterialAttributeData& a, const MaterialAttributeData& b) { - /* Need to check here (instead of in the outer for loop) as we - exit the loop right after the sort and thus duplicates that - occur after the first non-sorted pair wouldn't get detected. */ - CORRADE_ASSERT(a.name() != b.name(), - "Trade::MaterialData: duplicate attribute" << a.name(), false); - return a.name() < b.name(); - }); - break; + /* Go through all layers and sort each independently */ + const UnsignedInt implicitLayerData[]{UnsignedInt(_data.size())}; + const Containers::ArrayView layerOffsets = + _layerOffsets ? _layerOffsets : Containers::arrayView(implicitLayerData); + UnsignedInt begin = 0; + for(std::size_t i = 0; i != layerOffsets.size(); ++i) { + const UnsignedInt end = layerOffsets[i]; + CORRADE_ASSERT(begin <= end && end <= _data.size(), + "Trade::MaterialData: invalid range (" << Debug::nospace << begin << Debug::nospace << "," << end << Debug::nospace <<") for layer" << i << "with" << _data.size() << "attributes in total", ); + + /* Check if attributes are sorted already. If not, sort them. Can't + just call std::sort() unconditionally because if the data would be + immutable (for example when acquiring release()d immutable data from + another instance) it could cause crashes. (I expected not, but + apparently ASan blows up on that.) */ + if(end - begin > 1) for(std::size_t i = begin + 1; i != end; ++i) { + if(_data[i - 1].name() < _data[i].name()) continue; + + std::sort(_data + begin, _data + end, [](const MaterialAttributeData& a, const MaterialAttributeData& b) { + /* Need to check here (instead of in the outer for loop) as we + exit the loop right after the sort and thus duplicates that + occur after the first non-sorted pair wouldn't get detected. */ + CORRADE_ASSERT(a.name() != b.name(), + "Trade::MaterialData: duplicate attribute" << a.name(), false); + return a.name() < b.name(); + }); + break; + } + + begin = end; } } -MaterialData::MaterialData(const MaterialTypes types, const std::initializer_list data, const void* const importerState): MaterialData{types, Implementation::initializerListToArrayWithDefaultDeleter(data), importerState} {} +MaterialData::MaterialData(const MaterialTypes types, const std::initializer_list attributeData, const std::initializer_list layerData, const void* const importerState): MaterialData{types, Implementation::initializerListToArrayWithDefaultDeleter(attributeData), Implementation::initializerListToArrayWithDefaultDeleter(layerData), importerState} {} -MaterialData::MaterialData(const MaterialTypes types, DataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: _data{Containers::Array{const_cast(data.data()), data.size(), [](MaterialAttributeData*, std::size_t){}}}, _types{types}, _importerState{importerState} { +MaterialData::MaterialData(const MaterialTypes types, DataFlags, const Containers::ArrayView attributeData, DataFlags, Containers::ArrayView layerData, const void* const importerState) noexcept: _data{Containers::Array{const_cast(attributeData.data()), attributeData.size(), [](MaterialAttributeData*, std::size_t){}}}, _layerOffsets{Containers::Array{const_cast(layerData.data()), layerData.size(), [](UnsignedInt*, std::size_t){}}}, _types{types}, _importerState{importerState} { #ifndef CORRADE_NO_ASSERT /* Not checking what's already done in MaterialAttributeData constructor */ for(std::size_t i = 0; i != _data.size(); ++i) CORRADE_ASSERT(_data[i]._data.type != MaterialAttributeType{}, "Trade::MaterialData: attribute" << i << "doesn't specify anything", ); - if(_data.size() > 1) for(std::size_t i = 1; i != _data.size(); ++i) { - CORRADE_ASSERT(_data[i - 1].name() != _data[i].name(), - "Trade::MaterialData: duplicate attribute" << _data[i].name(), ); - CORRADE_ASSERT(_data[i - 1].name() < _data[i].name(), - "Trade::MaterialData:" << _data[i].name() << "has to be sorted before" << _data[i - 1].name() << "if passing non-owned data", ); + /* Go through all layers and verify that each is independently sorted */ + const UnsignedInt implicitLayerData[]{UnsignedInt(_data.size())}; + const Containers::ArrayView layerOffsets = + _layerOffsets ? _layerOffsets : Containers::arrayView(implicitLayerData); + UnsignedInt begin = 0; + for(std::size_t i = 0; i != layerOffsets.size(); ++i) { + const UnsignedInt end = layerOffsets[i]; + CORRADE_ASSERT(begin <= end && end <= _data.size(), + "Trade::MaterialData: invalid range (" << Debug::nospace << begin << Debug::nospace << "," << end << Debug::nospace <<") for layer" << i << "with" << _data.size() << "attributes in total", ); + + if(end - begin > 1) for(std::size_t i = begin + 1; i != end; ++i) { + CORRADE_ASSERT(_data[i - 1].name() != _data[i].name(), + "Trade::MaterialData: duplicate attribute" << _data[i].name(), ); + CORRADE_ASSERT(_data[i - 1].name() < _data[i].name(), + "Trade::MaterialData:" << _data[i].name() << "has to be sorted before" << _data[i - 1].name() << "if passing non-owned data", ); + } + + begin = end; } #endif } @@ -228,105 +269,276 @@ Containers::StringView MaterialData::attributeString(const MaterialAttribute nam return AttributeMap[UnsignedInt(name) - 1].name; } -UnsignedInt MaterialData::attributeFor(const Containers::StringView name) const { - const MaterialAttributeData* const found = std::lower_bound(_data.begin(), _data.end(), name, [](const MaterialAttributeData& a, const Containers::StringView& b) { +UnsignedInt MaterialData::layerFor(const Containers::StringView layer) const { + for(std::size_t i = 1; i < _layerOffsets.size(); ++i) { + if(_layerOffsets[i] > _layerOffsets[i - 1] && + _data[_layerOffsets[i - 1]].name() == "$LayerName"_s && + _data[_layerOffsets[i - 1]].value() == layer) + return i; + } + return ~UnsignedInt{}; +} + +bool MaterialData::hasLayer(const Containers::StringView layer) const { + return layerFor(layer) != ~UnsignedInt{}; +} + +UnsignedInt MaterialData::layerId(const Containers::StringView layer) const { + const UnsignedInt id = layerFor(layer); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::layerId(): layer" << layer << "not found", {}); + return id; +} + +Containers::StringView MaterialData::layerName(const UnsignedInt layer) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::layerName(): index" << layer << "out of range for" << layerCount() << "layers", {}); + /* Deliberately ignore this attribute in the base material */ + if(layer && _layerOffsets[layer] > _layerOffsets[layer - 1] && _data[_layerOffsets[layer - 1]].name() == "$LayerName") + return _data[_layerOffsets[layer - 1]].value(); + return {}; +} + +UnsignedInt MaterialData::attributeCount(const UnsignedInt layer) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attributeCount(): index" << layer << "out of range for" << layerCount() << "layers", {}); + if(!_layerOffsets) return _data.size(); + if(!layer) return _layerOffsets[0]; + return _layerOffsets[layer] - _layerOffsets[layer - 1]; +} + +UnsignedInt MaterialData::attributeCount(const Containers::StringView layer) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attributeCount(): layer" << layer << "not found", {}); + return attributeCount(layerId); +} + +UnsignedInt MaterialData::attributeFor(const UnsignedInt layer, const Containers::StringView name) const { + const MaterialAttributeData* begin = _data.begin() + + (layer && _layerOffsets ? _layerOffsets[layer - 1] : 0); + const MaterialAttributeData* end = + (_layerOffsets ? _data.begin() + _layerOffsets[layer] : _data.end()); + const MaterialAttributeData* const found = std::lower_bound(begin, end, name, [](const MaterialAttributeData& a, const Containers::StringView& b) { return a.name() < b; }); - if(found == _data.end() || found->name() != name) return ~UnsignedInt{}; - return found - _data.begin(); + if(found == end || found->name() != name) return ~UnsignedInt{}; + return found - begin; } -bool MaterialData::hasAttribute(const Containers::StringView name) const { - return attributeFor(name) != ~UnsignedInt{}; +bool MaterialData::hasAttribute(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::hasAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + return attributeFor(layer, name) != ~UnsignedInt{}; } -bool MaterialData::hasAttribute(const MaterialAttribute name) const { +bool MaterialData::hasAttribute(const UnsignedInt layer, const MaterialAttribute name) const { const Containers::StringView string = attributeString(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::hasAttribute(): invalid name" << name, {}); - return hasAttribute(string); + return hasAttribute(layer, string); +} + +bool MaterialData::hasAttribute(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::hasAttribute(): layer" << layer << "not found", {}); + return hasAttribute(layerId, name); +} + +bool MaterialData::hasAttribute(const Containers::StringView layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::hasAttribute(): invalid name" << name, {}); + return hasAttribute(layer, string); +} + +UnsignedInt MaterialData::attributeId(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attributeId(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attributeId(): attribute" << name << "not found in layer" << layer, {}); + return id; } -UnsignedInt MaterialData::attributeId(const Containers::StringView name) const { - const UnsignedInt id = attributeFor(name); +UnsignedInt MaterialData::attributeId(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeId(): invalid name" << name, {}); + return attributeId(layer, string); +} + +UnsignedInt MaterialData::attributeId(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attributeId(): layer" << layer << "not found", {}); + const UnsignedInt id = attributeFor(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, - "Trade::MaterialData::attributeId(): attribute" << name << "not found", {}); + "Trade::MaterialData::attributeId(): attribute" << name << "not found in layer" << layer, {}); return id; } -UnsignedInt MaterialData::attributeId(const MaterialAttribute name) const { +UnsignedInt MaterialData::attributeId(const Containers::StringView layer, const MaterialAttribute name) const { const Containers::StringView string = attributeString(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeId(): invalid name" << name, {}); - return attributeId(string); + return attributeId(layer, string); +} + +Containers::StringView MaterialData::attributeName(const UnsignedInt layer, const UnsignedInt id) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attributeName(): index" << layer << "out of range for" << layerCount() << "layers", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attributeName(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return _data[layerOffset(layer) + id]._data.data + 1; } -Containers::StringView MaterialData::attributeName(const UnsignedInt id) const { - CORRADE_ASSERT(id < _data.size(), - "Trade::MaterialData::attributeName(): index" << id << "out of range for" << _data.size() << "attributes", {}); - return _data[id]._data.data + 1; +Containers::StringView MaterialData::attributeName(const Containers::StringView layer, const UnsignedInt id) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attributeName(): layer" << layer << "not found", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attributeName(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return _data[layerOffset(layerId) + id]._data.data + 1; +} + +MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const UnsignedInt id) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attributeType(): index" << layer << "out of range for" << layerCount() << "layers", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attributeType(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return _data[layerOffset(layer) + id]._data.type; +} + +MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attributeType(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attributeType(): attribute" << name << "not found in layer" << layer, {}); + return _data[layerOffset(layer) + id]._data.type; +} + +MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeType(): invalid name" << name, {}); + return attributeType(layer, string); } -MaterialAttributeType MaterialData::attributeType(const UnsignedInt id) const { - CORRADE_ASSERT(id < _data.size(), - "Trade::MaterialData::attributeType(): index" << id << "out of range for" << _data.size() << "attributes", {}); - return _data[id]._data.type; +MaterialAttributeType MaterialData::attributeType(const Containers::StringView layer, const UnsignedInt id) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attributeType(): layer" << layer << "not found", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attributeType(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return _data[layerOffset(layerId) + id]._data.type; } -MaterialAttributeType MaterialData::attributeType(const Containers::StringView name) const { - const UnsignedInt id = attributeFor(name); +MaterialAttributeType MaterialData::attributeType(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attributeType(): layer" << layer << "not found", {}); + const UnsignedInt id = attributeFor(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, - "Trade::MaterialData::attributeType(): attribute" << name << "not found", {}); - return _data[id]._data.type; + "Trade::MaterialData::attributeType(): attribute" << name << "not found in layer" << layer, {}); + return _data[layerOffset(layerId) + id]._data.type; } -MaterialAttributeType MaterialData::attributeType(const MaterialAttribute name) const { +MaterialAttributeType MaterialData::attributeType(const Containers::StringView layer, const MaterialAttribute name) const { const Containers::StringView string = attributeString(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeType(): invalid name" << name, {}); - return attributeType(string); + return attributeType(layer, string); } -const void* MaterialData::attribute(const UnsignedInt id) const { - CORRADE_ASSERT(id < _data.size(), - "Trade::MaterialData::attribute(): index" << id << "out of range for" << _data.size() << "attributes", {}); - return _data[id].value(); +const void* MaterialData::attribute(const UnsignedInt layer, const UnsignedInt id) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attribute(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return _data[layerOffset(layer) + id].value(); } -const void* MaterialData::attribute(const Containers::StringView name) const { - const UnsignedInt id = attributeFor(name); +const void* MaterialData::attribute(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, - "Trade::MaterialData::attribute(): attribute" << name << "not found", {}); - return _data[id].value(); + "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); + return _data[layerOffset(layer) + id].value(); } -const void* MaterialData::attribute(const MaterialAttribute name) const { +const void* MaterialData::attribute(const UnsignedInt layer, const MaterialAttribute name) const { const Containers::StringView string = attributeString(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); - return attribute(string); + return attribute(layer, string); +} + +const void* MaterialData::attribute(const Containers::StringView layer, const UnsignedInt id) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attribute(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return _data[layerOffset(layerId) + id].value(); +} + +const void* MaterialData::attribute(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); + const UnsignedInt id = attributeFor(layerId, name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); + return _data[layerOffset(layerId) + id].value(); +} + +const void* MaterialData::attribute(const Containers::StringView layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); + return attribute(layer, string); } #ifndef DOXYGEN_GENERATING_OUTPUT /* On Windows (MSVC, clang-cl and MinGw) it needs an explicit export otherwise the symbol doesn't get exported. */ -template<> MAGNUM_TRADE_EXPORT Containers::StringView MaterialData::attribute(const UnsignedInt id) const { +template<> MAGNUM_TRADE_EXPORT Containers::StringView MaterialData::attribute(const UnsignedInt layer, const UnsignedInt id) const { /* Can't delegate to attribute() returning const void* because that doesn't include the size */ - CORRADE_ASSERT(id < _data.size(), - "Trade::MaterialData::attribute(): index" << id << "out of range for" << _data.size() << "attributes", {}); - CORRADE_ASSERT(_data[id]._data.type == MaterialAttributeType::String, - "Trade::MaterialData::attribute():" << (_data[id]._data.data + 1) << "of" << _data[id]._data.type << "can't be retrieved as a string", {}); - return {_data[id]._data.s.nameValue + Implementation::MaterialAttributeDataSize - _data[id]._data.s.size - 3, _data[id]._data.s.size, Containers::StringViewFlag::NullTerminated}; + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attribute(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + const Trade::MaterialAttributeData& data = _data[layerOffset(layer) + id]; + CORRADE_ASSERT(data._data.type == MaterialAttributeType::String, + "Trade::MaterialData::attribute():" << (data._data.data + 1) << "of" << data._data.type << "can't be retrieved as a string", {}); + return {data._data.s.nameValue + Implementation::MaterialAttributeDataSize - data._data.s.size - 3, data._data.s.size, Containers::StringViewFlag::NullTerminated}; +} + +const void* MaterialData::tryAttribute(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::tryAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); + if(id == ~UnsignedInt{}) return nullptr; + return _data[layerOffset(layer) + id].value(); +} + +const void* MaterialData::tryAttribute(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); + return tryAttribute(layer, string); } #endif -const void* MaterialData::tryAttribute(const Containers::StringView name) const { - const UnsignedInt id = attributeFor(name); +const void* MaterialData::tryAttribute(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::tryAttribute(): layer" << layer << "not found", {}); + const UnsignedInt id = attributeFor(layerId, name); if(id == ~UnsignedInt{}) return nullptr; - return _data[id].value(); + return _data[layerOffset(layerId) + id].value(); } -const void* MaterialData::tryAttribute(const MaterialAttribute name) const { +const void* MaterialData::tryAttribute(const Containers::StringView layer, const MaterialAttribute name) const { const Containers::StringView string = attributeString(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); - return tryAttribute(string); + return tryAttribute(layer, string); } #ifdef MAGNUM_BUILD_DEPRECATED @@ -356,7 +568,11 @@ Float MaterialData::alphaMask() const { return attributeOr(MaterialAttribute::AlphaMask, 0.5f); } -Containers::Array MaterialData::release() { +Containers::Array MaterialData::releaseLayerData() { + return std::move(_layerOffsets); +} + +Containers::Array MaterialData::releaseAttributeData() { return std::move(_data); } @@ -366,7 +582,11 @@ Debug& operator<<(Debug& debug, const MaterialAttribute value) { if(UnsignedInt(value) - 1 >= Containers::arraySize(AttributeMap)) return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; - return debug << "::" << Debug::nospace << AttributeMap[UnsignedInt(value) - 1].name; + /* LayerName is prefixed with $, drop that */ + Containers::StringView string = AttributeMap[UnsignedInt(value) - 1].name; + if(string[0] == '$') string = string.suffix(1); + + return debug << "::" << Debug::nospace << string; } Debug& operator<<(Debug& debug, const MaterialAttributeType value) { diff --git a/src/Magnum/Trade/MaterialData.h b/src/Magnum/Trade/MaterialData.h index 83ab9c932..1fe44c733 100644 --- a/src/Magnum/Trade/MaterialData.h +++ b/src/Magnum/Trade/MaterialData.h @@ -46,9 +46,12 @@ namespace Magnum { namespace Trade { @brief Material attribute name @m_since_latest -Convenience aliases to actual attribute name strings, in the same form and -capitalization --- so for example @ref MaterialAttribute::DoubleSided is an -alias for @cpp "DoubleSided" @ce. When this enum si used in +Convenience aliases to actual attribute name strings. In most cases the alias +is in the same form and capitalization --- so for example +@ref MaterialAttribute::DoubleSided is an alias for @cpp "DoubleSided" @ce, the +only exception is @ref MaterialAttribute::LayerName which is @cpp "$LayerName" @ce. + +When this enum si used in @ref MaterialAttributeData constructors, the data are additionally checked for type compatibility. Other than that, there is no difference to the string variants. @@ -57,6 +60,17 @@ variants. enum class MaterialAttribute: UnsignedInt { /* Zero used for an invalid value */ + /** + * Layer name, @ref MaterialAttributeType::String. + * + * Unlike other attributes where string name matches the enum name, in this + * case the corresponding string is @cpp "$LayerName" @ce, done in order to + * have the layer name attribute appear first in each layer and thus + * simplify layer implementation. + * @see @ref MaterialData::layerName() + */ + LayerName = 1, + /** * Alpha mask, @ref MaterialAttributeType::Float. * @@ -67,7 +81,7 @@ enum class MaterialAttribute: UnsignedInt { * @see @ref MaterialAlphaMode, @ref MaterialData::alphaMode(), * @ref MaterialData::alphaMask() */ - AlphaMask = 1, + AlphaMask, /** * Alpha blending, @ref MaterialAttributeType::Bool. @@ -399,7 +413,7 @@ class MAGNUM_TRADE_EXPORT MaterialAttributeData { > constexpr /*implicit*/ MaterialAttributeData(Containers::StringView name, const T& value) noexcept; /** - * @brief Construct with a string name and value + * @brief Construct with a string name and string value * @param name Attribute name * @param value Attribute value * @@ -440,6 +454,21 @@ class MAGNUM_TRADE_EXPORT MaterialAttributeData { #endif > /*implicit*/ MaterialAttributeData(MaterialAttribute name, const T& value) noexcept: MaterialAttributeData{name, Implementation::MaterialAttributeTypeFor::type(), &value} {} + /** + * @brief Construct with a predefined name and string value + * @param name Attribute name + * @param value Attribute value + * + * Compared to @ref MaterialAttributeData(Containers::StringView, Containers::StringView) + * checks that the attribute is in expected type. The + * @ref MaterialAttribute gets converted to a corresponding string + * name. Apart from the type check, the following two instances are + * equivalent: + * + * @snippet MagnumTrade.cpp MaterialAttributeData-name + */ + /*implicit*/ MaterialAttributeData(MaterialAttribute name, Containers::StringView value) noexcept: MaterialAttributeData{name, MaterialAttributeType::String, &value} {} + /** * @brief Construct from a type-erased value * @param name Attribute name @@ -749,30 +778,74 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @brief Construct * @param types Which material types are described by this * data. Can be an empty set. - * @param data Attribute data + * @param attributeData Attribute data * @param importerState Importer-specific state * - * The @p data gets sorted by name internally, expecting no duplicates. + * The @p attributeData gets sorted by name internally, expecting no + * duplicates. */ - explicit MaterialData(MaterialTypes types, Containers::Array&& data, const void* importerState = nullptr) noexcept; + explicit MaterialData(MaterialTypes types, Containers::Array&& attributeData, const void* importerState = nullptr) noexcept: MaterialData{types, std::move(attributeData), nullptr, importerState} {} /** @overload */ /* Not noexcept because allocation happens inside */ - explicit MaterialData(MaterialTypes types, std::initializer_list data, const void* importerState = nullptr); + explicit MaterialData(MaterialTypes types, std::initializer_list attributeData, const void* importerState = nullptr): MaterialData{types, attributeData, {}, importerState} {} /** * @brief Construct a non-owned material data + * @param types Which material types are described by + * this data. Can be an empty set. + * @param attributeDataFlags Ignored. Used only for a safer + * distinction from the owning constructor. + * @param attributeData Attribute data + * @param importerState Importer-specific state + * + * The @p attributeData is expected to be already sorted by name, + * without duplicates. + */ + explicit MaterialData(MaterialTypes types, DataFlags attributeDataFlags, Containers::ArrayView attributeData, const void* importerState = nullptr) noexcept: MaterialData{types, attributeDataFlags, attributeData, {}, nullptr, importerState} {} + + /** + * @brief Construct with layers * @param types Which material types are described by this * data. Can be an empty set. - * @param dataFlags Ignored. Used only for a safer distinction - * from the owning constructor. - * @param data Attribute data + * @param attributeData Attribute data + * @param layerData Layer offset data + * @param importerState Importer-specific state + * + * The @p attributeData gets sorted by name internally, expecting no + * duplicates inside each layer. The @p layerData is expected to be + * either empty or a monotonically non-decreasing sequence of offsets + * not larger than @p attributeData size, with *i*-th item specifying + * end offset of *i*-th layer. + */ + explicit MaterialData(MaterialTypes types, Containers::Array&& attributeData, Containers::Array&& layerData, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MaterialData(MaterialTypes types, std::initializer_list attributeData, std::initializer_list layerData, const void* importerState = nullptr); + + /** + * @brief Construct a non-owned material data with layers + * @param types Which material types are described by + * this data. Can be an empty set. + * @param attributeDataFlags Ignored. Used only for a safer + * distinction from the owning constructor. + * @param attributeData Attribute data + * @param layerDataFlags Ignored. Used only for a safer + * distinction from the owning constructor. + * @param layerData Layer offset data * @param importerState Importer-specific state * * The @p data is expected to be already sorted by name, without - * duplicates. + * duplicates inside each layer. The @p layerData is expected to be + * either empty or a monotonically non-decreasing sequence of offsets + * not larger than @p attributeData size, with *i*-th item specifying + * end offset of *i*-th layer. */ - explicit MaterialData(MaterialTypes types, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + /* The second (ignored) DataFlags is present in order to make it ready + for a possible extension where only one of the data is non-owned. + But so far I didn't see a need. */ + explicit MaterialData(MaterialTypes types, DataFlags attributeDataFlags, Containers::ArrayView attributeData, DataFlags layerDataFlags, Containers::ArrayView layerData, const void* importerState = nullptr) noexcept; ~MaterialData(); @@ -806,67 +879,256 @@ class MAGNUM_TRADE_EXPORT MaterialData { } #endif + /** + * @brief Raw layer offset data + * + * May return @cpp nullptr @ce if the material doesn't have any extra + * layers. + * @see @ref releaseLayerData() + */ + Containers::ArrayView layerData() const { return _layerOffsets; } + /** * @brief Raw attribute data * * Returns @cpp nullptr @ce if the material has no attributes. - * @see @ref release() + * @see @ref releaseAttributeData() */ - Containers::ArrayView data() const { return _data; } + Containers::ArrayView attributeData() const { return _data; } - /** @brief Attribute count */ - UnsignedInt attributeCount() const { return _data.size(); } + /** + * @brief Layer count + * + * There's always at least the base material, so this function returns + * at least @cpp 1 @ce. + */ + UnsignedInt layerCount() const { + return _layerOffsets.empty() ? 1 : _layerOffsets.size(); + } + + /** + * @brief Whether a material has given named layer + * + * Layers with no name assigned are skipped. The base material (layer + * @cpp 0 @ce is skipped as well) to avoid confusing base material with + * a layer. If you want to create a material consisting of just a + * layer, use @cpp 0 @ce for the first layer offset in the constructor. + * @see @ref hasAttribute() + */ + bool hasLayer(Containers::StringView layer) const; /** - * @brief Whether the material has given attribute + * @brief ID of a named layer * + * The @p layer is expected to exist. + * @see @ref hasLayer() + */ + UnsignedInt layerId(Containers::StringView layer) const; + + /** + * @brief Layer name + * + * Retrieves a @ref MaterialAttribute::LayerName attribute from given + * layer, if present. Returns a @cpp nullptr @ce view if the layer + * has no name, and an empty non-null view if the layer name is empty. + * The @p layer is expected to be smaller than @ref layerCount() const. + * + * The name, if present, is ignored for the base material (layer + * @cpp 0 @ce) to avoid confsing base material with a layer. If you + * want to create a material consisting of just a layer, use @cpp 0 @ce + * for the first layer offset in the constructor. + */ + Containers::StringView layerName(UnsignedInt layer) const; + + /** + * @brief Attribute count in given layer + * + * The @p layer is expected to be smaller than @ref layerCount() const. + */ + UnsignedInt attributeCount(UnsignedInt layer) const; + + /** + * @brief Attribute count in a named layer + * + * The @p layer is expected to exist. + * @see @ref hasLayer() + */ + UnsignedInt attributeCount(Containers::StringView layer) const; + + /** + * @brief Attribute count in the base material + * + * Equivalent to calling @ref attributeCount(UnsignedInt) const with + * @p layer set to @cpp 0 @ce. + */ + UnsignedInt attributeCount() const { return attributeCount(0); } + + /** + * @brief Whether a material layer has given attribute + * + * The @p layer is expected to be smaller than @ref layerCount() const. + * @see @ref tryAttribute(), @ref attributeOr(), @ref hasLayer() + */ + bool hasAttribute(UnsignedInt layer, Containers::StringView name) const; + bool hasAttribute(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Whether a named material layer has given attribute + * + * The @p layer is expected to exist. + * @see @ref tryAttribute(), @ref attributeOr(), @ref hasLayer() + */ + bool hasAttribute(Containers::StringView layer, Containers::StringView name) const; + bool hasAttribute(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Whether the base material has given attribute + * + * Equivalent to calling @ref hasAttribute(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. * @see @ref tryAttribute(), @ref attributeOr() */ - bool hasAttribute(Containers::StringView name) const; - bool hasAttribute(MaterialAttribute name) const; /**< @overload */ + bool hasAttribute(Containers::StringView name) const { + return hasAttribute(0, name); + } + bool hasAttribute(MaterialAttribute name) const { + return hasAttribute(0, name); + } /**< @overload */ /** - * @brief ID of a named attribute + * @brief ID of a named attribute in given material layer * - * The @p name is expected to exist. + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p name is expected to exist in that layer. + * @see @ref hasAttribute() */ - UnsignedInt attributeId(Containers::StringView name) const; - UnsignedInt attributeId(MaterialAttribute name) const; /**< @overload */ + UnsignedInt attributeId(UnsignedInt layer, Containers::StringView name) const; + UnsignedInt attributeId(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ /** - * @brief Attribute name + * @brief ID of a named attribute in a named material layer * - * The @p id is expected to be smaller than @ref attributeCount() const. - * The returned view always has + * The @p layer is expected to exist and @p name is expected to exist + * in that layer. + * @see @ref hasLayer(), @ref hasAttribute() + */ + UnsignedInt attributeId(Containers::StringView layer, Containers::StringView name) const; + UnsignedInt attributeId(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief ID of a named attribute in the base material + * + * Equivalent to calling @ref attributeId(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + UnsignedInt attributeId(Containers::StringView name) const { + return attributeId(0, name); + } + UnsignedInt attributeId(MaterialAttribute name) const { + return attributeId(0, name); + } /**< @overload */ + + /** + * @brief Name of an attribute in given material layer + * + * The @p layer is expected to be smaller than @ref layerCount() const + * and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const + * in that layer. The returned view always has * @ref Corrade::Containers::StringViewFlag::NullTerminated set. * @see @ref attributeType() */ - Containers::StringView attributeName(UnsignedInt id) const; + Containers::StringView attributeName(UnsignedInt layer, UnsignedInt id) const; /** - * @brief Attribute type + * @brief Name of an attribute in a named material layer * - * The @p id is expected to be smaller than @ref attributeCount() const. + * The @p layer is expected to exist and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const + * in that layer. + * @see @ref hasLayer() + */ + Containers::StringView attributeName(Containers::StringView layer, UnsignedInt id) const; + + /** + * @brief Name of an attribute in the base material + * + * Equivalent to calling @ref attributeName(UnsignedInt, UnsignedInt) const + * with @p layer set to @cpp 0 @ce. + */ + Containers::StringView attributeName(UnsignedInt id) const { + return attributeName(0, id); + } + + /** + * @brief Type of an attribute in given material layer + * + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const + * in that layer. * @see @ref attributeName() */ - MaterialAttributeType attributeType(UnsignedInt id) const; + MaterialAttributeType attributeType(UnsignedInt layer, UnsignedInt id) const; /** - * @brief Type of a named attribute + * @brief Type of a named attribute in given material layer * - * The @p name is expected to exist. + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p name is expected to exist in that layer. * @see @ref hasAttribute() */ - MaterialAttributeType attributeType(Containers::StringView name) const; + MaterialAttributeType attributeType(UnsignedInt layer, Containers::StringView name) const; /** @overload */ - MaterialAttributeType attributeType(MaterialAttribute name) const; + MaterialAttributeType attributeType(UnsignedInt layer, MaterialAttribute name) const; /** - * @brief Type-erased attribute value + * @brief Type of an attribute in a named material layer * - * The @p id is expected to be smaller than @ref attributeCount() const. - * Cast the pointer to a concrete type based on @ref type(). Note that - * in case of a @ref MaterialAttributeType::Pointer or a + * The @p layer is expected to exist and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const + * in that layer. + * @see @ref hasLayer() + */ + MaterialAttributeType attributeType(Containers::StringView layer, UnsignedInt id) const; + + /** + * @brief Type of a named attribute in a named material layer + * + * The @p layer is expected to exist and @p name is expected to exist + * in that layer. + * @see @ref hasLayer(), @ref hasAttribute() + */ + MaterialAttributeType attributeType(Containers::StringView layer, Containers::StringView name) const; + MaterialAttributeType attributeType(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Type of an attribute in the base material + * + * Equivalent to calling @ref attributeType(UnsignedInt, UnsignedInt) const + * with @p layer set to @cpp 0 @ce. + */ + MaterialAttributeType attributeType(UnsignedInt id) const { + return attributeType(0, id); + } + + /** + * @brief Type of a named attribute in the base material + * + * Equivalent to calling @ref attributeType(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + MaterialAttributeType attributeType(Containers::StringView name) const { + return attributeType(0, name); + } + MaterialAttributeType attributeType(MaterialAttribute name) const { + return attributeType(0, name); + } /**< @overload */ + + /** + * @brief Type-erased value of an attribute in given material layer + * + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const + * in that layer. Cast the pointer to a concrete type based on + * @ref type(). Note that in case of a + * @ref MaterialAttributeType::Pointer or a * @ref MaterialAttributeType::MutablePointer, returns a * *pointer to a pointer*, not the pointer value itself. * @@ -876,13 +1138,14 @@ class MAGNUM_TRADE_EXPORT MaterialData { * string size in case the string data contain zero bytes, thus prefer * to use typed access in that case. */ - const void* attribute(UnsignedInt id) const; + const void* attribute(UnsignedInt layer, UnsignedInt id) const; /** - * @brief Type-erased value of a named attribute + * @brief Type-erased value of a named attribute in given material layer * - * The @p name is expected to exist. Cast the pointer to a concrete - * type based on @ref attributeType(). Note that + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p name is expected to exist in that layer. Cast the pointer to + * a concrete type based on @ref attributeType(). Note that * in case of a @ref MaterialAttributeType::Pointer or a * @ref MaterialAttributeType::MutablePointer, returns a * *pointer to a pointer*, not the pointer value itself. @@ -894,64 +1157,263 @@ class MAGNUM_TRADE_EXPORT MaterialData { * to use typed access in that case. * @see @ref hasAttribute(), @ref tryAttribute(), @ref attributeOr() */ - const void* attribute(Containers::StringView name) const; - const void* attribute(MaterialAttribute name) const; /**< @overload */ + const void* attribute(UnsignedInt layer, Containers::StringView name) const; + const void* attribute(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ /** - * @brief Attribute value + * @brief Type-erased value of an attribute in a named material layer + * + * The @p layer is expected to exist and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const + * in that layer. Cast the pointer to a concrete type based on + * @ref attributeType(). Note that in case of a + * @ref MaterialAttributeType::Pointer or a + * @ref MaterialAttributeType::MutablePointer, returns a + * *pointer to a pointer*, not the pointer value itself. + * + * In case of a @ref MaterialAttributeType::String returns a + * null-terminated @cpp const char* @ce (not a pointer to + * @ref Containers::StringView). This doesn't preserve the actual + * string size in case the string data contain zero bytes, thus prefer + * to use typed access in that case. + * @see @ref hasLayer() + */ + const void* attribute(Containers::StringView layer, UnsignedInt id) const; + + /** + * @brief Type-erased value of a named attribute in a named material layer + * + * The @p layer is expected to exist and @p name is expected to exist + * in that layer. Cast the pointer to a concrete type based on + * @ref attributeType(). Note that in case of a + * @ref MaterialAttributeType::Pointer or a + * @ref MaterialAttributeType::MutablePointer, returns a + * *pointer to a pointer*, not the pointer value itself. + * + * In case of a @ref MaterialAttributeType::String returns a + * null-terminated @cpp const char* @ce (not a pointer to + * @ref Containers::StringView). This doesn't preserve the actual + * string size in case the string data contain zero bytes, thus prefer + * to use typed access in that case. + * @see @ref hasLayer(), @ref hasAttribute() + */ + const void* attribute(Containers::StringView layer, Containers::StringView name) const; + const void* attribute(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Type-erased value of an attribute in the base material + * + * Equivalent to calling @ref attribute(UnsignedInt, UnsignedInt) const + * with @p layer set to @cpp 0 @ce. + */ + const void* attribute(UnsignedInt id) const { + return attribute(0, id); + } + + /** + * @brief Type-erased value of a named attribute in the base material + * + * Equivalent to calling @ref attribute(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + const void* attribute(Containers::StringView name) const { + return attribute(0, name); + } + const void* attribute(MaterialAttribute name) const { + return attribute(0, name); + } /**< @overload */ + + /** + * @brief Value of an attribute in given material layer * - * The @p id is expected to be smaller than @ref attributeCount() const. - * Expects that @p T corresponds to @ref attributeType(UnsignedInt) const - * for given @p id. In case of a string, the returned view always has + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p id is expected to be smaller than + * @ref attributeCount(UnsignedInt) const in that layer. Expects that + * @p T corresponds to @ref attributeType(UnsignedInt, UnsignedInt) const + * for given @p layer and @p id. In case of a string, the returned view + * always has @ref Corrade::Containers::StringViewFlag::NullTerminated + * set. + */ + template T attribute(UnsignedInt layer, UnsignedInt id) const; + + /** + * @brief Value of a named attribute in given material layer + * + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p name is expected to exist in that layer. Expects that @p T + * corresponds to @ref attributeType(UnsignedInt, Containers::StringView) const + * for given @p layer and @p name. In case of a string, the returned + * view always has * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + * @see @ref hasLayer(), @ref hasAttribute() */ - template T attribute(UnsignedInt id) const; + template T attribute(UnsignedInt layer, Containers::StringView name) const; + template T attribute(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ /** - * @brief Value of a named attribute + * @brief Value of an attribute in a named material layer * - * The @p name is expected to exist. Expects that @p T corresponds to - * @ref attributeType(Containers::StringView) const for given @p name. - * In case of a string, the returned view always has + * The @p layer is expected to exist and @p id is expected to be + * smaller than @ref attributeCount(UnsignedInt) const in that layer. + * Expects that @p T corresponds to + * @ref attributeType(Containers::StringView, UnsignedInt) const + * for given @p layer and @p id. In case of a string, the returned view + * always has @ref Corrade::Containers::StringViewFlag::NullTerminated + * set. + * @see @ref hasLayer() + */ + template T attribute(Containers::StringView layer, UnsignedInt id) const; + + /** + * @brief Value of a named attribute in a named material layer + * + * The @p layer is expected to exist and @p name is expected to exist + * in that layer. Expects that @p T corresponds to + * @ref attributeType(Containers::StringView, Containers::StringView) const + * for given @p layer and @p name. In case of a string, the returned + * view always has * @ref Corrade::Containers::StringViewFlag::NullTerminated set. - * @see @ref hasAttribute() + * @see @ref hasLayer(), @ref hasAttribute() + */ + template T attribute(Containers::StringView layer, Containers::StringView name) const; + template T attribute(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Value of an attribute in the base material + * + * Equivalent to calling @ref attribute(UnsignedInt, UnsignedInt) const + * with @p layer set to @cpp 0 @ce. + */ + template T attribute(UnsignedInt id) const { + return attribute(0, id); + } + + /** + * @brief Value of a named attribute in the base material + * + * Equivalent to calling @ref attribute(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. */ - template T attribute(Containers::StringView name) const; - template T attribute(MaterialAttribute name) const; /**< @overload */ + template T attribute(Containers::StringView name) const { + return attribute(0, name); + } + template T attribute(MaterialAttribute name) const { + return attribute(0, name); + } /**< @overload */ /** - * @brief Type-erased attribute value, if exists + * @brief Type-erased attribute value in given material layer, if exists * - * Compared to @ref attribute(Containers::StringView name) const, if - * @p name doesn't exist, returns @cpp nullptr @ce instead of - * asserting. Cast the pointer to a concrete type based on - * @ref attributeType(). + * Compared to @ref attribute(UnsignedInt, Containers::StringView name) const, + * if @p name doesn't exist, returns @cpp nullptr @ce instead of + * asserting. Expects that @p layer is smaller than @ref layerCount() const. + * Cast the pointer to a concrete type based on @ref attributeType(). * @see @ref hasAttribute(), @ref attributeOr() */ - const void* tryAttribute(Containers::StringView name) const; - const void* tryAttribute(MaterialAttribute name) const; /**< @overload */ + const void* tryAttribute(UnsignedInt layer, Containers::StringView name) const; + const void* tryAttribute(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ /** - * @brief Value of a named attribute, if exists + * @brief Type-erased attribute value in a named material layer, if exists * - * Compared to @ref attribute(Containers::StringView name) const, if - * @p name doesn't exist, returns @ref Corrade::Containers::NullOpt - * instead of asserting. Expects that @p T corresponds to - * @ref attributeType(Containers::StringView) const for given @p name. + * Compared to @ref attribute(Containers::StringView, Containers::StringView name) const, + * if @p name doesn't exist, returns @cpp nullptr @ce instead of + * asserting. Expects that @p layer exists. Cast the pointer to a + * concrete type based on @ref attributeType(). + * @see @ref hasLayer(), @ref hasAttribute(), @ref attributeOr() */ - template Containers::Optional tryAttribute(Containers::StringView name) const; - template Containers::Optional tryAttribute(MaterialAttribute name) const; /**< @overload */ + const void* tryAttribute(Containers::StringView layer, Containers::StringView name) const; + const void* tryAttribute(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ /** - * @brief Value of a named attribute or a default + * @brief Value of a named attribute in given material layer, if exists * - * Compared to @ref attribute(Containers::StringView name) const, if - * @p name doesn't exist, returns @p defaultValue instead of asserting. - * Expects that @p T corresponds to - * @ref attributeType(Containers::StringView) const for given @p name. + * Compared to @ref attribute(UnsignedInt, Containers::StringView name) const, + * if @p name doesn't exist, returns @ref Corrade::Containers::NullOpt + * instead of asserting. Expects that @p layer is smaller than + * @ref layerCount() const and that @p T corresponds to + * @ref attributeType(UnsignedInt, Containers::StringView) const for + * given @p layer and @p name. */ - template T attributeOr(Containers::StringView name, const T& defaultValue) const; - template T attributeOr(MaterialAttribute name, const T& defaultValue) const; /**< @overload */ + template Containers::Optional tryAttribute(UnsignedInt layer, Containers::StringView name) const; + template Containers::Optional tryAttribute(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Value of a named attribute in a named material layer, if exists + * + * Compared to @ref attribute(Containers::StringView, Containers::StringView name) const, + * if @p name doesn't exist, returns @ref Corrade::Containers::NullOpt + * instead of asserting. Expects that @p layer exists and that @p T + * corresponds to @ref attributeType(Containers::StringView, Containers::StringView) const + * for given @p layer and @p name. + * @see @ref hasLayer() + */ + template Containers::Optional tryAttribute(Containers::StringView layer, Containers::StringView name) const; + template Containers::Optional tryAttribute(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Type-erased attribute value in the base material, if exists + * + * Equivalent to calling @ref tryAttribute(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + const void* tryAttribute(Containers::StringView name) const { + return tryAttribute(0, name); + } + const void* tryAttribute(MaterialAttribute name) const { + return tryAttribute(0, name); + } /**< @overload */ + + /** + * @brief Value of a named attribute in the base material, if exists + * + * Equivalent to calling @ref tryAttribute(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + template Containers::Optional tryAttribute(Containers::StringView name) const { + return tryAttribute(0, name); + } + template Containers::Optional tryAttribute(MaterialAttribute name) const { + return tryAttribute(0, name); + } /**< @overload */ + + /** + * @brief Value of a named attribute in given layer or a default + * + * Compared to @ref attribute(UnsignedInt, Containers::StringView name) const, + * if @p name doesn't exist, returns @p defaultValue instead of + * asserting. Expects that @p layer is smaller than @ref layerCount() + * const + * and that @p T corresponds to @ref attributeType(UnsignedInt, Containers::StringView) const + * for given @p layer and @p name. + */ + template T attributeOr(UnsignedInt layer, Containers::StringView name, const T& defaultValue) const; + template T attributeOr(UnsignedInt layer, MaterialAttribute name, const T& defaultValue) const; /**< @overload */ + + /** + * @brief Value of a named attribute in a named layer or a default + * + * Compared to @ref attribute(Containers::StringView, Containers::StringView name) const, + * if @p name doesn't exist, returns @p defaultValue instead of + * asserting. Expects that @p layer exists and that @p T corresponds to + * @ref attributeType(Containers::StringView, Containers::StringView) const + * for given @p layer and @p name. + * @see @ref hasLayer() + */ + template T attributeOr(Containers::StringView layer, Containers::StringView name, const T& defaultValue) const; + template T attributeOr(Containers::StringView layer, MaterialAttribute name, const T& defaultValue) const; /**< @overload */ + + /** + * @brief Value of a named attribute in the base material or a default + * + * Equivalent to calling @ref attributeOr(UnsignedInt, Containers::StringView, const T&) const + * with @p layer set to @cpp 0 @ce. + */ + template T attributeOr(Containers::StringView name, const T& defaultValue) const { + return attributeOr(0, name, defaultValue); + } + template T attributeOr(MaterialAttribute name, const T& defaultValue) const { + return attributeOr(0, name, defaultValue); + }/**< @overload */ /** * @brief Whether a material is double-sided @@ -995,16 +1457,38 @@ class MAGNUM_TRADE_EXPORT MaterialData { Float alphaMask() const; /** - * @brief Release data storage + * @brief Release layer data storage * - * Releases the ownership of the attribute array and resets internal - * state to default. The material then behaves like if it has no - * attributes. Note that the returned array has a custom no-op + * Releases the ownership of the layer offset array and resets internal + * layer-related state to default. The material then behaves like if it + * has no layers. 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 data() + * + * @attention Querying attributes after calling @ref releaseLayerData() + * has undefined behavior and might lead to crashes. This is done + * intentionally in order to simplify the interaction between this + * function and @ref releaseAttributeData(). + * @see @ref layerData() */ - Containers::Array release(); + Containers::Array releaseLayerData(); + + /** + * @brief Release attribute data storage + * + * Releases the ownership of the attribute array and resets internal + * attribute-related state to default. The material then behaves like + * if 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. + * + * @attention Querying layers after calling @ref releaseAttributeData() + * has undefined behavior and might lead to crashes. This is done + * intentionally in order to simplify the interaction between this + * function and @ref releaseLayerData(). + * @see @ref attributeData() + */ + Containers::Array releaseAttributeData(); /** * @brief Importer-specific state @@ -1015,10 +1499,15 @@ class MAGNUM_TRADE_EXPORT MaterialData { private: static Containers::StringView attributeString(MaterialAttribute name); - /* Internal helper that doesn't assert, unlike attributeId() */ - UnsignedInt attributeFor(Containers::StringView name) const; + /* Internal helpers that don't assert, unlike layerId() / attributeId() */ + UnsignedInt layerFor(Containers::StringView layer) const; + UnsignedInt layerOffset(UnsignedInt layer) const { + return layer && _layerOffsets ? _layerOffsets[layer - 1] : 0; + } + UnsignedInt attributeFor(UnsignedInt layer, Containers::StringView name) const; Containers::Array _data; + Containers::Array _layerOffsets; MaterialTypes _types; const void* _importerState; }; @@ -1131,55 +1620,113 @@ template T MaterialAttributeData::value() const { template<> Containers::StringView MaterialAttributeData::value() const; #endif -template T MaterialData::attribute(UnsignedInt id) const { - const void* const value = attribute(id); +template T MaterialData::attribute(const UnsignedInt layer, const UnsignedInt id) const { + const void* const value = attribute(layer, id); #ifdef CORRADE_GRACEFUL_ASSERT if(!value) return {}; #endif - CORRADE_ASSERT(Implementation::MaterialAttributeTypeFor::type() == _data[id]._data.type, - "Trade::MaterialData::attribute(): improper type requested for" << (_data[id]._data.data + 1) << "of" << _data[id]._data.type, {}); + const Trade::MaterialAttributeData& data = _data[layerOffset(layer) + id]; + CORRADE_ASSERT(Implementation::MaterialAttributeTypeFor::type() == data._data.type, + "Trade::MaterialData::attribute(): improper type requested for" << (data._data.data + 1) << "of" << data._data.type, {}); return *reinterpret_cast(value); } #ifndef DOXYGEN_GENERATING_OUTPUT -template<> Containers::StringView MaterialData::attribute(UnsignedInt) const; +template<> Containers::StringView MaterialData::attribute(UnsignedInt, UnsignedInt) const; #endif -template T MaterialData::attribute(Containers::StringView name) const { - const UnsignedInt id = attributeFor(name); +template T MaterialData::attribute(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); + return attribute(layer, id); +} + +template T MaterialData::attribute(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); + return attribute(layer, string); +} + +template T MaterialData::attribute(const Containers::StringView layer, const UnsignedInt id) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attribute(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return attribute(layerId, id); +} + +template T MaterialData::attribute(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); + const UnsignedInt id = attributeFor(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, - "Trade::MaterialData::attribute(): attribute" << name << "not found", {}); - return attribute(id); + "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); + return attribute(layerId, id); } -template T MaterialData::attribute(MaterialAttribute name) const { +template T MaterialData::attribute(const Containers::StringView layer, const MaterialAttribute name) const { const Containers::StringView string = attributeString(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); - return attribute(string); + return attribute(layer, string); } -template Containers::Optional MaterialData::tryAttribute(Containers::StringView name) const { - const UnsignedInt id = attributeFor(name); +template Containers::Optional MaterialData::tryAttribute(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::tryAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); if(id == ~UnsignedInt{}) return {}; - return attribute(id); + return attribute(layer, id); +} + +template Containers::Optional MaterialData::tryAttribute(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); + return tryAttribute(layer, string); +} + +template Containers::Optional MaterialData::tryAttribute(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::tryAttribute(): layer" << layer << "not found", {}); + return tryAttribute(layerId, name); } -template Containers::Optional MaterialData::tryAttribute(MaterialAttribute name) const { +template Containers::Optional MaterialData::tryAttribute(const Containers::StringView layer, const MaterialAttribute name) const { const Containers::StringView string = attributeString(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); - return tryAttribute(string); + return tryAttribute(layer, string); } -template T MaterialData::attributeOr(Containers::StringView name, const T& defaultValue) const { - const UnsignedInt id = attributeFor(name); +template T MaterialData::attributeOr(const UnsignedInt layer, const Containers::StringView name, const T& defaultValue) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attributeOr(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); if(id == ~UnsignedInt{}) return defaultValue; - return attribute(id); + return attribute(layer, id); +} + +template T MaterialData::attributeOr(const UnsignedInt layer, const MaterialAttribute name, const T& defaultValue) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << name, {}); + return attributeOr(layer, string, defaultValue); +} + +template T MaterialData::attributeOr(const Containers::StringView layer, const Containers::StringView name, const T& defaultValue) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attributeOr(): layer" << layer << "not found", {}); + return attributeOr(layerId, name, defaultValue); } -template T MaterialData::attributeOr(MaterialAttribute name, const T& defaultValue) const { +template T MaterialData::attributeOr(const Containers::StringView layer, const MaterialAttribute name, const T& defaultValue) const { const Containers::StringView string = attributeString(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << name, {}); - return attributeOr(string, defaultValue); + return attributeOr(layer, string, defaultValue); } }} diff --git a/src/Magnum/Trade/Test/MaterialDataTest.cpp b/src/Magnum/Trade/Test/MaterialDataTest.cpp index ddc690703..1ed038814 100644 --- a/src/Magnum/Trade/Test/MaterialDataTest.cpp +++ b/src/Magnum/Trade/Test/MaterialDataTest.cpp @@ -57,13 +57,15 @@ class MaterialDataTest: public TestSuite::Tester { void constructAttributePointer(); void constructAttributeMutablePointer(); - void constructAttributeStringValue(); + void constructAttributeStringNameStringValue(); + void constructAttributeNameStringValue(); void constructAttributeInvalidName(); void constructAttributeWrongTypeForName(); void constructAttributeInvalidType(); void constructAttributeTooLarge(); void constructAttributeTooLargeString(); + void constructAttributeTooLargeNameString(); void constructAttributeWrongAccessType(); void constructAttributeWrongAccessPointerType(); void constructAttributeWrongAccessTypeString(); @@ -73,10 +75,17 @@ class MaterialDataTest: public TestSuite::Tester { void constructDuplicateAttribute(); void constructFromImmutableSortedArray(); + void constructLayers(); + void constructLayersNotMonotonic(); + void constructLayersOffsetOutOfBounds(); + void constructNonOwned(); + void constructNonOwnedLayers(); void constructNonOwnedEmptyAttribute(); void constructNonOwnedNotSorted(); void constructNonOwnedDuplicateAttribute(); + void constructNonOwnedLayersNotMonotonic(); + void constructNonOwnedLayersOffsetOutOfBounds(); void constructCopy(); void constructMove(); @@ -86,13 +95,25 @@ class MaterialDataTest: public TestSuite::Tester { void accessString(); void accessOptional(); void accessOutOfBounds(); - void accessInvalidAttributeName(); void accessNotFound(); + void accessInvalidAttributeName(); void accessWrongType(); void accessWrongPointerType(); void accessWrongTypeString(); - void release(); + void accessLayerLayerNameInBaseMaterial(); + void accessLayerEmptyLayer(); + void accessLayerIndexOptional(); + void accessLayerStringOptional(); + void accessLayerOutOfBounds(); + void accessLayerNotFound(); + void accessOutOfBoundsInLayerIndex(); + void accessOutOfBoundsInLayerString(); + void accessNotFoundInLayerIndex(); + void accessNotFoundInLayerString(); + + void releaseAttributes(); + void releaseLayers(); #ifdef MAGNUM_BUILD_DEPRECATED void constructPhongDeprecated(); @@ -167,13 +188,15 @@ MaterialDataTest::MaterialDataTest() { &MaterialDataTest::constructAttributePointer, &MaterialDataTest::constructAttributeMutablePointer, - &MaterialDataTest::constructAttributeStringValue, + &MaterialDataTest::constructAttributeStringNameStringValue, + &MaterialDataTest::constructAttributeNameStringValue, &MaterialDataTest::constructAttributeInvalidName, &MaterialDataTest::constructAttributeWrongTypeForName, &MaterialDataTest::constructAttributeInvalidType, &MaterialDataTest::constructAttributeTooLarge, &MaterialDataTest::constructAttributeTooLargeString, + &MaterialDataTest::constructAttributeTooLargeNameString, &MaterialDataTest::constructAttributeWrongAccessType, &MaterialDataTest::constructAttributeWrongAccessPointerType, &MaterialDataTest::constructAttributeWrongAccessTypeString, @@ -186,10 +209,17 @@ MaterialDataTest::MaterialDataTest() { addTests({&MaterialDataTest::constructFromImmutableSortedArray, + &MaterialDataTest::constructLayers, + &MaterialDataTest::constructLayersNotMonotonic, + &MaterialDataTest::constructLayersOffsetOutOfBounds, + &MaterialDataTest::constructNonOwned, + &MaterialDataTest::constructNonOwnedLayers, &MaterialDataTest::constructNonOwnedEmptyAttribute, &MaterialDataTest::constructNonOwnedNotSorted, &MaterialDataTest::constructNonOwnedDuplicateAttribute, + &MaterialDataTest::constructNonOwnedLayersNotMonotonic, + &MaterialDataTest::constructNonOwnedLayersOffsetOutOfBounds, &MaterialDataTest::constructCopy, &MaterialDataTest::constructMove, @@ -199,13 +229,25 @@ MaterialDataTest::MaterialDataTest() { &MaterialDataTest::accessString, &MaterialDataTest::accessOptional, &MaterialDataTest::accessOutOfBounds, - &MaterialDataTest::accessInvalidAttributeName, &MaterialDataTest::accessNotFound, + &MaterialDataTest::accessInvalidAttributeName, &MaterialDataTest::accessWrongType, &MaterialDataTest::accessWrongPointerType, &MaterialDataTest::accessWrongTypeString, - &MaterialDataTest::release, + &MaterialDataTest::accessLayerLayerNameInBaseMaterial, + &MaterialDataTest::accessLayerEmptyLayer, + &MaterialDataTest::accessLayerIndexOptional, + &MaterialDataTest::accessLayerStringOptional, + &MaterialDataTest::accessLayerOutOfBounds, + &MaterialDataTest::accessLayerNotFound, + &MaterialDataTest::accessOutOfBoundsInLayerIndex, + &MaterialDataTest::accessOutOfBoundsInLayerString, + &MaterialDataTest::accessNotFoundInLayerIndex, + &MaterialDataTest::accessNotFoundInLayerString, + + &MaterialDataTest::releaseAttributes, + &MaterialDataTest::releaseLayers, #ifdef MAGNUM_BUILD_DEPRECATED &MaterialDataTest::constructPhongDeprecated, @@ -302,9 +344,14 @@ void MaterialDataTest::attributeMap() { CORRADE_COMPARE(materialAttributeTypeSize(MaterialAttributeType::typeName), sizeof(type)); \ CORRADE_COMPARE_AS(sizeof(type) + Containers::arraySize(#name_) + sizeof(MaterialAttributeType), sizeof(MaterialAttributeData), TestSuite::Compare::LessOrEqual); \ break; + #define _cnt(name_, string, typeName, type) \ + case MaterialAttribute::name_: \ + CORRADE_COMPARE((MaterialAttributeData{MaterialAttribute::name_, type{}}.name()), string); \ + break; #include "Magnum/Trade/Implementation/materialAttributeProperties.hpp" #undef _c #undef _ct + #undef _cnt } #ifdef __GNUC__ #pragma GCC diagnostic pop @@ -451,7 +498,7 @@ void MaterialDataTest::constructAttributeMutablePointer() { CORRADE_COMPARE(typeErased.value(), &data); } -void MaterialDataTest::constructAttributeStringValue() { +void MaterialDataTest::constructAttributeStringNameStringValue() { /* Explicitly using a non-null-terminated view on input to check the null byte isn't read by accident*/ MaterialAttributeData attribute{"name that's long", "and a value\0that's also long but still fits!!"_s.except(1)}; @@ -486,6 +533,33 @@ void MaterialDataTest::constructAttributeStringValue() { CORRADE_COMPARE(typeErased.value()[typeErased.value().size()], '\0'); } +void MaterialDataTest::constructAttributeNameStringValue() { + /* Explicitly using a non-null-terminated view on input to check the null + byte isn't read by accident*/ + + MaterialAttributeData attribute{MaterialAttribute::LayerName, "a value\0that's long but still fits!!"_s.except(1)}; + CORRADE_COMPARE(attribute.name(), "$LayerName"); + CORRADE_COMPARE(attribute.name().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(attribute.name()[attribute.name().size()], '\0'); + CORRADE_COMPARE(attribute.type(), MaterialAttributeType::String); + /* Pointer access will stop at the first null byte, but typed access won't */ + CORRADE_COMPARE(static_cast(attribute.value()), "a value"_s); + CORRADE_COMPARE(attribute.value(), "a value\0that's long but still fits!"_s); + CORRADE_COMPARE(attribute.value().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(attribute.value()[attribute.value().size()], '\0'); + + /* Type-erased variant */ + const Containers::StringView value = "a value\0that's long but still fits!!"_s.except(1); + MaterialAttributeData typeErased{MaterialAttribute::LayerName, MaterialAttributeType::String, &value}; + CORRADE_COMPARE(typeErased.name(), "$LayerName"); + CORRADE_COMPARE(typeErased.name().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(typeErased.name()[typeErased.name().size()], '\0'); + CORRADE_COMPARE(typeErased.type(), MaterialAttributeType::String); + CORRADE_COMPARE(typeErased.value(), "a value\0that's long but still fits!"_s); + CORRADE_COMPARE(typeErased.value().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(typeErased.value()[typeErased.value().size()], '\0'); +} + void MaterialDataTest::constructAttributeInvalidName() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -558,6 +632,18 @@ void MaterialDataTest::constructAttributeTooLargeString() { "Trade::MaterialAttributeData: name attribute is long and value This is a problem, got a long piece of text! too long, expected at most 60 bytes in total but got 61\n"); } +void MaterialDataTest::constructAttributeTooLargeNameString() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MaterialAttributeData{MaterialAttribute::LayerName, "This is a problem, got a huge, yuuge value to store"}; + CORRADE_COMPARE(out.str(), + "Trade::MaterialAttributeData: name $LayerName and value This is a problem, got a huge, yuuge value to store too long, expected at most 60 bytes in total but got 61\n"); +} + void MaterialDataTest::constructAttributeWrongAccessType() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -607,10 +693,15 @@ void MaterialDataTest::construct() { }, &state}; CORRADE_COMPARE(data.types(), MaterialType::Phong); + CORRADE_COMPARE(data.layerCount(), 1); + CORRADE_VERIFY(!data.layerData()); CORRADE_COMPARE(data.attributeCount(), 4); - CORRADE_COMPARE(data.data().size(), 4); + CORRADE_COMPARE(data.attributeData().size(), 4); CORRADE_COMPARE(data.importerState(), &state); + CORRADE_COMPARE(data.layerName(0), ""); + CORRADE_VERIFY(!data.hasLayer("")); + /* Verify sorting */ CORRADE_COMPARE(data.attributeName(0), "AmbientTextureMatrix"); CORRADE_COMPARE(data.attributeName(1), "DiffuseTextureCoordinates"); @@ -734,6 +825,213 @@ void MaterialDataTest::constructFromImmutableSortedArray() { CORRADE_COMPARE(data.attributeName(1), "yay this is last"); } +void MaterialDataTest::constructLayers() { + int state; + MaterialData data{MaterialType::Phong, { + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + + /* Layer name gets sorted first by the constructor */ + {"highlightColor", 0x335566ff_rgbaf}, + {MaterialAttribute::AlphaBlend, true}, + {MaterialAttribute::LayerName, "ClearCoat"}, + + /* Empty layer here */ + + /* Unnamed but nonempty layer */ + {"thickness", 0.015f}, + {MaterialAttribute::NormalTexture, 3u} + }, { + 2, 5, 5, 7 + }, &state}; + + CORRADE_COMPARE(data.types(), MaterialType::Phong); + CORRADE_COMPARE(data.importerState(), &state); + + CORRADE_COMPARE(data.layerCount(), 4); + CORRADE_COMPARE(data.layerData().size(), 4); + + CORRADE_COMPARE(data.attributeData().size(), 7); + CORRADE_COMPARE(data.attributeCount(0), 2); + CORRADE_COMPARE(data.attributeCount(1), 3); + CORRADE_COMPARE(data.attributeCount(2), 0); + CORRADE_COMPARE(data.attributeCount(3), 2); + CORRADE_COMPARE(data.attributeCount("ClearCoat"), 3); + + /* Layer access */ + CORRADE_COMPARE(data.layerName(0), ""); + CORRADE_COMPARE(data.layerName(1), "ClearCoat"); + CORRADE_COMPARE(data.layerName(2), ""); + CORRADE_COMPARE(data.layerName(3), ""); + + CORRADE_VERIFY(data.hasLayer("ClearCoat")); + CORRADE_VERIFY(!data.hasLayer("")); + CORRADE_VERIFY(!data.hasLayer("DoubleSided")); + + CORRADE_COMPARE(data.layerId("ClearCoat"), 1); + + /* Verify sorting in each layer */ + CORRADE_COMPARE(data.attributeName(0, 0), "DiffuseTextureCoordinates"); + CORRADE_COMPARE(data.attributeName(0, 1), "DoubleSided"); + + CORRADE_COMPARE(data.attributeName(1, 0), "$LayerName"); + CORRADE_COMPARE(data.attributeName(1, 1), "AlphaBlend"); + CORRADE_COMPARE(data.attributeName(1, 2), "highlightColor"); + + CORRADE_COMPARE(data.attributeName(3, 0), "NormalTexture"); + CORRADE_COMPARE(data.attributeName(3, 1), "thickness"); + + /* Access by layer ID and attribute ID */ + CORRADE_COMPARE(data.attributeType(0, 0), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(data.attributeType(1, 2), MaterialAttributeType::Vector4); + CORRADE_COMPARE(data.attributeType(3, 1), MaterialAttributeType::Float); + + CORRADE_COMPARE(data.attribute(0, 0), 5); + CORRADE_COMPARE(data.attribute(1, 2), 0x335566ff_rgbaf); + CORRADE_COMPARE(data.attribute(3, 1), 0.015f); + + CORRADE_COMPARE(*static_cast(data.attribute(0, 0)), 5); + CORRADE_COMPARE(*static_cast(data.attribute(1, 2)), 0x335566ff_rgbaf); + CORRADE_COMPARE(*static_cast(data.attribute(3, 1)), 0.015f); + + /* Access by layer ID and attribute name */ + CORRADE_VERIFY(data.hasAttribute(0, MaterialAttribute::DiffuseTextureCoordinates)); + CORRADE_VERIFY(!data.hasAttribute(0, MaterialAttribute::AlphaBlend)); + CORRADE_VERIFY(data.hasAttribute(1, MaterialAttribute::AlphaBlend)); + CORRADE_VERIFY(data.hasAttribute(1, MaterialAttribute::LayerName)); + CORRADE_VERIFY(!data.hasAttribute(2, MaterialAttribute::LayerName)); + CORRADE_VERIFY(!data.hasAttribute(2, MaterialAttribute::NormalTexture)); + CORRADE_VERIFY(data.hasAttribute(3, MaterialAttribute::NormalTexture)); + + CORRADE_COMPARE(data.attributeId(0, MaterialAttribute::DiffuseTextureCoordinates), 0); + CORRADE_COMPARE(data.attributeId(1, MaterialAttribute::AlphaBlend), 1); + CORRADE_COMPARE(data.attributeId(1, MaterialAttribute::LayerName), 0); + CORRADE_COMPARE(data.attributeId(3, MaterialAttribute::NormalTexture), 0); + + CORRADE_COMPARE(data.attributeType(0, MaterialAttribute::DiffuseTextureCoordinates), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(data.attributeType(1, MaterialAttribute::AlphaBlend), MaterialAttributeType::Bool); + CORRADE_COMPARE(data.attributeType(1, MaterialAttribute::LayerName), MaterialAttributeType::String); + CORRADE_COMPARE(data.attributeType(3, MaterialAttribute::NormalTexture), MaterialAttributeType::UnsignedInt); + + CORRADE_COMPARE(data.attribute(0, MaterialAttribute::DiffuseTextureCoordinates), 5); + CORRADE_COMPARE(data.attribute(1, MaterialAttribute::AlphaBlend), true); + CORRADE_COMPARE(data.attribute(1, MaterialAttribute::LayerName), "ClearCoat"); + CORRADE_COMPARE(data.attribute(3, MaterialAttribute::NormalTexture), 3); + + CORRADE_COMPARE(*static_cast(data.attribute(0, MaterialAttribute::DiffuseTextureCoordinates)), 5); + CORRADE_COMPARE(*static_cast(data.attribute(1, MaterialAttribute::AlphaBlend)), true); + CORRADE_COMPARE(static_cast(data.attribute(1, MaterialAttribute::LayerName)), "ClearCoat"_s); + CORRADE_COMPARE(*static_cast(data.attribute(3, MaterialAttribute::NormalTexture)), 3); + + /* Access by layer ID and attribute string */ + CORRADE_VERIFY(data.hasAttribute(0, "DoubleSided")); + CORRADE_VERIFY(!data.hasAttribute(0, "highlightColor")); + CORRADE_VERIFY(data.hasAttribute(1, "highlightColor")); + CORRADE_VERIFY(data.hasAttribute(1, "$LayerName")); + CORRADE_VERIFY(!data.hasAttribute(2, "$LayerName")); + CORRADE_VERIFY(!data.hasAttribute(2, "NormalTexture")); + CORRADE_VERIFY(data.hasAttribute(3, "NormalTexture")); + + CORRADE_COMPARE(data.attributeId(0, "DoubleSided"), 1); + CORRADE_COMPARE(data.attributeId(1, "highlightColor"), 2); + CORRADE_COMPARE(data.attributeId(1, "$LayerName"), 0); + CORRADE_COMPARE(data.attributeId(3, "NormalTexture"), 0); + + CORRADE_COMPARE(data.attributeType(0, "DoubleSided"), MaterialAttributeType::Bool); + CORRADE_COMPARE(data.attributeType(1, "highlightColor"), MaterialAttributeType::Vector4); + CORRADE_COMPARE(data.attributeType(1, "$LayerName"), MaterialAttributeType::String); + CORRADE_COMPARE(data.attributeType(3, "NormalTexture"), MaterialAttributeType::UnsignedInt); + + CORRADE_COMPARE(data.attribute(0, "DoubleSided"), true); + CORRADE_COMPARE(data.attribute(1, "highlightColor"), 0x335566ff_rgbaf); + CORRADE_COMPARE(data.attribute(1, "$LayerName"), "ClearCoat"); + CORRADE_COMPARE(data.attribute(3, "NormalTexture"), 3); + + CORRADE_COMPARE(*static_cast(data.attribute(0, "DoubleSided")), true); + CORRADE_COMPARE(*static_cast(data.attribute(1, "highlightColor")), 0x335566ff_rgbaf); + CORRADE_COMPARE(static_cast(data.attribute(1, "$LayerName")), "ClearCoat"_s); + CORRADE_COMPARE(*static_cast(data.attribute(3, "NormalTexture")), 3); + + /* Access by layer string and attribute ID */ + CORRADE_COMPARE(data.attributeName("ClearCoat", 1), "AlphaBlend"); + CORRADE_COMPARE(data.attributeName("ClearCoat", 2), "highlightColor"); + + CORRADE_COMPARE(data.attributeType("ClearCoat", 1), MaterialAttributeType::Bool); + CORRADE_COMPARE(data.attributeType("ClearCoat", 2), MaterialAttributeType::Vector4); + + CORRADE_COMPARE(data.attribute("ClearCoat", 1), true); + CORRADE_COMPARE(data.attribute("ClearCoat", 2), 0x335566ff_rgbaf); + + CORRADE_COMPARE(*static_cast(data.attribute("ClearCoat", 1)), true); + CORRADE_COMPARE(*static_cast(data.attribute("ClearCoat", 2)), 0x335566ff_rgbaf); + + /* Access by layer string and attribute name */ + CORRADE_VERIFY(data.hasAttribute("ClearCoat", MaterialAttribute::AlphaBlend)); + CORRADE_VERIFY(data.hasAttribute("ClearCoat", MaterialAttribute::LayerName)); + + CORRADE_COMPARE(data.attributeId("ClearCoat", MaterialAttribute::AlphaBlend), 1); + CORRADE_COMPARE(data.attributeId("ClearCoat", MaterialAttribute::LayerName), 0); + + CORRADE_COMPARE(data.attributeType("ClearCoat", MaterialAttribute::AlphaBlend), MaterialAttributeType::Bool); + CORRADE_COMPARE(data.attributeType("ClearCoat", MaterialAttribute::LayerName), MaterialAttributeType::String); + + CORRADE_COMPARE(data.attribute("ClearCoat", MaterialAttribute::AlphaBlend), true); + CORRADE_COMPARE(data.attribute("ClearCoat", MaterialAttribute::LayerName), "ClearCoat"); + + CORRADE_COMPARE(*static_cast(data.attribute("ClearCoat", MaterialAttribute::AlphaBlend)), true); + CORRADE_COMPARE(static_cast(data.attribute("ClearCoat", MaterialAttribute::LayerName)), "ClearCoat"_s); + + /* Access by layer string and attribute string */ + CORRADE_VERIFY(data.hasAttribute("ClearCoat", "highlightColor")); + CORRADE_VERIFY(data.hasAttribute("ClearCoat", "$LayerName")); + + CORRADE_COMPARE(data.attributeId("ClearCoat", "highlightColor"), 2); + CORRADE_COMPARE(data.attributeId("ClearCoat", "$LayerName"), 0); + + CORRADE_COMPARE(data.attributeType("ClearCoat", "highlightColor"), MaterialAttributeType::Vector4); + CORRADE_COMPARE(data.attributeType("ClearCoat", "$LayerName"), MaterialAttributeType::String); + + CORRADE_COMPARE(data.attribute("ClearCoat", "highlightColor"), 0x335566ff_rgbaf); + CORRADE_COMPARE(data.attribute("ClearCoat", "$LayerName"), "ClearCoat"); + + CORRADE_COMPARE(*static_cast(data.attribute("ClearCoat", "highlightColor")), 0x335566ff_rgbaf); + CORRADE_COMPARE(static_cast(data.attribute("ClearCoat", "$LayerName")), "ClearCoat"_s); +} + +void MaterialDataTest::constructLayersNotMonotonic() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MaterialData data{MaterialType::Phong, { + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + {MaterialAttribute::AlphaBlend, true}, + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::NormalTexture, 3u} + }, {2, 5, 4, 5}}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData: invalid range (5, 4) for layer 2 with 5 attributes in total\n"); +} + +void MaterialDataTest::constructLayersOffsetOutOfBounds() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MaterialData data{MaterialType::Phong, { + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + {MaterialAttribute::AlphaBlend, true}, + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::NormalTexture, 3u} + }, {2, 6}}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData: invalid range (2, 6) for layer 1 with 5 attributes in total\n"); +} + void MaterialDataTest::constructNonOwned() { constexpr MaterialAttributeData attributes[]{ {"AmbientTextureMatrix"_s, Matrix3{{0.5f, 0.0f, 0.0f}, @@ -749,9 +1047,11 @@ void MaterialDataTest::constructNonOwned() { /* Expecting the same output as in construct() */ CORRADE_COMPARE(data.types(), MaterialType::Phong); + CORRADE_COMPARE(data.layerCount(), 1); + CORRADE_VERIFY(!data.layerData()); CORRADE_COMPARE(data.attributeCount(), 4); - CORRADE_COMPARE(data.data().size(), 4); - CORRADE_COMPARE(data.data().data(), attributes); + CORRADE_COMPARE(data.attributeData().size(), 4); + CORRADE_COMPARE(data.attributeData().data(), attributes); CORRADE_COMPARE(data.importerState(), &state); /* We sorted the input already */ @@ -764,6 +1064,67 @@ void MaterialDataTest::constructNonOwned() { owned vs non-owned */ } +void MaterialDataTest::constructNonOwnedLayers() { + constexpr MaterialAttributeData attributes[]{ + {"DiffuseCoordinateSet"_s, 5u}, + {"DoubleSided"_s, true}, + + {"$LayerName"_s, "ClearCoat"_s}, + {"AlphaBlend"_s, true}, + {"highlightColor"_s, Vector4{0.2f, 0.6f, 0.4f, 1.0f}}, + + /* Empty layer here */ + + /* Unnamed but nonempty layer */ + {"NormalTexture"_s, 3u}, + {"thickness"_s, 0.015f} + }; + + constexpr UnsignedInt layers[]{ + 2, 5, 5, 7 + }; + + int state; + MaterialData data{MaterialType::Phong, + {}, attributes, + {}, layers, &state}; + + /* Expecting the same output as in constructLayers() */ + CORRADE_COMPARE(data.types(), MaterialType::Phong); + CORRADE_COMPARE(data.importerState(), &state); + + CORRADE_COMPARE(data.layerCount(), 4); + CORRADE_COMPARE(data.layerData().size(), 4); + CORRADE_COMPARE(data.layerData().data(), layers); + + CORRADE_COMPARE(data.attributeData().size(), 7); + CORRADE_COMPARE(data.attributeData().data(), attributes); + CORRADE_COMPARE(data.attributeCount(0), 2); + CORRADE_COMPARE(data.attributeCount(1), 3); + CORRADE_COMPARE(data.attributeCount(2), 0); + CORRADE_COMPARE(data.attributeCount(3), 2); + + /* Layer access */ + CORRADE_COMPARE(data.layerName(0), ""); + CORRADE_COMPARE(data.layerName(1), "ClearCoat"); + CORRADE_COMPARE(data.layerName(2), ""); + CORRADE_COMPARE(data.layerName(3), ""); + + /* We sorted the input already */ + CORRADE_COMPARE(data.attributeName(0, 0), "DiffuseCoordinateSet"); + CORRADE_COMPARE(data.attributeName(0, 1), "DoubleSided"); + + CORRADE_COMPARE(data.attributeName(1, 0), "$LayerName"); + CORRADE_COMPARE(data.attributeName(1, 1), "AlphaBlend"); + CORRADE_COMPARE(data.attributeName(1, 2), "highlightColor"); + + CORRADE_COMPARE(data.attributeName(3, 0), "NormalTexture"); + CORRADE_COMPARE(data.attributeName(3, 1), "thickness"); + + /* No need to verify the contents as there's no difference in access in + owned vs non-owned */ +} + void MaterialDataTest::constructNonOwnedEmptyAttribute() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -816,6 +1177,56 @@ void MaterialDataTest::constructNonOwnedDuplicateAttribute() { CORRADE_COMPARE(out.str(), "Trade::MaterialData: duplicate attribute DiffuseTextureCoordinates\n"); } +void MaterialDataTest::constructNonOwnedLayersNotMonotonic() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialAttributeData attributes[]{ + {MaterialAttribute::AlphaBlend, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::NormalTexture, 3u} + }; + + UnsignedInt layers[]{ + 2, 5, 4, 5 + }; + + std::ostringstream out; + Error redirectError{&out}; + MaterialData data{MaterialType::Phong, + {}, attributes, + {}, layers}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData: invalid range (5, 4) for layer 2 with 5 attributes in total\n"); +} + +void MaterialDataTest::constructNonOwnedLayersOffsetOutOfBounds() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialAttributeData attributes[]{ + {MaterialAttribute::AlphaBlend, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::NormalTexture, 3u} + }; + + UnsignedInt layers[]{ + 2, 6 + }; + + std::ostringstream out; + Error redirectError{&out}; + MaterialData data{MaterialType::Phong, + {}, attributes, + {}, layers}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData: invalid range (2, 6) for layer 1 with 5 attributes in total\n"); +} + void MaterialDataTest::constructCopy() { CORRADE_VERIFY(!(std::is_constructible{})); CORRADE_VERIFY(!(std::is_assignable{})); @@ -825,24 +1236,31 @@ void MaterialDataTest::constructMove() { int state; MaterialData a{MaterialType::Phong, { {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::AlphaBlend, true}, {"boredomFactor", 5} + }, { + 1, 1, 3 }, &state}; MaterialData b{std::move(a)}; + CORRADE_COMPARE(a.layerCount(), 1); CORRADE_COMPARE(a.attributeCount(), 0); CORRADE_COMPARE(b.types(), MaterialType::Phong); - CORRADE_COMPARE(b.attributeCount(), 2); - CORRADE_COMPARE(b.attributeName(0), "DoubleSided"); + CORRADE_COMPARE(b.layerCount(), 3); + CORRADE_COMPARE(b.attributeCount(2), 2); + CORRADE_COMPARE(b.attributeName(2, 0), "AlphaBlend"); CORRADE_COMPARE(b.importerState(), &state); MaterialData c{MaterialTypes{}, { {MaterialAttribute::AlphaMask, 0.5f} - }}; + }, {1}}; c = std::move(b); CORRADE_COMPARE(b.attributeCount(), 1); + CORRADE_COMPARE(b.layerCount(), 1); CORRADE_COMPARE(c.types(), MaterialType::Phong); - CORRADE_COMPARE(c.attributeCount(), 2); - CORRADE_COMPARE(c.attributeName(0), "DoubleSided"); + CORRADE_COMPARE(c.layerCount(), 3); + CORRADE_COMPARE(c.attributeCount(2), 2); + CORRADE_COMPARE(c.attributeName(2, 0), "AlphaBlend"); CORRADE_COMPARE(c.importerState(), &state); CORRADE_VERIFY(std::is_nothrow_move_constructible::value); @@ -948,55 +1366,11 @@ void MaterialDataTest::accessOutOfBounds() { data.attribute(2); data.attribute(2); CORRADE_COMPARE(out.str(), - "Trade::MaterialData::attributeName(): index 2 out of range for 2 attributes\n" - "Trade::MaterialData::attributeType(): index 2 out of range for 2 attributes\n" - "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes\n" - "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes\n" - "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes\n"); -} - -void MaterialDataTest::accessInvalidAttributeName() { - #ifdef CORRADE_NO_ASSERT - CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); - #endif - - MaterialData data{{}, {}}; - - std::ostringstream out; - Error redirectError{&out}; - data.hasAttribute(MaterialAttribute(0x0)); - data.hasAttribute(MaterialAttribute(0xfefe)); - data.attributeId(MaterialAttribute(0x0)); - data.attributeId(MaterialAttribute(0xfefe)); - data.attributeType(MaterialAttribute(0x0)); - data.attributeType(MaterialAttribute(0xfefe)); - data.attribute(MaterialAttribute(0x0)); - data.attribute(MaterialAttribute(0xfefe)); - data.attribute(MaterialAttribute(0x0)); - data.attribute(MaterialAttribute(0xfefe)); - data.tryAttribute(MaterialAttribute(0x0)); - data.tryAttribute(MaterialAttribute(0xfefe)); - data.tryAttribute(MaterialAttribute(0x0)); - data.tryAttribute(MaterialAttribute(0xfefe)); - data.attributeOr(MaterialAttribute(0x0), 42); - data.attributeOr(MaterialAttribute(0xfefe), 42); - CORRADE_COMPARE(out.str(), - "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" - "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" - "Trade::MaterialData::attributeId(): invalid name Trade::MaterialAttribute(0x0)\n" - "Trade::MaterialData::attributeId(): invalid name Trade::MaterialAttribute(0xfefe)\n" - "Trade::MaterialData::attributeType(): invalid name Trade::MaterialAttribute(0x0)\n" - "Trade::MaterialData::attributeType(): invalid name Trade::MaterialAttribute(0xfefe)\n" - "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0x0)\n" - "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" - "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0x0)\n" - "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" - "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" - "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" - "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" - "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" - "Trade::MaterialData::attributeOr(): invalid name Trade::MaterialAttribute(0x0)\n" - "Trade::MaterialData::attributeOr(): invalid name Trade::MaterialAttribute(0xfefe)\n"); + "Trade::MaterialData::attributeName(): index 2 out of range for 2 attributes in layer 0\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 attributes in layer 0\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer 0\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer 0\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer 0\n"); } void MaterialDataTest::accessNotFound() { @@ -1017,10 +1391,10 @@ void MaterialDataTest::accessNotFound() { data.attribute("DiffuseColour"); data.attribute("DiffuseColour"); CORRADE_COMPARE(out.str(), - "Trade::MaterialData::attributeId(): attribute DiffuseColour not found\n" - "Trade::MaterialData::attributeType(): attribute DiffuseColour not found\n" - "Trade::MaterialData::attribute(): attribute DiffuseColour not found\n" - "Trade::MaterialData::attribute(): attribute DiffuseColour not found\n"); + "Trade::MaterialData::attributeId(): attribute DiffuseColour not found in layer 0\n" + "Trade::MaterialData::attributeType(): attribute DiffuseColour not found in layer 0\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found in layer 0\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found in layer 0\n"); } void MaterialDataTest::accessWrongType() { @@ -1109,19 +1483,392 @@ void MaterialDataTest::accessWrongTypeString() { "Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n"); } -void MaterialDataTest::release() { +void MaterialDataTest::accessLayerLayerNameInBaseMaterial() { + MaterialData data{{}, { + {MaterialAttribute::Shininess, 50.0f}, + {MaterialAttribute::LayerName, "base material name"} + }}; + + /* To avoid confusing the base material with a layer, LayerName is ignored + for the base material. */ + CORRADE_COMPARE(data.layerName(0), ""); + CORRADE_VERIFY(!data.hasLayer("base material name")); +} + +void MaterialDataTest::accessLayerEmptyLayer() { + /* If a layer is empty, its contents shouldn't leak into upper layers */ + MaterialData data{{}, { + {MaterialAttribute::NormalTexture, 3u}, + {MaterialAttribute::LayerName, "crumples"} + }, {0, 0, 2}}; + + CORRADE_COMPARE(data.layerName(0), ""); + CORRADE_COMPARE(data.layerName(1), ""); + CORRADE_COMPARE(data.layerName(2), "crumples"); + CORRADE_COMPARE(data.attributeCount(0), 0); + CORRADE_COMPARE(data.attributeCount(1), 0); + CORRADE_COMPARE(data.attributeCount(2), 2); + CORRADE_COMPARE(data.layerId("crumples"), 2); + CORRADE_COMPARE(data.attribute("crumples", MaterialAttribute::NormalTexture), 3u); +} + +void MaterialDataTest::accessLayerIndexOptional() { + MaterialData data{{}, { + {MaterialAttribute::DiffuseColor, 0x335566ff_rgbaf}, + {MaterialAttribute::AlphaMask, 0.5f}, + {MaterialAttribute::SpecularTexture, 3u} + }, {1, 3}}; + + /* This exists */ + CORRADE_VERIFY(data.tryAttribute(1, "SpecularTexture")); + CORRADE_VERIFY(data.tryAttribute(1, MaterialAttribute::SpecularTexture)); + CORRADE_COMPARE(*static_cast(data.tryAttribute(1, "SpecularTexture")), 3); + CORRADE_COMPARE(*static_cast(data.tryAttribute(1, MaterialAttribute::SpecularTexture)), 3); + CORRADE_COMPARE(data.tryAttribute(1, "SpecularTexture"), 3); + CORRADE_COMPARE(data.tryAttribute(1, MaterialAttribute::SpecularTexture), 3); + CORRADE_COMPARE(data.attributeOr(1, "SpecularTexture", 5u), 3); + CORRADE_COMPARE(data.attributeOr(1, MaterialAttribute::SpecularTexture, 5u), 3); + + /* This doesn't */ + CORRADE_VERIFY(!data.tryAttribute(1, "DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute(1, MaterialAttribute::DiffuseTexture)); + CORRADE_VERIFY(!data.tryAttribute(1, "DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute(1, MaterialAttribute::DiffuseTexture)); + CORRADE_COMPARE(data.attributeOr(1, "DiffuseTexture", 5u), 5); + CORRADE_COMPARE(data.attributeOr(1, MaterialAttribute::DiffuseTexture, 5u), 5); +} + +void MaterialDataTest::accessLayerStringOptional() { + MaterialData data{{}, { + {MaterialAttribute::DiffuseColor, 0x335566ff_rgbaf}, + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::AlphaMask, 0.5f}, + {MaterialAttribute::SpecularTexture, 3u} + }, {1, 4}}; + + /* This exists */ + CORRADE_VERIFY(data.tryAttribute("ClearCoat", "SpecularTexture")); + CORRADE_VERIFY(data.tryAttribute("ClearCoat", MaterialAttribute::SpecularTexture)); + CORRADE_COMPARE(*static_cast(data.tryAttribute("ClearCoat", "SpecularTexture")), 3); + CORRADE_COMPARE(*static_cast(data.tryAttribute("ClearCoat", MaterialAttribute::SpecularTexture)), 3); + CORRADE_COMPARE(data.tryAttribute("ClearCoat", "SpecularTexture"), 3); + CORRADE_COMPARE(data.tryAttribute("ClearCoat", MaterialAttribute::SpecularTexture), 3); + CORRADE_COMPARE(data.attributeOr("ClearCoat", "SpecularTexture", 5u), 3); + CORRADE_COMPARE(data.attributeOr("ClearCoat", MaterialAttribute::SpecularTexture, 5u), 3); + + /* This doesn't */ + CORRADE_VERIFY(!data.tryAttribute("ClearCoat", "DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute("ClearCoat", MaterialAttribute::DiffuseTexture)); + CORRADE_VERIFY(!data.tryAttribute("ClearCoat", "DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute("ClearCoat", MaterialAttribute::DiffuseTexture)); + CORRADE_COMPARE(data.attributeOr("ClearCoat", "DiffuseTexture", 5u), 5); + CORRADE_COMPARE(data.attributeOr("ClearCoat", MaterialAttribute::DiffuseTexture, 5u), 5); +} + +void MaterialDataTest::accessLayerOutOfBounds() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {MaterialAttribute::AlphaMask, 0.5f}, + {MaterialAttribute::SpecularTexture, 3u} + }, {0, 2}}; + + std::ostringstream out; + Error redirectError{&out}; + data.layerName(2); + data.attributeCount(2); + data.hasAttribute(2, "AlphaMask"); + data.hasAttribute(2, MaterialAttribute::AlphaMask); + data.attributeId(2, "AlphaMask"); + data.attributeId(2, MaterialAttribute::AlphaMask); + data.attributeName(2, 0); + data.attributeType(2, 0); + data.attributeType(2, "AlphaMask"); + data.attributeType(2, MaterialAttribute::AlphaMask); + data.attribute(2, 0); + data.attribute(2, "AlphaMask"); + data.attribute(2, MaterialAttribute::AlphaMask); + data.attribute(2, 0); + data.attribute(2, "AlphaMask"); + data.attribute(2, MaterialAttribute::AlphaMask); + data.attribute(2, 0); + data.tryAttribute(2, "AlphaMask"); + data.tryAttribute(2, MaterialAttribute::AlphaMask); + data.tryAttribute(2, "AlphaMask"); + data.tryAttribute(2, MaterialAttribute::AlphaMask); + data.attributeOr(2, "AlphaMask", false); + data.attributeOr(2, MaterialAttribute::AlphaMask, false); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::layerName(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeCount(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::hasAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::hasAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeId(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeId(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeName(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::tryAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::tryAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::tryAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::tryAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeOr(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeOr(): index 2 out of range for 2 layers\n"); +} + +void MaterialDataTest::accessLayerNotFound() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {MaterialAttribute::LayerName, "clearCoat"}, + {MaterialAttribute::AlphaMask, 0.5f}, + }, {0, 2}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attributeCount("ClearCoat"); + data.hasAttribute("ClearCoat", "AlphaMask"); + data.hasAttribute("ClearCoat", MaterialAttribute::AlphaMask); + data.attributeId("ClearCoat", "AlphaMask"); + data.attributeId("ClearCoat", MaterialAttribute::AlphaMask); + data.attributeName("ClearCoat", 0); + data.attributeType("ClearCoat", 0); + data.attributeType("ClearCoat", "AlphaMask"); + data.attributeType("ClearCoat", MaterialAttribute::AlphaMask); + data.attribute("ClearCoat", 0); + data.attribute("ClearCoat", "AlphaMask"); + data.attribute("ClearCoat", MaterialAttribute::AlphaMask); + data.attribute("ClearCoat", 0); + data.attribute("ClearCoat", "AlphaMask"); + data.attribute("ClearCoat", MaterialAttribute::AlphaMask); + data.attribute("ClearCoat", 0); + data.tryAttribute("ClearCoat", "AlphaMask"); + data.tryAttribute("ClearCoat", MaterialAttribute::AlphaMask); + data.tryAttribute("ClearCoat", "AlphaMask"); + data.tryAttribute("ClearCoat", MaterialAttribute::AlphaMask); + data.attributeOr("ClearCoat", "AlphaMask", false); + data.attributeOr("ClearCoat", MaterialAttribute::AlphaMask, false); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attributeCount(): layer ClearCoat not found\n" + "Trade::MaterialData::hasAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::hasAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeId(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeId(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeName(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeType(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeType(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeType(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::tryAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::tryAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::tryAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::tryAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeOr(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeOr(): layer ClearCoat not found\n"); +} + +void MaterialDataTest::accessOutOfBoundsInLayerIndex() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {MaterialAttribute::AlphaMask, 0.5f}, + {MaterialAttribute::SpecularTexture, 3u} + }, {0, 2}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attributeName(1, 2); + data.attributeType(1, 2); + data.attribute(1, 2); + data.attribute(1, 2); + data.attribute(1, 2); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attributeName(): index 2 out of range for 2 attributes in layer 1\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 attributes in layer 1\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer 1\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer 1\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer 1\n"); +} + +void MaterialDataTest::accessOutOfBoundsInLayerString() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::AlphaMask, 0.5f} + }, {0, 2}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attributeName("ClearCoat", 2); + data.attributeType("ClearCoat", 2); + data.attribute("ClearCoat", 2); + data.attribute("ClearCoat", 2); + data.attribute("ClearCoat", 2); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attributeName(): index 2 out of range for 2 attributes in layer ClearCoat\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 attributes in layer ClearCoat\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer ClearCoat\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer ClearCoat\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer ClearCoat\n"); +} + +void MaterialDataTest::accessNotFoundInLayerIndex() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {"DiffuseColor", 0xff3366aa_rgbaf} + }, {0, 1}}; + + CORRADE_VERIFY(!data.hasAttribute(1, "DiffuseColour")); + + std::ostringstream out; + Error redirectError{&out}; + data.attributeId(1, "DiffuseColour"); + data.attributeType(1, "DiffuseColour"); + data.attribute(1, "DiffuseColour"); + data.attribute(1, "DiffuseColour"); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attributeId(): attribute DiffuseColour not found in layer 1\n" + "Trade::MaterialData::attributeType(): attribute DiffuseColour not found in layer 1\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found in layer 1\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found in layer 1\n"); +} + +void MaterialDataTest::accessNotFoundInLayerString() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {MaterialAttribute::LayerName, "ClearCoat"}, + {"DiffuseColor", 0xff3366aa_rgbaf} + }, {0, 1}}; + + CORRADE_VERIFY(!data.hasAttribute(1, "DiffuseColour")); + + std::ostringstream out; + Error redirectError{&out}; + data.attributeId("ClearCoat", "DiffuseColour"); + data.attributeType("ClearCoat", "DiffuseColour"); + data.attribute("ClearCoat", "DiffuseColour"); + data.attribute("ClearCoat", "DiffuseColour"); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attributeId(): attribute DiffuseColour not found in layer ClearCoat\n" + "Trade::MaterialData::attributeType(): attribute DiffuseColour not found in layer ClearCoat\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found in layer ClearCoat\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found in layer ClearCoat\n"); +} + +void MaterialDataTest::accessInvalidAttributeName() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, {}}; + + /* The name should be converted to a string first and foremost and only + then delegated to another overload. Which means all asserts should + print the leaf function name. */ + std::ostringstream out; + Error redirectError{&out}; + data.hasAttribute(0, MaterialAttribute(0x0)); + data.hasAttribute("Layer", MaterialAttribute(0xfefe)); + data.attributeId(0, MaterialAttribute(0x0)); + data.attributeId("Layer", MaterialAttribute(0xfefe)); + data.attributeType(0, MaterialAttribute(0x0)); + data.attributeType("Layer", MaterialAttribute(0xfefe)); + data.attribute(0, MaterialAttribute(0x0)); + data.attribute("Layer", MaterialAttribute(0xfefe)); + data.attribute(0, MaterialAttribute(0x0)); + data.attribute("Layer", MaterialAttribute(0xfefe)); + data.tryAttribute(0, MaterialAttribute(0x0)); + data.tryAttribute("Layer", MaterialAttribute(0xfefe)); + data.tryAttribute(0, MaterialAttribute(0x0)); + data.tryAttribute("Layer", MaterialAttribute(0xfefe)); + data.attributeOr(0, MaterialAttribute(0x0), 42); + data.attributeOr("Layer", MaterialAttribute(0xfefe), 42); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::attributeId(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::attributeId(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::attributeType(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::attributeType(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::attributeOr(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::attributeOr(): invalid name Trade::MaterialAttribute(0xfefe)\n"); +} + +void MaterialDataTest::releaseAttributes() { MaterialData data{{}, { {"DiffuseColor", 0xff3366aa_rgbaf}, {MaterialAttribute::NormalTexture, 0u} - }}; + }, {1, 2}}; - const void* pointer = data.data().data(); + const void* pointer = data.attributeData().data(); - Containers::Array released = data.release(); + Containers::Array released = data.releaseAttributeData(); CORRADE_COMPARE(released.data(), pointer); CORRADE_COMPARE(released.size(), 2); - CORRADE_VERIFY(!data.data()); - CORRADE_COMPARE(data.attributeCount(), 0); + CORRADE_VERIFY(data.layerData()); + CORRADE_COMPARE(data.layerCount(), 2); + CORRADE_VERIFY(!data.attributeData()); + /* This is based on the layer offsets, not an actual attribute count, so + it's inconsistent, yes */ + CORRADE_COMPARE(data.attributeCount(), 1); +} + +void MaterialDataTest::releaseLayers() { + MaterialData data{{}, { + {"DiffuseColor", 0xff3366aa_rgbaf}, + {MaterialAttribute::NormalTexture, 0u} + }, {1, 2}}; + + const void* pointer = data.layerData().data(); + + Containers::Array released = data.releaseLayerData(); + CORRADE_COMPARE(released.data(), pointer); + CORRADE_COMPARE(released.size(), 2); + CORRADE_VERIFY(!data.layerData()); + /* Returns always at least 1 (now it sees no layer data and thus thinks + there's just the implicit base material) */ + CORRADE_COMPARE(data.layerCount(), 1); + CORRADE_VERIFY(data.attributeData()); + /* No layer offsets anymore, so this is the total attribute count instead + of the base material attribute count. It's inconsistent, yes. */ + CORRADE_COMPARE(data.attributeCount(), 2); } #ifdef MAGNUM_BUILD_DEPRECATED @@ -1453,16 +2200,16 @@ void MaterialDataTest::phongAccessInvalidTextures() { data.normalTextureMatrix(); data.normalTextureCoordinates(); CORRADE_COMPARE(out.str(), - "Trade::MaterialData::attribute(): attribute AmbientTexture not found\n" + "Trade::MaterialData::attribute(): attribute AmbientTexture not found in layer 0\n" "Trade::PhongMaterialData::ambientTextureMatrix(): the material doesn't have an ambient texture\n" "Trade::PhongMaterialData::ambientTextureCoordinates(): the material doesn't have an ambient texture\n" - "Trade::MaterialData::attribute(): attribute DiffuseTexture not found\n" + "Trade::MaterialData::attribute(): attribute DiffuseTexture not found in layer 0\n" "Trade::PhongMaterialData::diffuseTextureMatrix(): the material doesn't have a diffuse texture\n" "Trade::PhongMaterialData::diffuseTextureCoordinates(): the material doesn't have a diffuse texture\n" - "Trade::MaterialData::attribute(): attribute SpecularTexture not found\n" + "Trade::MaterialData::attribute(): attribute SpecularTexture not found in layer 0\n" "Trade::PhongMaterialData::specularTextureMatrix(): the material doesn't have a specular texture\n" "Trade::PhongMaterialData::specularTextureCoordinates(): the material doesn't have a specular texture\n" - "Trade::MaterialData::attribute(): attribute NormalTexture not found\n" + "Trade::MaterialData::attribute(): attribute NormalTexture not found in layer 0\n" "Trade::PhongMaterialData::normalTextureMatrix(): the material doesn't have a normal texture\n" "Trade::PhongMaterialData::normalTextureCoordinates(): the material doesn't have a normal texture\n"); } @@ -1470,8 +2217,8 @@ void MaterialDataTest::phongAccessInvalidTextures() { void MaterialDataTest::debugAttribute() { std::ostringstream out; - Debug{&out} << MaterialAttribute::DiffuseTextureCoordinates << MaterialAttribute(0xfefe) << MaterialAttribute{}; - CORRADE_COMPARE(out.str(), "Trade::MaterialAttribute::DiffuseTextureCoordinates Trade::MaterialAttribute(0xfefe) Trade::MaterialAttribute(0x0)\n"); + Debug{&out} << MaterialAttribute::DiffuseTextureCoordinates << MaterialAttribute::LayerName << MaterialAttribute(0xfefe) << MaterialAttribute{}; + CORRADE_COMPARE(out.str(), "Trade::MaterialAttribute::DiffuseTextureCoordinates Trade::MaterialAttribute::LayerName Trade::MaterialAttribute(0xfefe) Trade::MaterialAttribute(0x0)\n"); } void MaterialDataTest::debugAttributeType() {