/* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "Magnum/Math/Complex.h" #include "Magnum/Math/Vector2.h" #include "Magnum/SceneTools/Combine.h" #include "Magnum/Trade/SceneData.h" namespace Magnum { namespace SceneTools { namespace Test { namespace { struct CombineTest: TestSuite::Tester { explicit CombineTest(); void fields(); template void fieldsStrings(); void fieldsAlignment(); void fieldsMappingShared(); void fieldsMappingSharedPartial(); void fieldsMappingPlaceholderFieldPlaceholder(); void fieldsMappingSharedFieldPlaceholder(); void fieldsStringPlaceholder(); void fieldsOffsetOnly(); void fieldsFromDataOffsetOnly(); }; using namespace Containers::Literals; const struct { const char* name; Trade::SceneMappingType objectType; } FieldsData[]{ {"UnsignedByte output", Trade::SceneMappingType::UnsignedByte}, {"UnsignedShort output", Trade::SceneMappingType::UnsignedShort}, {"UnsignedInt output", Trade::SceneMappingType::UnsignedInt}, {"UnsignedLong output", Trade::SceneMappingType::UnsignedLong}, }; CombineTest::CombineTest() { addInstancedTests({&CombineTest::fields}, Containers::arraySize(FieldsData)); addTests({&CombineTest::fieldsStrings, &CombineTest::fieldsStrings, &CombineTest::fieldsStrings, &CombineTest::fieldsStrings, &CombineTest::fieldsAlignment, &CombineTest::fieldsMappingShared, &CombineTest::fieldsMappingSharedPartial, &CombineTest::fieldsMappingPlaceholderFieldPlaceholder, &CombineTest::fieldsMappingSharedFieldPlaceholder, &CombineTest::fieldsStringPlaceholder, &CombineTest::fieldsOffsetOnly, &CombineTest::fieldsFromDataOffsetOnly}); } using namespace Math::Literals; void CombineTest::fields() { auto&& data = FieldsData[testCaseInstanceId()]; setTestCaseDescription(data.name); /* Testing the four possible object types, it should be possible to combine them. Make them all non-contiguous to catch accidents in the internal casting / copying code.*/ const struct Mesh { UnsignedInt mapping; UnsignedByte mesh; } meshData[]{ {45, 3}, {78, 5}, {23, 17} }; auto meshes = Containers::stridedArrayView(meshData); const struct Parent { UnsignedShort mapping; Short parent; } parentData[]{ {0, -1}, {1, 0} }; auto parents = Containers::stridedArrayView(parentData); const struct Translation { UnsignedByte mapping; Vector2d translation; } translationData[]{ {16, {1.5, -0.5}} }; auto translations = Containers::stridedArrayView(translationData); const struct Foo { UnsignedLong mapping; Int foo[2]; } fooData[]{ {15, {0, 1}}, {23, {2, 3}} }; auto foos = Containers::stridedArrayView(fooData); const struct Bool { UnsignedShort mapping; bool bit; } boolData[]{ {23, false}, {24, true}, {25, false}, {26, true} }; auto bools = Containers::stridedArrayView(boolData); const struct Bits { UnsignedByte mapping; UnsignedByte bits; } bitsData[]{ {13, 1 << 3 | 1 << 4}, {25, 1 << 4}, {77, 1 << 1 | 1 << 2 | 1 << 3}, }; auto bits = Containers::stridedArrayView(bitsData); Trade::SceneData scene = combineFields(data.objectType, 167, { Trade::SceneFieldData{Trade::SceneField::Mesh, meshes.slice(&Mesh::mapping), meshes.slice(&Mesh::mesh)}, Trade::SceneFieldData{Trade::SceneField::Parent, parents.slice(&Parent::mapping), parents.slice(&Parent::parent), Trade::SceneFieldFlag::ImplicitMapping}, Trade::SceneFieldData{Trade::SceneField::Translation, translations.slice(&Translation::mapping), translations.slice(&Translation::translation)}, /* Array field */ Trade::SceneFieldData{Trade::sceneFieldCustom(15), foos.slice(&Foo::mapping), Containers::arrayCast<2, const Int>(foos.slice(&Foo::foo)), Trade::SceneFieldFlag::OrderedMapping}, /* Empty field */ Trade::SceneFieldData{Trade::SceneField::Camera, Containers::ArrayView{}, Containers::ArrayView{}}, /* Bit field */ Trade::SceneFieldData{Trade::sceneFieldCustom(16), bools.slice(&Bool::mapping), bools.slice(&Bool::bit).sliceBit(0), Trade::SceneFieldFlag::ImplicitMapping}, /* Bit array field */ Trade::SceneFieldData{Trade::sceneFieldCustom(17), bits.slice(&Bits::mapping), Containers::StridedBitArrayView2D{Containers::BitArrayView{bitsData}, &bitsData[0].bits, 1, {3, 4}, {sizeof(Bits)*8, 1}}, Trade::SceneFieldFlag::OrderedMapping}, }); CORRADE_COMPARE(scene.dataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); CORRADE_COMPARE(scene.mappingType(), data.objectType); CORRADE_COMPARE(scene.mappingBound(), 167); CORRADE_COMPARE(scene.fieldCount(), 7); CORRADE_COMPARE(scene.fieldName(0), Trade::SceneField::Mesh); CORRADE_COMPARE(scene.fieldFlags(0), Trade::SceneFieldFlags{}); CORRADE_COMPARE(scene.fieldType(0), Trade::SceneFieldType::UnsignedByte); CORRADE_COMPARE(scene.fieldArraySize(0), 0); CORRADE_COMPARE_AS(scene.mappingAsArray(0), Containers::arrayView({ 45, 78, 23 }), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.field(0), meshes.slice(&Mesh::mesh), TestSuite::Compare::Container); CORRADE_COMPARE(scene.fieldName(1), Trade::SceneField::Parent); CORRADE_COMPARE(scene.fieldFlags(1), Trade::SceneFieldFlag::ImplicitMapping); CORRADE_COMPARE(scene.fieldType(1), Trade::SceneFieldType::Short); CORRADE_COMPARE(scene.fieldArraySize(1), 0); CORRADE_COMPARE_AS(scene.mappingAsArray(1), Containers::arrayView({ 0, 1 }), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.field(1), parents.slice(&Parent::parent), TestSuite::Compare::Container); CORRADE_COMPARE(scene.fieldName(2), Trade::SceneField::Translation); CORRADE_COMPARE(scene.fieldFlags(2), Trade::SceneFieldFlags{}); CORRADE_COMPARE(scene.fieldType(2), Trade::SceneFieldType::Vector2d); CORRADE_COMPARE(scene.fieldArraySize(2), 0); CORRADE_COMPARE_AS(scene.mappingAsArray(2), Containers::arrayView({16}), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.field(2), translations.slice(&Translation::translation), TestSuite::Compare::Container); CORRADE_COMPARE(scene.fieldName(3), Trade::sceneFieldCustom(15)); CORRADE_COMPARE(scene.fieldFlags(3), Trade::SceneFieldFlag::OrderedMapping); CORRADE_COMPARE(scene.fieldType(3), Trade::SceneFieldType::Int); CORRADE_COMPARE(scene.fieldArraySize(3), 2); CORRADE_COMPARE_AS(scene.mappingAsArray(3), Containers::arrayView({15, 23}), TestSuite::Compare::Container); /** @todo clean up once it's possible to compare multidimensional containers */ CORRADE_COMPARE_AS(scene.field(3)[0], (Containers::arrayCast<2, const Int>(foos.slice(&Foo::foo)))[0], TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.field(3)[1], (Containers::arrayCast<2, const Int>(foos.slice(&Foo::foo)))[1], TestSuite::Compare::Container); CORRADE_COMPARE(scene.fieldName(4), Trade::SceneField::Camera); CORRADE_COMPARE(scene.fieldFlags(4), Trade::SceneFieldFlags{}); CORRADE_COMPARE(scene.fieldType(4), Trade::SceneFieldType::UnsignedShort); CORRADE_COMPARE(scene.fieldSize(4), 0); CORRADE_COMPARE(scene.fieldArraySize(4), 0); CORRADE_COMPARE(scene.fieldName(5), Trade::sceneFieldCustom(16)); CORRADE_COMPARE(scene.fieldFlags(5), Trade::SceneFieldFlag::ImplicitMapping); CORRADE_COMPARE(scene.fieldType(5), Trade::SceneFieldType::Bit); CORRADE_COMPARE(scene.fieldArraySize(5), 0); CORRADE_COMPARE_AS(scene.mappingAsArray(5), Containers::arrayView({23u, 24u, 25u, 26u}), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.fieldBits(5), Containers::stridedArrayView({false, true, false, true}).sliceBit(0), TestSuite::Compare::Container); CORRADE_COMPARE(scene.fieldName(6), Trade::sceneFieldCustom(17)); CORRADE_COMPARE(scene.fieldFlags(6), Trade::SceneFieldFlag::OrderedMapping); CORRADE_COMPARE(scene.fieldType(6), Trade::SceneFieldType::Bit); CORRADE_COMPARE(scene.fieldArraySize(6), 4); CORRADE_COMPARE_AS(scene.mappingAsArray(6), Containers::arrayView({13u, 25u, 77u}), TestSuite::Compare::Container); /** @todo clean up once it's possible to compare multidimensional containers */ CORRADE_COMPARE_AS(scene.fieldBitArrays(6)[0], Containers::stridedArrayView({false, false, true, true}).sliceBit(0), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.fieldBitArrays(6)[1], Containers::stridedArrayView({false, false, false, true}).sliceBit(0), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.fieldBitArrays(6)[2], Containers::stridedArrayView({true, true, true, false}).sliceBit(0), TestSuite::Compare::Container); } /* Taken from SceneDataTest */ template struct StringFieldTraits; template<> struct StringFieldTraits { static const char* name() { return "8"; } static Trade::SceneFieldType offsetType() { return Trade::SceneFieldType::StringOffset8; } static Trade::SceneFieldType rangeType() { return Trade::SceneFieldType::StringRange8; } static Trade::SceneFieldType rangeNullTerminatedType() { return Trade::SceneFieldType::StringRangeNullTerminated8; } }; template<> struct StringFieldTraits { static const char* name() { return "16"; } static Trade::SceneFieldType offsetType() { return Trade::SceneFieldType::StringOffset16; } static Trade::SceneFieldType rangeType() { return Trade::SceneFieldType::StringRange16; } static Trade::SceneFieldType rangeNullTerminatedType() { return Trade::SceneFieldType::StringRangeNullTerminated16; } }; template<> struct StringFieldTraits { static const char* name() { return "32"; } static Trade::SceneFieldType offsetType() { return Trade::SceneFieldType::StringOffset32; } static Trade::SceneFieldType rangeType() { return Trade::SceneFieldType::StringRange32; } static Trade::SceneFieldType rangeNullTerminatedType() { return Trade::SceneFieldType::StringRangeNullTerminated32; } }; template<> struct StringFieldTraits { static const char* name() { return "64"; } static Trade::SceneFieldType offsetType() { return Trade::SceneFieldType::StringOffset64; } static Trade::SceneFieldType rangeType() { return Trade::SceneFieldType::StringRange64; } static Trade::SceneFieldType rangeNullTerminatedType() { return Trade::SceneFieldType::StringRangeNullTerminated64; } }; template void CombineTest::fieldsStrings() { setTestCaseTemplateName(StringFieldTraits::name()); /* Null-terminated ranges */ Containers::StringView tagStrings = "SOFT\0" /* 0 */ "mouldy!"_s; /* 5, assumes it's stored null-terminated */ /* With null termination it's 13 bytes. If only 12 would be copied, the next ArrayTuple item (likely Name::mapping) would get aligned right after, failing the null terminator check */ CORRADE_COMPARE(tagStrings.size(), 12); const struct Tag { UnsignedByte mapping; T rangeNullTerminated; } tagsData[]{ {3, 0}, {7, 5}, {7, 0}, {1, 0} }; auto tags = Containers::stridedArrayView(tagsData); /* Non-null-terminated offsets */ Containers::StringView nameStrings = "Chair" /* 5 */ "Lampshade" /* 14 */ "Sofa37"_s; /* 20 */ CORRADE_COMPARE(nameStrings.size(), 20); const struct Name { UnsignedByte mapping; T offset; } namesData[]{ {3, 5}, {7, 14}, {1, 20} }; auto names = Containers::stridedArrayView(namesData); /* Null-terminated offsets */ Containers::StringView keyStrings = "color\0" /* 6 */ "age\0" /* 10 */ "age"_s; /* 14, assumes it's stored null-terminated */ const struct Key { UnsignedByte mapping; T offsetNullTerminated; } keysData[]{ {11, 6}, {3, 10}, {12, 14} }; auto keys = Containers::stridedArrayView(keysData); Containers::StringView valueStrings = "light\0brown" /* 0, 11 */ "ancient" /* 11, 7 */ "new"_s; /* 18, 3 */ /* Non-null-terminated ranges */ const struct Value { UnsignedByte mapping; Containers::Pair range; } valuesData[]{ {3, {18, 3}}, {12, {11, 7}}, {7, {18, 3}}, {11, {0, 11}} }; auto values = Containers::stridedArrayView(valuesData); /* Using just 8-bit mapping to not have any extra padding between things and thus better catch accidentally forgotten null termination and such */ Trade::SceneData scene = combineFields(Trade::SceneMappingType::UnsignedByte, 167, { Trade::SceneFieldData{Trade::sceneFieldCustom(0), tags.slice(&Tag::mapping), tagStrings.data(), StringFieldTraits::rangeNullTerminatedType(), tags.slice(&Tag::rangeNullTerminated)}, Trade::SceneFieldData{Trade::sceneFieldCustom(1), names.slice(&Name::mapping), nameStrings.data(), StringFieldTraits::offsetType(), names.slice(&Name::offset)}, Trade::SceneFieldData{Trade::sceneFieldCustom(2), keys.slice(&Key::mapping), keyStrings.data(), StringFieldTraits::offsetType(), keys.slice(&Key::offsetNullTerminated), Trade::SceneFieldFlag::NullTerminatedString}, Trade::SceneFieldData{Trade::sceneFieldCustom(3), values.slice(&Value::mapping), valueStrings.data(), StringFieldTraits::rangeType(), values.slice(&Value::range)}, /* Empty string field, shouldn't crash or anything */ Trade::SceneFieldData{Trade::sceneFieldCustom(4), Containers::ArrayView{}, nullptr, StringFieldTraits::offsetType(), Containers::ArrayView{}}, }); CORRADE_COMPARE(scene.fieldName(0), Trade::sceneFieldCustom(0)); CORRADE_COMPARE(scene.fieldFlags(0), Trade::SceneFieldFlag::NullTerminatedString); CORRADE_COMPARE(scene.fieldType(0), StringFieldTraits::rangeNullTerminatedType()); CORRADE_COMPARE_AS(scene.mapping(0), tags.slice(&Tag::mapping), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.field(0), tags.slice(&Tag::rangeNullTerminated), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.fieldStrings(0), (Containers::StringIterable{"SOFT", "mouldy!", "SOFT", "SOFT"}), TestSuite::Compare::Container); /* All should stay null-terminated -- i.e., the null terminator included in the size calculation when the string gets copied */ for(Containers::StringView i: scene.fieldStrings(0)) { CORRADE_COMPARE(i.flags(), Containers::StringViewFlag::NullTerminated); CORRADE_COMPARE(i[i.size()], '\0'); } CORRADE_COMPARE(scene.fieldName(1), Trade::sceneFieldCustom(1)); CORRADE_COMPARE(scene.fieldFlags(1), Trade::SceneFieldFlags{}); CORRADE_COMPARE(scene.fieldType(1), StringFieldTraits::offsetType()); CORRADE_COMPARE_AS(scene.mapping(1), names.slice(&Name::mapping), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.field(1), names.slice(&Name::offset), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.fieldStrings(1), (Containers::StringIterable{"Chair", "Lampshade", "Sofa37"}), TestSuite::Compare::Container); CORRADE_COMPARE(scene.fieldName(2), Trade::sceneFieldCustom(2)); CORRADE_COMPARE(scene.fieldFlags(2), Trade::SceneFieldFlag::NullTerminatedString); CORRADE_COMPARE(scene.fieldType(2), StringFieldTraits::offsetType()); CORRADE_COMPARE_AS(scene.mapping(2), keys.slice(&Key::mapping), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.field(2), keys.slice(&Key::offsetNullTerminated), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.fieldStrings(2), (Containers::StringIterable{"color", "age", "age"}), TestSuite::Compare::Container); /* All should stay null-terminated -- i.e., the null terminator included in the size calculation when the string gets copied */ for(Containers::StringView i: scene.fieldStrings(2)) { CORRADE_COMPARE(i.flags(), Containers::StringViewFlag::NullTerminated); CORRADE_COMPARE(i[i.size()], '\0'); } CORRADE_COMPARE(scene.fieldName(3), Trade::sceneFieldCustom(3)); CORRADE_COMPARE(scene.fieldFlags(3), Trade::SceneFieldFlags{}); CORRADE_COMPARE(scene.fieldType(3), StringFieldTraits::rangeType()); CORRADE_COMPARE_AS(scene.mapping(3), values.slice(&Value::mapping), TestSuite::Compare::Container); CORRADE_COMPARE_AS((scene.field>(3)), values.slice(&Value::range), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.fieldStrings(3), (Containers::StringIterable{"new", "ancient", "new", "light\0brown"_s}), TestSuite::Compare::Container); CORRADE_COMPARE(scene.fieldName(4), Trade::sceneFieldCustom(4)); CORRADE_COMPARE(scene.fieldFlags(4), Trade::SceneFieldFlags{}); CORRADE_COMPARE(scene.fieldType(4), StringFieldTraits::offsetType()); CORRADE_COMPARE_AS(scene.mapping(4), Containers::ArrayView{}, TestSuite::Compare::Container); CORRADE_COMPARE_AS((scene.field(4)), Containers::ArrayView{}, TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.fieldStrings(4), Containers::StringIterable{}, TestSuite::Compare::Container); } void CombineTest::fieldsAlignment() { const UnsignedShort meshMappingData[]{15, 23, 47}; const UnsignedByte meshFieldData[]{0, 1, 2}; const UnsignedShort translationMappingData[]{5}; /* 1 byte padding before */ const Vector2d translationFieldData[]{{1.5, 3.0}}; /* 4 byte padding before */ Trade::SceneData scene = combineFields(Trade::SceneMappingType::UnsignedShort, 167, { Trade::SceneFieldData{Trade::SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, Trade::SceneFieldData{Trade::SceneField::Translation, Containers::arrayView(translationMappingData), Containers::arrayView(translationFieldData)} }); CORRADE_COMPARE(scene.dataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); CORRADE_COMPARE(scene.mappingType(), Trade::SceneMappingType::UnsignedShort); CORRADE_COMPARE(scene.mappingBound(), 167); CORRADE_COMPARE(scene.fieldCount(), 2); CORRADE_COMPARE(scene.fieldName(0), Trade::SceneField::Mesh); CORRADE_COMPARE(scene.fieldType(0), Trade::SceneFieldType::UnsignedByte); CORRADE_COMPARE(scene.fieldArraySize(0), 0); CORRADE_COMPARE_AS(scene.mapping(0), Containers::arrayView(meshMappingData), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.field(0), Containers::arrayView(meshFieldData), TestSuite::Compare::Container); CORRADE_COMPARE_AS(reinterpret_cast(scene.mapping(0).data()), 2, TestSuite::Compare::Divisible); CORRADE_COMPARE(scene.mapping(0).data(), scene.data()); CORRADE_COMPARE(scene.mapping(0).stride()[0], 2); CORRADE_COMPARE_AS(reinterpret_cast(scene.field(0).data()), 1, TestSuite::Compare::Divisible); CORRADE_COMPARE(scene.field(0).data(), scene.data() + 3*2); CORRADE_COMPARE(scene.field(0).stride()[0], 1); CORRADE_COMPARE(scene.fieldName(1), Trade::SceneField::Translation); CORRADE_COMPARE(scene.fieldType(1), Trade::SceneFieldType::Vector2d); CORRADE_COMPARE(scene.fieldArraySize(1), 0); CORRADE_COMPARE_AS(scene.mapping(1), Containers::arrayView(translationMappingData), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.field(1), Containers::arrayView(translationFieldData), TestSuite::Compare::Container); CORRADE_COMPARE_AS(reinterpret_cast(scene.mapping(1).data()), 2, TestSuite::Compare::Divisible); CORRADE_COMPARE(scene.mapping(1).data(), scene.data() + 3*2 + 3 + 1); CORRADE_COMPARE(scene.mapping(1).stride()[0], 2); CORRADE_COMPARE_AS(reinterpret_cast(scene.field(1).data()), 8, TestSuite::Compare::Divisible); CORRADE_COMPARE(scene.field(1).data(), scene.data() + 3*2 + 3 + 1 + 2 + 4); CORRADE_COMPARE(scene.field(1).stride()[0], 16); } void CombineTest::fieldsMappingShared() { const UnsignedShort meshMappingData[3]{}; const UnsignedByte meshFieldData[3]{}; const Int meshMaterialFieldData[3]{}; const UnsignedShort translationRotationMappingData[2]{}; const Vector2 translationFieldData[2]{}; const Complex rotationFieldData[2]{}; Trade::SceneData scene = combineFields(Trade::SceneMappingType::UnsignedInt, 173, { /* Deliberately in an arbitrary order to avoid false assumptions like fields sharing the same object mapping always being after each other */ Trade::SceneFieldData{Trade::SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, Trade::SceneFieldData{Trade::SceneField::Translation, Containers::arrayView(translationRotationMappingData), Containers::arrayView(translationFieldData)}, Trade::SceneFieldData{Trade::SceneField::MeshMaterial, Containers::arrayView(meshMappingData), Containers::arrayView(meshMaterialFieldData)}, Trade::SceneFieldData{Trade::SceneField::Rotation, Containers::arrayView(translationRotationMappingData), Containers::arrayView(rotationFieldData)} }); CORRADE_COMPARE(scene.dataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); CORRADE_COMPARE(scene.mappingType(), Trade::SceneMappingType::UnsignedInt); CORRADE_COMPARE(scene.mappingBound(), 173); CORRADE_COMPARE(scene.fieldCount(), 4); CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::Mesh), 3); CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::MeshMaterial), 3); CORRADE_COMPARE(scene.mapping(Trade::SceneField::Mesh).data(), scene.mapping(Trade::SceneField::MeshMaterial).data()); CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::Translation), 2); CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::Rotation), 2); CORRADE_COMPARE(scene.mapping(Trade::SceneField::Translation).data(), scene.mapping(Trade::SceneField::Rotation).data()); } void CombineTest::fieldsMappingSharedPartial() { const UnsignedShort mappingData[]{15, 23, 47, 26, 3}; /* Field data don't have any special treatment so their values aren't tested */ const UnsignedByte meshData[3]{}; const UnsignedShort lightData[2]{}; const Int parentData[3]{}; Trade::SceneData scene = combineFields(Trade::SceneMappingType::UnsignedInt, 173, { Trade::SceneFieldData{Trade::SceneField::Mesh, Containers::arrayView(mappingData).prefix(3), Containers::arrayView(meshData)}, Trade::SceneFieldData{Trade::SceneField::Light, Containers::arrayView(mappingData).prefix(2), Containers::arrayView(lightData)}, Trade::SceneFieldData{Trade::SceneField::Parent, Containers::stridedArrayView(mappingData).every(2), Containers::arrayView(parentData)}, }); CORRADE_COMPARE(scene.dataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); CORRADE_COMPARE(scene.mappingType(), Trade::SceneMappingType::UnsignedInt); CORRADE_COMPARE(scene.mappingBound(), 173); CORRADE_COMPARE(scene.fieldCount(), 3); CORRADE_COMPARE_AS(scene.mapping(Trade::SceneField::Mesh), Containers::arrayView({15u, 23u, 47u}), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.mapping(Trade::SceneField::Light), Containers::arrayView({15u, 23u}), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.mapping(Trade::SceneField::Parent), Containers::arrayView({15u, 47u, 3u}), TestSuite::Compare::Container); /* All mappings should be deinterleaved */ for(UnsignedInt i = 0; i != scene.fieldCount(); ++i) { CORRADE_ITERATION(scene.fieldName(i)); CORRADE_COMPARE(scene.mapping(i).stride(), sizeof(UnsignedInt)); } } void CombineTest::fieldsMappingPlaceholderFieldPlaceholder() { const UnsignedShort meshMappingData[]{15, 23, 47}; const UnsignedByte meshFieldData[]{0, 1, 2}; Trade::SceneData scene = combineFields(Trade::SceneMappingType::UnsignedShort, 173, { Trade::SceneFieldData{Trade::SceneField::Camera, Containers::ArrayView{nullptr, 1}, Containers::ArrayView{nullptr, 1}}, Trade::SceneFieldData{Trade::SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, /* Looks like sharing object mapping with the Camera field, but actually both are placeholders */ Trade::SceneFieldData{Trade::SceneField::Light, Containers::ArrayView{nullptr, 2}, Containers::ArrayView{nullptr, 2}}, /* Array field */ Trade::SceneFieldData{Trade::sceneFieldCustom(15), Containers::ArrayView{nullptr, 2}, Containers::StridedArrayView2D{{nullptr, 16}, {2, 4}}}, /* Bit array field */ Trade::SceneFieldData{Trade::sceneFieldCustom(16), Containers::ArrayView{nullptr, 3}, Containers::StridedBitArrayView2D{{nullptr, 1, 8}, {3, 2}}}, }); CORRADE_COMPARE(scene.dataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); CORRADE_COMPARE(scene.mappingType(), Trade::SceneMappingType::UnsignedShort); CORRADE_COMPARE(scene.mappingBound(), 173); CORRADE_COMPARE(scene.fieldCount(), 5); CORRADE_COMPARE(scene.fieldType(Trade::SceneField::Camera), Trade::SceneFieldType::UnsignedShort); CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::Camera), 1); CORRADE_COMPARE(scene.fieldArraySize(Trade::SceneField::Camera), 0); CORRADE_COMPARE(scene.mapping(Trade::SceneField::Camera).data(), scene.data()); CORRADE_COMPARE(scene.mapping(Trade::SceneField::Camera).stride()[0], 2); CORRADE_COMPARE(scene.field(Trade::SceneField::Camera).data(), scene.data() + 2); CORRADE_COMPARE(scene.field(Trade::SceneField::Camera).stride()[0], 2); CORRADE_COMPARE(scene.fieldType(Trade::SceneField::Mesh), Trade::SceneFieldType::UnsignedByte); CORRADE_COMPARE(scene.fieldArraySize(Trade::SceneField::Mesh), 0); CORRADE_COMPARE_AS(scene.mapping(Trade::SceneField::Mesh), Containers::arrayView(meshMappingData), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.field(Trade::SceneField::Mesh), Containers::arrayView(meshFieldData), TestSuite::Compare::Container); CORRADE_COMPARE(scene.fieldType(Trade::SceneField::Light), Trade::SceneFieldType::UnsignedInt); CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::Light), 2); CORRADE_COMPARE(scene.fieldArraySize(Trade::SceneField::Light), 0); CORRADE_COMPARE(scene.mapping(Trade::SceneField::Light).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1); CORRADE_COMPARE(scene.mapping(Trade::SceneField::Light).stride()[0], 2); CORRADE_COMPARE(scene.field(Trade::SceneField::Light).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1 + 2*2 + 2); CORRADE_COMPARE(scene.field(Trade::SceneField::Light).stride()[0], 4); CORRADE_COMPARE(scene.fieldType(Trade::sceneFieldCustom(15)), Trade::SceneFieldType::Short); CORRADE_COMPARE(scene.fieldSize(Trade::sceneFieldCustom(15)), 2); CORRADE_COMPARE(scene.fieldArraySize(Trade::sceneFieldCustom(15)), 4); CORRADE_COMPARE(scene.mapping(Trade::sceneFieldCustom(15)).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1 + 2*2 + 2 + 2*4); CORRADE_COMPARE(scene.mapping(Trade::sceneFieldCustom(15)).stride()[0], 2); CORRADE_COMPARE(scene.field(Trade::sceneFieldCustom(15)).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1 + 2*2 + 2 + 2*4 + 2*2); CORRADE_COMPARE(scene.field(Trade::sceneFieldCustom(15)).stride()[0], 4*2); CORRADE_COMPARE(scene.fieldType(Trade::sceneFieldCustom(16)), Trade::SceneFieldType::Bit); CORRADE_COMPARE(scene.fieldSize(Trade::sceneFieldCustom(16)), 3); CORRADE_COMPARE(scene.fieldArraySize(Trade::sceneFieldCustom(16)), 2); CORRADE_COMPARE(scene.mapping(Trade::sceneFieldCustom(16)).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1 + 2*2 + 2 + 2*4 + 2*2 + 2*8); CORRADE_COMPARE(scene.mapping(Trade::sceneFieldCustom(16)).stride()[0], 2); CORRADE_COMPARE(scene.fieldBitArrays(Trade::sceneFieldCustom(16)).data(), scene.data() + 2 + 2 + 3*2 + 3 + 1 + 2*2 + 2 + 2*4 + 2*2 + 2*8 + 3*2); CORRADE_COMPARE(scene.fieldBitArrays(Trade::sceneFieldCustom(16)).offset(), 0); CORRADE_COMPARE(scene.fieldBitArrays(Trade::sceneFieldCustom(16)).stride()[0], 2); } void CombineTest::fieldsMappingSharedFieldPlaceholder() { const UnsignedInt meshMappingData[]{15, 23, 47}; const UnsignedByte meshFieldData[]{0, 1, 2}; Trade::SceneData scene = combineFields(Trade::SceneMappingType::UnsignedInt, 173, { Trade::SceneFieldData{Trade::SceneField::Mesh, Containers::arrayView(meshMappingData), Containers::arrayView(meshFieldData)}, Trade::SceneFieldData{Trade::SceneField::MeshMaterial, Containers::arrayView(meshMappingData), Containers::ArrayView{nullptr, 3}}, }); CORRADE_COMPARE(scene.dataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); CORRADE_COMPARE(scene.mappingType(), Trade::SceneMappingType::UnsignedInt); CORRADE_COMPARE(scene.mappingBound(), 173); CORRADE_COMPARE(scene.fieldCount(), 2); CORRADE_COMPARE(scene.fieldType(Trade::SceneField::Mesh), Trade::SceneFieldType::UnsignedByte); CORRADE_COMPARE(scene.fieldArraySize(Trade::SceneField::Mesh), 0); CORRADE_COMPARE_AS(scene.mapping(0), Containers::arrayView(meshMappingData), TestSuite::Compare::Container); CORRADE_COMPARE_AS(scene.field(0), Containers::arrayView(meshFieldData), TestSuite::Compare::Container); CORRADE_COMPARE(scene.fieldType(Trade::SceneField::MeshMaterial), Trade::SceneFieldType::Int); CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::MeshMaterial), 3); CORRADE_COMPARE(scene.fieldArraySize(Trade::SceneField::MeshMaterial), 0); CORRADE_COMPARE(scene.mapping(Trade::SceneField::MeshMaterial).data(), scene.mapping(Trade::SceneField::Mesh).data()); CORRADE_COMPARE_AS(scene.mapping(Trade::SceneField::MeshMaterial), Containers::arrayView(meshMappingData), TestSuite::Compare::Container); CORRADE_COMPARE(scene.field(Trade::SceneField::MeshMaterial).data(), scene.data() + 3*4 + 3 + 1); CORRADE_COMPARE(scene.field(Trade::SceneField::MeshMaterial).stride()[0], 4); } void CombineTest::fieldsStringPlaceholder() { CORRADE_SKIP_IF_NO_ASSERT(); const struct Data { UnsignedByte mapping; UnsignedByte mesh; } data[3]{}; auto view = Containers::stridedArrayView(data); std::ostringstream out; Error redirectError{&out}; /* A null string data pointer could work in this case (because it doesn't need to be accessed), but disallowing it always for consistency */ combineFields(Trade::SceneMappingType::UnsignedByte, 167, { /* Just to verify it prints correct field IDs */ Trade::SceneFieldData{Trade::SceneField::Mesh, view.slice(&Data::mapping), view.slice(&Data::mesh)}, Trade::SceneFieldData{Trade::sceneFieldCustom(16), view.slice(&Data::mapping), nullptr, Trade::SceneFieldType::StringOffset8, /* Have to fake a pointer because in some cases (ARM64 Linux) the distance between nullptr and stack-allocated memory (such as `data`) *may* be larger than what can fit into 48 bits, triggering an assert */ Containers::arrayView(reinterpret_cast(0xfece5), 3)}, }); /* With placeholder field data it's impossible to know the actual string size */ combineFields(Trade::SceneMappingType::UnsignedByte, 167, { Trade::SceneFieldData{Trade::sceneFieldCustom(16), view.slice(&Data::mapping), /* Have to fake a pointer because in some cases (ARM64 Linux) the distance between nullptr and stack-allocated memory (such as `data`) *may* be larger than what can fit into 48 bits, triggering an assert */ reinterpret_cast(0xfece5), Trade::SceneFieldType::StringRangeNullTerminated16, Containers::StridedArrayView1D{{nullptr, 6}, 3}}, }); CORRADE_COMPARE(out.str(), "SceneTools::combineFields(): string field 1 has a placeholder string data\n" "SceneTools::combineFields(): string field 0 has a placeholder data\n"); } void CombineTest::fieldsOffsetOnly() { CORRADE_SKIP_IF_NO_ASSERT(); const struct Field { UnsignedInt mapping; UnsignedByte mesh; UnsignedShort light; } data[]{ {1, 3, 5}, {4, 6, 8}, }; auto view = Containers::stridedArrayView(data); std::ostringstream out; Error redirectError{&out}; combineFields(Trade::SceneMappingType::UnsignedInt, 173, { Trade::SceneFieldData{Trade::SceneField::Mesh, view.slice(&Field::mapping), view.slice(&Field::mesh)}, Trade::SceneFieldData{Trade::SceneField::Light, 2, Trade::SceneMappingType::UnsignedInt, offsetof(Field, mapping), sizeof(Field), Trade::SceneFieldType::UnsignedShort, offsetof(Field, light), sizeof(Field)} }); CORRADE_COMPARE(out.str(), "SceneTools::combineFields(): field 1 is offset-only\n"); } void CombineTest::fieldsFromDataOffsetOnly() { /* Same as fieldFromData(), but wrapped in a Scene first, which makes it work */ const struct Field { UnsignedInt mapping; UnsignedByte mesh; UnsignedShort light; } data[]{ {1, 3, 5}, {4, 6, 8}, }; auto view = Containers::stridedArrayView(data); Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 22, {}, data, { Trade::SceneFieldData{Trade::SceneField::Mesh, view.slice(&Field::mapping), view.slice(&Field::mesh)}, Trade::SceneFieldData{Trade::SceneField::Light, 2, Trade::SceneMappingType::UnsignedInt, offsetof(Field, mapping), sizeof(Field), Trade::SceneFieldType::UnsignedShort, offsetof(Field, light), sizeof(Field)} }}; Trade::SceneData combined = combineFields(scene); /* Since it's tightly packed, it's less data now */ CORRADE_COMPARE(combined.data().size(), 2*4 + 2*1 + 2*2); CORRADE_COMPARE_AS(combined.data().size(), sizeof(data), TestSuite::Compare::Less); /* The two mappings are shared */ CORRADE_COMPARE_AS(combined.mapping(Trade::SceneField::Mesh), Containers::arrayView({1u, 4u}), TestSuite::Compare::Container); CORRADE_COMPARE_AS(combined.mapping(Trade::SceneField::Light), Containers::arrayView({1u, 4u}), TestSuite::Compare::Container); CORRADE_COMPARE(combined.mapping(Trade::SceneField::Light).data(), combined.mapping(Trade::SceneField::Mesh).data()); CORRADE_COMPARE_AS(combined.field(Trade::SceneField::Mesh), Containers::arrayView({3, 6}), TestSuite::Compare::Container); CORRADE_COMPARE_AS(combined.field(Trade::SceneField::Light), Containers::arrayView({5, 8}), TestSuite::Compare::Container); } }}}} CORRADE_TEST_MAIN(Magnum::SceneTools::Test::CombineTest)