From e6d673c279ac8a7cc067899adb6e52048bc40660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 24 Jun 2021 15:18:44 +0200 Subject: [PATCH] Propagate configuration options in Any* plugins. Minor but very important convenience feature, especially useful when dealing with command-line apps. This now works: magnum-imageconverter a.png a.jpg -c jpegQuality=0.75 The AnyImageConverter gets the jpegQuality option and then automatically propagates it to the concrete plugin (which is either JpegImageConverter or StbImageConverter), possibly warning in case the target plugin doesn't recognize given option (i.e., doesn't list it in its default configuration). Previously the user had to always specify a concrete converter implementation using -C, which was rather annoying and nonintuitive. --- doc/changelog.dox | 10 + .../Implementation/converterUtilities.h | 10 +- src/Magnum/MeshTools/sceneconverter.cpp | 4 +- src/Magnum/ShaderTools/shaderconverter.cpp | 2 +- src/Magnum/Trade/imageconverter.cpp | 4 +- .../AnyAudioImporter/AnyImporter.cpp | 12 +- .../AnyAudioImporter/AnyImporter.h | 12 + .../Test/AnyAudioImporterTest.cpp | 26 +- .../AnyImageConverter/AnyImageConverter.cpp | 9 +- .../AnyImageConverter/AnyImageConverter.h | 11 + .../Test/AnyImageConverterTest.cpp | 66 +++- .../AnyImageConverter/Test/CMakeLists.txt | 6 +- .../AnyImageConverter/Test/configure.h.cmake | 15 + .../AnyImageImporter/AnyImageImporter.cpp | 17 +- .../AnyImageImporter/AnyImageImporter.h | 14 + .../Test/AnyImageImporterTest.cpp | 78 ++++- .../AnyImageImporter/Test/CMakeLists.txt | 5 +- .../AnyImageImporter/Test/configure.h.cmake | 15 + .../Test/depth32f-custom-channels.exr | Bin 0 -> 327 bytes .../AnySceneConverter/AnySceneConverter.cpp | 9 +- .../AnySceneConverter/AnySceneConverter.h | 11 + .../Test/AnySceneConverterTest.cpp | 68 ++++- .../AnySceneConverter/Test/CMakeLists.txt | 5 +- .../AnySceneConverter/Test/configure.h.cmake | 1 + .../AnySceneConverter/Test/objectid.ply | Bin 0 -> 249 bytes .../AnySceneImporter/AnySceneImporter.cpp | 9 +- .../AnySceneImporter/AnySceneImporter.h | 15 + .../Test/AnySceneImporterTest.cpp | 64 +++- .../AnyShaderConverter/AnyConverter.cpp | 17 ++ .../AnyShaderConverter/AnyConverter.h | 18 ++ .../Test/AnyConverterTest.cpp | 284 +++++++++++++++++- .../AnyShaderConverter/Test/CMakeLists.txt | 3 +- .../Test/version-not-first.glsl | 4 + .../Implementation/propagateConfiguration.h | 69 +++++ 34 files changed, 863 insertions(+), 30 deletions(-) create mode 100644 src/MagnumPlugins/AnyImageImporter/Test/depth32f-custom-channels.exr create mode 100644 src/MagnumPlugins/AnySceneConverter/Test/objectid.ply create mode 100644 src/MagnumPlugins/AnyShaderConverter/Test/version-not-first.glsl create mode 100644 src/MagnumPlugins/Implementation/propagateConfiguration.h diff --git a/doc/changelog.dox b/doc/changelog.dox index 4244c5844..f793a0160 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -336,6 +336,16 @@ See also: @subsubsection changelog-latest-changes-trade Trade library - Recognizing TIFF file header magic in @ref Trade::AnyImageImporter "AnyImageImporter" +- @ref Audio::AnyImporter "AnyAudioImporter", + @relativeref{Trade,AnyImageImporter}, @relativeref{Trade,AnyImageConverter}, + @relativeref{Trade,AnySceneImporter}, @relativeref{Trade,AnySceneConverter} + and @ref ShaderTools::AnyConverter "AnyShaderConverter" are now capable of + propagating configuration options to the concrete plugin, which is useful + mainly when using @ref magnum-imageconverter and other utilities as you can + now specify just the `-i` / `-c` options without having to specify which + plugin to apply the option to with `-I` / `-C`. For better usability, the + plugins also warn if the user specifies and option that is not present in + the target implementation. - Added @ref Trade::PhongMaterialData::hasCommonTextureTransformation(), @ref Trade::PhongMaterialData::ambientTextureMatrix(), @ref Trade::PhongMaterialData::diffuseTextureMatrix(), diff --git a/src/Magnum/Implementation/converterUtilities.h b/src/Magnum/Implementation/converterUtilities.h index 906e8ac63..b86090fb1 100644 --- a/src/Magnum/Implementation/converterUtilities.h +++ b/src/Magnum/Implementation/converterUtilities.h @@ -38,7 +38,7 @@ namespace Magnum { namespace Implementation { /* Used only in executables where we don't want it to be exported */ namespace { -void setOptions(PluginManager::AbstractPlugin& plugin, const std::string& options) { +void setOptions(PluginManager::AbstractPlugin& plugin, const std::string& anyPluginName, const std::string& options) { for(const std::string& option: Utility::String::splitWithoutEmptyParts(options, ',')) { auto keyValue = Utility::String::partition(option, '='); Utility::String::trimInPlace(keyValue[0]); @@ -60,8 +60,12 @@ void setOptions(PluginManager::AbstractPlugin& plugin, const std::string& option /* Provide a warning message in case the plugin doesn't define given option in its default config. The plugin is not *required* to have those tho (could be backward compatibility entries, for example), so - not an error. */ - if(groupNotRecognized || !group->hasValue(keyParts.back())) { + not an error. + + If it's an Any* plugin, then this check is provided by it directly, + and since the Any* plugin obviously don't expose the options of the concrete plugins, this warning would fire for them always, which + wouldn't help anything. */ + if((groupNotRecognized || !group->hasValue(keyParts.back())) && plugin.plugin() != anyPluginName) { Warning{} << "Option" << keyValue[0] << "not recognized by" << plugin.plugin(); } diff --git a/src/Magnum/MeshTools/sceneconverter.cpp b/src/Magnum/MeshTools/sceneconverter.cpp index be9639758..e7c814b0c 100644 --- a/src/Magnum/MeshTools/sceneconverter.cpp +++ b/src/Magnum/MeshTools/sceneconverter.cpp @@ -263,7 +263,7 @@ used.)") /* Set options, if passed */ if(args.isSet("verbose")) importer->addFlags(Trade::ImporterFlag::Verbose); - Implementation::setOptions(*importer, args.value("importer-options")); + Implementation::setOptions(*importer, "AnySceneImporter", args.value("importer-options")); std::chrono::high_resolution_clock::duration importTime; @@ -823,7 +823,7 @@ used.)") /* Set options, if passed */ if(args.isSet("verbose")) converter->addFlags(Trade::SceneConverterFlag::Verbose); if(i < args.arrayValueCount("converter-options")) - Implementation::setOptions(*converter, args.arrayValue("converter-options", i)); + Implementation::setOptions(*converter, "AnySceneConverter", args.arrayValue("converter-options", i)); /* This is the last --converter (or the implicit AnySceneConverter at the end), output to a file and exit the loop */ diff --git a/src/Magnum/ShaderTools/shaderconverter.cpp b/src/Magnum/ShaderTools/shaderconverter.cpp index d0c2c0233..55d1c252b 100644 --- a/src/Magnum/ShaderTools/shaderconverter.cpp +++ b/src/Magnum/ShaderTools/shaderconverter.cpp @@ -366,7 +366,7 @@ see documentation of a particular converter for more information.)") /* Set options if passed */ if(i < args.arrayValueCount("converter-options")) - Implementation::setOptions(*converter, args.arrayValue("converter-options", i)); + Implementation::setOptions(*converter, "AnyShaderConverter", args.arrayValue("converter-options", i)); /* Parse format, if passed. If --info is desired, implicitly set the output format to SPIR-V */ diff --git a/src/Magnum/Trade/imageconverter.cpp b/src/Magnum/Trade/imageconverter.cpp index 17a304e5a..b261efd50 100644 --- a/src/Magnum/Trade/imageconverter.cpp +++ b/src/Magnum/Trade/imageconverter.cpp @@ -245,7 +245,7 @@ key=true; configuration subgroups are delimited with /.)") /* Set options, if passed */ if(args.isSet("verbose")) importer->addFlags(Trade::ImporterFlag::Verbose); - Implementation::setOptions(*importer, args.value("importer-options")); + Implementation::setOptions(*importer, "AnyImageImporter", args.value("importer-options")); /* Print image info, if requested */ if(args.isSet("info")) { @@ -329,7 +329,7 @@ key=true; configuration subgroups are delimited with /.)") /* Set options, if passed */ if(args.isSet("verbose")) converter->addFlags(Trade::ImageConverterFlag::Verbose); - Implementation::setOptions(*converter, args.value("converter-options")); + Implementation::setOptions(*converter, "AnyImageConverter", args.value("converter-options")); /* Save output file */ if(!converter->convertToFile(*image, output)) { diff --git a/src/MagnumPlugins/AnyAudioImporter/AnyImporter.cpp b/src/MagnumPlugins/AnyAudioImporter/AnyImporter.cpp index 677c6f3f9..62334ea3d 100644 --- a/src/MagnumPlugins/AnyAudioImporter/AnyImporter.cpp +++ b/src/MagnumPlugins/AnyAudioImporter/AnyImporter.cpp @@ -27,10 +27,13 @@ #include #include +#include #include #include #include +#include "MagnumPlugins/Implementation/propagateConfiguration.h" + namespace Magnum { namespace Audio { AnyImporter::AnyImporter(PluginManager::Manager& manager): AbstractImporter{manager} {} @@ -74,9 +77,16 @@ void AnyImporter::doOpenFile(const std::string& filename) { return; } + /* Instantiate the plugin */ + Containers::Pointer importer = static_cast*>(manager())->instantiate(plugin); + + /* Propagate configuration */ + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); + Magnum::Implementation::propagateConfiguration("Audio::AnyImporter::openFile():", {}, metadata->name(), configuration(), importer->configuration()); + /* Try to open the file (error output should be printed by the plugin itself) */ - Containers::Pointer importer = static_cast*>(manager())->instantiate(plugin); if(!importer->openFile(filename)) return; /* Success, save the instance */ diff --git a/src/MagnumPlugins/AnyAudioImporter/AnyImporter.h b/src/MagnumPlugins/AnyAudioImporter/AnyImporter.h index 25e1ae371..e54d85f7c 100644 --- a/src/MagnumPlugins/AnyAudioImporter/AnyImporter.h +++ b/src/MagnumPlugins/AnyAudioImporter/AnyImporter.h @@ -99,6 +99,18 @@ target_link_libraries(your-app PRIVATE Magnum::AnyAudioImporter) @endcode See @ref building, @ref cmake and @ref plugins for more information. + +@section Audio-AnyImporter-proxy Interface proxying and option propagation + +On a call to @ref openFile(), a file format is detected from the extension and +a corresponding plugin is loaded. After that, options set through +@ref configuration() are propagated to the concrete implementation, with a +warning emitted in case given option is not present in the default +configuration of the target plugin. + +Calls to the @ref format(), @ref frequency() and @ref data() functions are then +proxied to the concrete implementation. The @ref close() function closes and +discards the internally instantiated plugin; @ref isOpened() works as usual. */ class MAGNUM_ANYAUDIOIMPORTER_EXPORT AnyImporter: public AbstractImporter { public: diff --git a/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp b/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp index a898cf772..52a3aac32 100644 --- a/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp +++ b/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -44,6 +45,9 @@ struct AnyImporterTest: TestSuite::Tester { void unknown(); + void propagateConfiguration(); + void propagateConfigurationUnknown(); + /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _manager{"nonexistent"}; }; @@ -72,7 +76,10 @@ AnyImporterTest::AnyImporterTest() { addInstancedTests({&AnyImporterTest::detect}, Containers::arraySize(DetectData)); - addTests({&AnyImporterTest::unknown}); + addTests({&AnyImporterTest::unknown, + + &AnyImporterTest::propagateConfiguration, + &AnyImporterTest::propagateConfigurationUnknown}); /* Load the plugin directly from the build tree. Otherwise it's static and already loaded. */ @@ -133,6 +140,23 @@ void AnyImporterTest::unknown() { CORRADE_COMPARE(output.str(), "Audio::AnyImporter::openFile(): cannot determine the format of sound.mid\n"); } +void AnyImporterTest::propagateConfiguration() { + CORRADE_SKIP("No importer has any configuration options to test."); +} + +void AnyImporterTest::propagateConfigurationUnknown() { + if(!(_manager.loadState("WavAudioImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("WavAudioImporter plugin not enabled, cannot test"); + + Containers::Pointer importer = _manager.instantiate("AnyAudioImporter"); + importer->configuration().setValue("noSuchOption", "isHere"); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(importer->openFile(WAV_FILE)); + CORRADE_COMPARE(out.str(), "Audio::AnyImporter::openFile(): option noSuchOption not recognized by WavAudioImporter\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Audio::Test::AnyImporterTest) diff --git a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp index c92cbae8a..5ea4570f4 100644 --- a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp +++ b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp @@ -34,6 +34,7 @@ #include #include "Magnum/Trade/ImageData.h" +#include "MagnumPlugins/Implementation/propagateConfiguration.h" namespace Magnum { namespace Trade { @@ -84,11 +85,12 @@ bool AnyImageConverter::doConvertToFile(const ImageView2D& image, const Containe Error{} << "Trade::AnyImageConverter::convertToFile(): cannot load the" << plugin << "plugin"; return false; } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & ImageConverterFlag::Verbose) { Debug d; d << "Trade::AnyImageConverter::convertToFile(): using" << plugin; - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -97,6 +99,9 @@ bool AnyImageConverter::doConvertToFile(const ImageView2D& image, const Containe Containers::Pointer converter = static_cast*>(manager())->instantiate(plugin); converter->setFlags(flags()); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnyImageConverter::convertToFile():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to convert the file (error output should be printed by the plugin itself) */ return converter->convertToFile(image, filename); diff --git a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.h b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.h index c2133b902..ab3afded3 100644 --- a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.h +++ b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.h @@ -104,6 +104,17 @@ target_link_libraries(your-app PRIVATE Magnum::AnyImageConverter) See @ref building, @ref cmake, @ref plugins and @ref file-formats for more information. + +@section Trade-AnyImageConverter-proxy Interface proxying and option propagation + +On a call to @ref convertToFile(), a target file format is detected from the +extension and a corresponding plugin is loaded. After that, flags set via +@ref setFlags() and options set through @ref configuration() are propagated to +the concrete implementation, with a warning emitted in case given option is not +present in the default configuration of the target plugin. + +The output of the @ref convertToFile() function called on the concrete +implementation is then proxied back. */ class MAGNUM_ANYIMAGECONVERTER_EXPORT AnyImageConverter: public AbstractImageConverter { public: diff --git a/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp b/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp index 07c14bb61..f518a5723 100644 --- a/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp +++ b/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -53,6 +55,12 @@ struct AnyImageConverterTest: TestSuite::Tester { void propagateFlags2D(); void propagateFlagsCompressed2D(); + void propagateConfiguration2D(); + void propagateConfigurationUnknown2D(); + void propagateConfigurationCompressed2D(); + void propagateConfigurationCompressedUnknown2D(); + /* configuration propagation fully tested in AnySceneImporter, as there the + plugins have configuration subgroups as well */ /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _manager{"nonexistent"}; @@ -94,7 +102,11 @@ AnyImageConverterTest::AnyImageConverterTest() { &AnyImageConverterTest::unknownCompressed2D, &AnyImageConverterTest::propagateFlags2D, - &AnyImageConverterTest::propagateFlagsCompressed2D}); + &AnyImageConverterTest::propagateFlagsCompressed2D, + &AnyImageConverterTest::propagateConfiguration2D, + &AnyImageConverterTest::propagateConfigurationUnknown2D, + &AnyImageConverterTest::propagateConfigurationCompressed2D, + &AnyImageConverterTest::propagateConfigurationCompressedUnknown2D}); /* Load the plugin directly from the build tree. Otherwise it's static and already loaded. */ @@ -204,6 +216,58 @@ void AnyImageConverterTest::propagateFlagsCompressed2D() { CORRADE_SKIP("No file formats to store compressed data yet."); } +void AnyImageConverterTest::propagateConfiguration2D() { + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "depth32f-custom-channels.exr"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + const Float Depth32fData[] = { + 0.125f, 0.250f, 0.375f, + 0.500f, 0.625f, 0.750f + }; + + const ImageView2D Depth32f{PixelFormat::Depth32F, {3, 2}, Depth32fData}; + + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + converter->configuration().setValue("layer", "left"); + converter->configuration().setValue("depth", "height"); + CORRADE_VERIFY(converter->convertToFile(Depth32f, filename)); + /* Compare to an expected output to ensure the custom channels names were + used */ + CORRADE_COMPARE_AS(filename, EXR_FILE, TestSuite::Compare::File); +} + +void AnyImageConverterTest::propagateConfigurationUnknown2D() { + if(!(_manager.loadState("TgaImageConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("TgaImageConverter plugin not enabled, cannot test"); + + /* Just test that the exported file exists */ + Containers::Pointer converter = _manager.instantiate("AnyImageConverter"); + converter->configuration().setValue("noSuchOption", "isHere"); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertToFile(Image, Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "output.tga"))); + CORRADE_COMPARE(out.str(), "Trade::AnyImageConverter::convertToFile(): option noSuchOption not recognized by TgaImageConverter\n"); +} + +void AnyImageConverterTest::propagateConfigurationCompressed2D() { + CORRADE_SKIP("No file formats to store compressed data yet."); +} + +void AnyImageConverterTest::propagateConfigurationCompressedUnknown2D() { + CORRADE_SKIP("No file formats to store compressed data yet."); +} + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::AnyImageConverterTest) diff --git a/src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt b/src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt index 86a88dded..1a8d49da1 100644 --- a/src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt @@ -25,8 +25,10 @@ if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(ANYIMAGECONVERTER_TEST_OUTPUT_DIR "write") + set(EXR_FILE depth32f-custom-channels.exr) else() set(ANYIMAGECONVERTER_TEST_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) + set(EXR_FILE ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/AnyImageImporter/Test/depth32f-custom-channels.exr) endif() # CMake before 3.8 has broken $ expressions for iOS (see @@ -47,7 +49,9 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h INPUT ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) corrade_add_test(AnyImageConverterTest AnyImageConverterTest.cpp - LIBRARIES MagnumTrade) + LIBRARIES MagnumTrade + FILES + ../../AnyImageImporter/Test/depth32f-custom-channels.exr) target_include_directories(AnyImageConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_ANYIMAGECONVERTER_BUILD_STATIC) target_link_libraries(AnyImageConverterTest PRIVATE AnyImageConverter) diff --git a/src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake b/src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake index 05a19d408..4719ac532 100644 --- a/src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake +++ b/src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake @@ -26,3 +26,18 @@ #cmakedefine ANYIMAGECONVERTER_PLUGIN_FILENAME "${ANYIMAGECONVERTER_PLUGIN_FILENAME}" #cmakedefine TGAIMAGECONVERTER_PLUGIN_FILENAME "${TGAIMAGECONVERTER_PLUGIN_FILENAME}" #define ANYIMAGECONVERTER_TEST_OUTPUT_DIR "${ANYIMAGECONVERTER_TEST_OUTPUT_DIR}" +#define EXR_FILE "${EXR_FILE}" + +#ifdef CORRADE_TARGET_WINDOWS +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_BINARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_RELEASE_BINARY_INSTALL_DIR}" +#endif +#else +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_LIBRARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_RELEASE_LIBRARY_INSTALL_DIR}" +#endif +#endif diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp index 786dd3ff0..877852f55 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp @@ -35,6 +35,7 @@ #include #include "Magnum/Trade/ImageData.h" +#include "MagnumPlugins/Implementation/propagateConfiguration.h" namespace Magnum { namespace Trade { @@ -124,11 +125,12 @@ void AnyImageImporter::doOpenFile(const std::string& filename) { Error{} << "Trade::AnyImageImporter::openFile(): cannot load the" << plugin << "plugin"; return; } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & ImporterFlag::Verbose) { Debug d; d << "Trade::AnyImageImporter::openFile(): using" << plugin; - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -137,6 +139,9 @@ void AnyImageImporter::doOpenFile(const std::string& filename) { Containers::Pointer importer = static_cast*>(manager())->instantiate(plugin); importer->setFlags(flags()); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnyImageImporter::openFile():", {}, metadata->name(), configuration(), importer->configuration()); + /* Try to open the file (error output should be printed by the plugin itself) */ if(!importer->openFile(filename)) return; @@ -219,11 +224,12 @@ void AnyImageImporter::doOpenData(Containers::ArrayView data) { Error{} << "Trade::AnyImageImporter::openData(): cannot load the" << plugin << "plugin"; return; } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & ImporterFlag::Verbose) { Debug d; d << "Trade::AnyImageImporter::openData(): using" << plugin; - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -232,6 +238,9 @@ void AnyImageImporter::doOpenData(Containers::ArrayView data) { Containers::Pointer importer = static_cast*>(manager())->instantiate(plugin); importer->setFlags(flags()); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnyImageImporter::openData():", {}, metadata->name(), configuration(), importer->configuration()); + /* Try to open the file (error output should be printed by the plugin itself) */ if(!importer->openData(data)) return; diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h index 92d6f332d..730adffca 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h @@ -127,6 +127,20 @@ target_link_libraries(your-app PRIVATE Magnum::AnyImageImporter) See @ref building, @ref cmake, @ref plugins and @ref file-formats for more information. + +@section Audio-AnyImageImporter-proxy Interface proxying and option propagation + +On a call to @ref openFile() / @ref openData(), a file format is detected from +the extension / file signature and a corresponding plugin is loaded. After +that, flags set via @ref setFlags() and options set through +@ref configuration() are propagated to the concrete implementation, with a +warning emitted in case given option is not present in the default +configuration of the target plugin. + +Calls to the @ref image2DCount(), @ref image2DLevelCount() and @ref image2D() +functions are then proxied to the concrete implementation. The @ref close() +function closes and discards the internally instantiated plugin; +@ref isOpened() works as usual. */ class MAGNUM_ANYIMAGEIMPORTER_EXPORT AnyImageImporter: public AbstractImporter { public: diff --git a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp index 8179fdde4..d210a440c 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp +++ b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp @@ -28,10 +28,13 @@ #include #include #include +#include #include #include #include +#include "Magnum/ImageView.h" +#include "Magnum/DebugTools/CompareImage.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" @@ -50,6 +53,10 @@ struct AnyImageImporterTest: TestSuite::Tester { void emptyData(); void propagateFlags(); + void propagateConfiguration(); + void propagateConfigurationUnknown(); + /* configuration propagation fully tested in AnySceneImporter, as there the + plugins have configuration subgroups as well */ /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _manager{"nonexistent"}; @@ -64,7 +71,7 @@ constexpr struct { const char* name; const char* filename; Containers::Optional>(*callback)(const std::string&, InputFileCallbackPolicy, Containers::Array&); - const char* verboseFunctionName; + const char* messageFunctionName; } LoadData[]{ {"TGA", TGA_FILE, nullptr, "openFile"}, {"TGA data", TGA_FILE, fileCallback, "openData"} @@ -116,6 +123,15 @@ const struct { {"TIFF, but no zero byte", "MM\xff\x2a"_s, "4d4dff2a"} }; +constexpr struct { + const char* name; + const char* filename; + Containers::Optional>(*callback)(const std::string&, InputFileCallbackPolicy, Containers::Array&); +} PropagateConfigurationData[]{ + {"EXR", EXR_FILE, nullptr}, + {"EXR data", EXR_FILE, fileCallback} +}; + AnyImageImporterTest::AnyImageImporterTest() { addInstancedTests({&AnyImageImporterTest::load}, Containers::arraySize(LoadData)); @@ -133,6 +149,12 @@ AnyImageImporterTest::AnyImageImporterTest() { addInstancedTests({&AnyImageImporterTest::propagateFlags}, Containers::arraySize(LoadData)); + addInstancedTests({&AnyImageImporterTest::propagateConfiguration}, + Containers::arraySize(PropagateConfigurationData)); + + addInstancedTests({&AnyImageImporterTest::propagateConfigurationUnknown}, + Containers::arraySize(LoadData)); + /* Load the plugin directly from the build tree. Otherwise it's static and already loaded. */ #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME @@ -244,7 +266,59 @@ void AnyImageImporterTest::propagateFlags() { CORRADE_COMPARE(out.str(), Utility::formatString( "Trade::AnyImageImporter::{}(): using TgaImporter\n" "Trade::TgaImporter::image2D(): converting from BGR to RGB\n", - data.verboseFunctionName)); + data.messageFunctionName)); +} + +void AnyImageImporterTest::propagateConfiguration() { + auto&& data = PropagateConfigurationData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImporter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImporter plugin can't be loaded."); + + Containers::Pointer importer = manager.instantiate("AnyImageImporter"); + importer->configuration().setValue("layer", "left"); + importer->configuration().setValue("depth", "height"); + + Containers::Array storage; + importer->setFileCallback(data.callback, storage); + CORRADE_VERIFY(importer->openFile(data.filename)); + + Containers::Optional image = importer->image2D(0); + CORRADE_VERIFY(image); + + /* Comparing image contents to verify the custom channels were used */ + const Float Depth32fData[] = { + 0.125f, 0.250f, 0.375f, + 0.500f, 0.625f, 0.750f + }; + const ImageView2D Depth32f{PixelFormat::Depth32F, {3, 2}, Depth32fData}; + CORRADE_COMPARE_AS(*image, Depth32f, + DebugTools::CompareImage); +} + +void AnyImageImporterTest::propagateConfigurationUnknown() { + auto&& data = LoadData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("TgaImporter plugin not enabled, cannot test"); + + Containers::Pointer importer = _manager.instantiate("AnyImageImporter"); + importer->configuration().setValue("noSuchOption", "isHere"); + + Containers::Array storage; + importer->setFileCallback(data.callback, storage); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(importer->openFile(data.filename)); + CORRADE_COMPARE(out.str(), Utility::formatString("Trade::AnyImageImporter::{}(): option noSuchOption not recognized by TgaImporter\n", data.messageFunctionName)); } }}}} diff --git a/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt b/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt index 03a5c386a..1510bb328 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt @@ -26,9 +26,11 @@ if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(TEST_FILE_DIR .) set(TGA_FILE rgb.tga) + set(EXR_FILE depth32f-custom-channels.exr) else() set(TEST_FILE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(TGA_FILE ${CMAKE_CURRENT_SOURCE_DIR}/rgb.tga) + set(EXR_FILE ${CMAKE_CURRENT_SOURCE_DIR}/depth32f-custom-channels.exr) endif() # CMake before 3.8 has broken $ expressions for iOS (see @@ -49,8 +51,9 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h INPUT ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) corrade_add_test(AnyImageImporterTest AnyImageImporterTest.cpp - LIBRARIES MagnumTrade + LIBRARIES MagnumTrade MagnumDebugTools FILES + depth32f-custom-channels.exr gray.jpg image.exr image.tiff diff --git a/src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake b/src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake index 5380890bf..a07bc6682 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake +++ b/src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake @@ -27,3 +27,18 @@ #cmakedefine TGAIMPORTER_PLUGIN_FILENAME "${TGAIMPORTER_PLUGIN_FILENAME}" #define TGA_FILE "${TGA_FILE}" #define TEST_FILE_DIR "${TEST_FILE_DIR}" +#define EXR_FILE "${EXR_FILE}" + +#ifdef CORRADE_TARGET_WINDOWS +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR}" +#endif +#else +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR}" +#endif +#endif diff --git a/src/MagnumPlugins/AnyImageImporter/Test/depth32f-custom-channels.exr b/src/MagnumPlugins/AnyImageImporter/Test/depth32f-custom-channels.exr new file mode 100644 index 0000000000000000000000000000000000000000..02b41f97a7b676d8095418163ae15b534b4ea546 GIT binary patch literal 327 zcmaitJr2S!422&*10y2~5*r(c9-swr03;UX(74o6NUFvvuyHhwMnWs`vta4PPx0sH zX1VJ10pz8!mW&U^uoq0jpP@n)CGn{gL?%|R6LNR1YVw|){qhkQXeH{vR=W$1?wT;h zakr}&6)DZWGV1o1Gi+&BYpU^faUT_5$!vT@x$Kp&Lt&f}4C4&<%;z*n7 #include "Magnum/Trade/ImageData.h" +#include "MagnumPlugins/Implementation/propagateConfiguration.h" namespace Magnum { namespace Trade { @@ -67,11 +68,12 @@ bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers:: Error{} << "Trade::AnySceneConverter::convertToFile(): cannot load the" << plugin << "plugin"; return false; } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & SceneConverterFlag::Verbose) { Debug d; d << "Trade::AnySceneConverter::convertToFile(): using" << plugin; - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -80,6 +82,9 @@ bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers:: Containers::Pointer converter = static_cast*>(manager())->instantiate(plugin); converter->setFlags(flags()); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnySceneConverter::convertToFile():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to convert the file (error output should be printed by the plugin itself) */ return converter->convertToFile(mesh, filename); diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h index 4dce14d30..82934b2d8 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h @@ -91,6 +91,17 @@ target_link_libraries(your-app PRIVATE Magnum::AnySceneConverter) See @ref building, @ref cmake, @ref plugins and @ref file-formats for more information. + +@section Trade-AnySceneConverter-proxy Interface proxying and option propagation + +On a call to @ref convertToFile(), a target file format is detected from the +extension and a corresponding plugin is loaded. After that, flags set via +@ref setFlags() and options set through @ref configuration() are propagated to +the concrete implementation, with a warning emitted in case given option is not +present in the default configuration of the target plugin. + +The output of the @ref convertToFile() function called on the concrete +implementation is then proxied back. */ class MAGNUM_ANYSCENECONVERTER_EXPORT AnySceneConverter: public AbstractSceneConverter { public: diff --git a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp index 73c52eae1..3e2361688 100644 --- a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp +++ b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,10 @@ struct AnySceneConverterTest: TestSuite::Tester { void unknown(); void propagateFlags(); + void propagateConfiguration(); + void propagateConfigurationUnknown(); + /* configuration propagation fully tested in AnySceneImporter, as there the + plugins have configuration subgroups as well */ /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _manager{"nonexistent"}; @@ -72,7 +77,9 @@ AnySceneConverterTest::AnySceneConverterTest() { addTests({&AnySceneConverterTest::unknown, - &AnySceneConverterTest::propagateFlags}); + &AnySceneConverterTest::propagateFlags, + &AnySceneConverterTest::propagateConfiguration, + &AnySceneConverterTest::propagateConfigurationUnknown}); /* Load the plugin directly from the build tree. Otherwise it's static and already loaded. */ @@ -180,6 +187,65 @@ void AnySceneConverterTest::propagateFlags() { CORRADE_SKIP("No plugin with verbose output available to test flag propagation."); } +void AnySceneConverterTest::propagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("StanfordSceneConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYSCENECONVERTER_TEST_OUTPUT_DIR, "file.ply"); + + const struct Data { + Vector3 position; + UnsignedInt objectId; + } data[] { + {{-0.5f, -0.5f, 0.0f}, 4678}, + {{ 0.5f, -0.5f, 0.0f}, 3232}, + {{ 0.0f, 0.5f, 0.0f}, 1536} + }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, {}, data, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::stridedArrayView(data).slice(&Data::position)}, + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, Containers::stridedArrayView(data).slice(&Data::objectId)}, + }}; + + Containers::Pointer converter = manager.instantiate("AnySceneConverter"); + converter->configuration().setValue("objectIdAttribute", "OID"); + CORRADE_VERIFY(converter->convertToFile(mesh, filename)); + /* Compare to an expected output to ensure the custom attribute name was + used */ + CORRADE_COMPARE_AS(filename, PLY_OBJECTID_FILE, TestSuite::Compare::File); +} + +void AnySceneConverterTest::propagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("StanfordSceneConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + const Vector3 positions[] { + {-0.5f, -0.5f, 0.0f}, + { 0.5f, -0.5f, 0.0f}, + { 0.0f, 0.5f, 0.0f} + }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Containers::Pointer converter = manager.instantiate("AnySceneConverter"); + converter->configuration().setValue("noSuchOption", "isHere"); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertToFile(mesh, Utility::Directory::join(ANYSCENECONVERTER_TEST_OUTPUT_DIR, "file.ply"))); + CORRADE_COMPARE(out.str(), "Trade::AnySceneConverter::convertToFile(): option noSuchOption not recognized by StanfordSceneConverter\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::AnySceneConverterTest) diff --git a/src/MagnumPlugins/AnySceneConverter/Test/CMakeLists.txt b/src/MagnumPlugins/AnySceneConverter/Test/CMakeLists.txt index 2672a88ca..38f58507c 100644 --- a/src/MagnumPlugins/AnySceneConverter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnySceneConverter/Test/CMakeLists.txt @@ -26,9 +26,11 @@ if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(ANYSCENECONVERTER_TEST_OUTPUT_DIR "write") set(PLY_FILE triangle.ply) + set(PLY_OBJECTID_FILE objectid.ply) else() set(ANYSCENECONVERTER_TEST_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) set(PLY_FILE ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/AnySceneImporter/Test/triangle.ply) + set(PLY_OBJECTID_FILE ${CMAKE_CURRENT_SOURCE_DIR}/objectid.ply) endif() # CMake before 3.8 has broken $ expressions for iOS (see @@ -51,7 +53,8 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h corrade_add_test(AnySceneConverterTest AnySceneConverterTest.cpp LIBRARIES MagnumTrade FILES - ../../AnySceneImporter/Test/triangle.ply) + ../../AnySceneImporter/Test/triangle.ply + objectid.ply) target_include_directories(AnySceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_ANYSCENECONVERTER_BUILD_STATIC) target_link_libraries(AnySceneConverterTest PRIVATE AnySceneConverter) diff --git a/src/MagnumPlugins/AnySceneConverter/Test/configure.h.cmake b/src/MagnumPlugins/AnySceneConverter/Test/configure.h.cmake index 2d8e8e8de..0fa2a7cae 100644 --- a/src/MagnumPlugins/AnySceneConverter/Test/configure.h.cmake +++ b/src/MagnumPlugins/AnySceneConverter/Test/configure.h.cmake @@ -26,6 +26,7 @@ #cmakedefine ANYSCENECONVERTER_PLUGIN_FILENAME "${ANYSCENECONVERTER_PLUGIN_FILENAME}" #define ANYSCENECONVERTER_TEST_OUTPUT_DIR "${ANYSCENECONVERTER_TEST_OUTPUT_DIR}" #define PLY_FILE "${PLY_FILE}" +#define PLY_OBJECTID_FILE "${PLY_OBJECTID_FILE}" #ifdef CORRADE_TARGET_WINDOWS #ifdef CORRADE_IS_DEBUG_BUILD diff --git a/src/MagnumPlugins/AnySceneConverter/Test/objectid.ply b/src/MagnumPlugins/AnySceneConverter/Test/objectid.ply new file mode 100644 index 0000000000000000000000000000000000000000..525034f3a5e9e439c72933683dbce8d6f68143c3 GIT binary patch literal 249 zcmZ8bK?=e!5L^{J1W&$TUmzAA;6(&aJ|V<(Lj!3V(o`B><4^pWYqb`77?>GmXD7HU zOcQIB@MgV=Svs4NBT2rr%44zH3vyJGPk5)8sK=g09!;&_U9uuoQWOC2`T@>cW&p=gKR1IOJ%Ba9>Z2Bn GCF2dBen`#$ literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp index 00e78ff2c..12d60b569 100644 --- a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp +++ b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp @@ -43,6 +43,7 @@ #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/SkinData.h" #include "Magnum/Trade/TextureData.h" +#include "MagnumPlugins/Implementation/propagateConfiguration.h" #ifdef MAGNUM_BUILD_DEPRECATED #define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ @@ -140,11 +141,12 @@ void AnySceneImporter::doOpenFile(const std::string& filename) { Error{} << "Trade::AnySceneImporter::openFile(): cannot load the" << plugin << "plugin"; return; } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & ImporterFlag::Verbose) { Debug d; d << "Trade::AnySceneImporter::openFile(): using" << plugin; - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -153,6 +155,9 @@ void AnySceneImporter::doOpenFile(const std::string& filename) { Containers::Pointer importer = static_cast*>(manager())->instantiate(plugin); importer->setFlags(flags()); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnySceneImporter::openFile():", {}, metadata->name(), configuration(), importer->configuration()); + /* Try to open the file (error output should be printed by the plugin itself) */ if(!importer->openFile(filename)) return; diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h index 312252b75..836d504d6 100644 --- a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h +++ b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h @@ -127,6 +127,21 @@ target_link_libraries(your-app PRIVATE Magnum::AnySceneImporter) See @ref building, @ref cmake, @ref plugins and @ref file-formats for more information. + +@section Trade-AnySceneImporter-proxy Interface proxying and option propagation + +On a call to @ref openFile(), a file format is detected from the extension and +a corresponding plugin is loaded. After that, flags set via @ref setFlags() and +options set through @ref configuration() are propagated to the concrete +implementation, with a warning emitted in case given option is not present in +the default configuration of the target plugin. + +Calls to the @ref animation(), @ref scene(), @ref light(), @ref camera(), +@ref object2D(), @ref object3D(), @ref skin2D(), @ref skin3D(), @ref mesh(), +@ref material(), @ref texture(), @ref image1D(), @ref image2D(), @ref image3D() +and corresponding count-/name-related functions are then proxied to the +concrete implementation. The @ref close() function closes and discards the +internally instantiated plugin; @ref isOpened() works as usual. */ class MAGNUM_ANYSCENEIMPORTER_EXPORT AnySceneImporter: public AbstractImporter { public: diff --git a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp index 62c1898aa..888cc45fc 100644 --- a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp +++ b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -57,6 +58,8 @@ struct AnySceneImporterTest: TestSuite::Tester { void unknown(); void propagateFlags(); + void propagateConfiguration(); + void propagateConfigurationUnknown(); /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _manager{"nonexistent"}; @@ -98,7 +101,9 @@ AnySceneImporterTest::AnySceneImporterTest() { addTests({&AnySceneImporterTest::unknown, - &AnySceneImporterTest::propagateFlags}); + &AnySceneImporterTest::propagateFlags, + &AnySceneImporterTest::propagateConfiguration, + &AnySceneImporterTest::propagateConfigurationUnknown}); /* Load the plugin directly from the build tree. Otherwise it's static and already loaded. */ @@ -209,6 +214,63 @@ void AnySceneImporterTest::propagateFlags() { CORRADE_COMPARE(out.str().substr(0, expected.size()), expected); } +void AnySceneImporterTest::propagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + #ifdef ANYSCENEIMPORTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("AssimpImporter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("AssimpImporter plugin can't be loaded."); + /* Ensure Assimp is used for PLY files and not our StanfordImporter */ + manager.setPreferredPlugins("StanfordImporter", {"AssimpImporter"}); + + Containers::Pointer importer = manager.instantiate("AnySceneImporter"); + + { + CORRADE_VERIFY(importer->openFile(PLY_FILE)); + + Containers::Optional mesh = importer->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_VERIFY(!mesh->hasAttribute(Trade::MeshAttribute::Normal)); + } { + importer->configuration().addGroup("postprocess")->setValue("GenNormals", true); + CORRADE_VERIFY(importer->openFile(PLY_FILE)); + + Containers::Optional mesh = importer->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_VERIFY(mesh->hasAttribute(Trade::MeshAttribute::Normal)); + } +} + +void AnySceneImporterTest::propagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + #ifdef ANYSCENEIMPORTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("AssimpImporter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("AssimpImporter plugin can't be loaded."); + /* Ensure Assimp is used for PLY files and not our StanfordImporter. This + thus also accidentally checks that correct plugin name (and not the + alias) is used in the warning messages. */ + manager.setPreferredPlugins("StanfordImporter", {"AssimpImporter"}); + + Containers::Pointer importer = manager.instantiate("AnySceneImporter"); + importer->configuration().setValue("noSuchOption", "isHere"); + importer->configuration().addGroup("postprocess"); + importer->configuration().group("postprocess")->setValue("notHere", false); + importer->configuration().group("postprocess")->addGroup("feh")->setValue("noHereNotEither", false); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(importer->openFile(PLY_FILE)); + CORRADE_COMPARE(out.str(), + "Trade::AnySceneImporter::openFile(): option noSuchOption not recognized by AssimpImporter\n" + "Trade::AnySceneImporter::openFile(): option postprocess/notHere not recognized by AssimpImporter\n" + "Trade::AnySceneImporter::openFile(): option postprocess/feh/noHereNotEither not recognized by AssimpImporter\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::AnySceneImporterTest) diff --git a/src/MagnumPlugins/AnyShaderConverter/AnyConverter.cpp b/src/MagnumPlugins/AnyShaderConverter/AnyConverter.cpp index 130527699..c5c7888db 100644 --- a/src/MagnumPlugins/AnyShaderConverter/AnyConverter.cpp +++ b/src/MagnumPlugins/AnyShaderConverter/AnyConverter.cpp @@ -35,6 +35,8 @@ #include #include +#include "MagnumPlugins/Implementation/propagateConfiguration.h" + namespace Magnum { namespace ShaderTools { struct AnyConverter::State { @@ -223,6 +225,9 @@ std::pair AnyConverter::doValidateFile(const Stage sta if(!_state->definitionViews.empty()) converter->setDefinitions(_state->definitionViews); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("ShaderTools::AnyConverter::validateFile():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to validate the file (error output should be printed by the plugin itself) */ return converter->validateFile(stage, filename); @@ -277,6 +282,9 @@ std::pair AnyConverter::doValidateData(const Stage sta if(!_state->definitionViews.empty()) converter->setDefinitions(_state->definitionViews); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("ShaderTools::AnyConverter::validateData():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to validate the data (error output should be printed by the plugin itself) */ return converter->validateData(stage, data); @@ -358,6 +366,9 @@ bool AnyConverter::doConvertFileToFile(const Stage stage, const Containers::Stri if(!_state->optimizationLevel.isEmpty()) converter->setOptimizationLevel(_state->optimizationLevel); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("ShaderTools::AnyConverter::convertFileToFile():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to convert the file (error output should be printed by the plugin itself) */ return converter->convertFileToFile(stage, from, to); @@ -440,6 +451,9 @@ Containers::Array AnyConverter::doConvertFileToData(const Stage stage, con if(!_state->optimizationLevel.isEmpty()) converter->setOptimizationLevel(_state->optimizationLevel); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("ShaderTools::AnyConverter::convertFileToData():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to convert the file (error output should be printed by the plugin itself) */ return converter->convertFileToData(stage, filename); @@ -520,6 +534,9 @@ Containers::Array AnyConverter::doConvertDataToData(const Stage stage, con if(!_state->optimizationLevel.isEmpty()) converter->setOptimizationLevel(_state->optimizationLevel); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("ShaderTools::AnyConverter::convertDataToData():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to convert the file (error output should be printed by the plugin itself) */ return converter->convertDataToData(stage, from); diff --git a/src/MagnumPlugins/AnyShaderConverter/AnyConverter.h b/src/MagnumPlugins/AnyShaderConverter/AnyConverter.h index f1529dd53..a8c4dcc5d 100644 --- a/src/MagnumPlugins/AnyShaderConverter/AnyConverter.h +++ b/src/MagnumPlugins/AnyShaderConverter/AnyConverter.h @@ -121,6 +121,24 @@ target_link_libraries(your-app PRIVATE Magnum::AnyShaderConverter) @endcode See @ref building, @ref cmake and @ref plugins for more information. + +@section ShaderTools-AnyConverter-proxy Interface proxying and option propagation + +On a call to @ref validateFile() / @ref validateData(), @ref convertFileToFile() +/ @ref convertFileToData() / @ref convertDataToData(), an input/output file +format is detected from either the extensions or taken from the +@ref setInputFormat() and @ref setOutputFormat() calls and a corresponding +plugin is loaded. After that, everything set via @ref setFlags(), +@ref setInputFormat(), @ref setOutputFormat(), @ref setDefinitions(), +@ref setDebugInfoLevel(), @ref setOptimizationLevel() is propagated to the +concrete implementation, with an error emitted in case the target plugin +doesn't support given feature. Options set through @ref configuration() are +propagated as well, with just a warning emitted in case given option is not +present in the default configuration of the target plugin. + +The output of the @ref validateFile() / @ref validateData(), +@ref convertFileToFile() / @ref convertFileToData() / @ref convertDataToData() +function called on the concrete implementation is then proxied back. */ class MAGNUM_ANYSHADERCONVERTER_EXPORT AnyConverter: public AbstractConverter { public: diff --git a/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp b/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp index 08e862f5d..dc4c46317 100644 --- a/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp +++ b/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,8 @@ struct AnyConverterTest: TestSuite::Tester { void validateFilePropagateInputVersion(); void validateFilePropagateOutputVersion(); void validateFilePropagatePreprocess(); + void validateFilePropagateConfiguration(); + void validateFilePropagateConfigurationUnknown(); void validateData(); void validateDataPluginLoadFailed(); @@ -62,6 +65,8 @@ struct AnyConverterTest: TestSuite::Tester { void validateDataPropagateInputVersion(); void validateDataPropagateOutputVersion(); void validateDataPropagatePreprocess(); + void validateDataPropagateConfiguration(); + void validateDataPropagateConfigurationUnknown(); void convertFileToFile(); void convertFileToFilePluginLoadFailed(); @@ -77,6 +82,8 @@ struct AnyConverterTest: TestSuite::Tester { void convertFileToFilePropagatePreprocess(); void convertFileToFilePropagateDebugInfo(); void convertFileToFilePropagateOptimization(); + void convertFileToFilePropagateConfiguration(); + void convertFileToFilePropagateConfigurationUnknown(); void convertFileToData(); void convertFileToDataPluginLoadFailed(); @@ -92,6 +99,8 @@ struct AnyConverterTest: TestSuite::Tester { void convertFileToDataPropagatePreprocess(); void convertFileToDataPropagateDebugInfo(); void convertFileToDataPropagateOptimization(); + void convertFileToDataPropagateConfiguration(); + void convertFileToDataPropagateConfigurationUnknown(); void convertDataToData(); void convertDataToDataPluginLoadFailed(); @@ -107,6 +116,8 @@ struct AnyConverterTest: TestSuite::Tester { void convertDataToDataPropagatePreprocess(); void convertDataToDataPropagateDebugInfo(); void convertDataToDataPropagateOptimization(); + void convertDataToDataPropagateConfiguration(); + void convertDataToDataPropagateConfigurationUnknown(); void detectValidate(); void detectValidateExplicitFormat(); @@ -151,6 +162,8 @@ AnyConverterTest::AnyConverterTest() { &AnyConverterTest::validateFilePropagateInputVersion, &AnyConverterTest::validateFilePropagateOutputVersion, &AnyConverterTest::validateFilePropagatePreprocess, + &AnyConverterTest::validateFilePropagateConfiguration, + &AnyConverterTest::validateFilePropagateConfigurationUnknown, &AnyConverterTest::validateData, &AnyConverterTest::validateDataPluginLoadFailed, @@ -161,6 +174,8 @@ AnyConverterTest::AnyConverterTest() { &AnyConverterTest::validateDataPropagateInputVersion, &AnyConverterTest::validateDataPropagateOutputVersion, &AnyConverterTest::validateDataPropagatePreprocess, + &AnyConverterTest::validateDataPropagateConfiguration, + &AnyConverterTest::validateDataPropagateConfigurationUnknown, &AnyConverterTest::convertFileToFile, &AnyConverterTest::convertFileToFilePluginLoadFailed, @@ -176,6 +191,8 @@ AnyConverterTest::AnyConverterTest() { &AnyConverterTest::convertFileToFilePropagatePreprocess, &AnyConverterTest::convertFileToFilePropagateDebugInfo, &AnyConverterTest::convertFileToFilePropagateOptimization, + &AnyConverterTest::convertFileToFilePropagateConfiguration, + &AnyConverterTest::convertFileToFilePropagateConfigurationUnknown, &AnyConverterTest::convertFileToData, &AnyConverterTest::convertFileToDataPluginLoadFailed, @@ -191,6 +208,8 @@ AnyConverterTest::AnyConverterTest() { &AnyConverterTest::convertFileToDataPropagatePreprocess, &AnyConverterTest::convertFileToDataPropagateDebugInfo, &AnyConverterTest::convertFileToDataPropagateOptimization, + &AnyConverterTest::convertFileToDataPropagateConfiguration, + &AnyConverterTest::convertFileToDataPropagateConfigurationUnknown, &AnyConverterTest::convertDataToData, &AnyConverterTest::convertDataToDataPluginLoadFailed, @@ -205,7 +224,9 @@ AnyConverterTest::AnyConverterTest() { &AnyConverterTest::convertDataToDataPropagateOutputVersion, &AnyConverterTest::convertDataToDataPropagatePreprocess, &AnyConverterTest::convertDataToDataPropagateDebugInfo, - &AnyConverterTest::convertDataToDataPropagateOptimization}); + &AnyConverterTest::convertDataToDataPropagateOptimization, + &AnyConverterTest::convertDataToDataPropagateConfiguration, + &AnyConverterTest::convertDataToDataPropagateConfigurationUnknown}); addInstancedTests({&AnyConverterTest::detectValidate}, Containers::arraySize(DetectValidateData)); @@ -398,6 +419,53 @@ void AnyConverterTest::validateFilePropagatePreprocess() { std::make_pair(true, Utility::formatString("WARNING: {}:10: 'different__but_also_wrong' : identifiers containing consecutive underscores (\"__\") are reserved", filename))); } +void AnyConverterTest::validateFilePropagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + + const std::string filename = Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "version-not-first.glsl"); + + { + CORRADE_COMPARE(converter->validateFile(Stage::Fragment, filename), + std::make_pair(false, Utility::formatString("ERROR: {}:2: '#version' : must occur first in shader \nERROR: 1 compilation errors. No code generated.", filename))); + } { + converter->configuration().setValue("permissive", true); + /* Lol stupid thing, apparently it has two differently worded messages + for the same thing? Dumpster fire. */ + CORRADE_COMPARE(converter->validateFile(Stage::Fragment, filename), + std::make_pair(true, "WARNING: 0:0: '#version' : Illegal to have non-comment, non-whitespace tokens before #version")); + } +} + +void AnyConverterTest::validateFilePropagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + converter->configuration().setValue("noSuchOption", "isHere"); + /* So it doesn't warn about anything */ + converter->setDefinitions({{"reserved__identifier", "sorry"}}); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_COMPARE(converter->validateFile(Stage::Fragment, Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.glsl")), + std::make_pair(true, "")); + CORRADE_COMPARE(out.str(), + "ShaderTools::AnyConverter::validateFile(): option noSuchOption not recognized by GlslangShaderConverter\n"); +} + void AnyConverterTest::validateData() { PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME @@ -577,6 +645,55 @@ void AnyConverterTest::validateDataPropagatePreprocess() { std::make_pair(true, "WARNING: 0:10: 'different__but_also_wrong' : identifiers containing consecutive underscores (\"__\") are reserved")); } +void AnyConverterTest::validateDataPropagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + converter->setInputFormat(Format::Glsl); + + const std::string filename = Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "version-not-first.glsl"); + + { + CORRADE_COMPARE(converter->validateData(Stage::Fragment, Utility::Directory::read(filename)), + std::make_pair(false, "ERROR: 0:2: '#version' : must occur first in shader \nERROR: 1 compilation errors. No code generated.")); + } { + converter->configuration().setValue("permissive", true); + /* Lol stupid thing, apparently it has two differently worded messages + for the same thing? Dumpster fire. */ + CORRADE_COMPARE(converter->validateData(Stage::Fragment, Utility::Directory::read(filename)), + std::make_pair(true, Utility::formatString("WARNING: 0:0: '#version' : Illegal to have non-comment, non-whitespace tokens before #version"))); + } +} + +void AnyConverterTest::validateDataPropagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + converter->setInputFormat(Format::Glsl); + converter->configuration().setValue("noSuchOption", "isHere"); + /* So it doesn't warn about anything */ + converter->setDefinitions({{"reserved__identifier", "sorry"}}); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_COMPARE(converter->validateData(Stage::Fragment, Utility::Directory::read(Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.glsl"))), + std::make_pair(true, "")); + CORRADE_COMPARE(out.str(), + "ShaderTools::AnyConverter::validateData(): option noSuchOption not recognized by GlslangShaderConverter\n"); +} + void AnyConverterTest::convertFileToFile() { PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME @@ -880,6 +997,59 @@ void AnyConverterTest::convertFileToFilePropagateOptimization() { "ShaderTools::SpirvToolsConverter::convertDataToData(): optimization level should be 0, 1, s, legalizeHlsl or empty but got 2\n"); } +void AnyConverterTest::convertFileToFilePropagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + + const std::string input = Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "version-not-first.glsl"); + const std::string output = Utility::Directory::join(ANYSHADERCONVERTER_TEST_OUTPUT_DIR, "file.spv"); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->convertFileToFile(Stage::Fragment, input, output)); + CORRADE_COMPARE(out.str(), + Utility::formatString("ShaderTools::GlslangConverter::convertDataToData(): compilation failed:\nERROR: {}:2: '#version' : must occur first in shader \nERROR: 1 compilation errors. No code generated.\n", input)); + } { + converter->configuration().setValue("permissive", true); + /* Lol stupid thing, apparently it has two differently worded messages + for the same thing? Dumpster fire. */ + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertFileToFile(Stage::Fragment, input, output)); + CORRADE_COMPARE(out.str(), + "ShaderTools::GlslangConverter::convertDataToData(): compilation succeeded with the following message:\nWARNING: 0:0: '#version' : Illegal to have non-comment, non-whitespace tokens before #version\n"); + } +} + +void AnyConverterTest::convertFileToFilePropagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + converter->configuration().setValue("noSuchOption", "isHere"); + /* So it doesn't warn about anything */ + converter->setDefinitions({{"reserved__identifier", "sorry"}}); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertFileToFile(Stage::Fragment, Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.glsl"), Utility::Directory::join(ANYSHADERCONVERTER_TEST_OUTPUT_DIR, "file.glsl"))); + CORRADE_COMPARE(out.str(), + "ShaderTools::AnyConverter::convertFileToFile(): option noSuchOption not recognized by GlslangShaderConverter\n"); +} + void AnyConverterTest::convertFileToData() { PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME @@ -1191,6 +1361,61 @@ void AnyConverterTest::convertFileToDataPropagateOptimization() { "ShaderTools::SpirvToolsConverter::convertDataToData(): optimization level should be 0, 1, s, legalizeHlsl or empty but got 2\n"); } +void AnyConverterTest::convertFileToDataPropagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + + converter->setOutputFormat(Format::Spirv); + + const std::string input = Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "version-not-first.glsl"); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->convertFileToData(Stage::Fragment, input)); + CORRADE_COMPARE(out.str(), + Utility::formatString("ShaderTools::GlslangConverter::convertDataToData(): compilation failed:\nERROR: {}:2: '#version' : must occur first in shader \nERROR: 1 compilation errors. No code generated.\n", input)); + } { + converter->configuration().setValue("permissive", true); + /* Lol stupid thing, apparently it has two differently worded messages + for the same thing? Dumpster fire. */ + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertFileToData(Stage::Fragment, input)); + CORRADE_COMPARE(out.str(), + "ShaderTools::GlslangConverter::convertDataToData(): compilation succeeded with the following message:\nWARNING: 0:0: '#version' : Illegal to have non-comment, non-whitespace tokens before #version\n"); + } +} + +void AnyConverterTest::convertFileToDataPropagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + converter->setOutputFormat(Format::Spirv); + converter->configuration().setValue("noSuchOption", "isHere"); + /* So it doesn't warn about anything */ + converter->setDefinitions({{"reserved__identifier", "sorry"}}); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertFileToData(Stage::Fragment, Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.glsl"))); + CORRADE_COMPARE(out.str(), + "ShaderTools::AnyConverter::convertFileToData(): option noSuchOption not recognized by GlslangShaderConverter\n"); +} + void AnyConverterTest::convertDataToData() { PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME @@ -1509,6 +1734,63 @@ void AnyConverterTest::convertDataToDataPropagateOptimization() { "ShaderTools::SpirvToolsConverter::convertDataToData(): optimization level should be 0, 1, s, legalizeHlsl or empty but got 2\n"); } +void AnyConverterTest::convertDataToDataPropagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + + converter->setInputFormat(Format::Glsl); + converter->setOutputFormat(Format::Spirv); + + const std::string input = Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "version-not-first.glsl"); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->convertDataToData(Stage::Fragment, Utility::Directory::read(input))); + CORRADE_COMPARE(out.str(), + "ShaderTools::GlslangConverter::convertDataToData(): compilation failed:\nERROR: 0:2: '#version' : must occur first in shader \nERROR: 1 compilation errors. No code generated.\n"); + } { + converter->configuration().setValue("permissive", true); + /* Lol stupid thing, apparently it has two differently worded messages + for the same thing? Dumpster fire. */ + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertDataToData(Stage::Fragment, Utility::Directory::read(input))); + CORRADE_COMPARE(out.str(), + "ShaderTools::GlslangConverter::convertDataToData(): compilation succeeded with the following message:\nWARNING: 0:0: '#version' : Illegal to have non-comment, non-whitespace tokens before #version\n"); + } +} + +void AnyConverterTest::convertDataToDataPropagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + converter->setInputFormat(Format::Glsl); + converter->setOutputFormat(Format::Spirv); + converter->configuration().setValue("noSuchOption", "isHere"); + /* So it doesn't warn about anything */ + converter->setDefinitions({{"reserved__identifier", "sorry"}}); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertDataToData(Stage::Fragment, Utility::Directory::read(Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.glsl")))); + CORRADE_COMPARE(out.str(), + "ShaderTools::AnyConverter::convertDataToData(): option noSuchOption not recognized by GlslangShaderConverter\n"); +} + void AnyConverterTest::detectValidate() { auto&& data = DetectValidateData[testCaseInstanceId()]; setTestCaseDescription(data.name); diff --git a/src/MagnumPlugins/AnyShaderConverter/Test/CMakeLists.txt b/src/MagnumPlugins/AnyShaderConverter/Test/CMakeLists.txt index 2c91fe1f5..87fa08ce5 100644 --- a/src/MagnumPlugins/AnyShaderConverter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnyShaderConverter/Test/CMakeLists.txt @@ -49,7 +49,8 @@ corrade_add_test(AnyShaderConverterTest AnyConverterTest.cpp LIBRARIES MagnumShaderTools FILES file.glsl - file.spv) + file.spv + version-not-first.glsl) target_include_directories(AnyShaderConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_ANYSHADERCONVERTER_BUILD_STATIC) target_link_libraries(AnyShaderConverterTest PRIVATE AnyShaderConverter) diff --git a/src/MagnumPlugins/AnyShaderConverter/Test/version-not-first.glsl b/src/MagnumPlugins/AnyShaderConverter/Test/version-not-first.glsl new file mode 100644 index 000000000..15197432d --- /dev/null +++ b/src/MagnumPlugins/AnyShaderConverter/Test/version-not-first.glsl @@ -0,0 +1,4 @@ +#define a haha +#version 450 + +void main() {} diff --git a/src/MagnumPlugins/Implementation/propagateConfiguration.h b/src/MagnumPlugins/Implementation/propagateConfiguration.h new file mode 100644 index 000000000..0bf903f17 --- /dev/null +++ b/src/MagnumPlugins/Implementation/propagateConfiguration.h @@ -0,0 +1,69 @@ +#ifndef Magnum_Implementation_propagateConfiguration_h +#define Magnum_Implementation_propagateConfiguration_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +#include "Magnum/Magnum.h" + +/* Used by Any* plugins to propagate configuration to the concrete + implementation. Assumes that the Any* plugin itself doesn't have any + configuration options and so propagates all groups and values that were + set, emitting a warning if the target doesn't have such option in its + default configuration. */ + +namespace Magnum { namespace Implementation { + +/* Used only in plugins where we don't want it to be exported */ +namespace { + +void propagateConfiguration(const char* warningPrefix, const Containers::String& groupPrefix, const Containers::StringView plugin, const Utility::ConfigurationGroup& src, Utility::ConfigurationGroup& dst) { + using namespace Containers::Literals; + + /* Propagate values */ + for(Containers::Pair value: src.values()) { + if(!dst.hasValue(value.first())) { + Warning{} << warningPrefix << "option" << "/"_s.joinWithoutEmptyParts({groupPrefix, value.first()}) << "not recognized by" << plugin; + } + + dst.setValue(value.first(), value.second()); + } + + /* Recursively propagate groups */ + for(Containers::Pair> group: src.groups()) { + Utility::ConfigurationGroup* dstGroup = dst.group(group.first()); + if(!dstGroup) dstGroup = dst.addGroup(group.first()); + propagateConfiguration(warningPrefix, "/"_s.joinWithoutEmptyParts({groupPrefix, group.first()}), plugin, group.second(), *dstGroup); + } +} + +} + +}} + +#endif