Browse Source

{image,scene}converter: extract and test --info printing code.

It was quite a pile, and all of it was written just once, relying only
on hopefully-available model files that would hopefully touch most code
paths. Which means, extremely annoying to make changes in.

I extracted the code to a header that can be tested with a mocked-up
importer and without having to execute the utility itself, deduplicated
the image info printing code, fixed various inconsistencies (such as
data/field flags sometimes denoted with superfluous "flags:" and
sometimes not) and TODOs (such as 2D/3D skins, where there was no format
whatsoever that would have 2D skin support, so the code couldn't get
written).

Now it's finally possible to easily add the remaining missing features,
such as printing camera info.
pull/580/head
Vladimír Vondruš 4 years ago
parent
commit
cc29cbcc73
  1. 3
      src/Magnum/SceneTools/CMakeLists.txt
  2. 966
      src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h
  3. 26
      src/Magnum/SceneTools/Test/CMakeLists.txt
  4. 1131
      src/Magnum/SceneTools/Test/SceneConverterTest.cpp
  5. 12
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt
  6. 3
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt
  7. 8
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt
  8. 28
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt
  9. 21
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt
  10. 16
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt
  11. 5
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt
  12. 98
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt
  13. 25
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt
  14. 11
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt
  15. 10
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt
  16. 8
      src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt
  17. 26
      src/Magnum/SceneTools/Test/configure.h.cmake
  18. 941
      src/Magnum/SceneTools/sceneconverter.cpp
  19. 81
      src/Magnum/Trade/Implementation/converterUtilities.h
  20. 7
      src/Magnum/Trade/Test/CMakeLists.txt
  21. 192
      src/Magnum/Trade/Test/ImageConverterTest.cpp
  22. 23
      src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt
  23. 42
      src/Magnum/Trade/imageconverter.cpp

3
src/Magnum/SceneTools/CMakeLists.txt

@ -44,7 +44,8 @@ set(MagnumSceneTools_HEADERS
set(MagnumSceneTools_PRIVATE_HEADERS
Implementation/combine.h
Implementation/convertToSingleFunctionObjects.h)
Implementation/convertToSingleFunctionObjects.h
Implementation/sceneConverterUtilities.h)
## Objects shared between main and test library
#add_library(MagnumSceneToolsObjects OBJECT

966
src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h

@ -0,0 +1,966 @@
#ifndef Magnum_SceneTools_Implementation_sceneConverterUtilities_h
#define Magnum_SceneTools_Implementation_sceneConverterUtilities_h
/*
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 <cctype> /* std::isupper() */
#include <unordered_map> /* sceneFieldNames */
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Utility/Arguments.h>
#include "Magnum/Math/FunctionsBatch.h"
#include "Magnum/Trade/AnimationData.h"
#include "Magnum/Trade/LightData.h"
#include "Magnum/Trade/MaterialData.h"
#include "Magnum/Trade/MeshData.h"
#include "Magnum/Trade/SceneData.h"
#include "Magnum/Trade/SkinData.h"
#include "Magnum/Trade/TextureData.h"
#include "Magnum/Trade/Implementation/converterUtilities.h"
namespace Magnum { namespace SceneTools { namespace Implementation {
using namespace Containers::Literals;
/* Used only in executables where we don't want it to be exported -- in
particular magnum-sceneconverter and its tests */
namespace {
/** @todo const Array& doesn't work, minmax() would fail to match */
template<class T> Containers::String calculateBounds(Containers::Array<T>&& attribute) {
/** @todo clean up when Debug::toString() exists */
std::ostringstream out;
Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << Debug::packed << Math::minmax(attribute);
return out.str();
}
/* Named attribute index from a global index */
/** @todo some helper for this directly on the MeshData class? */
UnsignedInt namedAttributeId(const Trade::MeshData& mesh, UnsignedInt id) {
const Trade::MeshAttribute name = mesh.attributeName(id);
for(UnsignedInt i = 0; i != mesh.attributeCount(name); ++i)
if(mesh.attributeId(name, i) == id) return i;
CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
bool printInfo(const Debug::Flags useColor, const bool useColor24, const Utility::Arguments& args, Trade::AbstractImporter& importer, std::chrono::high_resolution_clock::duration& importTime) {
struct AnimationInfo {
UnsignedInt animation;
Trade::AnimationData data{{}, {}};
Containers::String name;
};
struct SkinInfo {
bool twoDimensions;
UnsignedInt skin;
UnsignedInt jointCount;
Containers::String name;
};
struct LightInfo {
UnsignedInt light;
Trade::LightData data{{}, {}, {}};
Containers::String name;
};
struct MaterialInfo {
UnsignedInt material;
Trade::MaterialData data{{}, {}};
Containers::String name;
};
struct TextureInfo {
UnsignedInt texture;
Trade::TextureData data{{}, {}, {}, {}, {}, {}};
Containers::String name;
};
struct MeshAttributeInfo {
std::size_t offset;
Int stride;
UnsignedInt arraySize;
Trade::MeshAttribute name;
Containers::String customName;
VertexFormat format;
Containers::String bounds;
};
struct MeshInfo {
UnsignedInt mesh, level;
MeshPrimitive primitive;
UnsignedInt indexCount, vertexCount;
std::size_t indexOffset;
Int indexStride;
Containers::String indexBounds;
MeshIndexType indexType;
Containers::Array<MeshAttributeInfo> attributes;
std::size_t indexDataSize, vertexDataSize;
Trade::DataFlags indexDataFlags, vertexDataFlags;
Containers::String name;
};
struct SceneFieldInfo {
Trade::SceneField name;
Trade::SceneFieldFlags flags;
Trade::SceneFieldType type;
UnsignedInt arraySize;
std::size_t size;
};
struct SceneInfo {
UnsignedInt scene;
Trade::SceneMappingType mappingType;
UnsignedLong mappingBound;
Containers::Array<SceneFieldInfo> fields;
std::size_t dataSize;
Trade::DataFlags dataFlags;
Containers::String name;
};
struct ObjectInfo {
UnsignedLong object;
/* A bitfield, assuming no more than 32 scenes */
/** @todo might be too little? */
UnsignedInt scenes;
Containers::Array<Containers::Pair<Trade::SceneField, UnsignedInt>> fields;
Containers::String name;
};
/* Parse everything first to avoid errors interleaved with output */
bool error = false;
/* Object properties */
Containers::Array<ObjectInfo> objectInfos;
if(args.isSet("info") || args.isSet("info-objects")) {
objectInfos = Containers::Array<ObjectInfo>{std::size_t(importer.objectCount())};
for(UnsignedLong i = 0; i != importer.objectCount(); ++i) {
objectInfos[i].object = i;
objectInfos[i].name = importer.objectName(i);
}
}
/* Scene properties, together with counting how much is each mesh / light /
material / skin / object referenced (which gets used only if both
--info-scenes and --info-{lights,materials,skins,objects} is passed and
the file has at least one scene). Texture reference count is calculated
when parsing materials. */
Containers::Array<SceneInfo> sceneInfos;
/* Only the very latest GCC seems to support enum classes as keys and I
can't be bothered to write a std::hash specialization, so just making
the key typeless */
std::unordered_map<UnsignedInt, Containers::String> sceneFieldNames;
Containers::Array<UnsignedInt> materialReferenceCount;
Containers::Array<UnsignedInt> lightReferenceCount;
Containers::Array<UnsignedInt> meshReferenceCount;
Containers::Array<UnsignedInt> skin2DReferenceCount;
Containers::Array<UnsignedInt> skin3DReferenceCount;
if((args.isSet("info") || args.isSet("info-scenes")) && importer.sceneCount()) {
materialReferenceCount = Containers::Array<UnsignedInt>{importer.materialCount()};
lightReferenceCount = Containers::Array<UnsignedInt>{importer.lightCount()};
meshReferenceCount = Containers::Array<UnsignedInt>{importer.meshCount()};
skin2DReferenceCount = Containers::Array<UnsignedInt>{importer.skin2DCount()};
skin3DReferenceCount = Containers::Array<UnsignedInt>{importer.skin3DCount()};
for(UnsignedInt i = 0; i != importer.sceneCount(); ++i) {
Containers::Optional<Trade::SceneData> scene = importer.scene(i);
if(!scene) {
error = true;
continue;
}
SceneInfo info{};
info.scene = i;
info.mappingType = scene->mappingType();
info.mappingBound = scene->mappingBound();
info.dataSize = scene->data().size();
info.dataFlags = scene->dataFlags();
info.name = importer.sceneName(i);
for(UnsignedInt j = 0; j != scene->fieldCount(); ++j) {
const Trade::SceneField name = scene->fieldName(j);
if(name == Trade::SceneField::Mesh) for(const Containers::Pair<UnsignedInt, Containers::Pair<UnsignedInt, Int>>& meshMaterial: scene->meshesMaterialsAsArray()) {
if(meshMaterial.second().first() < meshReferenceCount.size())
++meshReferenceCount[meshMaterial.second().first()];
if(UnsignedInt(meshMaterial.second().second()) < materialReferenceCount.size())
++materialReferenceCount[meshMaterial.second().second()];
}
if(name == Trade::SceneField::Skin) for(const Containers::Pair<UnsignedInt, UnsignedInt> skin: scene->skinsAsArray()) {
if(scene->is2D() && skin.second() < skin2DReferenceCount.size())
++skin2DReferenceCount[skin.second()];
if(scene->is3D() && skin.second() < skin3DReferenceCount.size())
++skin3DReferenceCount[skin.second()];
}
if(name == Trade::SceneField::Light) for(const Containers::Pair<UnsignedInt, UnsignedInt>& light: scene->lightsAsArray()) {
if(light.second() < lightReferenceCount.size())
++lightReferenceCount[light.second()];
}
arrayAppend(info.fields, InPlaceInit,
name,
scene->fieldFlags(j),
scene->fieldType(j),
scene->fieldArraySize(j),
scene->fieldSize(j));
/* If the field has a custom name, save it into the map. Not
putting it into the fields array as the map is reused by
object info as well. */
if(Trade::isSceneFieldCustom(name)) {
/* Fetch the name only if it's not already there */
const auto inserted = sceneFieldNames.emplace(sceneFieldCustom(name), Containers::String{});
if(inserted.second)
inserted.first->second = importer.sceneFieldName(name);
}
if(objectInfos) for(const UnsignedInt object: scene->mappingAsArray(j)) {
if(object >= objectInfos.size()) continue;
objectInfos[object].object = object;
objectInfos[object].scenes |= 1 << i;
/* If the field is repeated, increase the count instead */
if(!objectInfos[object].fields.isEmpty() && objectInfos[object].fields.back().first() == name)
++objectInfos[object].fields.back().second();
else
arrayAppend(objectInfos[object].fields, InPlaceInit, name, 1u);
}
}
arrayAppend(sceneInfos, std::move(info));
}
}
/* Animation properties */
Containers::Array<AnimationInfo> animationInfos;
if(args.isSet("info") || args.isSet("info-animations")) for(UnsignedInt i = 0; i != importer.animationCount(); ++i) {
Containers::Optional<Trade::AnimationData> animation;
{
Trade::Implementation::Duration d{importTime};
if(!(animation = importer.animation(i))) {
error = true;
continue;
}
}
AnimationInfo info{};
info.animation = i;
info.name = importer.animationName(i);
info.data = *std::move(animation);
arrayAppend(animationInfos, std::move(info));
}
/* Skin properties */
Containers::Array<SkinInfo> skinInfos;
if(args.isSet("info") || args.isSet("info-skins")) {
for(UnsignedInt i = 0; i != importer.skin2DCount(); ++i) {
Containers::Optional<Trade::SkinData2D> skin;
{
Trade::Implementation::Duration d{importTime};
if(!(skin = importer.skin2D(i))) {
error = true;
continue;
}
}
SkinInfo info{};
info.twoDimensions = true;
info.skin = i;
info.name = importer.skin2DName(i);
info.jointCount = skin->joints().size();
arrayAppend(skinInfos, std::move(info));
}
for(UnsignedInt i = 0; i != importer.skin3DCount(); ++i) {
Containers::Optional<Trade::SkinData3D> skin;
{
Trade::Implementation::Duration d{importTime};
if(!(skin = importer.skin3D(i))) {
error = true;
continue;
}
}
SkinInfo info{};
info.twoDimensions = false;
info.skin = i;
info.name = importer.skin3DName(i);
info.jointCount = skin->joints().size();
arrayAppend(skinInfos, std::move(info));
}
}
/* Light properties */
Containers::Array<LightInfo> lightInfos;
if(args.isSet("info") || args.isSet("info-lights")) for(UnsignedInt i = 0; i != importer.lightCount(); ++i) {
Containers::Optional<Trade::LightData> light;
{
Trade::Implementation::Duration d{importTime};
if(!(light = importer.light(i))) {
error = true;
continue;
}
}
LightInfo info{};
info.light = i;
info.name = importer.lightName(i);
info.data = *std::move(light);
arrayAppend(lightInfos, std::move(info));
}
/* Material properties, together with how much is each texture shared
(which gets used only if both --info-materials and --info-textures is
passed and the file has at least one material). */
Containers::Array<MaterialInfo> materialInfos;
Containers::Array<UnsignedInt> textureReferenceCount;
if((args.isSet("info") || args.isSet("info-materials")) && importer.materialCount()) {
textureReferenceCount = Containers::Array<UnsignedInt>{importer.textureCount()};
for(UnsignedInt i = 0; i != importer.materialCount(); ++i) {
Containers::Optional<Trade::MaterialData> material;
{
Trade::Implementation::Duration d{importTime};
if(!(material = importer.material(i))) {
error = true;
continue;
}
}
/* Calculate texture reference count for all properties that look
like a texture */
for(UnsignedInt j = 0; j != material->layerCount(); ++j) {
for(UnsignedInt k = 0; k != material->attributeCount(j); ++k) {
if(material->attributeType(j, k) != Trade::MaterialAttributeType::UnsignedInt || !material->attributeName(j, k).hasSuffix("Texture"_s))
continue;
const UnsignedInt texture = material->attribute<UnsignedInt>(j, k);
/** @todo once StridedBitArrayView2D exists, fix this to
count each material only once by having one bit for
every material and texture */
if(texture < textureReferenceCount.size())
++textureReferenceCount[texture];
}
}
MaterialInfo info{};
info.material = i;
info.name = importer.materialName(i);
info.data = *std::move(material);
arrayAppend(materialInfos, std::move(info));
}
}
/* Mesh properties */
Containers::Array<MeshInfo> meshInfos;
if(args.isSet("info") || args.isSet("info-meshes")) for(UnsignedInt i = 0; i != importer.meshCount(); ++i) {
for(UnsignedInt j = 0; j != importer.meshLevelCount(i); ++j) {
Containers::Optional<Trade::MeshData> mesh;
{
Trade::Implementation::Duration d{importTime};
if(!(mesh = importer.mesh(i, j))) {
error = true;
continue;
}
}
MeshInfo info{};
info.mesh = i;
info.level = j;
info.primitive = mesh->primitive();
info.vertexCount = mesh->vertexCount();
info.vertexDataSize = mesh->vertexData().size();
info.vertexDataFlags = mesh->vertexDataFlags();
if(!j) {
info.name = importer.meshName(i);
}
if(mesh->isIndexed()) {
info.indexCount = mesh->indexCount();
info.indexType = mesh->indexType();
info.indexOffset = mesh->indexOffset();
info.indexStride = mesh->indexStride();
info.indexDataSize = mesh->indexData().size();
info.indexDataFlags = mesh->indexDataFlags();
if(args.isSet("bounds"))
info.indexBounds = calculateBounds(mesh->indicesAsArray());
}
for(UnsignedInt k = 0; k != mesh->attributeCount(); ++k) {
const Trade::MeshAttribute name = mesh->attributeName(k);
/* Calculate bounds, if requested, if this is not an
implementation-specific format and if it's not a custom
attribute */
Containers::String bounds;
if(args.isSet("bounds") && !isVertexFormatImplementationSpecific(mesh->attributeFormat(k))) switch(name) {
case Trade::MeshAttribute::Position:
bounds = calculateBounds(mesh->positions3DAsArray(namedAttributeId(*mesh, k)));
break;
case Trade::MeshAttribute::Tangent:
bounds = calculateBounds(mesh->tangentsAsArray(namedAttributeId(*mesh, k)));
break;
case Trade::MeshAttribute::Bitangent:
bounds = calculateBounds(mesh->bitangentsAsArray(namedAttributeId(*mesh, k)));
break;
case Trade::MeshAttribute::Normal:
bounds = calculateBounds(mesh->normalsAsArray(namedAttributeId(*mesh, k)));
break;
case Trade::MeshAttribute::TextureCoordinates:
bounds = calculateBounds(mesh->textureCoordinates2DAsArray(namedAttributeId(*mesh, k)));
break;
case Trade::MeshAttribute::Color:
bounds = calculateBounds(mesh->colorsAsArray(namedAttributeId(*mesh, k)));
break;
case Trade::MeshAttribute::ObjectId:
bounds = calculateBounds(mesh->objectIdsAsArray(namedAttributeId(*mesh, k)));
break;
}
arrayAppend(info.attributes, InPlaceInit,
mesh->attributeOffset(k),
mesh->attributeStride(k),
mesh->attributeArraySize(k),
name, Trade::isMeshAttributeCustom(name) ?
importer.meshAttributeName(name) : "",
mesh->attributeFormat(k),
bounds);
}
arrayAppend(meshInfos, std::move(info));
}
}
/* Texture properties, together with how much is each image shared (which
gets used only if both --info-textures and --info-images is passed and
the file has at least one texture). */
Containers::Array<TextureInfo> textureInfos;
Containers::Array<UnsignedInt> image1DReferenceCount;
Containers::Array<UnsignedInt> image2DReferenceCount;
Containers::Array<UnsignedInt> image3DReferenceCount;
if((args.isSet("info") || args.isSet("info-textures")) && importer.textureCount()) {
image1DReferenceCount = Containers::Array<UnsignedInt>{importer.image1DCount()};
image2DReferenceCount = Containers::Array<UnsignedInt>{importer.image2DCount()};
image3DReferenceCount = Containers::Array<UnsignedInt>{importer.image3DCount()};
for(UnsignedInt i = 0; i != importer.textureCount(); ++i) {
Containers::Optional<Trade::TextureData> texture;
{
Trade::Implementation::Duration d{importTime};
if(!(texture = importer.texture(i))) {
error = true;
continue;
}
}
switch(texture->type()) {
case Trade::TextureType::Texture1D:
if(texture->image() < image1DReferenceCount.size())
++image1DReferenceCount[texture->image()];
break;
case Trade::TextureType::Texture1DArray:
case Trade::TextureType::Texture2D:
if(texture->image() < image2DReferenceCount.size())
++image2DReferenceCount[texture->image()];
break;
case Trade::TextureType::CubeMap:
case Trade::TextureType::CubeMapArray:
case Trade::TextureType::Texture2DArray:
case Trade::TextureType::Texture3D:
if(texture->image() < image3DReferenceCount.size())
++image3DReferenceCount[texture->image()];
break;
}
TextureInfo info{};
info.texture = i;
info.name = importer.textureName(i);
info.data = *std::move(texture);
arrayAppend(textureInfos, std::move(info));
}
}
Containers::Array<Trade::Implementation::ImageInfo> imageInfos;
if(args.isSet("info") || args.isSet("info-images")) {
imageInfos = Trade::Implementation::imageInfo(importer, error, importTime);
}
std::size_t totalSceneDataSize = 0;
for(const SceneInfo& info: sceneInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Scene" << info.scene << Debug::nospace << ":" << Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor;
d << Debug::newline;
d << " Bound:" << info.mappingBound << "objects"
<< Debug::color(Debug::Color::Blue) << "@" << Debug::packed
<< Debug::color(Debug::Color::Cyan) << info.mappingType
<< Debug::resetColor << "(" << Debug::nospace
<< Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB";
if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable))
d << Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Green) << info.dataFlags
<< Debug::resetColor;
d << Debug::nospace << ")";
d << Debug::newline << " Fields:";
for(const SceneFieldInfo& field: info.fields) {
d << Debug::newline << " "
<< Debug::boldColor(Debug::Color::Default);
if(Trade::isSceneFieldCustom(field.name)) {
d << "Custom(" << Debug::nospace
<< Trade::sceneFieldCustom(field.name)
<< Debug::nospace << ":" << Debug::nospace
<< Debug::color(Debug::Color::Yellow)
<< sceneFieldNames[sceneFieldCustom(field.name)]
<< Debug::nospace
<< Debug::boldColor(Debug::Color::Default) << ")";
} else d << Debug::packed << field.name;
d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << field.type;
if(field.arraySize)
d << Debug::nospace << Utility::format("[{}]", field.arraySize);
d << Debug::resetColor;
if(field.flags) d << Debug::nospace << ","
<< Debug::packed << Debug::color(Debug::Color::Green)
<< field.flags << Debug::resetColor;
d << Debug::nospace << "," << field.size << "entries";
}
totalSceneDataSize += info.dataSize;
}
if(!sceneInfos.isEmpty())
Debug{} << "Total scene data size:" << Utility::format("{:.1f}", totalSceneDataSize/1024.0f) << "kB";
for(const ObjectInfo& info: objectInfos) {
/* Objects without a name and not referenced by any scenes are useless,
ignore */
if(!info.name && !info.scenes) continue;
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Object" << info.object << Debug::resetColor;
if(sceneInfos) {
const UnsignedInt count = Math::popcount(info.scenes);
if(!count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << count << "scenes)";
if(!count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow)
<< info.name << Debug::resetColor;
if(info.scenes) {
d << Debug::newline << " Fields:";
for(std::size_t i = 0; i != info.fields.size(); ++i) {
if(i) d << Debug::nospace << ",";
const Containers::Pair<Trade::SceneField, UnsignedInt> nameCount = info.fields[i];
d << Debug::color(Debug::Color::Cyan);
if(Trade::isSceneFieldCustom(nameCount.first())) {
d << "Custom(" << Debug::nospace
<< Trade::sceneFieldCustom(nameCount.first())
<< Debug::nospace << ":" << Debug::nospace
<< Debug::color(Debug::Color::Yellow)
<< sceneFieldNames[sceneFieldCustom(nameCount.first())]
<< Debug::nospace
<< Debug::color(Debug::Color::Cyan) << ")";
} else d << Debug::packed << nameCount.first();
if(nameCount.second() != 1)
d << Debug::nospace << Utility::format("[{}]", nameCount.second());
d << Debug::resetColor;
}
}
}
std::size_t totalAnimationDataSize = 0;
for(const AnimationInfo& info: animationInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Animation" << info.animation << Debug::nospace << ":" << Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor;
d << Debug::newline << " Duration: {" << Debug::nospace
/** @todo have a nice packed printing for Range instead */
<< info.data.duration().min() << Debug::nospace << ","
<< info.data.duration().max() << Debug::nospace << "} ("
<< Debug::nospace << Utility::format("{:.1f}", info.data.data().size()/1024.0f) << "kB";
if(info.data.dataFlags() != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable))
d << Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Green)
<< info.data.dataFlags() << Debug::resetColor;
d << Debug::nospace << ")";
for(UnsignedInt i = 0; i != info.data.trackCount(); ++i) {
d << Debug::newline << " Track" << i << Debug::nospace << ":"
<< Debug::packed << Debug::boldColor(Debug::Color::Default)
<< info.data.trackTargetType(i)
<< Debug::color(Debug::Color::Blue) << "@"
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.trackType(i) << Debug::resetColor;
if(info.data.trackType(i) != info.data.trackResultType(i))
d << Debug::color(Debug::Color::Blue) << "->"
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.trackResultType(i) << Debug::resetColor;
d << Debug::nospace << "," << info.data.track(i).size()
<< "keyframes";
if(info.data.track(i).duration() != info.data.duration())
d << Debug::newline << " Duration: {" << Debug::nospace
/** @todo have a nice packed printing for Range instead */
<< info.data.track(i).duration().min() << Debug::nospace
<< "," << info.data.track(i).duration().max()
<< Debug::nospace << "}";
d << Debug::newline
<< " Interpolation:"
<< Debug::packed << Debug::color(info.data.track(i).interpolation() == Animation::Interpolation::Custom ? Debug::Color::Yellow : Debug::Color::Cyan)
<< info.data.track(i).interpolation() << Debug::resetColor
<< Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Cyan)
<< info.data.track(i).before() << Debug::resetColor
<< Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Cyan)
<< info.data.track(i).after() << Debug::resetColor;
/** @todo might be useful to show bounds here as well, though not
so much for things like complex numbers or quats */
}
totalAnimationDataSize += info.data.data().size();
}
if(!animationInfos.isEmpty())
Debug{} << "Total animation data size:" << Utility::format("{:.1f}", totalAnimationDataSize/1024.0f) << "kB";
for(const SkinInfo& info: skinInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << (info.twoDimensions ? "2D skin" : "3D skin") << info.skin
<< Debug::resetColor;
/* Print reference count only if there actually are scenes and they
were parsed, otherwise this information is useless */
if((info.twoDimensions && skin2DReferenceCount) ||
(!info.twoDimensions && skin3DReferenceCount))
{
const UnsignedInt count = info.twoDimensions ? skin2DReferenceCount[info.skin] : skin3DReferenceCount[info.skin];
if(!count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << count << "objects)";
if(!count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow)
<< info.name << Debug::resetColor;
d << Debug::newline << " " << info.jointCount << "joints";
}
for(const LightInfo& info: lightInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Light" << info.light << Debug::resetColor;
/* Print reference count only if there actually are scenes and they
were parsed, otherwise this information is useless */
if(lightReferenceCount) {
const UnsignedInt count = lightReferenceCount[info.light];
if(!count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << count << "objects)";
if(!count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow)
<< info.name << Debug::resetColor;
d << Debug::newline << " Type:" << Debug::packed
<< Debug::color(Debug::Color::Cyan)
<< info.data.type() << Debug::resetColor;
if(info.data.type() == Trade::LightData::Type::Spot)
d << Debug::nospace << "," << Debug::packed
<< Deg(info.data.innerConeAngle()) << Debug::nospace
<< "° -" << Debug::packed << Deg(info.data.outerConeAngle())
<< Debug::nospace << "°";
d << Debug::newline << " Color:";
if(useColor24) d << Debug::color
<< Math::pack<Color3ub>(info.data.color());
d << Debug::packed << info.data.color();
if(!Math::equal(info.data.intensity(), 1.0f))
d << "*" << info.data.intensity();
if(info.data.type() != Trade::LightData::Type::Ambient &&
info.data.type() != Trade::LightData::Type::Directional)
d << Debug::newline << " Attenuation:" << Debug::packed
<< info.data.attenuation();
if(info.data.range() != Constants::inf())
d << Debug::newline << " Range:" << Debug::packed
<< info.data.range();
}
for(const MaterialInfo& info: materialInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Material" << info.material << Debug::resetColor;
/* Print reference count only if there actually are scenes and they
were parsed, otherwise this information is useless */
if(materialReferenceCount) {
const UnsignedInt count = materialReferenceCount[info.material];
if(!count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << count << "objects)";
if(!count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor;
d << Debug::newline << " Type:" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.data.types() << Debug::resetColor;
for(UnsignedInt i = 0; i != info.data.layerCount(); ++i) {
/* Print extra layers with extra indent */
const char* indent;
if(info.data.layerCount() != 1 && i != 0) {
d << Debug::newline << " Layer" << i << Debug::nospace << ":";
if(!info.data.layerName(i).isEmpty()) {
if(std::isupper(info.data.layerName(i)[0]))
d << Debug::boldColor(Debug::Color::Default);
else
d << Debug::color(Debug::Color::Yellow);
d << info.data.layerName(i) << Debug::resetColor;
}
indent = " ";
} else {
d << Debug::newline << " Base layer:";
indent = " ";
}
for(UnsignedInt j = 0; j != info.data.attributeCount(i); ++j) {
/* Ignore layer name (which is always first) unless it's in the
base material, in which case we print it as it wouldn't
otherwise be shown anywhere */
if(i && !j && info.data.attributeName(i, j) == " LayerName")
continue;
d << Debug::newline << indent;
if(std::isupper(info.data.attributeName(i, j)[0]))
d << Debug::boldColor(Debug::Color::Default);
else
d << Debug::color(Debug::Color::Yellow);
d << info.data.attributeName(i, j) << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.attributeType(i, j) << Debug::resetColor << Debug::nospace
<< ":";
switch(info.data.attributeType(i, j)) {
#define _c(type) case Trade::MaterialAttributeType::type: \
d << Debug::packed << info.data.attribute<type>(i, j); \
break;
/* LCOV_EXCL_START */
_c(Float)
_c(Deg)
_c(Rad)
_c(UnsignedInt)
_c(Int)
_c(UnsignedLong)
_c(Long)
_c(Vector2)
_c(Vector2ui)
_c(Vector2i)
/* Vector3 handled below */
_c(Vector3ui)
_c(Vector3i)
/* Vector4 handled below */
_c(Vector4ui)
_c(Vector4i)
_c(Matrix2x2)
_c(Matrix2x3)
_c(Matrix2x4)
_c(Matrix3x2)
_c(Matrix3x3)
_c(Matrix3x4)
_c(Matrix4x2)
_c(Matrix4x3)
/* LCOV_EXCL_STOP */
#undef _c
case Trade::MaterialAttributeType::Bool:
d << info.data.attribute<bool>(i, j);
break;
case Trade::MaterialAttributeType::Vector3:
/** @todo hasSuffix() might be more robust against
false positives, but KHR_materials_specular in glTF
uses ColorFactor :/ */
if(useColor24 && info.data.attributeName(i, j).contains("Color"_s))
d << Debug::color << Math::pack<Color3ub>(info.data.attribute<Vector3>(i, j));
d << Debug::packed << info.data.attribute<Vector3>(i, j);
break;
case Trade::MaterialAttributeType::Vector4:
/** @todo hasSuffix() might be more robust against
false positives, but KHR_materials_specular in glTF
uses ColorFactor :/ */
if(useColor24 && info.data.attributeName(i, j).contains("Color"_s))
d << Debug::color << Math::pack<Color3ub>(info.data.attribute<Vector4>(i, j).rgb());
d << Debug::packed << info.data.attribute<Vector4>(i, j);
break;
case Trade::MaterialAttributeType::Pointer:
d << info.data.attribute<const void*>(i, j);
break;
case Trade::MaterialAttributeType::MutablePointer:
d << info.data.attribute<void*>(i, j);
break;
case Trade::MaterialAttributeType::String:
d << info.data.attribute<Containers::StringView>(i, j);
break;
case Trade::MaterialAttributeType::TextureSwizzle:
d << Debug::packed << info.data.attribute<Trade::MaterialTextureSwizzle>(i, j);
break;
}
}
}
}
std::size_t totalMeshDataSize = 0;
for(const MeshInfo& info: meshInfos) {
Debug d{useColor};
if(info.level == 0) {
d << Debug::boldColor(Debug::Color::Default) << "Mesh" << info.mesh << Debug::resetColor;
/* Print reference count only if there actually are scenes and they
were parsed, otherwise this information is useless */
if(meshReferenceCount) {
const UnsignedInt count = meshReferenceCount[info.mesh];
if(!count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << count << "objects)";
if(!count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor;
d << Debug::newline;
}
d << " Level" << info.level << Debug::nospace << ":"
<< info.vertexCount << "vertices" << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.primitive << Debug::resetColor << "(" << Debug::nospace
<< Utility::format("{:.1f}", info.vertexDataSize/1024.0f)
<< "kB";
if(info.vertexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable))
d << Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Green)
<< info.vertexDataFlags << Debug::resetColor;
d << Debug::nospace << ")";
for(const MeshAttributeInfo& attribute: info.attributes) {
d << Debug::newline << " "
<< Debug::boldColor(Debug::Color::Default);
if(Trade::isMeshAttributeCustom(attribute.name)) {
d << "Custom(" << Debug::nospace
<< Trade::meshAttributeCustom(attribute.name)
<< Debug::nospace << ":" << Debug::nospace
<< Debug::color(Debug::Color::Yellow)
<< attribute.customName << Debug::nospace
<< Debug::boldColor(Debug::Color::Default) << ")";
} else d << Debug::packed << attribute.name;
d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << attribute.format;
if(attribute.arraySize)
d << Debug::nospace << Utility::format("[{}]", attribute.arraySize);
d << Debug::resetColor;
d << Debug::nospace << ", offset" << attribute.offset;
d << Debug::nospace << ", stride"
<< attribute.stride;
if(attribute.bounds)
d << Debug::newline << " Bounds:" << attribute.bounds;
}
if(info.indexType != MeshIndexType{}) {
d << Debug::newline << " " << info.indexCount << "indices" << Debug::color(Debug::Color::Blue) << "@"
<< Debug::packed << Debug::color(Debug::Color::Cyan) << info.indexType << Debug::resetColor << Debug::nospace << ", offset" << info.indexOffset << Debug::nospace << ", stride" << info.indexStride << "(" << Debug::nospace
<< Utility::format("{:.1f}", info.indexDataSize/1024.0f)
<< "kB";
if(info.indexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable))
d << Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Green) << info.indexDataFlags << Debug::resetColor;
d << Debug::nospace << ")";
if(info.indexBounds)
d << Debug::newline << " Bounds:" << info.indexBounds;
}
totalMeshDataSize += info.vertexDataSize + info.indexDataSize;
}
if(!meshInfos.isEmpty())
Debug{} << "Total mesh data size:" << Utility::format("{:.1f}", totalMeshDataSize/1024.0f) << "kB";
for(const TextureInfo& info: textureInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Texture" << info.texture << Debug::resetColor;
/* Print reference count only if there actually are materials and they
were parsed, otherwise this information is useless */
if(textureReferenceCount) {
const UnsignedInt count = textureReferenceCount[info.texture];
if(!count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << count << "material attributes)";
if(!count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow)
<< info.name << Debug::resetColor;
d << Debug::newline;
d << " Type:"
<< Debug::packed
<< Debug::color(Debug::Color::Cyan) << info.data.type()
<< Debug::resetColor << Debug::nospace << ", image"
<< info.data.image();
d << Debug::newline << " Minification, mipmap and magnification:"
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.minificationFilter() << Debug::nospace << ","
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.mipmapFilter() << Debug::nospace << ","
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.magnificationFilter() << Debug::resetColor;
/** @todo show only the dimensions that matter for a particular texture
type */
d << Debug::newline << " Wrapping:" << Debug::resetColor << "{" << Debug::nospace
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.wrapping()[0] << Debug::resetColor
<< Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1]
<< Debug::resetColor << Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1]
<< Debug::resetColor << Debug::nospace << "}";
}
Trade::Implementation::printImageInfo(useColor, imageInfos, image1DReferenceCount, image2DReferenceCount, image3DReferenceCount);
return error;
}
}
}}}
#endif

26
src/Magnum/SceneTools/Test/CMakeLists.txt

@ -27,7 +27,33 @@
# property that would have to be set on each target separately.
set(CMAKE_FOLDER "Magnum/SceneTools/Test")
if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID)
set(SCENETOOLS_TEST_DIR ".")
else()
set(SCENETOOLS_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR})
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake
${CMAKE_CURRENT_BINARY_DIR}/configure.h)
corrade_add_test(SceneToolsCombineTest CombineTest.cpp LIBRARIES MagnumTrade)
corrade_add_test(SceneToolsConvertToSingleFun___Test ConvertToSingleFunctionObjectsTest.cpp LIBRARIES MagnumTrade)
corrade_add_test(SceneToolsFlattenMeshHierarchyTest FlattenMeshHierarchyTest.cpp LIBRARIES MagnumSceneToolsTestLib)
corrade_add_test(SceneToolsOrderClusterParentsTest OrderClusterParentsTest.cpp LIBRARIES MagnumSceneToolsTestLib)
corrade_add_test(SceneToolsSceneConverterTest SceneConverterTest.cpp
LIBRARIES MagnumSceneTools
FILES
SceneConverterTestFiles/info-animations.txt
SceneConverterTestFiles/info-images.txt
SceneConverterTestFiles/info-lights.txt
SceneConverterTestFiles/info-materials.txt
SceneConverterTestFiles/info-meshes-bounds.txt
SceneConverterTestFiles/info-meshes.txt
SceneConverterTestFiles/info-objects.txt
SceneConverterTestFiles/info-references.txt
SceneConverterTestFiles/info-scenes-objects.txt
SceneConverterTestFiles/info-scenes.txt
SceneConverterTestFiles/info-skins.txt
SceneConverterTestFiles/info-textures.txt)
target_include_directories(SceneToolsSceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

1131
src/Magnum/SceneTools/Test/SceneConverterTest.cpp

File diff suppressed because it is too large Load Diff

12
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt

@ -0,0 +1,12 @@
Animation 0:
Duration: {0.5, 1.25} (0.1 kB)
Track 0: Translation2D @ Vector2, 3 keyframes
Interpolation: Linear, DefaultConstructed, Constant
Track 1: Rotation2D @ CubicHermite2D -> Vector2, 3 keyframes
Interpolation: Constant, Extrapolated, Extrapolated
Animation 1: Custom track duration and interpolator function
Duration: {0.1, 1.3} (0.1 kB, ExternallyOwned)
Track 0: Scaling3D @ Vector3, 5 keyframes
Duration: {0.75, 1.25}
Interpolation: Custom, DefaultConstructed, Constant
Total animation data size: 0.2 kB

3
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt

@ -0,0 +1,3 @@
1D image 0:
Level 0: {1024} @ R32F (4.0 kB)
Total image data size: 4.0 kB

8
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt

@ -0,0 +1,8 @@
Light 0:
Type: Spot, 55° - 85°
Color: {0.203922, 0.341176, 1} * 15
Attenuation: {1.2, 0.3, 0.04}
Range: 100
Light 1: Directional light with always-implicit attenuation and range
Type: Directional
Color: {1, 0.341176, 0.203922} * 5

28
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt

@ -0,0 +1,28 @@
Material 0:
Type: PbrMetallicRoughness
Base layer:
BaseColor @ Vector4: {0.231373, 0.823529, 0.403922, 0.6}
DoubleSided @ Bool: true
EmissiveColor @ Vector3: {0.054902, 0.619608, 0.792157}
RoughnessTexture @ UnsignedInt: 67
RoughnessTextureMatrix @ Matrix3x3: {1, 0, 0.25,
0, 1, 0.75,
0, 0, 1}
RoughnessTextureSwizzle @ TextureSwizzle: B
deadBeef @ Pointer: 0xdeadbeef
notAColour3 @ Vector3: {0.2, 0.3, 0.4}
notAColour4 @ Vector4: {0.1, 0.2, 0.3, 0.4}
reflectionAngle @ Deg: 35
undeadBeef @ MutablePointer: 0xbeefbeef
Material 1: Lots o' laierz
Type: Phong|PbrClearCoat
Base layer:
DiffuseColor @ Vector4: {0.780392, 0.811765, 0.184314, 0.6}
Layer 1: ClearCoat
LayerFactor @ Float: 0.5
LayerFactorTexture @ UnsignedInt: 3
Layer 2: anEmptyLayer
Layer 3:
LayerFactor @ Float: 0.25
LayerFactorTexture @ UnsignedInt: 2
yes @ String: a string

21
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt

@ -0,0 +1,21 @@
Mesh 0:
Level 0: 2 vertices @ Lines (0.2 kB, {})
Position @ Vector3, offset 0, stride 12
Bounds: ({0.1, -0.1, -0.2}, {0.2, 0, 0.2})
Tangent @ Vector3, offset 24, stride 12
Bounds: ({0.2, -0.2, 0.2}, {0.3, 0.8, 0.8})
Bitangent @ Vector3, offset 48, stride 12
Bounds: ({0.3, 0.2, 0}, {0.4, 0.9, 1})
ObjectId @ UnsignedShort, offset 72, stride 2
Bounds: (12, 155)
Normal @ Vector3, offset 76, stride 12
Bounds: ({0, 0, 0}, {1, 1, 1})
TextureCoordinates @ Vector2, offset 100, stride 8
Bounds: ({0.5, 0.5}, {1.5, 0.5})
Color @ Vector4, offset 116, stride 16
Bounds: ({0.6, 0.2, 0.2, 0}, {1, 0.4, 0.4, 0.2})
ObjectId @ UnsignedInt, offset 148, stride 4
Bounds: (15, 337)
3 indices @ UnsignedByte, offset 0, stride 1 (0.0 kB, {})
Bounds: (3, 176)
Total mesh data size: 0.2 kB

16
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt

@ -0,0 +1,16 @@
Mesh 0:
Level 0: 50 vertices @ Points (0.6 kB, ExternallyOwned|Mutable)
Position @ Vector3, offset 0, stride 12
70 indices @ UnsignedShort, offset 0, stride 2 (0.1 kB, ExternallyOwned)
Mesh 1: LODs? No, meshets.
Level 0: 250 vertices @ Triangles (6.8 kB)
Position @ Vector3, offset 0, stride 12
Tangent @ Vector4, offset 3000, stride 16
Level 1: 135 vertices @ Meshlets (83.8 kB)
Custom(25:vertices) @ UnsignedInt[64], offset 0, stride 256
Custom(26:triangles) @ Vector3ub[126], offset 34560, stride 378
Custom(37:) @ UnsignedByte, offset 85590, stride 1
Custom(116:vertexCount) @ UnsignedByte, offset 85725, stride 1
Mesh 2:
Level 0: 15 vertices @ Instances (0.0 kB)
Total mesh data size: 91.4 kB

5
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt

@ -0,0 +1,5 @@
Object 0: Parent-less mesh
Object 2: Two meshes, shared among two scenes
Object 4: Two custom arrays
Object 6: Only in the second scene, but no fields, thus same as unreferenced
Object 8: Not in any scene

98
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt

@ -0,0 +1,98 @@
Scene 0:
Bound: 2 objects @ UnsignedInt (0.1 kB, {})
Fields:
Transformation @ Matrix4x4, 0 entries
Mesh @ UnsignedInt, 4 entries
MeshMaterial @ Int, 4 entries
Light @ UnsignedInt, 4 entries
Skin @ UnsignedInt, 4 entries
Scene 1:
Bound: 4 objects @ UnsignedInt (0.0 kB, {})
Fields:
Transformation @ Matrix3x3, 0 entries
Mesh @ UnsignedInt, 3 entries
Skin @ UnsignedInt, 3 entries
Total scene data size: 0.1 kB
Object 0 (referenced by 1 scenes):
Fields: Mesh, MeshMaterial, Light, Skin
Object 1 (referenced by 2 scenes):
Fields: Mesh[2], MeshMaterial[2], Light[2], Skin[2], Mesh, Skin
Object 2 (referenced by 0 scenes): Not referenced
Object 3 (referenced by 1 scenes):
Fields: Mesh, Skin
2D skin 0 (referenced by 1 objects):
2 joints
2D skin 1 (referenced by 1 objects):
3 joints
2D skin 2 (referenced by 0 objects): Not referenced
1 joints
3D skin 0 (referenced by 0 objects): Not referenced
2 joints
3D skin 1 (referenced by 2 objects):
1 joints
3D skin 2 (referenced by 1 objects):
3 joints
Light 0 (referenced by 2 objects):
Type: Directional
Color: {0.341176, 1, 0.203922} * 5
Light 1 (referenced by 0 objects): Not referenced
Type: Ambient
Color: {1, 0.341176, 0.203922} * 0.1
Light 2 (referenced by 1 objects):
Type: Directional
Color: {0.203922, 0.341176, 1}
Material 0 (referenced by 2 objects):
Type: {}
Base layer:
BaseColorTexture @ UnsignedInt: 2
DiffuseTexture @ UnsignedInt: 2
Material 1 (referenced by 1 objects):
Type: {}
Base layer:
EmissiveTexture @ UnsignedInt: 4
NormalTexture @ UnsignedInt: 17
lookupTexture @ UnsignedInt: 0
volumeTexture @ UnsignedInt: 3
Material 2 (referenced by 0 objects): Not referenced
Type: {}
Base layer:
Mesh 0 (referenced by 2 objects):
Level 0: 5 vertices @ Points (0.0 kB)
Mesh 1 (referenced by 0 objects): Not referenced
Level 0: 4 vertices @ Lines (0.0 kB)
Mesh 2 (referenced by 3 objects):
Level 0: 4 vertices @ TriangleFan (0.0 kB)
Total mesh data size: 0.0 kB
Texture 0 (referenced by 1 material attributes):
Type: Texture1D, image 1
Minification, mipmap and magnification: Nearest, Nearest, Linear
Wrapping: {Repeat, Repeat, Repeat}
Texture 1 (referenced by 0 material attributes): Not referenced
Type: Texture1DArray, image 225
Minification, mipmap and magnification: Nearest, Nearest, Linear
Wrapping: {Repeat, Repeat, Repeat}
Texture 2 (referenced by 2 material attributes):
Type: Texture2D, image 0
Minification, mipmap and magnification: Nearest, Nearest, Linear
Wrapping: {Repeat, Repeat, Repeat}
Texture 3 (referenced by 1 material attributes):
Type: Texture3D, image 1
Minification, mipmap and magnification: Nearest, Nearest, Linear
Wrapping: {Repeat, Repeat, Repeat}
Texture 4 (referenced by 1 material attributes):
Type: Texture2D, image 0
Minification, mipmap and magnification: Nearest, Nearest, Linear
Wrapping: {Repeat, Repeat, Repeat}
1D image 0 (referenced by 0 textures): Not referenced
Level 0: {1} @ RGBA8I (0.0 kB)
1D image 1 (referenced by 1 textures):
Level 0: {4} @ R8I (0.0 kB)
2D image 0 (referenced by 2 textures):
Level 0: {1, 2} @ RGBA8I (0.0 kB)
2D image 1 (referenced by 0 textures): Not referenced
Level 0: {4, 1} @ R8I (0.0 kB)
3D image 0 (referenced by 0 textures): Not referenced
Level 0: {1, 2, 1} @ RGBA8I (0.0 kB)
3D image 1 (referenced by 1 textures):
Level 0: {4, 1, 1} @ R8I (0.0 kB)
Total image data size: 0.0 kB

25
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt

@ -0,0 +1,25 @@
Scene 0: A simple scene
Bound: 4 objects @ UnsignedInt (0.1 kB)
Fields:
Parent @ Int, 3 entries
Mesh @ UnsignedInt, OrderedMapping, 4 entries
Scene 1:
Bound: 8 objects @ UnsignedByte (0.0 kB, ExternallyOwned|Mutable)
Fields:
Custom(42:) @ Double, 2 entries
Custom(1337:DirectionVector) @ Short[3], 3 entries
Total scene data size: 0.1 kB
Object 0 (referenced by 1 scenes): Parent-less mesh
Fields: Mesh
Object 1 (referenced by 1 scenes):
Fields: Parent, Mesh
Object 2 (referenced by 2 scenes): Two meshes, shared among two scenes
Fields: Parent, Mesh[2], Custom(1337:DirectionVector)
Object 3 (referenced by 2 scenes):
Fields: Parent, Custom(42:)
Object 4 (referenced by 1 scenes): Two custom arrays
Fields: Custom(1337:DirectionVector)[2]
Object 6 (referenced by 0 scenes): Only in the second scene, but no fields, thus same as unreferenced
Object 7 (referenced by 1 scenes):
Fields: Custom(42:)
Object 8 (referenced by 0 scenes): Not in any scene

11
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt

@ -0,0 +1,11 @@
Scene 0: A simple scene
Bound: 4 objects @ UnsignedInt (0.1 kB)
Fields:
Parent @ Int, 3 entries
Mesh @ UnsignedInt, OrderedMapping, 4 entries
Scene 1:
Bound: 8 objects @ UnsignedByte (0.0 kB, ExternallyOwned|Mutable)
Fields:
Custom(42:) @ Double, 2 entries
Custom(1337:DirectionVector) @ Short[3], 3 entries
Total scene data size: 0.1 kB

10
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt

@ -0,0 +1,10 @@
2D skin 0:
5 joints
2D skin 1: Second 2D skin, external data
15 joints
3D skin 0: First 3D skin, external data
12 joints
3D skin 1:
2 joints
3D skin 2:
1 joints

8
src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt

@ -0,0 +1,8 @@
Texture 0:
Type: Texture1D, image 666
Minification, mipmap and magnification: Nearest, Nearest, Linear
Wrapping: {Repeat, Repeat, Repeat}
Texture 1: Name!
Type: Texture2DArray, image 3
Minification, mipmap and magnification: Linear, Linear, Nearest
Wrapping: {MirroredRepeat, ClampToEdge, ClampToEdge}

26
src/Magnum/SceneTools/Test/configure.h.cmake

@ -0,0 +1,26 @@
/*
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.
*/
#define SCENETOOLS_TEST_DIR "${SCENETOOLS_TEST_DIR}"

941
src/Magnum/SceneTools/sceneconverter.cpp

@ -23,9 +23,7 @@
DEALINGS IN THE SOFTWARE.
*/
#include <cctype> /* std::isupper() */
#include <sstream>
#include <unordered_map> /* sceneFieldNames */
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/Reference.h>
@ -35,25 +33,16 @@
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#include "Magnum/PixelFormat.h"
#include "Magnum/Implementation/converterUtilities.h"
#include "Magnum/Math/Color.h"
#include "Magnum/Math/Matrix4.h"
#include "Magnum/Math/FunctionsBatch.h"
#include "Magnum/MeshTools/Concatenate.h"
#include "Magnum/MeshTools/RemoveDuplicates.h"
#include "Magnum/MeshTools/Transform.h"
#include "Magnum/SceneTools/FlattenMeshHierarchy.h"
#include "Magnum/Trade/AbstractImporter.h"
#include "Magnum/Trade/AnimationData.h"
#include "Magnum/Trade/LightData.h"
#include "Magnum/Trade/MaterialData.h"
#include "Magnum/Trade/MeshData.h"
#include "Magnum/Trade/SceneData.h"
#include "Magnum/Trade/SkinData.h"
#include "Magnum/Trade/TextureData.h"
#include "Magnum/Trade/AbstractSceneConverter.h"
#include "Magnum/Trade/Implementation/converterUtilities.h"
#include "Magnum/Implementation/converterUtilities.h"
#include "Magnum/SceneTools/Implementation/sceneConverterUtilities.h"
namespace Magnum {
@ -225,23 +214,6 @@ using namespace Containers::Literals;
namespace {
/** @todo const Array& doesn't work, minmax() would fail to match */
template<class T> Containers::String calculateBounds(Containers::Array<T>&& attribute) {
/** @todo clean up when Debug::toString() exists */
std::ostringstream out;
Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << Math::minmax(attribute);
return out.str();
}
/* Named attribute index from a global index */
/** @todo some helper for this directly on the MeshData class? */
UnsignedInt namedAttributeId(const Trade::MeshData& mesh, UnsignedInt id) {
const Trade::MeshAttribute name = mesh.attributeName(id);
for(UnsignedInt i = 0; i != mesh.attributeCount(name); ++i)
if(mesh.attributeId(name, i) == id) return i;
CORRADE_INTERNAL_ASSERT_UNREACHABLE();
}
bool isInfoRequested(const Utility::Arguments& args) {
return args.isSet("info-animations") ||
args.isSet("info-images") ||
@ -403,912 +375,7 @@ is specified as well, the IDs reference attributes of the first mesh.)")
/* Print file info, if requested */
if(isInfoRequested(args)) {
struct AnimationInfo {
UnsignedInt animation;
Trade::AnimationData data{{}, {}};
Containers::String name;
};
struct SkinInfo {
UnsignedInt skin;
Trade::SkinData3D data{{}, {}};
Containers::String name;
};
struct LightInfo {
UnsignedInt light;
Trade::LightData data{{}, {}, {}};
Containers::String name;
};
struct MaterialInfo {
UnsignedInt material;
Trade::MaterialData data{{}, {}};
Containers::String name;
};
struct TextureInfo {
UnsignedInt texture;
Trade::TextureData data{{}, {}, {}, {}, {}, {}};
Containers::String name;
};
struct MeshAttributeInfo {
std::size_t offset;
Int stride;
UnsignedInt arraySize;
Trade::MeshAttribute name;
Containers::String customName;
VertexFormat format;
Containers::String bounds;
};
struct MeshInfo {
UnsignedInt mesh, level;
MeshPrimitive primitive;
UnsignedInt indexCount, vertexCount;
std::size_t indexOffset;
Int indexStride;
Containers::String indexBounds;
MeshIndexType indexType;
Containers::Array<MeshAttributeInfo> attributes;
std::size_t indexDataSize, vertexDataSize;
Trade::DataFlags indexDataFlags, vertexDataFlags;
Containers::String name;
};
struct SceneFieldInfo {
Trade::SceneField name;
Trade::SceneFieldFlags flags;
Trade::SceneFieldType type;
UnsignedInt arraySize;
std::size_t size;
};
struct SceneInfo {
UnsignedInt scene;
Trade::SceneMappingType mappingType;
UnsignedLong mappingBound;
Containers::Array<SceneFieldInfo> fields;
std::size_t dataSize;
Trade::DataFlags dataFlags;
Containers::String name;
};
struct ObjectInfo {
UnsignedLong object;
/* A bitfield, assuming no more than 32 scenes */
/** @todo might be too little? */
UnsignedInt scenes;
Containers::Array<Containers::Pair<Trade::SceneField, UnsignedInt>> fields;
Containers::String name;
};
/* Parse everything first to avoid errors interleaved with output */
bool error = false;
/* Object properties */
Containers::Array<ObjectInfo> objectInfos;
if(args.isSet("info") || args.isSet("info-objects")) {
objectInfos = Containers::Array<ObjectInfo>{std::size_t(importer->objectCount())};
for(UnsignedLong i = 0; i != importer->objectCount(); ++i) {
objectInfos[i].object = i;
objectInfos[i].name = importer->objectName(i);
}
}
/* Scene properties, together with counting how much is each mesh /
light / material / skin / object referenced (which gets used only if
both --info-scenes and --info-{lights,materials,skins,objects} is
passed and the file has at least one scene). Texture reference count
is calculated when parsing materials. */
Containers::Array<SceneInfo> sceneInfos;
/* Only the very latest GCC seems to support enum classes as keys and
I can't be bothered to write a std::hash specialization, so just
making the key typeless */
std::unordered_map<UnsignedInt, Containers::String> sceneFieldNames;
Containers::Array<UnsignedInt> materialReferenceCount;
Containers::Array<UnsignedInt> lightReferenceCount;
Containers::Array<UnsignedInt> meshReferenceCount;
Containers::Array<UnsignedInt> skinReferenceCount;
if((args.isSet("info") || args.isSet("info-scenes")) && importer->sceneCount()) {
materialReferenceCount = Containers::Array<UnsignedInt>{importer->materialCount()};
lightReferenceCount = Containers::Array<UnsignedInt>{importer->lightCount()};
meshReferenceCount = Containers::Array<UnsignedInt>{importer->meshCount()};
skinReferenceCount = Containers::Array<UnsignedInt>{importer->skin3DCount()};
for(UnsignedInt i = 0; i != importer->sceneCount(); ++i) {
Containers::Optional<Trade::SceneData> scene = importer->scene(i);
if(!scene) {
error = true;
continue;
}
SceneInfo info{};
info.scene = i;
info.mappingType = scene->mappingType();
info.mappingBound = scene->mappingBound();
info.dataSize = scene->data().size();
info.dataFlags = scene->dataFlags();
info.name = importer->sceneName(i);
for(UnsignedInt j = 0; j != scene->fieldCount(); ++j) {
const Trade::SceneField name = scene->fieldName(j);
if(name == Trade::SceneField::Mesh) for(const Containers::Pair<UnsignedInt, Containers::Pair<UnsignedInt, Int>>& meshMaterial: scene->meshesMaterialsAsArray()) {
if(meshMaterial.second().first() < meshReferenceCount.size())
++meshReferenceCount[meshMaterial.second().first()];
if(UnsignedInt(meshMaterial.second().second()) < materialReferenceCount.size())
++materialReferenceCount[meshMaterial.second().second()];
}
if(name == Trade::SceneField::Skin) for(const Containers::Pair<UnsignedInt, UnsignedInt> skin: scene->skinsAsArray()) {
if(skin.second() < skinReferenceCount.size())
++skinReferenceCount[skin.second()];
/** @todo 2D/3D distinction */
}
if(name == Trade::SceneField::Light) for(const Containers::Pair<UnsignedInt, UnsignedInt>& light: scene->lightsAsArray()) {
if(light.second() < lightReferenceCount.size())
++lightReferenceCount[light.second()];
}
arrayAppend(info.fields, InPlaceInit,
name,
scene->fieldFlags(j),
scene->fieldType(j),
scene->fieldArraySize(j),
scene->fieldSize(j));
/* If the field has a custom name, save it into the map.
Not putting it into the fields array as the map is
reused by object info as well. */
if(Trade::isSceneFieldCustom(name)) {
/* Fetch the name only if it's not already there */
const auto inserted = sceneFieldNames.emplace(sceneFieldCustom(name), Containers::String{});
if(inserted.second)
inserted.first->second = importer->sceneFieldName(name);
}
if(objectInfos) for(const UnsignedInt object: scene->mappingAsArray(j)) {
if(object >= objectInfos.size()) continue;
objectInfos[object].object = object;
objectInfos[object].scenes |= 1 << i;
/* If the field is repeated, increase the count
instead */
if(!objectInfos[object].fields.isEmpty() && objectInfos[object].fields.back().first() == name)
++objectInfos[object].fields.back().second();
else
arrayAppend(objectInfos[object].fields, InPlaceInit, name, 1u);
}
}
arrayAppend(sceneInfos, std::move(info));
}
}
/* Animation properties */
Containers::Array<AnimationInfo> animationInfos;
if(args.isSet("info") || args.isSet("info-animations")) for(UnsignedInt i = 0; i != importer->animationCount(); ++i) {
Containers::Optional<Trade::AnimationData> animation;
{
Trade::Implementation::Duration d{importTime};
if(!(animation = importer->animation(i))) {
error = true;
continue;
}
}
AnimationInfo info{};
info.animation = i;
info.name = importer->animationName(i);
info.data = *std::move(animation);
arrayAppend(animationInfos, std::move(info));
}
/* Skin properties */
Containers::Array<SkinInfo> skinInfos;
if(args.isSet("info") || args.isSet("info-skins")) for(UnsignedInt i = 0; i != importer->skin3DCount(); ++i) {
Containers::Optional<Trade::SkinData3D> skin;
{
Trade::Implementation::Duration d{importTime};
if(!(skin = importer->skin3D(i))) {
error = true;
continue;
}
}
SkinInfo info{};
info.skin = i;
info.name = importer->skin3DName(i);
info.data = *std::move(skin);
arrayAppend(skinInfos, std::move(info));
}
/* Light properties */
Containers::Array<LightInfo> lightInfos;
if(args.isSet("info") || args.isSet("info-lights")) for(UnsignedInt i = 0; i != importer->lightCount(); ++i) {
Containers::Optional<Trade::LightData> light;
{
Trade::Implementation::Duration d{importTime};
if(!(light = importer->light(i))) {
error = true;
continue;
}
}
LightInfo info{};
info.light = i;
info.name = importer->lightName(i);
info.data = *std::move(light);
arrayAppend(lightInfos, std::move(info));
}
/* Material properties, together with how much is each texture shared
(which gets used only if both --info-materials and --info-textures
is passed and the file has at least one material). */
Containers::Array<MaterialInfo> materialInfos;
Containers::Array<UnsignedInt> textureReferenceCount;
if((args.isSet("info") || args.isSet("info-materials")) && importer->materialCount()) {
textureReferenceCount = Containers::Array<UnsignedInt>{importer->textureCount()};
for(UnsignedInt i = 0; i != importer->materialCount(); ++i) {
Containers::Optional<Trade::MaterialData> material;
{
Trade::Implementation::Duration d{importTime};
if(!(material = importer->material(i))) {
error = true;
continue;
}
}
/* Calculate texture reference count for all properties that
look like a texture */
for(UnsignedInt j = 0; j != material->layerCount(); ++j) {
for(UnsignedInt k = 0; k != material->attributeCount(j); ++k) {
if(material->attributeType(j, k) != Trade::MaterialAttributeType::UnsignedInt || !Utility::String::endsWith(material->attributeName(j, k), "Texture"))
continue;
const UnsignedInt texture = material->attribute<UnsignedInt>(j, k);
/** @todo once StridedBitArrayView2D exists, fix this
to count each material only once by having one bit
for every material and texture */
if(texture < textureReferenceCount.size())
++textureReferenceCount[texture];
}
}
MaterialInfo info{};
info.material = i;
info.name = importer->materialName(i);
info.data = *std::move(material);
arrayAppend(materialInfos, std::move(info));
}
}
/* Mesh properties */
Containers::Array<MeshInfo> meshInfos;
if(args.isSet("info") || args.isSet("info-meshes")) for(UnsignedInt i = 0; i != importer->meshCount(); ++i) {
for(UnsignedInt j = 0; j != importer->meshLevelCount(i); ++j) {
Containers::Optional<Trade::MeshData> mesh;
{
Trade::Implementation::Duration d{importTime};
if(!(mesh = importer->mesh(i, j))) {
error = true;
continue;
}
}
MeshInfo info{};
info.mesh = i;
info.level = j;
info.primitive = mesh->primitive();
info.vertexCount = mesh->vertexCount();
info.vertexDataSize = mesh->vertexData().size();
info.vertexDataFlags = mesh->vertexDataFlags();
if(!j) {
info.name = importer->meshName(i);
}
if(mesh->isIndexed()) {
info.indexCount = mesh->indexCount();
info.indexType = mesh->indexType();
info.indexOffset = mesh->indexOffset();
info.indexStride = mesh->indexStride();
info.indexDataSize = mesh->indexData().size();
info.indexDataFlags = mesh->indexDataFlags();
if(args.isSet("bounds"))
info.indexBounds = calculateBounds(mesh->indicesAsArray());
}
for(UnsignedInt k = 0; k != mesh->attributeCount(); ++k) {
const Trade::MeshAttribute name = mesh->attributeName(k);
/* Calculate bounds, if requested, if this is not an
implementation-specific format and if it's not a custom
attribute */
Containers::String bounds;
if(args.isSet("bounds") && !isVertexFormatImplementationSpecific(mesh->attributeFormat(k))) switch(name) {
case Trade::MeshAttribute::Position:
bounds = calculateBounds(mesh->positions3DAsArray(namedAttributeId(*mesh, k)));
break;
case Trade::MeshAttribute::Tangent:
bounds = calculateBounds(mesh->tangentsAsArray(namedAttributeId(*mesh, k)));
break;
case Trade::MeshAttribute::Bitangent:
bounds = calculateBounds(mesh->bitangentsAsArray(namedAttributeId(*mesh, k)));
break;
case Trade::MeshAttribute::Normal:
bounds = calculateBounds(mesh->normalsAsArray(namedAttributeId(*mesh, k)));
break;
case Trade::MeshAttribute::TextureCoordinates:
bounds = calculateBounds(mesh->textureCoordinates2DAsArray(namedAttributeId(*mesh, k)));
break;
case Trade::MeshAttribute::Color:
bounds = calculateBounds(mesh->colorsAsArray(namedAttributeId(*mesh, k)));
break;
case Trade::MeshAttribute::ObjectId:
Debug{} << mesh->objectIdsAsArray(namedAttributeId(*mesh, k));
bounds = calculateBounds(mesh->objectIdsAsArray(namedAttributeId(*mesh, k)));
break;
}
arrayAppend(info.attributes, InPlaceInit,
mesh->attributeOffset(k),
mesh->attributeStride(k),
mesh->attributeArraySize(k),
name, Trade::isMeshAttributeCustom(name) ?
importer->meshAttributeName(name) : "",
mesh->attributeFormat(k),
bounds);
}
arrayAppend(meshInfos, std::move(info));
}
}
/* Texture properties, together with how much is each image shared
(which gets used only if both --info-textures and --info-images is
passed and the file has at least one texture). */
Containers::Array<TextureInfo> textureInfos;
Containers::Array<UnsignedInt> image1DReferenceCount;
Containers::Array<UnsignedInt> image2DReferenceCount;
Containers::Array<UnsignedInt> image3DReferenceCount;
if((args.isSet("info") || args.isSet("info-textures")) && importer->textureCount()) {
image1DReferenceCount = Containers::Array<UnsignedInt>{importer->image1DCount()};
image2DReferenceCount = Containers::Array<UnsignedInt>{importer->image2DCount()};
image3DReferenceCount = Containers::Array<UnsignedInt>{importer->image3DCount()};
for(UnsignedInt i = 0; i != importer->textureCount(); ++i) {
Containers::Optional<Trade::TextureData> texture;
{
Trade::Implementation::Duration d{importTime};
if(!(texture = importer->texture(i))) {
error = true;
continue;
}
}
switch(texture->type()) {
case Trade::TextureType::Texture1D:
if(texture->image() < image1DReferenceCount.size())
++image1DReferenceCount[texture->image()];
break;
case Trade::TextureType::Texture1DArray:
case Trade::TextureType::Texture2D:
if(texture->image() < image2DReferenceCount.size())
++image2DReferenceCount[texture->image()];
break;
case Trade::TextureType::CubeMap:
case Trade::TextureType::CubeMapArray:
case Trade::TextureType::Texture2DArray:
case Trade::TextureType::Texture3D:
if(texture->image() < image3DReferenceCount.size())
++image3DReferenceCount[texture->image()];
break;
}
TextureInfo info{};
info.texture = i;
info.name = importer->textureName(i);
info.data = *std::move(texture);
arrayAppend(textureInfos, std::move(info));
}
}
Containers::Array<Trade::Implementation::ImageInfo> imageInfos;
if(args.isSet("info") || args.isSet("info-images")) {
imageInfos = Trade::Implementation::imageInfo(*importer, error, importTime);
}
std::size_t totalSceneDataSize = 0;
for(const SceneInfo& info: sceneInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Scene" << info.scene << Debug::nospace << ":" << Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor;
d << Debug::newline;
d << " Bound:" << info.mappingBound << "objects"
<< Debug::color(Debug::Color::Blue) << "@" << Debug::packed
<< Debug::color(Debug::Color::Cyan) << info.mappingType
<< Debug::resetColor << "(" << Debug::nospace
<< Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB";
if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable))
d << Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Green) << info.dataFlags
<< Debug::resetColor;
d << Debug::nospace << ")";
d << Debug::newline << " Fields:";
for(const SceneFieldInfo& field: info.fields) {
d << Debug::newline << " "
<< Debug::boldColor(Debug::Color::Default);
if(Trade::isSceneFieldCustom(field.name)) {
d << "Custom(" << Debug::nospace
<< Trade::sceneFieldCustom(field.name)
<< Debug::nospace << ":" << Debug::nospace
<< Debug::color(Debug::Color::Yellow)
<< sceneFieldNames[sceneFieldCustom(field.name)]
<< Debug::nospace
<< Debug::boldColor(Debug::Color::Default) << ")";
} else d << Debug::packed << field.name;
d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << field.type;
if(field.arraySize)
d << Debug::nospace << Utility::format("[{}]", field.arraySize);
d << Debug::resetColor;
if(field.flags) d << Debug::nospace << ", flags:"
<< Debug::packed << Debug::color(Debug::Color::Green)
<< field.flags << Debug::resetColor;
d << Debug::nospace << "," << field.size << "entries";
}
totalSceneDataSize += info.dataSize;
}
if(!sceneInfos.isEmpty())
Debug{} << "Total scene data size:" << Utility::format("{:.1f}", totalSceneDataSize/1024.0f) << "kB";
for(const ObjectInfo& info: objectInfos) {
/* Objects without a name and not referenced by any scenes are
useless, ignore */
if(!info.name && !info.scenes) continue;
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Object" << info.object << Debug::resetColor;
if(sceneInfos) {
const UnsignedInt count = Math::popcount(info.scenes);
if(!count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << count << "scenes)";
if(!count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow)
<< info.name << Debug::resetColor;
if(info.scenes) {
d << Debug::newline << " Fields:";
for(std::size_t i = 0; i != info.fields.size(); ++i) {
if(i) d << Debug::nospace << ",";
const Containers::Pair<Trade::SceneField, UnsignedInt> nameCount = info.fields[i];
d << Debug::color(Debug::Color::Cyan);
if(Trade::isSceneFieldCustom(nameCount.first())) {
d << "Custom(" << Debug::nospace
<< Trade::sceneFieldCustom(nameCount.first())
<< Debug::nospace << ":" << Debug::nospace
<< Debug::color(Debug::Color::Yellow)
<< sceneFieldNames[sceneFieldCustom(nameCount.first())]
<< Debug::nospace
<< Debug::color(Debug::Color::Cyan) << ")";
} else d << Debug::packed << nameCount.first();
if(nameCount.second() != 1)
d << Debug::nospace << Utility::format("[{}]", nameCount.second());
d << Debug::resetColor;
}
}
}
std::size_t totalAnimationDataSize = 0;
for(const AnimationInfo& info: animationInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Animation" << info.animation << Debug::nospace << ":" << Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor;
d << Debug::newline << " Duration:" << info.data.duration()
<< "(" << Debug::nospace << Utility::format("{:.1f}", info.data.data().size()/1024.0f) << "kB";
if(info.data.dataFlags() != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable))
d << Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Green)
<< info.data.dataFlags() << Debug::resetColor;
d << Debug::nospace << ")";
for(UnsignedInt i = 0; i != info.data.trackCount(); ++i) {
d << Debug::newline << " Track" << i << Debug::nospace << ":"
<< Debug::packed << Debug::boldColor(Debug::Color::Default)
<< info.data.trackTargetType(i)
<< Debug::color(Debug::Color::Blue) << "@"
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.trackType(i) << Debug::resetColor;
if(info.data.trackType(i) != info.data.trackResultType(i))
d << Debug::color(Debug::Color::Blue) << "->"
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.trackResultType(i) << Debug::resetColor;
d << Debug::nospace << "," << info.data.track(i).size()
<< "keyframes";
if(info.data.track(i).duration() != info.data.duration())
d << Debug::nospace << "," << info.data.track(i).duration();
d << Debug::newline
<< " Interpolation:"
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.track(i).interpolation() << Debug::resetColor
<< Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Cyan)
<< info.data.track(i).before() << Debug::resetColor
<< Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Cyan)
<< info.data.track(i).after() << Debug::resetColor;
/** @todo might be useful to show bounds here as well, though
not so much for things like complex numbers or quats */
}
totalAnimationDataSize += info.data.data().size();
}
if(!animationInfos.isEmpty())
Debug{} << "Total animation data size:" << Utility::format("{:.1f}", totalAnimationDataSize/1024.0f) << "kB";
for(const SkinInfo& info: skinInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Skin" << info.skin
<< Debug::resetColor;
/* Print reference count only if there actually are scenes and they
were parsed, otherwise this information is useless */
if(skinReferenceCount) {
const UnsignedInt count = skinReferenceCount[info.skin];
if(!count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << count << "objects)";
if(!count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow)
<< info.name << Debug::resetColor;
d << Debug::newline << " " << info.data.joints().size() << "joints";
}
for(const LightInfo& info: lightInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Light" << info.light << Debug::resetColor;
/* Print reference count only if there actually are scenes and they
were parsed, otherwise this information is useless */
if(lightReferenceCount) {
const UnsignedInt count = lightReferenceCount[info.light];
if(!count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << count << "objects)";
if(!count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow)
<< info.name << Debug::resetColor;
d << Debug::newline << " Type:" << Debug::packed
<< Debug::color(Debug::Color::Cyan)
<< info.data.type() << Debug::resetColor;
if(info.data.type() == Trade::LightData::Type::Spot)
d << Debug::nospace << "," << Debug::packed
<< Deg(info.data.innerConeAngle()) << Debug::nospace
<< "° -" << Debug::packed << Deg(info.data.outerConeAngle())
<< Debug::nospace << "°";
d << Debug::newline << " Color:";
if(useColor24) d << Debug::color
<< Math::pack<Color3ub>(info.data.color());
d << Debug::packed << info.data.color();
if(!Math::equal(info.data.intensity(), 1.0f))
d << "*" << info.data.intensity();
d << Debug::newline << " Attenuation:" << Debug::packed
<< info.data.attenuation();
if(info.data.range() != Constants::inf())
d << Debug::newline << " Range:" << Debug::packed
<< info.data.range();
}
for(const MaterialInfo& info: materialInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Material" << info.material << Debug::resetColor;
/* Print reference count only if there actually are scenes and they
were parsed, otherwise this information is useless */
if(materialReferenceCount) {
const UnsignedInt count = materialReferenceCount[info.material];
if(!count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << count << "objects)";
if(!count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor;
d << Debug::newline << " Type:" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.data.types() << Debug::resetColor;
for(UnsignedInt i = 0; i != info.data.layerCount(); ++i) {
/* Print extra layers with extra indent */
const char* indent;
if(info.data.layerCount() != 1 && i != 0) {
d << Debug::newline << " Layer" << i << Debug::nospace << ":";
if(!info.data.layerName(i).isEmpty()) {
if(std::isupper(info.data.layerName(i)[0]))
d << Debug::boldColor(Debug::Color::Default);
else
d << Debug::color(Debug::Color::Yellow);
d << info.data.layerName(i) << Debug::resetColor;
}
indent = " ";
} else {
d << Debug::newline << " Base layer:";
indent = " ";
}
for(UnsignedInt j = 0; j != info.data.attributeCount(i); ++j) {
/* Ignore layer name (which is always first) unless it's in
the base material, in which case we print it as it
wouldn't otherwise be shown anywhere */
if(i && !j && info.data.attributeName(i, j) == " LayerName")
continue;
d << Debug::newline << indent;
if(std::isupper(info.data.attributeName(i, j)[0]))
d << Debug::boldColor(Debug::Color::Default);
else
d << Debug::color(Debug::Color::Yellow);
d << info.data.attributeName(i, j) << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.attributeType(i, j) << Debug::resetColor << Debug::nospace
<< ":";
switch(info.data.attributeType(i, j)) {
case Trade::MaterialAttributeType::Bool:
d << info.data.attribute<bool>(i, j);
break;
#define _c(type) case Trade::MaterialAttributeType::type: \
d << Debug::packed << info.data.attribute<type>(i, j); \
break;
_c(Float)
_c(Deg)
_c(Rad)
_c(UnsignedInt)
_c(Int)
_c(UnsignedLong)
_c(Long)
_c(Vector2)
_c(Vector2ui)
_c(Vector2i)
case Trade::MaterialAttributeType::Vector3:
/** @todo hasSuffix() might be more robust against
false positives, but KHR_materials_specular in
glTF uses ColorFactor :/ */
if(useColor24 && info.data.attributeName(i, j).contains("Color"_s))
d << Debug::color << Math::pack<Color3ub>(info.data.attribute<Vector3>(i, j));
d << Debug::packed << info.data.attribute<Vector3>(i, j);
break;
_c(Vector3ui)
_c(Vector3i)
case Trade::MaterialAttributeType::Vector4:
/** @todo hasSuffix() might be more robust against
false positives, but KHR_materials_specular in
glTF uses ColorFactor :/ */
if(useColor24 && info.data.attributeName(i, j).contains("Color"_s))
d << Debug::color << Math::pack<Color3ub>(info.data.attribute<Vector4>(i, j).rgb());
d << Debug::packed << info.data.attribute<Vector4>(i, j);
break;
_c(Vector4ui)
_c(Vector4i)
_c(Matrix2x2)
_c(Matrix2x3)
_c(Matrix2x4)
_c(Matrix3x2)
_c(Matrix3x3)
_c(Matrix3x4)
_c(Matrix4x2)
_c(Matrix4x3)
#undef _c
case Trade::MaterialAttributeType::Pointer:
d << info.data.attribute<const void*>(i, j);
break;
case Trade::MaterialAttributeType::MutablePointer:
d << info.data.attribute<void*>(i, j);
break;
case Trade::MaterialAttributeType::String:
d << info.data.attribute<Containers::StringView>(i, j);
break;
case Trade::MaterialAttributeType::TextureSwizzle:
d << Debug::packed << info.data.attribute<Trade::MaterialTextureSwizzle>(i, j);
break;
}
}
}
}
std::size_t totalMeshDataSize = 0;
for(const MeshInfo& info: meshInfos) {
Debug d{useColor};
if(info.level == 0) {
d << Debug::boldColor(Debug::Color::Default) << "Mesh" << info.mesh << Debug::resetColor;
/* Print reference count only if there actually are scenes and
they were parsed, otherwise this information is useless */
if(meshReferenceCount) {
const UnsignedInt count = meshReferenceCount[info.mesh];
if(!count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << count << "objects)";
if(!count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor;
d << Debug::newline;
}
d << " Level" << info.level << Debug::nospace << ":"
<< info.vertexCount << "vertices" << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.primitive << Debug::resetColor << "(" << Debug::nospace
<< Utility::format("{:.1f}", info.vertexDataSize/1024.0f)
<< "kB";
if(info.vertexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable))
d << Debug::nospace << ", flags:" << Debug::packed
<< Debug::color(Debug::Color::Green)
<< info.vertexDataFlags << Debug::resetColor;
d << Debug::nospace << ")";
for(const MeshAttributeInfo& attribute: info.attributes) {
d << Debug::newline << " "
<< Debug::boldColor(Debug::Color::Default);
if(Trade::isMeshAttributeCustom(attribute.name)) {
d << "Custom(" << Debug::nospace
<< Trade::meshAttributeCustom(attribute.name)
<< Debug::nospace << ":" << Debug::nospace
<< Debug::color(Debug::Color::Yellow)
<< attribute.customName << Debug::nospace
<< Debug::boldColor(Debug::Color::Default) << ")";
} else d << Debug::packed << attribute.name;
d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << attribute.format;
if(attribute.arraySize)
d << Debug::nospace << Utility::format("[{}]", attribute.arraySize);
d << Debug::resetColor;
d << Debug::nospace << ", offset" << attribute.offset;
d << Debug::nospace << ", stride"
<< attribute.stride;
if(attribute.bounds)
d << Debug::newline << " bounds:" << attribute.bounds;
}
if(info.indexType != MeshIndexType{}) {
d << Debug::newline << " " << info.indexCount << "indices" << Debug::color(Debug::Color::Blue) << "@"
<< Debug::packed << Debug::color(Debug::Color::Cyan) << info.indexType << Debug::resetColor << Debug::nospace << ", offset" << info.indexOffset << Debug::nospace << ", stride" << info.indexStride << "(" << Debug::nospace
<< Utility::format("{:.1f}", info.indexDataSize/1024.0f)
<< "kB";
if(info.indexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable))
d << Debug::nospace << ", flags:" << Debug::packed
<< Debug::color(Debug::Color::Green) << info.indexDataFlags << Debug::resetColor;
d << Debug::nospace << ")";
if(info.indexBounds)
d << Debug::newline << " bounds:" << info.indexBounds;
}
totalMeshDataSize += info.vertexDataSize + info.indexDataSize;
}
if(!meshInfos.isEmpty())
Debug{} << "Total mesh data size:" << Utility::format("{:.1f}", totalMeshDataSize/1024.0f) << "kB";
for(const TextureInfo& info: textureInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Texture" << info.texture << Debug::resetColor;
/* Print reference count only if there actually are materials and
they were parsed, otherwise this information is useless */
if(textureReferenceCount) {
const UnsignedInt count = textureReferenceCount[info.texture];
if(!count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << count << "material attributes)";
if(!count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow)
<< info.name << Debug::resetColor;
d << Debug::newline;
d << " Type:"
<< Debug::packed
<< Debug::color(Debug::Color::Cyan) << info.data.type()
<< Debug::resetColor << Debug::nospace << ", image"
<< info.data.image();
d << Debug::newline << " Minification, mipmap and magnification:"
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.minificationFilter() << Debug::nospace << ","
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.mipmapFilter() << Debug::nospace << ","
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.magnificationFilter() << Debug::resetColor;
d << Debug::newline << " Wrapping:" << Debug::resetColor << "{" << Debug::nospace
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< info.data.wrapping()[0] << Debug::resetColor
<< Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1]
<< Debug::resetColor << Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1]
<< Debug::resetColor << Debug::nospace << "}";
}
std::size_t totalImageDataSize = 0;
for(const Trade::Implementation::ImageInfo& info: imageInfos) {
Debug d{useColor};
if(info.level == 0) {
d << Debug::boldColor(Debug::Color::Default);
if(info.size.z()) d << "3D image";
else if(info.size.y()) d << "2D image";
else d << "1D image";
d << info.image << Debug::resetColor;
/* Print reference count only if there actually are textures
and they were parsed otherwise this information is
useless */
Containers::Optional<UnsignedInt> count;
if(info.size.z() && image3DReferenceCount) {
count = image3DReferenceCount[info.image];
} else if(info.size.y() && image2DReferenceCount) {
count = image2DReferenceCount[info.image];
} else if(image1DReferenceCount) {
count = image1DReferenceCount[info.image];
}
if(count) {
if(!*count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << *count << "textures)";
if(!*count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow)
<< info.name << Debug::resetColor;
d << Debug::newline;
}
d << " Level" << info.level << Debug::nospace << ":";
if(info.flags.one) {
d << Debug::packed << Debug::color(Debug::Color::Cyan);
if(info.size.z()) d << info.flags.three;
else if(info.size.y()) d << info.flags.two;
else d << info.flags.one;
d << Debug::resetColor;
}
d << Debug::packed;
if(info.size.z()) d << info.size;
else if(info.size.y()) d << info.size.xy();
else d << Math::Vector<1, Int>(info.size.x());
d << Debug::color(Debug::Color::Blue) << "@" << Debug::resetColor;
d << Debug::packed;
if(info.compressed) d << Debug::color(Debug::Color::Yellow) << info.compressedFormat;
else d << Debug::color(Debug::Color::Cyan) << info.format;
d << Debug::resetColor << "(" << Debug::nospace << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB";
if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable))
d << Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Green) << info.dataFlags
<< Debug::resetColor;
d << Debug::nospace << ")";
totalImageDataSize += info.dataSize;
}
if(!imageInfos.isEmpty())
Debug{} << "Total image data size:" << Utility::format("{:.1f}", totalImageDataSize/1024.0f) << "kB";
const bool error = SceneTools::Implementation::printInfo(useColor, useColor24, args, *importer, importTime);
if(args.isSet("profile")) {
Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast<std::chrono::milliseconds>(importTime).count())/1.0e3f << "seconds";

81
src/Magnum/Trade/Implementation/converterUtilities.h

@ -28,13 +28,17 @@
#include <chrono>
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/String.h>
#include <Corrade/Utility/Format.h>
#include "Magnum/PixelFormat.h"
#include "Magnum/Trade/AbstractImporter.h"
#include "Magnum/Trade/ImageData.h"
namespace Magnum { namespace Trade { namespace Implementation {
/* Used only in executables where we don't want it to be exported */
/* Used only in executables where we don't want it to be exported -- in
particular magnum-imageconverter, magnum-sceneconverter and their tests */
namespace {
struct Duration {
@ -75,7 +79,7 @@ struct ImageInfo {
Containers::Array<ImageInfo> imageInfo(AbstractImporter& importer, bool& error, std::chrono::high_resolution_clock::duration& importTime) {
Containers::Array<ImageInfo> infos;
for(UnsignedInt i = 0; i != importer.image1DCount(); ++i) {
const std::string name = importer.image1DName(i);
const Containers::String name = importer.image1DName(i);
const UnsignedInt levelCount = importer.image1DLevelCount(i);
for(UnsignedInt j = 0; j != levelCount; ++j) {
@ -97,11 +101,11 @@ Containers::Array<ImageInfo> imageInfo(AbstractImporter& importer, bool& error,
image->data().size(),
image->dataFlags(),
ImageInfoFlags{image->flags()},
j ? "" : importer.image1DName(i));
j ? "" : name);
}
}
for(UnsignedInt i = 0; i != importer.image2DCount(); ++i) {
const std::string name = importer.image2DName(i);
const Containers::String name = importer.image2DName(i);
const UnsignedInt levelCount = importer.image2DLevelCount(i);
for(UnsignedInt j = 0; j != levelCount; ++j) {
@ -127,7 +131,7 @@ Containers::Array<ImageInfo> imageInfo(AbstractImporter& importer, bool& error,
}
}
for(UnsignedInt i = 0; i != importer.image3DCount(); ++i) {
const std::string name = importer.image3DName(i);
const Containers::String name = importer.image3DName(i);
const UnsignedInt levelCount = importer.image3DLevelCount(i);
for(UnsignedInt j = 0; j != levelCount; ++j) {
@ -156,6 +160,73 @@ Containers::Array<ImageInfo> imageInfo(AbstractImporter& importer, bool& error,
return infos;
}
void printImageInfo(const Debug::Flags useColor, const Containers::ArrayView<const ImageInfo> imageInfos, const Containers::ArrayView<const UnsignedInt> image1DReferenceCount, const Containers::ArrayView<const UnsignedInt> image2DReferenceCount, const Containers::ArrayView<const UnsignedInt> image3DReferenceCount) {
std::size_t totalImageDataSize = 0;
for(const Trade::Implementation::ImageInfo& info: imageInfos) {
Debug d{useColor};
if(info.level == 0) {
d << Debug::boldColor(Debug::Color::Default);
if(info.size.z()) d << "3D image";
else if(info.size.y()) d << "2D image";
else d << "1D image";
d << info.image << Debug::resetColor;
/* Print reference count only if there actually are any (i.e., the
arrays are non-empty) otherwise this information is useless */
Containers::Optional<UnsignedInt> count;
if(info.size.z() && image3DReferenceCount) {
count = image3DReferenceCount[info.image];
} else if(info.size.y() && image2DReferenceCount) {
count = image2DReferenceCount[info.image];
} else if(image1DReferenceCount) {
count = image1DReferenceCount[info.image];
}
if(count) {
if(!*count) d << Debug::color(Debug::Color::Red);
d << "(referenced by" << *count << "textures)";
if(!*count) d << Debug::resetColor;
}
d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow)
<< info.name << Debug::resetColor;
d << Debug::newline;
}
d << " Level" << info.level << Debug::nospace << ":";
if(info.flags.one) {
d << Debug::packed << Debug::color(Debug::Color::Cyan);
if(info.size.z()) d << info.flags.three;
else if(info.size.y()) d << info.flags.two;
else d << info.flags.one;
d << Debug::resetColor;
}
d << Debug::packed;
if(info.size.z()) d << info.size;
else if(info.size.y()) d << info.size.xy();
/* Kinda unnecessary, but makes the output more consistent if also 1D
size is in {}s */
else d << Math::Vector<1, Int>(info.size.x());
d << Debug::color(Debug::Color::Blue) << "@" << Debug::resetColor;
d << Debug::packed;
/* Compressed formats are printed yellow. That kinda conflicts with
custom fields / attributes elsewhere, but is significant enough to
have it highlighted. */
if(info.compressed) d << Debug::color(Debug::Color::Yellow) << info.compressedFormat;
else d << Debug::color(Debug::Color::Cyan) << info.format;
d << Debug::resetColor << "(" << Debug::nospace << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB";
if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable))
d << Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Green) << info.dataFlags
<< Debug::resetColor;
d << Debug::nospace << ")";
totalImageDataSize += info.dataSize;
}
if(!imageInfos.isEmpty())
Debug{} << "Total image data size:" << Utility::format("{:.1f}", totalImageDataSize/1024.0f) << "kB";
}
}
}}}

7
src/Magnum/Trade/Test/CMakeLists.txt

@ -54,6 +54,13 @@ corrade_add_test(TradeAnimationDataTest AnimationDataTest.cpp LIBRARIES MagnumTr
corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTrade)
corrade_add_test(TradeFlatMaterialDataTest FlatMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeImageConverterTest ImageConverterTest.cpp
LIBRARIES MagnumTrade
FILES
ImageConverterTestFiles/info.txt)
target_include_directories(TradeImageConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib)

192
src/Magnum/Trade/Test/ImageConverterTest.cpp

@ -0,0 +1,192 @@
/*
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 <sstream>
#include <Corrade/Containers/StringStl.h> /** @todo remove once Debug is stream-free */
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/StringToFile.h>
#include <Corrade/Utility/DebugStl.h> /** @todo remove once Debug is stream-free */
#include <Corrade/Utility/Path.h>
#include "Magnum/Trade/Implementation/converterUtilities.h"
#include "configure.h"
namespace Magnum { namespace Trade { namespace Test { namespace {
struct ImageConverterTest: TestSuite::Tester {
explicit ImageConverterTest();
void infoImplementation();
void infoImplementationError();
};
ImageConverterTest::ImageConverterTest() {
addTests({&ImageConverterTest::infoImplementation,
&ImageConverterTest::infoImplementationError});
}
void ImageConverterTest::infoImplementation() {
struct Importer: Trade::AbstractImporter {
Trade::ImporterFeatures doFeatures() const override { return {}; }
bool doIsOpened() const override { return true; }
void doClose() override {}
/* Three 1D images, one with two levels and named, one compressed,
one just to not have two of everything */
UnsignedInt doImage1DCount() const override { return 3; }
UnsignedInt doImage1DLevelCount(UnsignedInt id) override {
return id == 1 ? 2 : 1;
}
Containers::String doImage1DName(UnsignedInt id) override {
return id == 2 ? "Third 1D image just so there aren't two" : "";
}
Containers::Optional<Trade::ImageData1D> doImage1D(UnsignedInt id, UnsignedInt level) override {
if(id == 0 && level == 0)
return Trade::ImageData1D{CompressedPixelFormat::Astc10x10RGBAF, 1024, Containers::Array<char>{NoInit, 4096}};
if(id == 1 && level == 0)
return Trade::ImageData1D{PixelFormat::RGBA8Snorm, 16, Containers::Array<char>{NoInit, 64}};
if(id == 1 && level == 1)
return Trade::ImageData1D{PixelFormat::RGBA8Snorm, 8, Containers::Array<char>{NoInit, 32}};
if(id == 2 && level == 0)
return Trade::ImageData1D{PixelFormat::Depth16Unorm, 4, Containers::Array<char>{NoInit, 8}};
CORRADE_INTERNAL_ASSERT_UNREACHABLE();
}
/* Two 2D images, one with three levels and named, the other compressed
and array */
UnsignedInt doImage2DCount() const override { return 2; }
UnsignedInt doImage2DLevelCount(UnsignedInt id) override {
return id == 0 ? 3 : 1;
}
Containers::String doImage2DName(UnsignedInt id) override {
return id == 0 ? "A very nice mipmapped 2D image" : "";
}
Containers::Optional<Trade::ImageData2D> doImage2D(UnsignedInt id, UnsignedInt level) override {
if(id == 0 && level == 0)
return Trade::ImageData2D{PixelFormat::RG16F, {256, 128}, Containers::Array<char>{NoInit, 131072}};
if(id == 0 && level == 1)
return Trade::ImageData2D{PixelFormat::RG16F, {128, 64}, Containers::Array<char>{NoInit, 32768}};
if(id == 0 && level == 2)
return Trade::ImageData2D{PixelFormat::RG16F, {64, 32}, Containers::Array<char>{NoInit, 8192}};
if(id == 1)
return Trade::ImageData2D{CompressedPixelFormat::PvrtcRGB2bppUnorm, {4, 8}, Containers::Array<char>{NoInit, 32}, ImageFlag2D::Array};
CORRADE_INTERNAL_ASSERT_UNREACHABLE();
}
/* One 2D cube map array image, one 3D mipmapped & named and two 2D
array; with one externally owned */
UnsignedInt doImage3DCount() const override { return 4; }
UnsignedInt doImage3DLevelCount(UnsignedInt id) override {
return id == 1 ? 2 : 1;
}
Containers::String doImage3DName(UnsignedInt id) override {
return id == 1 ? "Volume kills!" : "";
}
Containers::Optional<Trade::ImageData3D> doImage3D(UnsignedInt id, UnsignedInt level) override {
if(id == 0 && level == 0)
return Trade::ImageData3D{PixelFormat::R8Unorm, {16, 16, 12}, Containers::Array<char>{NoInit, 3072}, ImageFlag3D::CubeMap|ImageFlag3D::Array};
if(id == 1 && level == 0)
return Trade::ImageData3D{PixelFormat::R8Unorm, {16, 16, 16}, Containers::Array<char>{NoInit, 4096}};
if(id == 1 && level == 1)
return Trade::ImageData3D{PixelFormat::R8Unorm, {8, 8, 6}, Containers::Array<char>{NoInit, 2048}};
if(id == 2 && level == 0)
return Trade::ImageData3D{CompressedPixelFormat::Bc1RGBSrgb, {4, 1, 1}, Containers::Array<char>{NoInit, 16}, ImageFlag3D::Array};
if(id == 3 && level == 0)
return Trade::ImageData3D{PixelFormat::R32F, {1, 4, 1}, Trade::DataFlag::ExternallyOwned|Trade::DataFlag::Mutable, data, ImageFlag3D::Array};
CORRADE_INTERNAL_ASSERT_UNREACHABLE();
}
char data[16];
} importer;
bool error = false;
std::chrono::high_resolution_clock::duration time;
Containers::Array<Implementation::ImageInfo> infos = Implementation::imageInfo(importer, error, time);
CORRADE_VERIFY(!error);
CORRADE_COMPARE(infos.size(), 13);
/* Print to visually verify coloring */
{
Debug{} << "======================== visual color verification start =======================";
Implementation::printImageInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, infos, nullptr, nullptr, nullptr);
Debug{} << "======================== visual color verification end =========================";
}
std::ostringstream out;
Debug redirectOutput{&out};
Implementation::printImageInfo(Debug::Flag::DisableColors, infos, nullptr, nullptr, nullptr);
CORRADE_COMPARE_AS(out.str(),
Utility::Path::join(TRADE_TEST_DIR, "ImageConverterTestFiles/info.txt"),
TestSuite::Compare::StringToFile);
}
void ImageConverterTest::infoImplementationError() {
struct Importer: Trade::AbstractImporter {
Trade::ImporterFeatures doFeatures() const override { return {}; }
bool doIsOpened() const override { return true; }
void doClose() override {}
UnsignedInt doImage1DCount() const override { return 2; }
Containers::Optional<Trade::ImageData1D> doImage1D(UnsignedInt id, UnsignedInt) override {
Error{} << "1D image" << id << "error!";
return {};
}
UnsignedInt doImage2DCount() const override { return 2; }
Containers::Optional<Trade::ImageData2D> doImage2D(UnsignedInt id, UnsignedInt) override {
Error{} << "2D image" << id << "error!";
return {};
}
UnsignedInt doImage3DCount() const override { return 2; }
Containers::Optional<Trade::ImageData3D> doImage3D(UnsignedInt id, UnsignedInt) override {
Error{} << "3D image" << id << "error!";
return {};
}
} importer;
bool error = false;
std::chrono::high_resolution_clock::duration time;
std::ostringstream out;
Debug redirectOutput{&out};
Error redirectError{&out};
Containers::Array<Implementation::ImageInfo> infos = Implementation::imageInfo(importer, error, time);
/* It should return a failure and no output */
CORRADE_VERIFY(error);
CORRADE_VERIFY(infos.isEmpty());
/* But it should not exit after first error */
CORRADE_COMPARE(out.str(),
"1D image 0 error!\n"
"1D image 1 error!\n"
"2D image 0 error!\n"
"2D image 1 error!\n"
"3D image 0 error!\n"
"3D image 1 error!\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::Trade::Test::ImageConverterTest)

23
src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt

@ -0,0 +1,23 @@
1D image 0:
Level 0: {1024} @ Astc10x10RGBAF (4.0 kB)
1D image 1:
Level 0: {16} @ RGBA8Snorm (0.1 kB)
Level 1: {8} @ RGBA8Snorm (0.0 kB)
1D image 2: Third 1D image just so there aren't two
Level 0: {4} @ Depth16Unorm (0.0 kB)
2D image 0: A very nice mipmapped 2D image
Level 0: {256, 128} @ RG16F (128.0 kB)
Level 1: {128, 64} @ RG16F (32.0 kB)
Level 2: {64, 32} @ RG16F (8.0 kB)
2D image 1:
Level 0: Array {4, 8} @ PvrtcRGB2bppUnorm (0.0 kB)
3D image 0:
Level 0: Array|CubeMap {16, 16, 12} @ R8Unorm (3.0 kB)
3D image 1: Volume kills!
Level 0: {16, 16, 16} @ R8Unorm (4.0 kB)
Level 1: {8, 8, 6} @ R8Unorm (2.0 kB)
3D image 2:
Level 0: Array {4, 1, 1} @ Bc1RGBSrgb (0.0 kB)
3D image 3:
Level 0: Array {1, 4, 1} @ R32F (0.0 kB, ExternallyOwned|Mutable)
Total image data size: 181.2 kB

42
src/Magnum/Trade/imageconverter.cpp

@ -595,47 +595,7 @@ no -C / --converter is specified, AnyImageConverter is used.)")
else
useColor = Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors;
std::size_t totalImageDataSize = 0;
for(const Trade::Implementation::ImageInfo& info: infos) {
Debug d{useColor};
if(info.level == 0) {
d << Debug::boldColor(Debug::Color::Default);
if(info.size.z()) d << "3D image";
else if(info.size.y()) d << "2D image";
else d << "1D image";
d << info.image << Debug::nospace << ":"
<< Debug::resetColor;
if(info.name) d << Debug::boldColor(Debug::Color::Yellow)
<< info.name << Debug::resetColor;
d << Debug::newline;
}
d << " Level" << info.level << Debug::nospace << ":";
if(info.flags.one) {
d << Debug::packed << Debug::color(Debug::Color::Cyan);
if(info.size.z()) d << info.flags.three;
else if(info.size.y()) d << info.flags.two;
else d << info.flags.one;
d << Debug::resetColor;
}
d << Debug::packed;
if(info.size.z()) d << info.size;
else if(info.size.y()) d << info.size.xy();
else d << Math::Vector<1, Int>(info.size.x());
d << Debug::color(Debug::Color::Blue) << "@" << Debug::resetColor;
d << Debug::packed;
if(info.compressed) d << Debug::color(Debug::Color::Yellow) << info.compressedFormat;
else d << Debug::color(Debug::Color::Cyan) << info.format;
d << Debug::resetColor << "(" << Debug::nospace << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB";
if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable))
d << Debug::nospace << "," << Debug::packed
<< Debug::color(Debug::Color::Green)
<< info.dataFlags << Debug::resetColor;
d << Debug::nospace << ")";
totalImageDataSize += info.dataSize;
}
if(!infos.isEmpty())
Debug{} << "Total image data size:" << Utility::format("{:.1f}", totalImageDataSize/1024.0f) << "kB";
Trade::Implementation::printImageInfo(useColor, infos, nullptr, nullptr, nullptr);
if(args.isSet("profile")) {
Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast<std::chrono::milliseconds>(importTime).count())/1.0e3f << "seconds";

Loading…
Cancel
Save