Browse Source

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.
pull/527/head
Vladimír Vondruš 5 years ago
parent
commit
e6d673c279
  1. 10
      doc/changelog.dox
  2. 10
      src/Magnum/Implementation/converterUtilities.h
  3. 4
      src/Magnum/MeshTools/sceneconverter.cpp
  4. 2
      src/Magnum/ShaderTools/shaderconverter.cpp
  5. 4
      src/Magnum/Trade/imageconverter.cpp
  6. 12
      src/MagnumPlugins/AnyAudioImporter/AnyImporter.cpp
  7. 12
      src/MagnumPlugins/AnyAudioImporter/AnyImporter.h
  8. 26
      src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp
  9. 9
      src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp
  10. 11
      src/MagnumPlugins/AnyImageConverter/AnyImageConverter.h
  11. 66
      src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp
  12. 6
      src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt
  13. 15
      src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake
  14. 17
      src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp
  15. 14
      src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h
  16. 78
      src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp
  17. 5
      src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt
  18. 15
      src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake
  19. BIN
      src/MagnumPlugins/AnyImageImporter/Test/depth32f-custom-channels.exr
  20. 9
      src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp
  21. 11
      src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h
  22. 68
      src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp
  23. 5
      src/MagnumPlugins/AnySceneConverter/Test/CMakeLists.txt
  24. 1
      src/MagnumPlugins/AnySceneConverter/Test/configure.h.cmake
  25. BIN
      src/MagnumPlugins/AnySceneConverter/Test/objectid.ply
  26. 9
      src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp
  27. 15
      src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h
  28. 64
      src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp
  29. 17
      src/MagnumPlugins/AnyShaderConverter/AnyConverter.cpp
  30. 18
      src/MagnumPlugins/AnyShaderConverter/AnyConverter.h
  31. 284
      src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp
  32. 3
      src/MagnumPlugins/AnyShaderConverter/Test/CMakeLists.txt
  33. 4
      src/MagnumPlugins/AnyShaderConverter/Test/version-not-first.glsl
  34. 69
      src/MagnumPlugins/Implementation/propagateConfiguration.h

10
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(),

10
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();
}

4
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 */

2
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 */

4
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)) {

12
src/MagnumPlugins/AnyAudioImporter/AnyImporter.cpp

@ -27,10 +27,13 @@
#include <Corrade/Containers/Array.h>
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/PluginManager/PluginMetadata.h>
#include <Corrade/Utility/Assert.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/String.h>
#include "MagnumPlugins/Implementation/propagateConfiguration.h"
namespace Magnum { namespace Audio {
AnyImporter::AnyImporter(PluginManager::Manager<AbstractImporter>& manager): AbstractImporter{manager} {}
@ -74,9 +77,16 @@ void AnyImporter::doOpenFile(const std::string& filename) {
return;
}
/* Instantiate the plugin */
Containers::Pointer<AbstractImporter> importer = static_cast<PluginManager::Manager<AbstractImporter>*>(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<AbstractImporter> importer = static_cast<PluginManager::Manager<AbstractImporter>*>(manager())->instantiate(plugin);
if(!importer->openFile(filename)) return;
/* Success, save the instance */

12
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:

26
src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp

@ -27,6 +27,7 @@
#include <Corrade/Containers/Array.h>
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/ConfigurationGroup.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/FormatStl.h>
@ -44,6 +45,9 @@ struct AnyImporterTest: TestSuite::Tester {
void unknown();
void propagateConfiguration();
void propagateConfigurationUnknown();
/* Explicitly forbid system-wide plugin dependencies */
PluginManager::Manager<AbstractImporter> _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<AbstractImporter> 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)

9
src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp

@ -34,6 +34,7 @@
#include <Corrade/Utility/String.h>
#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<AbstractImageConverter> converter = static_cast<PluginManager::Manager<AbstractImageConverter>*>(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);

11
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:

66
src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp

@ -27,6 +27,8 @@
#include <Corrade/Containers/StringStl.h>
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/File.h>
#include <Corrade/Utility/ConfigurationGroup.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/FormatStl.h>
@ -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<AbstractImageConverter> _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<AbstractImageConverter> 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<AbstractImageConverter> 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<AbstractImageConverter> 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)

6
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 $<TARGET_FILE*> expressions for iOS (see
@ -47,7 +49,9 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/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}/$<CONFIG>)
if(MAGNUM_ANYIMAGECONVERTER_BUILD_STATIC)
target_link_libraries(AnyImageConverterTest PRIVATE AnyImageConverter)

15
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

17
src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp

@ -35,6 +35,7 @@
#include <Corrade/Utility/String.h>
#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<AbstractImporter> importer = static_cast<PluginManager::Manager<AbstractImporter>*>(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<const char> 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<const char> data) {
Containers::Pointer<AbstractImporter> importer = static_cast<PluginManager::Manager<AbstractImporter>*>(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;

14
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:

78
src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp

@ -28,10 +28,13 @@
#include <Corrade/Containers/StringView.h>
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/ConfigurationGroup.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/FormatStl.h>
#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<AbstractImporter> _manager{"nonexistent"};
@ -64,7 +71,7 @@ constexpr struct {
const char* name;
const char* filename;
Containers::Optional<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, Containers::Array<char>&);
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<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, Containers::Array<char>&);
} 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<AbstractImporter> 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<AbstractImporter> importer = manager.instantiate("AnyImageImporter");
importer->configuration().setValue("layer", "left");
importer->configuration().setValue("depth", "height");
Containers::Array<char> storage;
importer->setFileCallback(data.callback, storage);
CORRADE_VERIFY(importer->openFile(data.filename));
Containers::Optional<Trade::ImageData2D> 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<AbstractImporter> importer = _manager.instantiate("AnyImageImporter");
importer->configuration().setValue("noSuchOption", "isHere");
Containers::Array<char> 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));
}
}}}}

5
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 $<TARGET_FILE*> expressions for iOS (see
@ -49,8 +51,9 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/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

15
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

BIN
src/MagnumPlugins/AnyImageImporter/Test/depth32f-custom-channels.exr

Binary file not shown.

9
src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp

@ -34,6 +34,7 @@
#include <Corrade/Utility/String.h>
#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<AbstractSceneConverter> converter = static_cast<PluginManager::Manager<AbstractSceneConverter>*>(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);

11
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:

68
src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp

@ -29,6 +29,7 @@
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/File.h>
#include <Corrade/Utility/ConfigurationGroup.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/FormatStl.h>
@ -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<AbstractSceneConverter> _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<AbstractSceneConverter> 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<AbstractSceneConverter> 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<AbstractSceneConverter> 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<AbstractSceneConverter> 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)

5
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 $<TARGET_FILE*> expressions for iOS (see
@ -51,7 +53,8 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/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}/$<CONFIG>)
if(MAGNUM_ANYSCENECONVERTER_BUILD_STATIC)
target_link_libraries(AnySceneConverterTest PRIVATE AnySceneConverter)

1
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

BIN
src/MagnumPlugins/AnySceneConverter/Test/objectid.ply

Binary file not shown.

9
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<AbstractImporter> importer = static_cast<PluginManager::Manager<AbstractImporter>*>(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;

15
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:

64
src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp

@ -28,6 +28,7 @@
#include <Corrade/Containers/Optional.h>
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/ConfigurationGroup.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/FormatStl.h>
@ -57,6 +58,8 @@ struct AnySceneImporterTest: TestSuite::Tester {
void unknown();
void propagateFlags();
void propagateConfiguration();
void propagateConfigurationUnknown();
/* Explicitly forbid system-wide plugin dependencies */
PluginManager::Manager<AbstractImporter> _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<AbstractImporter> 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<AbstractImporter> importer = manager.instantiate("AnySceneImporter");
{
CORRADE_VERIFY(importer->openFile(PLY_FILE));
Containers::Optional<Trade::MeshData> 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<Trade::MeshData> mesh = importer->mesh(0);
CORRADE_VERIFY(mesh);
CORRADE_VERIFY(mesh->hasAttribute(Trade::MeshAttribute::Normal));
}
}
void AnySceneImporterTest::propagateConfigurationUnknown() {
PluginManager::Manager<AbstractImporter> 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<AbstractImporter> 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)

17
src/MagnumPlugins/AnyShaderConverter/AnyConverter.cpp

@ -35,6 +35,8 @@
#include <Corrade/Utility/FormatStl.h>
#include <Corrade/Utility/String.h>
#include "MagnumPlugins/Implementation/propagateConfiguration.h"
namespace Magnum { namespace ShaderTools {
struct AnyConverter::State {
@ -223,6 +225,9 @@ std::pair<bool, Containers::String> 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<bool, Containers::String> 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<char> 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<char> 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);

18
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:

284
src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp

@ -29,6 +29,7 @@
#include <Corrade/Containers/StringStl.h>
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/ConfigurationGroup.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/FormatStl.h>
@ -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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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<AbstractConverter> 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);

3
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}/$<CONFIG>)
if(MAGNUM_ANYSHADERCONVERTER_BUILD_STATIC)
target_link_libraries(AnyShaderConverterTest PRIVATE AnyShaderConverter)

4
src/MagnumPlugins/AnyShaderConverter/Test/version-not-first.glsl

@ -0,0 +1,4 @@
#define a haha
#version 450
void main() {}

69
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š <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/Reference.h>
#include <Corrade/Utility/ConfigurationGroup.h>
#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<Containers::StringView, Containers::StringView> 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<Containers::StringView, Containers::Reference<const Utility::ConfigurationGroup>> 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
Loading…
Cancel
Save