diff --git a/src/Magnum/MaterialTools/CMakeLists.txt b/src/Magnum/MaterialTools/CMakeLists.txt index f7305b40e..77d3bbe07 100644 --- a/src/Magnum/MaterialTools/CMakeLists.txt +++ b/src/Magnum/MaterialTools/CMakeLists.txt @@ -32,9 +32,11 @@ set(MagnumMaterialTools_SRCS PhongToPbrMetallicRoughness.cpp) # Files compiled with different flags for main library and unit test library -set(MagnumMaterialTools_GracefulAssert_SRCS ) +set(MagnumMaterialTools_GracefulAssert_SRCS + Filter.cpp) set(MagnumMaterialTools_HEADERS + Filter.h PhongToPbrMetallicRoughness.h visibility.h) @@ -57,11 +59,7 @@ endif() # Main MaterialTools library add_library(MagnumMaterialTools ${SHARED_OR_STATIC} $ - ${MagnumMaterialTools_HEADERS} - ${MagnumMaterialTools_PRIVATE_HEADERS} - # XCode workaround, see file comment for details. - # TODO remove once MagnumMaterialTools_GracefulAssert_SRCS is non-empty - ${PROJECT_SOURCE_DIR}/src/dummy.cpp) + ${MagnumMaterialTools_GracefulAssert_SRCS}) set_target_properties(MagnumMaterialTools PROPERTIES DEBUG_POSTFIX "-d") if(NOT MAGNUM_BUILD_STATIC) set_target_properties(MagnumMaterialTools PROPERTIES VERSION ${MAGNUM_LIBRARY_VERSION} SOVERSION ${MAGNUM_LIBRARY_SOVERSION}) @@ -79,19 +77,19 @@ install(TARGETS MagnumMaterialTools install(FILES ${MagnumMaterialTools_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/MaterialTools) if(MAGNUM_BUILD_TESTS) - # # Library with graceful assert for testing - # add_library(MagnumMaterialToolsTestLib ${SHARED_OR_STATIC} ${EXCLUDE_FROM_ALL_IF_TEST_TARGET} - # $ - # ${MagnumMaterialTools_GracefulAssert_SRCS}) - # set_target_properties(MagnumMaterialToolsTestLib PROPERTIES DEBUG_POSTFIX "-d") - # target_compile_definitions(MagnumMaterialToolsTestLib PRIVATE - # "CORRADE_GRACEFUL_ASSERT" "MagnumMaterialTools_EXPORTS") - # if(MAGNUM_BUILD_STATIC_PIC) - # set_target_properties(MagnumMaterialToolsTestLib PROPERTIES POSITION_INDEPENDENT_CODE ON) - # endif() - # target_link_libraries(MagnumMaterialToolsTestLib PUBLIC - # Magnum - # MagnumTrade) + # Library with graceful assert for testing + add_library(MagnumMaterialToolsTestLib ${SHARED_OR_STATIC} ${EXCLUDE_FROM_ALL_IF_TEST_TARGET} + $ + ${MagnumMaterialTools_GracefulAssert_SRCS}) + set_target_properties(MagnumMaterialToolsTestLib PROPERTIES DEBUG_POSTFIX "-d") + target_compile_definitions(MagnumMaterialToolsTestLib PRIVATE + "CORRADE_GRACEFUL_ASSERT" "MagnumMaterialTools_EXPORTS") + if(MAGNUM_BUILD_STATIC_PIC) + set_target_properties(MagnumMaterialToolsTestLib PROPERTIES POSITION_INDEPENDENT_CODE ON) + endif() + target_link_libraries(MagnumMaterialToolsTestLib PUBLIC + Magnum + MagnumTrade) add_subdirectory(Test ${EXCLUDE_FROM_ALL_IF_TEST_TARGET}) endif() diff --git a/src/Magnum/MaterialTools/Filter.cpp b/src/Magnum/MaterialTools/Filter.cpp new file mode 100644 index 000000000..7803df824 --- /dev/null +++ b/src/Magnum/MaterialTools/Filter.cpp @@ -0,0 +1,139 @@ +/* + 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 "Filter.h" + +#include +#include +#include + +#include "Magnum/Trade/MaterialData.h" + +namespace Magnum { namespace MaterialTools { + +namespace { + +/* - if inputLayersToKeep is nullptr, all layers are kept, + - if inputAttributesToKeep is nullptr then only attributes present in + enabled layers are kept, + - if neither is nullptr then inputAttributesToKeep is patched to have zero + bits for all attributes in filtered-out layers, + - both can't be nullptr */ +Trade::MaterialData filterAttributesLayersImplementation(const Trade::MaterialData& material, const Containers::BitArrayView inputAttributesToKeep, const Containers::BitArrayView inputLayersToKeep) { + CORRADE_INTERNAL_ASSERT(inputAttributesToKeep.data() || inputLayersToKeep.data()); + + const std::size_t totalAttributeCount = material.attributeDataOffset(material.layerCount()); + + /* Generate the input attribute bit array or make a mutable copy. Using an + Array and not a BitArray so we can preserve the bit offset and + copy without a shift. */ + /** @todo copy() for BitArray so we don't have to work around like this */ + /** @todo if the input would be mutable already, we could avoid the + allocation, but does that matter at all? */ + Containers::Array patchedInputAttributesToKeepData; + Containers::MutableBitArrayView patchedInputAttributesToKeep; + if(inputAttributesToKeep.data()) { + const std::size_t sizeInBytes = (inputAttributesToKeep.offset() + inputAttributesToKeep.size() + 7)/8; + patchedInputAttributesToKeepData = Containers::Array{NoInit, sizeInBytes}; + Utility::copy({inputAttributesToKeep.data(), sizeInBytes}, patchedInputAttributesToKeepData); + patchedInputAttributesToKeep = {patchedInputAttributesToKeepData.data(), inputAttributesToKeep.offset(), inputAttributesToKeep.size()}; + } else { + patchedInputAttributesToKeepData = Containers::Array{NoInit, (totalAttributeCount + 7)/8}; + patchedInputAttributesToKeep = {patchedInputAttributesToKeepData.data(), 0, totalAttributeCount}; + patchedInputAttributesToKeep.setAll(); + } + + /* Patch the attribute array to have zeros for all attributes in + filtered-out layers */ + if(inputLayersToKeep.data()) for(UnsignedInt i = 0; i != material.layerCount(); ++i) { + if(!inputLayersToKeep[i]) patchedInputAttributesToKeep + .slice(material.attributeDataOffset(i), + material.attributeDataOffset(i + 1)) + .resetAll(); + } + + /* Count of layers to keep. If the base layer is to be removed, it just + gets empty -- otherwise, say, a ClearColor layer would become a base + layer and that's generally unwanted */ + std::size_t layerCount; + if(inputLayersToKeep.data()) { + layerCount = inputLayersToKeep.count(); + if(!inputLayersToKeep[0]) ++layerCount; + } else layerCount = material.layerCount(); + + /* Fill in the layer offsets based on count of attributes in each, skipping + layers that are filtered away */ + Containers::Array layers{NoInit, layerCount}; + /** @todo some "iterate set bits" utility, or "next set bit" */ + UnsignedInt layerOffset = 0; + for(UnsignedInt i = 0; i != material.layerCount(); ++i) { + if(inputLayersToKeep.data() && !inputLayersToKeep[i]) { + /* The base layer stays, just gets empty */ + if(i == 0) layers[layerOffset++] = 0; + continue; + } + layers[layerOffset++] = patchedInputAttributesToKeep.prefix(material.attributeDataOffset(i + 1)).count(); + } + CORRADE_INTERNAL_ASSERT(layerOffset == layers.size()); + + /* Copy attributes that aren't filtered away */ + Containers::Array attributes{NoInit, patchedInputAttributesToKeep.count()}; + /** @todo some copyMasked() utility */ + UnsignedInt attributeOffset = 0; + for(UnsignedInt i = 0; i != totalAttributeCount; ++i) { + if(!patchedInputAttributesToKeep[i]) continue; + attributes[attributeOffset++] = material.attributeData()[i]; + } + CORRADE_INTERNAL_ASSERT(attributeOffset == attributes.size()); + + return Trade::MaterialData{material.types(), std::move(attributes), std::move(layers)}; +} + +} + +Trade::MaterialData filterAttributes(const Trade::MaterialData& material, const Containers::BitArrayView attributesToKeep) { + CORRADE_ASSERT(attributesToKeep.size() == material.attributeData().size(), + "MaterialTools::filterAttributes(): expected" << material.attributeData().size() << "bits but got" << attributesToKeep.size(), (Trade::MaterialData{{}, {}})); + + return filterAttributesLayersImplementation(material, attributesToKeep, nullptr); +} + +Trade::MaterialData filterLayers(const Trade::MaterialData& material, const Containers::BitArrayView layersToKeep) { + CORRADE_ASSERT(layersToKeep.size() == material.layerCount(), + "MaterialTools::filterLayers(): expected" << material.layerCount() << "bits but got" << layersToKeep.size(), (Trade::MaterialData{{}, {}})); + + return filterAttributesLayersImplementation(material, nullptr, layersToKeep); +} + +Trade::MaterialData filterAttributesLayers(const Trade::MaterialData& material, const Containers::BitArrayView attributesToKeep, const Containers::BitArrayView layersToKeep) { + CORRADE_ASSERT(attributesToKeep.size() == material.attributeData().size(), + "MaterialTools::filterAttributesLayers(): expected" << material.attributeData().size() << "attribute bits but got" << attributesToKeep.size(), (Trade::MaterialData{{}, {}})); + CORRADE_ASSERT(layersToKeep.size() == material.layerCount(), + "MaterialTools::filterAttributesLayers(): expected" << material.layerCount() << "layer bits but got" << layersToKeep.size(), (Trade::MaterialData{{}, {}})); + + return filterAttributesLayersImplementation(material, attributesToKeep, layersToKeep); +} + +}} diff --git a/src/Magnum/MaterialTools/Filter.h b/src/Magnum/MaterialTools/Filter.h new file mode 100644 index 000000000..669eb50b6 --- /dev/null +++ b/src/Magnum/MaterialTools/Filter.h @@ -0,0 +1,83 @@ +#ifndef Magnum_MaterialTools_Filter_h +#define Magnum_MaterialTools_Filter_h +/* + 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. +*/ + +/** @file + * @brief Function @ref Magnum::MaterialTools::filterAttributes(), @ref Magnum::MaterialTools::filterLayers(), @ref filterAttributesLayers() + * @m_since_latest + */ + +#include "Magnum/MaterialTools/visibility.h" +#include "Magnum/Trade/Trade.h" + +namespace Magnum { namespace MaterialTools { + +/** +@brief Filter material attributes +@m_since_latest + +Returns a material with only the attributes for which the corresponding bit in +@p attributesToKeep was set. Attributes in additional layers are referenced by +bit ranges corresponding to @ref Trade::MaterialData::attributeDataOffset() for +a particular layer. The output layer ranges are then recalculated based on how +many attributes are left in those. Empty layers are kept, +@ref Trade::MaterialData::types() are transferred unchanged. + +The size of @p attributesToKeep is expected to be equal to the number of +attributes in all layers (i.e., size of the +@ref Trade::MaterialData::attributeData() array). +@see @ref filterLayers(), @ref filterAttributesLayers() +*/ +MAGNUM_MATERIALTOOLS_EXPORT Trade::MaterialData filterAttributes(const Trade::MaterialData& material, Containers::BitArrayView attributesToKeep); + +/** +@brief Filter material layers +@m_since_latest + +Returns a material with only the layers for which the corresponding bit in +@p layersToKeep was set. The only exception is the base layer, which is left +empty if removed. Attributes in other layers are kept untouched, +@ref Trade::MaterialData::types() are transferred unchanged. + +The size of @p layerCount is expected to be equal to +@ref Trade::MaterialData::layerCount(). +@see @ref filterAttributes(), @ref filterAttributesLayers() +*/ +MAGNUM_MATERIALTOOLS_EXPORT Trade::MaterialData filterLayers(const Trade::MaterialData& material, Containers::BitArrayView layersToKeep); + +/** +@brief Filter material attributes and layers +@m_since_latest + +Performs what @ref filterAttributes() and @ref filterLayers() do, but in a +single step. Bits in @p attributesToKeep that correspond to layers that are +removed are ignored. +*/ +MAGNUM_MATERIALTOOLS_EXPORT Trade::MaterialData filterAttributesLayers(const Trade::MaterialData& material, Containers::BitArrayView attributesToKeep, Containers::BitArrayView layersToKeep); + +}} + +#endif diff --git a/src/Magnum/MaterialTools/Test/CMakeLists.txt b/src/Magnum/MaterialTools/Test/CMakeLists.txt index 5565ad8f3..6b6a12c73 100644 --- a/src/Magnum/MaterialTools/Test/CMakeLists.txt +++ b/src/Magnum/MaterialTools/Test/CMakeLists.txt @@ -27,4 +27,5 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "Magnum/MaterialTools/Test") +corrade_add_test(MaterialToolsFilterTest FilterTest.cpp LIBRARIES MagnumDebugTools MagnumMaterialToolsTestLib) corrade_add_test(MaterialToolsPhongToPbrMetall___Test PhongToPbrMetallicRoughnessTest.cpp LIBRARIES MagnumDebugTools MagnumMaterialTools) diff --git a/src/Magnum/MaterialTools/Test/FilterTest.cpp b/src/Magnum/MaterialTools/Test/FilterTest.cpp new file mode 100644 index 000000000..d4e712ccf --- /dev/null +++ b/src/Magnum/MaterialTools/Test/FilterTest.cpp @@ -0,0 +1,323 @@ +/* + 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 "Magnum/DebugTools/CompareMaterial.h" +#include "Magnum/MaterialTools/Filter.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Trade/MaterialData.h" + +namespace Magnum { namespace MaterialTools { namespace Test { namespace { + +struct FilterTest: TestSuite::Tester { + explicit FilterTest(); + + void attributes(); + void attributesMultipleLayers(); + void attributesWrongBitCount(); + + void layers(); + void layersRemoveBase(); + void layersWrongBitCount(); + + void attributesLayers(); + void attributesLayersRemoveBaseLayer(); + void attributesLayersWrongBitCount(); +}; + +FilterTest::FilterTest() { + addTests({&FilterTest::attributes, + &FilterTest::attributesMultipleLayers, + &FilterTest::attributesWrongBitCount, + + &FilterTest::layers, + &FilterTest::layersRemoveBase, + &FilterTest::layersWrongBitCount, + + &FilterTest::attributesLayers, + &FilterTest::attributesLayersRemoveBaseLayer, + &FilterTest::attributesLayersWrongBitCount}); +} + +using namespace Math::Literals; + +void FilterTest::attributes() { + /* Supplying the attributes as external in order to make sure they're + sorted for correct numbering */ + const Trade::MaterialAttributeData attributes[]{ + {Trade::MaterialAttribute::AlphaBlend, true}, /* 0 */ + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, /* 1 */ + {Trade::MaterialAttribute::BaseColorTexture, 7u}, /* 2 */ + {Trade::MaterialAttribute::TextureCoordinates, 11u}, /* 3 */ + {Trade::MaterialAttribute::TextureLayer, 0u}, /* 4 */ + }; + Trade::MaterialData material{Trade::MaterialType::PbrClearCoat|Trade::MaterialType::Flat, Trade::DataFlags{}, attributes}; + + Containers::BitArray attributesToKeep{DirectInit, 5, true}; + attributesToKeep.reset(1); + attributesToKeep.reset(3); + attributesToKeep.reset(4); + + /* The types are kept intact even if they don't make sense, that's a job + for some higher-level utility that understands their relations to + present attributes */ + CORRADE_COMPARE_AS(filterAttributes(material, attributesToKeep), (Trade::MaterialData{Trade::MaterialType::PbrClearCoat|Trade::MaterialType::Flat, { + {Trade::MaterialAttribute::AlphaBlend, true}, + {Trade::MaterialAttribute::BaseColorTexture, 7u}, + }}), DebugTools::CompareMaterial); + + /* Removing all shouldn't do anything unexpected */ + CORRADE_COMPARE_AS(filterAttributes(material, Containers::BitArray{ValueInit, 5}), (Trade::MaterialData{Trade::MaterialType::PbrClearCoat|Trade::MaterialType::Flat, { + }}), DebugTools::CompareMaterial); +} + +void FilterTest::attributesMultipleLayers() { + const Trade::MaterialAttributeData attributes[]{ + {Trade::MaterialAttribute::AlphaBlend, true}, /* 0 */ + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, /* 1 */ + {Trade::MaterialAttribute::BaseColorTexture, 7u}, /* 2 */ + {Trade::MaterialLayer::ClearCoat}, /* 3 */ + {Trade::MaterialAttribute::LayerFactor, 0.7f}, /* 4 */ + {Trade::MaterialAttribute::Roughness, 0.25f}, /* 5 */ + /* One empty layer here */ + {"textureBlendMode", "strongly!"}, /* 6 */ + {"texturePointer", nullptr}, /* 7 */ + }; + const UnsignedInt layers[]{3, 6, 6, 8}; + Trade::MaterialData material{Trade::MaterialType::PbrClearCoat, Trade::DataFlags{}, attributes, Trade::DataFlags{}, layers}; + + Containers::BitArray attributesToKeep{DirectInit, 8, true}; + attributesToKeep.reset(0); + attributesToKeep.reset(2); + attributesToKeep.reset(4); + attributesToKeep.reset(6); + + CORRADE_COMPARE_AS(filterAttributes(material, attributesToKeep), (Trade::MaterialData{Trade::MaterialType::PbrClearCoat, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::Roughness, 0.25f}, + /* Empty layer stays */ + {"texturePointer", nullptr}, + }, {1, 3, 3, 4}}), DebugTools::CompareMaterial); + + /* Removing all shouldn't do anything unexpected */ + CORRADE_COMPARE_AS(filterAttributes(material, Containers::BitArray{ValueInit, 8}), (Trade::MaterialData{Trade::MaterialType::PbrClearCoat, { + }, {0, 0, 0, 0}}), DebugTools::CompareMaterial); +} + +void FilterTest::attributesWrongBitCount() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Trade::MaterialData material{{}, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::BaseColorTexture, 7u}, + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::LayerFactor, 0.7f}, + }, {2, 4}}; + Containers::BitArrayView attributesToKeep{nullptr, 0, 5}; + + std::ostringstream out; + Error redirectError{&out}; + filterAttributes(material, attributesToKeep); + CORRADE_COMPARE(out.str(), "MaterialTools::filterAttributes(): expected 4 bits but got 5\n"); +} + +void FilterTest::layers() { + const Trade::MaterialAttributeData attributes[]{ + {Trade::MaterialAttribute::AlphaBlend, true}, /* 0 */ + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::BaseColorTexture, 7u}, + /* One empty layer here */ /* 1 #3 */ + {Trade::MaterialLayer::ClearCoat}, /* 2 #3 */ + {Trade::MaterialAttribute::LayerFactor, 0.7f}, + {Trade::MaterialAttribute::Roughness, 0.25f}, + {"textureBlendMode", "strongly!"}, /* 3 #6 */ + {"texturePointer", nullptr}, + /* Another empty layer here */ /* 4 #8 */ + {Trade::MaterialAttribute::NormalTextureSwizzle, /* 5 #8 */ + Trade::MaterialTextureSwizzle::RG}, + {"againSomething", false}, /* 6 #9 */ + }; + const UnsignedInt layers[]{3, 3, 6, 8, 8, 9, 10}; + Trade::MaterialData material{Trade::MaterialType::PbrClearCoat, Trade::DataFlags{}, attributes, Trade::DataFlags{}, layers}; + + Containers::BitArray layersToKeep{DirectInit, 7, true}; + layersToKeep.reset(1); + layersToKeep.reset(2); + layersToKeep.reset(5); + + CORRADE_COMPARE_AS(filterLayers(material, layersToKeep), (Trade::MaterialData{Trade::MaterialType::PbrClearCoat, { + {Trade::MaterialAttribute::AlphaBlend, true}, + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::BaseColorTexture, 7u}, + {"textureBlendMode", "strongly!"}, + {"texturePointer", nullptr}, + /* Second empty layer stays */ + {"againSomething", false}, + }, {3, 5, 5, 6}}), DebugTools::CompareMaterial); + + /* Removing all shouldn't do anything unexpected */ + CORRADE_COMPARE_AS(filterLayers(material, Containers::BitArray{ValueInit, 7}), (Trade::MaterialData{Trade::MaterialType::PbrClearCoat, { + }}), DebugTools::CompareMaterial); +} + +void FilterTest::layersRemoveBase() { + const Trade::MaterialAttributeData attributes[]{ + {Trade::MaterialAttribute::AlphaBlend, true}, /* 0 */ + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::BaseColorTexture, 7u}, + {Trade::MaterialLayer::ClearCoat}, /* 1 #3 */ + {Trade::MaterialAttribute::LayerFactor, 0.7f}, + }; + const UnsignedInt layers[]{3, 5}; + Trade::MaterialData material{Trade::MaterialType::PbrClearCoat, Trade::DataFlags{}, attributes, Trade::DataFlags{}, layers}; + + Containers::BitArray layersToKeep{DirectInit, 2, true}; + layersToKeep.reset(0); + + CORRADE_COMPARE_AS(filterLayers(material, layersToKeep), (Trade::MaterialData{Trade::MaterialType::PbrClearCoat, { + /* The base layer stays but it's empty */ + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::LayerFactor, 0.7f}, + }, {0, 2}}), DebugTools::CompareMaterial); +} + +void FilterTest::layersWrongBitCount() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Trade::MaterialData material{{}, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::BaseColorTexture, 7u}, + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::LayerFactor, 0.7f} + }, {2, 4}}; + Containers::BitArrayView layersToKeep{nullptr, 0, 3}; + + std::ostringstream out; + Error redirectError{&out}; + filterLayers(material, layersToKeep); + CORRADE_COMPARE(out.str(), "MaterialTools::filterLayers(): expected 2 bits but got 3\n"); +} + +void FilterTest::attributesLayers() { + const Trade::MaterialAttributeData attributes[]{ + {Trade::MaterialAttribute::AlphaBlend, true}, /* 0 #0 */ + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, /* #1 */ + {Trade::MaterialAttribute::BaseColorTexture, 7u}, /* #2 */ + /* One empty layer here */ /* 1 #3 */ + {Trade::MaterialLayer::ClearCoat}, /* 2 #3 */ + {Trade::MaterialAttribute::LayerFactor, 0.7f}, /* #4 */ + {Trade::MaterialAttribute::Roughness, 0.25f}, /* #5 */ + {"textureBlendMode", "strongly!"}, /* 3 #6 */ + {"texturePointer", nullptr}, /* #7 */ + /* Another empty layer here */ /* 4 #8 */ + {Trade::MaterialAttribute::NormalTextureSwizzle, /* 5 #8 */ + Trade::MaterialTextureSwizzle::RG}, + {"againSomething", false}, /* 6 #9 */ + }; + const UnsignedInt layers[]{3, 3, 6, 8, 8, 9, 10}; + Trade::MaterialData material{Trade::MaterialType::PbrClearCoat, Trade::DataFlags{}, attributes, Trade::DataFlags{}, layers}; + + Containers::BitArray attributesToKeep{DirectInit, 10, true}; + attributesToKeep.reset(1); + attributesToKeep.reset(4); /* in a removed layer, ignored */ + attributesToKeep.reset(6); + attributesToKeep.reset(8); /* becomes an empty layer */ + + Containers::BitArray layersToKeep{DirectInit, 7, true}; + layersToKeep.reset(1); + layersToKeep.reset(2); + + CORRADE_COMPARE_AS(filterAttributesLayers(material, attributesToKeep, layersToKeep), (Trade::MaterialData{Trade::MaterialType::PbrClearCoat, { + {Trade::MaterialAttribute::AlphaBlend, true}, + {Trade::MaterialAttribute::BaseColorTexture, 7u}, + {"texturePointer", nullptr}, + /* Second empty layer stays */ + /* Layer 5 is now empty */ + {"againSomething", false}, + }, {2, 3, 3, 3, 4}}), DebugTools::CompareMaterial); + + /* Removing all attributes should keep all layers but make them empty */ + CORRADE_COMPARE_AS(filterAttributesLayers(material, Containers::BitArray{ValueInit, 10}, Containers::BitArray{DirectInit, 7, true}), (Trade::MaterialData{Trade::MaterialType::PbrClearCoat, { + }, {0, 0, 0, 0, 0, 0, 0}}), DebugTools::CompareMaterial); + + /* Removing all layers should make the material completely empty */ + CORRADE_COMPARE_AS(filterLayers(material, Containers::BitArray{ValueInit, 7}), (Trade::MaterialData{Trade::MaterialType::PbrClearCoat, { + }}), DebugTools::CompareMaterial); +} + +void FilterTest::attributesLayersRemoveBaseLayer() { + const Trade::MaterialAttributeData attributes[]{ + {Trade::MaterialAttribute::AlphaBlend, true}, /* 0 #0 */ + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, /* #1 */ + {Trade::MaterialAttribute::BaseColorTexture, 7u}, /* #2 */ + {Trade::MaterialLayer::ClearCoat}, /* 1 #3 */ + {Trade::MaterialAttribute::LayerFactor, 0.7f}, /* #4 */ + }; + const UnsignedInt layers[]{3, 5}; + Trade::MaterialData material{Trade::MaterialType::PbrClearCoat, Trade::DataFlags{}, attributes, Trade::DataFlags{}, layers}; + + Containers::BitArray attributesToKeep{DirectInit, 5, true}; + attributesToKeep.reset(1); /* in a removed base layer, ignored */ + attributesToKeep.reset(3); + + Containers::BitArray layersToKeep{DirectInit, 2, true}; + layersToKeep.reset(0); + + CORRADE_COMPARE_AS(filterAttributesLayers(material, attributesToKeep, layersToKeep), (Trade::MaterialData{Trade::MaterialType::PbrClearCoat, { + /* The base layer stays but it's empty */ + {Trade::MaterialAttribute::LayerFactor, 0.7f}, + }, {0, 1}}), DebugTools::CompareMaterial); +} + +void FilterTest::attributesLayersWrongBitCount() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Trade::MaterialData material{{}, { + {Trade::MaterialAttribute::BaseColor, 0xffcc66ff_rgbaf}, + {Trade::MaterialAttribute::BaseColorTexture, 7u}, + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::LayerFactor, 0.7f}, + }, {2, 4}}; + Containers::BitArrayView attributesToKeep{nullptr, 0, 5}; + Containers::BitArrayView layersToKeep{nullptr, 0, 3}; + + std::ostringstream out; + Error redirectError{&out}; + filterAttributesLayers(material, attributesToKeep, layersToKeep.prefix(2)); + filterAttributesLayers(material, attributesToKeep.prefix(4), layersToKeep); + CORRADE_COMPARE(out.str(), + "MaterialTools::filterAttributesLayers(): expected 4 attribute bits but got 5\n" + "MaterialTools::filterAttributesLayers(): expected 2 layer bits but got 3\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::MaterialTools::Test::FilterTest)