mirror of https://github.com/mosra/magnum.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
395 lines
17 KiB
395 lines
17 KiB
/* |
|
This file is part of Magnum. |
|
|
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
|
2020, 2021, 2022 Vladimír Vondruš <mosra@centrum.cz> |
|
|
|
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 "CompareMaterial.h" |
|
|
|
#include <Corrade/Containers/Array.h> |
|
#include <Corrade/Containers/GrowableArray.h> |
|
#include <Corrade/Containers/Triple.h> |
|
#include <Corrade/Utility/Math.h> /* max() that works with enums */ |
|
#include <Corrade/TestSuite/Comparator.h> |
|
|
|
#include "Magnum/Math/Matrix.h" |
|
#include "Magnum/Math/Vector4.h" |
|
#include "Magnum/Trade/MaterialData.h" |
|
|
|
namespace Corrade { namespace TestSuite { |
|
|
|
using namespace Magnum; |
|
|
|
namespace { |
|
|
|
/* Higher values mean bigger change */ |
|
enum class MaterialState { |
|
Same, |
|
DifferentTypes, |
|
DifferentAttributeValues, |
|
DifferentAttributeTypes, |
|
DifferentAttributes, |
|
DifferentLayers |
|
}; |
|
|
|
enum class AttributeState { |
|
Same, |
|
DifferentValue, |
|
DifferentType, |
|
OnlyInExpected, |
|
OnlyInActual |
|
}; |
|
|
|
} |
|
|
|
struct Comparator<DebugTools::CompareMaterial>::State { |
|
MaterialState materialState = MaterialState::Same; |
|
|
|
/* Second is attribute ID in the actual material (unless AttributeState is |
|
OnlyInExpected), third is attribute ID in the expected material (unless |
|
AttributeState is OnlyInActual). */ |
|
Containers::Array<Containers::Triple<AttributeState, UnsignedInt, UnsignedInt>> attributes; |
|
/* Offsets into the attributes array for each layer. I.e., layer i is |
|
stored in `attributes[layerOffsets[i]]` to |
|
`attributes[layerOffsets[i + 1]]`. */ |
|
Containers::Array<UnsignedInt> layerOffsets; |
|
|
|
const Trade::MaterialData* actual{}; |
|
const Trade::MaterialData* expected{}; |
|
}; |
|
|
|
Comparator<DebugTools::CompareMaterial>::Comparator(): _state{InPlaceInit} {} |
|
|
|
Comparator<DebugTools::CompareMaterial>::~Comparator() = default; |
|
|
|
namespace { |
|
|
|
bool attributesEqual(const Trade::MaterialAttributeData& a, const Trade::MaterialAttributeData& b) { |
|
#ifdef CORRADE_TARGET_GCC |
|
#pragma GCC diagnostic push |
|
#pragma GCC diagnostic error "-Wswitch" |
|
#endif |
|
CORRADE_INTERNAL_ASSERT(a.type() == b.type()); |
|
switch(a.type()) { |
|
#define _c(type) case Trade::MaterialAttributeType::type: \ |
|
return Math::TypeTraits<type>::equals(a.value<type>(), b.value<type>()); |
|
#define _ct(name, type) case Trade::MaterialAttributeType::name: \ |
|
return Math::TypeTraits<type>::equals(a.value<type>(), b.value<type>()); |
|
_ct(Bool, bool) |
|
/* LCOV_EXCL_START */ |
|
_c(Float) |
|
_c(Deg) |
|
_c(Rad) |
|
_c(UnsignedInt) |
|
_c(Int) |
|
_c(UnsignedLong) |
|
_c(Long) |
|
_c(Vector2) |
|
_c(Vector2ui) |
|
_c(Vector2i) |
|
_c(Vector3) |
|
_c(Vector3ui) |
|
_c(Vector3i) |
|
_c(Vector4) |
|
_c(Vector4ui) |
|
_c(Vector4i) |
|
_c(Matrix2x2) |
|
_c(Matrix2x3) |
|
_c(Matrix2x4) |
|
_c(Matrix3x2) |
|
_c(Matrix3x3) |
|
_c(Matrix3x4) |
|
_c(Matrix4x2) |
|
_c(Matrix4x3) |
|
/* LCOV_EXCL_STOP */ |
|
_ct(Pointer, const void*) |
|
_ct(MutablePointer, void*) |
|
_ct(String, Containers::StringView) |
|
_ct(TextureSwizzle, Trade::MaterialTextureSwizzle) |
|
#undef _c |
|
#undef _ct |
|
case Trade::MaterialAttributeType::Buffer: |
|
return Containers::StringView{Containers::arrayCast<const char>(a.value<Containers::ArrayView<const void>>())} == Containers::StringView{Containers::arrayCast<const char>(b.value<Containers::ArrayView<const void>>())}; |
|
} |
|
#ifdef CORRADE_TARGET_GCC |
|
#pragma GCC diagnostic pop |
|
#endif |
|
|
|
CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
|
} |
|
|
|
} |
|
|
|
TestSuite::ComparisonStatusFlags Comparator<DebugTools::CompareMaterial>::operator()(const Trade::MaterialData& actual, const Trade::MaterialData& expected) { |
|
_state->actual = &actual; |
|
_state->expected = &expected; |
|
|
|
if(actual.types() != expected.types()) |
|
_state->materialState = MaterialState::DifferentTypes; |
|
|
|
/* The layer offset array has one extra item for the last layer count */ |
|
const std::size_t layerMax = Math::max(actual.layerCount(), expected.layerCount()); |
|
_state->layerOffsets = Containers::Array<UnsignedInt>{NoInit, layerMax + 1}; |
|
|
|
/* Go over all layers that are in both materials */ |
|
std::size_t layer = 0; |
|
for(const std::size_t layerMin = Math::min(actual.layerCount(), expected.layerCount()); layer != layerMin; ++layer) { |
|
_state->layerOffsets[layer] = _state->attributes.size(); |
|
UnsignedInt inActual = 0; |
|
UnsignedInt inExpected = 0; |
|
|
|
/* Take the earliest-sorted attribute from either material */ |
|
while(inActual != actual.attributeCount(layer) && inExpected != expected.attributeCount(layer)) { |
|
if(actual.attributeName(layer, inActual) == expected.attributeName(layer, inExpected)) { |
|
AttributeState attributeState; |
|
if(actual.attributeType(layer, inActual) != expected.attributeType(layer, inExpected)) { |
|
attributeState = AttributeState::DifferentType; |
|
_state->materialState = Utility::max(_state->materialState, MaterialState::DifferentAttributeTypes); |
|
} else if(!attributesEqual(actual.attributeData(layer, inActual), expected.attributeData(layer, inExpected))) { |
|
attributeState = AttributeState::DifferentValue; |
|
_state->materialState = Utility::max(_state->materialState, MaterialState::DifferentAttributeValues); |
|
} else { |
|
attributeState = AttributeState::Same; |
|
} |
|
|
|
arrayAppend(_state->attributes, InPlaceInit, attributeState, inActual, inExpected); |
|
++inActual; |
|
++inExpected; |
|
} else if(actual.attributeName(layer, inActual) < expected.attributeName(layer, inExpected)) { |
|
arrayAppend(_state->attributes, InPlaceInit, AttributeState::OnlyInActual, inActual, ~UnsignedInt{}); |
|
_state->materialState = Utility::max(_state->materialState, MaterialState::DifferentAttributes); |
|
++inActual; |
|
} else if(actual.attributeName(layer, inActual) > expected.attributeName(layer, inExpected)) { |
|
arrayAppend(_state->attributes, InPlaceInit, AttributeState::OnlyInExpected, ~UnsignedInt{}, inExpected); |
|
_state->materialState = Utility::max(_state->materialState, MaterialState::DifferentAttributes); |
|
++inExpected; |
|
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
|
} |
|
|
|
/* Consume remaining leftover attributes in either. Only one of these |
|
loops get entered. */ |
|
while(inActual < actual.attributeCount(layer)) { |
|
arrayAppend(_state->attributes, InPlaceInit, AttributeState::OnlyInActual, inActual, ~UnsignedInt{}); |
|
_state->materialState = Utility::max(_state->materialState, MaterialState::DifferentAttributes); |
|
++inActual; |
|
} |
|
while(inExpected < expected.attributeCount(layer)) { |
|
arrayAppend(_state->attributes, InPlaceInit, AttributeState::OnlyInExpected, ~UnsignedInt{}, inExpected); |
|
_state->materialState = Utility::max(_state->materialState, MaterialState::DifferentAttributes); |
|
++inExpected; |
|
} |
|
} |
|
|
|
/* Go over remaining actual/expected layers which weren't in |
|
expected/actual. Only one of these loops get entered. */ |
|
for(; layer < actual.layerCount(); ++layer) { |
|
_state->layerOffsets[layer] = _state->attributes.size(); |
|
|
|
for(UnsignedInt inActual = 0, inActualMax = actual.attributeCount(layer); inActual != inActualMax; ++inActual) |
|
arrayAppend(_state->attributes, InPlaceInit, AttributeState::OnlyInActual, inActual, ~UnsignedInt{}); |
|
|
|
_state->materialState = Utility::max(_state->materialState, MaterialState::DifferentLayers); |
|
} |
|
for(; layer < expected.layerCount(); ++layer) { |
|
_state->layerOffsets[layer] = _state->attributes.size(); |
|
|
|
for(UnsignedInt inExpected = 0, inExpectedMax = expected.attributeCount(layer); inExpected != inExpectedMax; ++inExpected) |
|
arrayAppend(_state->attributes, InPlaceInit, AttributeState::OnlyInExpected, ~UnsignedInt{}, inExpected); |
|
|
|
_state->materialState = Utility::max(_state->materialState, MaterialState::DifferentLayers); |
|
} |
|
|
|
CORRADE_INTERNAL_ASSERT(layer == _state->layerOffsets.size() - 1); |
|
_state->layerOffsets[layer] = _state->attributes.size(); |
|
|
|
/** @todo If there's a large sequence of Same attributes, elide the middle |
|
(mark it with SameEllipsis, e.g., and leave at most 3 before and 3 |
|
after each difference), and skip them when printing unless Verbose is |
|
set. Would become practically useful only once there's really a lot |
|
material attributes in a single layer, currently not really. */ |
|
|
|
return _state->materialState == MaterialState::Same ? TestSuite::ComparisonStatusFlags{} : TestSuite::ComparisonStatusFlag::Failed; |
|
} |
|
|
|
namespace { |
|
|
|
void printAttribute(Debug& out, const Trade::MaterialAttributeData& attribute, const AttributeState state, const bool isActual) { |
|
out << Debug::newline << " "; |
|
if(state == AttributeState::Same) |
|
out << " "; |
|
else if(isActual) |
|
out << Debug::color(Debug::Color::Green) << "+"; |
|
else |
|
out << Debug::color(Debug::Color::Red) << "-"; |
|
if(state == AttributeState::DifferentType || |
|
state == AttributeState::DifferentValue) |
|
out << Debug::resetColor; |
|
|
|
out << "" << attribute.name() << "@"; |
|
|
|
if(state == AttributeState::DifferentType) |
|
out << Debug::color(isActual ? Debug::Color::Green : Debug::Color::Red); |
|
out << Debug::packed << attribute.type(); |
|
if(state == AttributeState::DifferentType) |
|
out << Debug::resetColor; |
|
out << Debug::nospace << ":"; |
|
|
|
if(state == AttributeState::DifferentType || |
|
state == AttributeState::DifferentValue) |
|
out << Debug::color(isActual ? Debug::Color::Green : Debug::Color::Red); |
|
#ifdef CORRADE_TARGET_GCC |
|
#pragma GCC diagnostic push |
|
#pragma GCC diagnostic error "-Wswitch" |
|
#endif |
|
switch(attribute.type()) { |
|
#define _c(type) case Trade::MaterialAttributeType::type: \ |
|
out << Debug::packed << attribute.value<type>(); \ |
|
break; |
|
#define _ct(name, type) case Trade::MaterialAttributeType::name: \ |
|
out << Debug::packed << attribute.value<type>(); \ |
|
break; |
|
_ct(Bool, bool) |
|
/* LCOV_EXCL_START */ |
|
_c(Float) |
|
_c(Deg) |
|
_c(Rad) |
|
_c(UnsignedInt) |
|
_c(Int) |
|
_c(UnsignedLong) |
|
_c(Long) |
|
_c(Vector2) |
|
_c(Vector2ui) |
|
_c(Vector2i) |
|
_c(Vector3) |
|
_c(Vector3ui) |
|
_c(Vector3i) |
|
_c(Vector4) |
|
_c(Vector4ui) |
|
_c(Vector4i) |
|
_c(Matrix2x2) |
|
_c(Matrix2x3) |
|
_c(Matrix2x4) |
|
_c(Matrix3x2) |
|
_c(Matrix3x3) |
|
_c(Matrix3x4) |
|
_c(Matrix4x2) |
|
_c(Matrix4x3) |
|
/* LCOV_EXCL_STOP */ |
|
_ct(Pointer, const void*) |
|
_ct(MutablePointer, void*) |
|
_ct(String, Containers::StringView) |
|
_ct(TextureSwizzle, Trade::MaterialTextureSwizzle) |
|
#undef _c |
|
#undef _ct |
|
case Trade::MaterialAttributeType::Buffer: |
|
out << Containers::arrayCast<const char>(attribute.value<Containers::ArrayView<const void>>()); |
|
} |
|
#ifdef CORRADE_TARGET_GCC |
|
#pragma GCC diagnostic pop |
|
#endif |
|
|
|
if(state != AttributeState::Same) |
|
out << Debug::resetColor; |
|
} |
|
|
|
} |
|
|
|
void Comparator<DebugTools::CompareMaterial>::printMessage(const TestSuite::ComparisonStatusFlags, Debug& out, const Containers::StringView actual, const Containers::StringView expected) const { |
|
out << "Materials" << actual << "and" << expected; |
|
|
|
if(_state->materialState == MaterialState::DifferentLayers) { |
|
out << "have different layers."; |
|
} else if(_state->materialState == MaterialState::DifferentAttributes) { |
|
out << "have different attributes."; |
|
} else if(_state->materialState == MaterialState::DifferentAttributeTypes) { |
|
out << "have different attribute types."; |
|
} else if(_state->materialState == MaterialState::DifferentAttributeValues) { |
|
out << "have different attribute values."; |
|
} else if(_state->materialState == MaterialState::DifferentTypes) { |
|
out << "have different types."; |
|
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ |
|
|
|
out << Debug::color(Debug::Color::Green) << "Actual (+)" |
|
<< Debug::resetColor << "vs" << Debug::color(Debug::Color::Red) |
|
<< "expected (-)" << Debug::resetColor << Debug::nospace << ":"; |
|
|
|
/* Print the type, or both if they differ */ |
|
/** @todo Or maybe if it's MaterialState::DifferentType print just this and |
|
skip the attributes? Because everything else that's printed is the |
|
same... */ |
|
if(_state->expected->types()) { |
|
out << Debug::newline << " "; |
|
if(_state->expected->types() != _state->actual->types()) |
|
out << Debug::color(Debug::Color::Red) << "-" << Debug::nospace; |
|
else |
|
out << ""; |
|
if(_state->actual->types() && _state->expected->types() != _state->actual->types()) |
|
out << Debug::resetColor; |
|
out << "Types:"; |
|
if(_state->actual->types() && _state->expected->types() != _state->actual->types()) |
|
out << Debug::color(Debug::Color::Red); |
|
out << Debug::packed << _state->expected->types() << Debug::resetColor; |
|
} |
|
if(_state->actual->types() && _state->actual->types() != _state->expected->types()) { |
|
out << Debug::newline << " " << Debug::color(Debug::Color::Green) << "+" << Debug::nospace; |
|
if(_state->expected->types()) out << Debug::resetColor; |
|
out << "Types:"; |
|
if(_state->expected->types()) out << Debug::color(Debug::Color::Green); |
|
out << Debug::packed << _state->actual->types() << Debug::resetColor; |
|
} |
|
|
|
/* Print content of both materials, interleaved, with layers and attributes |
|
that differ marked with +/- */ |
|
for(UnsignedInt layer = 0, layerMax = _state->layerOffsets.size() - 1; layer != layerMax; ++layer) { |
|
/* Show layer header only if there's more than one and the base |
|
layer isn't empty */ |
|
if(_state->layerOffsets.size() != 2 || _state->layerOffsets[1] != 0) { |
|
out << Debug::newline << " "; |
|
if(layer >= _state->actual->layerCount()) |
|
out << Debug::color(Debug::Color::Red) << "-" << Debug::nospace; |
|
else if(layer >= _state->expected->layerCount()) |
|
out << Debug::color(Debug::Color::Green) << "+" << Debug::nospace; |
|
else |
|
out << ""; |
|
if(layer == 0) |
|
out << "Base layer:"; |
|
else |
|
out << "Layer" << layer << Debug::nospace << ":"; |
|
if(layer >= _state->actual->layerCount() || layer >= _state->expected->layerCount()) |
|
out << Debug::resetColor; |
|
} |
|
|
|
/* Print attribute values indented only if there's more than one |
|
layer */ |
|
for(UnsignedInt id = _state->layerOffsets[layer], idMax = _state->layerOffsets[layer + 1]; id != idMax; ++id) { |
|
const Containers::Triple<AttributeState, UnsignedInt, UnsignedInt> attribute = _state->attributes[id]; |
|
const AttributeState state =attribute.first(); |
|
if(state != AttributeState::OnlyInActual) |
|
printAttribute(out, _state->expected->attributeData(layer, attribute.third()), state, false); |
|
if(state != AttributeState::Same && state != AttributeState::OnlyInExpected) { |
|
printAttribute(out, _state->actual->attributeData(layer, attribute.second()), state, true); |
|
} |
|
} |
|
} |
|
} |
|
|
|
}}
|
|
|