diff --git a/doc/changelog.dox b/doc/changelog.dox index 4244c58445..f793a0160a 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 906e8ac63b..b86090fb1b 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 be9639758d..e7c814b0cb 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 d0c2c02332..55d1c252b6 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 17a304e5ac..b261efd50d 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 677c6f3f9b..62334ea3d7 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 25e1ae3718..e54d85f7cd 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 a898cf7727..52a3aac32e 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 c92cbae8a6..5ea4570f4e 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 c2133b902f..ab3afded3f 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 07c14bb613..f518a5723e 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 86a88dded7..1a8d49da18 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 05a19d4088..4719ac5321 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 786dd3ff09..877852f55a 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 92d6f332d6..730adffcae 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 8179fdde4b..d210a440c6 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 03a5c386ac..1510bb3284 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 5380890bf1..a07bc6682d 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 0000000000..02b41f97a7 Binary files /dev/null and b/src/MagnumPlugins/AnyImageImporter/Test/depth32f-custom-channels.exr differ diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp index b6bd015f35..a96587dfcd 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp @@ -34,6 +34,7 @@ #include #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 4dce14d30a..82934b2d82 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 73c52eae1f..3e23616884 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 2672a88cac..38f58507cd 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 2d8e8e8de5..0fa2a7caea 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 0000000000..525034f3a5 Binary files /dev/null and b/src/MagnumPlugins/AnySceneConverter/Test/objectid.ply differ diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp index 00e78ff2c3..12d60b569c 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 312252b75c..836d504d67 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 62c1898aa2..888cc45fc8 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 130527699b..c5c7888db3 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 f1529dd534..a8c4dcc5df 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 08e862f5d4..dc4c463173 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 2c91fe1f52..87fa08ce51 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 0000000000..15197432d4 --- /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 0000000000..0bf903f179 --- /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