From f41b1f4faecb497e1724bfcb14ce59d3a6e956a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 18 Apr 2023 22:56:00 +0200 Subject: [PATCH] SceneTools: support SceneFieldType::Bit in combineFields(). I'm (again) postponing implementation of a robust & optimized Utility::copy() for bit array views. Attempted something, but it got out of hand very quickly. Since the particular use case here isn't going to be a perf bottleneck I think, it's fine to do it with just a dumb loop for now. --- .../SceneTools/Implementation/combine.h | 93 ++++++++++++++---- src/Magnum/SceneTools/Test/CombineTest.cpp | 95 +++++++++++++++++-- 2 files changed, 161 insertions(+), 27 deletions(-) diff --git a/src/Magnum/SceneTools/Implementation/combine.h b/src/Magnum/SceneTools/Implementation/combine.h index 81a80d938..4137fea58 100644 --- a/src/Magnum/SceneTools/Implementation/combine.h +++ b/src/Magnum/SceneTools/Implementation/combine.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "Magnum/Math/Functions.h" @@ -46,7 +47,14 @@ namespace Magnum { namespace SceneTools { namespace Implementation { /** @todo move everything to Combine.cpp and anonymous namespace once that compatibility is dropped */ -template void combineCopyMappings(const Containers::ArrayView fields, const Containers::ArrayView> itemViews, const Containers::ArrayView> itemViewMappings) { +union CombineItemView { + explicit CombineItemView() {} + + Containers::StridedArrayView2D types; + Containers::MutableStridedBitArrayView2D bits; +}; + +template void combineCopyMappings(const Containers::ArrayView fields, const Containers::ArrayView itemViews, const Containers::ArrayView> itemViewMappings) { std::size_t latestMapping = 0; for(std::size_t i = 0; i != fields.size(); ++i) { /* If there are no shared object mappings, itemViewMappings should be @@ -64,7 +72,7 @@ template void combineCopyMappings(const Containers::ArrayView src = fields[i].mappingData(); - const Containers::StridedArrayView2D dst = Containers::arrayCast<2, T>(Containers::arrayCast<1, T>(itemViews[mapping])); + const Containers::StridedArrayView2D dst = Containers::arrayCast<2, T>(Containers::arrayCast<1, T>(itemViews[mapping].types)); if(fields[i].mappingType() == Trade::SceneMappingType::UnsignedByte) Math::castInto(Containers::arrayCast<2, const UnsignedByte>(Containers::arrayCast(src)), dst); else if(fields[i].mappingType() == Trade::SceneMappingType::UnsignedShort) @@ -91,10 +99,12 @@ inline Trade::SceneData combineFields(const Trade::SceneMappingType mappingType, Containers::Array> itemViewMappings{NoInit, fields.size()}; /* The item views are referenced from ArrayTuple::Item, not using a - growable array in order to avoid an accidental reallocation */ + growable array in order to avoid an accidental reallocation. It's either + of the two views in the union based on whether it's a mapping or data + view and what the data field type is */ /** @todo once never-reallocating allocators are present, use them instead of the manual offset */ - Containers::Array> itemViews{fields.size()*2}; + Containers::Array itemViews{fields.size()*2}; std::size_t itemViewOffset = 0; /* Go through all fields and collect ArrayTuple allocations for these */ @@ -126,7 +136,7 @@ inline Trade::SceneData combineFields(const Trade::SceneMappingType mappingType, std::size_t(field.size()), mappingTypeSize, mappingTypeAlignment, - itemViews[itemViewOffset]); + itemViews[itemViewOffset].types); ++itemViewOffset; } @@ -135,11 +145,19 @@ inline Trade::SceneData combineFields(const Trade::SceneMappingType mappingType, either. */ /** @todo field aliasing might be useful at some point */ itemViewMappings[i].second() = itemViewOffset; - arrayAppend(items, InPlaceInit, - NoInit, - std::size_t(field.size()), sceneFieldTypeSize(field.fieldType())*(field.fieldArraySize() ? field.fieldArraySize() : 1), - sceneFieldTypeAlignment(field.fieldType()), - itemViews[itemViewOffset]); + const Trade::SceneFieldType fieldType = field.fieldType(); + if(fieldType == Trade::SceneFieldType::Bit) { + arrayAppend(items, InPlaceInit, + NoInit, + Containers::Size2D{std::size_t(field.size()), field.fieldArraySize() ? field.fieldArraySize() : 1}, + itemViews[itemViewOffset].bits); + } else { + arrayAppend(items, InPlaceInit, + NoInit, + std::size_t(field.size()), sceneFieldTypeSize(fieldType)*(field.fieldArraySize() ? field.fieldArraySize() : 1), + sceneFieldTypeAlignment(fieldType), + itemViews[itemViewOffset].types); + } ++itemViewOffset; } @@ -163,24 +181,59 @@ inline Trade::SceneData combineFields(const Trade::SceneMappingType mappingType, /* Copy the field data over. No special handling needed here. */ for(std::size_t i = 0; i != fields.size(); ++i) { const Trade::SceneFieldData& field = fields[i]; - const Containers::StridedArrayView1D src = field.fieldData(); + const Trade::SceneFieldType fieldType = field.fieldType(); + if(fieldType == Trade::SceneFieldType::Bit) { + const Containers::StridedBitArrayView2D src = field.fieldBitData(); + + /* If the field has null field data, no need to copy anything. This + covers reserved fields but also fields of zero size. */ + if(!src.data()) continue; + + /** @todo this needs Utility::copy() for bits, which is HARD */ + const Containers::MutableStridedBitArrayView2D dst = itemViews[itemViewMappings[i].second()].bits; + const std::size_t arraySize = field.fieldArraySize() ? field.fieldArraySize() : 1; + for(std::size_t i = 0, iMax = field.size(); i != iMax; ++i) { + const Containers::StridedBitArrayView1D srcI = src[i]; + const Containers::MutableStridedBitArrayView1D dstI = dst[i]; + for(std::size_t j = 0; j != arraySize; ++j) + dstI.set(j, srcI[j]); + } + } else { + const Containers::StridedArrayView1D src = field.fieldData(); - /* If the field has null field data, no need to copy anything. This - covers reserved fields but also fields of zero size. */ - if(!src.data()) continue; + /* If the field has null field data, no need to copy anything. This + covers reserved fields but also fields of zero size. */ + if(!src.data()) continue; - /** @todo isn't there some less awful way to create a 2D view, sigh */ - Utility::copy(Containers::arrayCast<2, const char>(src, sceneFieldTypeSize(field.fieldType())*(field.fieldArraySize() ? field.fieldArraySize() : 1)), itemViews[itemViewMappings[i].second()]); + /** @todo isn't there some less awful way to create a 2D view, sigh */ + Utility::copy(Containers::arrayCast<2, const char>(src, sceneFieldTypeSize(fieldType)*(field.fieldArraySize() ? field.fieldArraySize() : 1)), itemViews[itemViewMappings[i].second()].types); + } } /* Map the fields to the new data */ Containers::Array outFields{fields.size()}; for(std::size_t i = 0; i != fields.size(); ++i) { const Trade::SceneFieldData& field = fields[i]; - outFields[i] = Trade::SceneFieldData{field.name(), - itemViews[itemViewMappings[i].first()], - field.fieldType(), itemViews[itemViewMappings[i].second()], - field.fieldArraySize(), field.flags()}; + const Trade::SceneFieldType fieldType = field.fieldType(); + if(fieldType == Trade::SceneFieldType::Bit) { + /* Pass arrays as 2D views, non-arrays as 1D views */ + if(field.fieldArraySize()) + outFields[i] = Trade::SceneFieldData{field.name(), + itemViews[itemViewMappings[i].first()].types, + itemViews[itemViewMappings[i].second()].bits, + field.flags()}; + else + outFields[i] = Trade::SceneFieldData{field.name(), + itemViews[itemViewMappings[i].first()].types, + /** @todo creating a 1D view isn't really easy either, huh? */ + itemViews[itemViewMappings[i].second()].bits.transposed<0, 1>()[0], + field.flags()}; + } else { + outFields[i] = Trade::SceneFieldData{field.name(), + itemViews[itemViewMappings[i].first()].types, + fieldType, itemViews[itemViewMappings[i].second()].types, + field.fieldArraySize(), field.flags()}; + } } return Trade::SceneData{mappingType, mappingBound, std::move(outData), std::move(outFields)}; diff --git a/src/Magnum/SceneTools/Test/CombineTest.cpp b/src/Magnum/SceneTools/Test/CombineTest.cpp index 10c01667c..a0b14eea6 100644 --- a/src/Magnum/SceneTools/Test/CombineTest.cpp +++ b/src/Magnum/SceneTools/Test/CombineTest.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -120,6 +121,27 @@ void CombineTest::fields() { }; 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), @@ -137,13 +159,23 @@ void CombineTest::fields() { Containers::arrayCast<2, const Int>(foos.slice(&Foo::foo)), Trade::SceneFieldFlag::OrderedMapping}, /* Empty field */ - Trade::SceneFieldData{Trade::SceneField::Camera, Containers::ArrayView{}, Containers::ArrayView{}} + 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(), 5); + CORRADE_COMPARE(scene.fieldCount(), 7); CORRADE_COMPARE(scene.fieldName(0), Trade::SceneField::Mesh); CORRADE_COMPARE(scene.fieldFlags(0), Trade::SceneFieldFlags{}); @@ -199,6 +231,36 @@ void CombineTest::fields() { 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); } void CombineTest::fieldsAlignment() { @@ -360,12 +422,16 @@ void CombineTest::fieldsMappingPlaceholderFieldPlaceholder() { 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(), 4); + CORRADE_COMPARE(scene.fieldCount(), 5); CORRADE_COMPARE(scene.fieldType(Trade::SceneField::Camera), Trade::SceneFieldType::UnsignedShort); CORRADE_COMPARE(scene.fieldSize(Trade::SceneField::Camera), 1); @@ -387,18 +453,33 @@ void CombineTest::fieldsMappingPlaceholderFieldPlaceholder() { 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).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).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)).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)).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() {