diff --git a/src/Magnum/ShaderTools/AbstractConverter.cpp b/src/Magnum/ShaderTools/AbstractConverter.cpp index 8e70275d0..fd063cfa2 100644 --- a/src/Magnum/ShaderTools/AbstractConverter.cpp +++ b/src/Magnum/ShaderTools/AbstractConverter.cpp @@ -79,13 +79,16 @@ AbstractConverter::AbstractConverter(PluginManager::AbstractManager& manager, co ConverterFeatures AbstractConverter::features() const { const ConverterFeatures features = doFeatures(); - CORRADE_ASSERT(features & ~ConverterFeature::InputFileCallback, "ShaderTools::AbstractConverter::features(): implementation reported no features", {}); + CORRADE_ASSERT(features & ~(ConverterFeature::InputFileCallback|ConverterFeature::Preprocess), + "ShaderTools::AbstractConverter::features(): implementation reported no features", {}); return features; } void AbstractConverter::setFlags(const ConverterFlags flags) { - CORRADE_ASSERT(!(flags & ConverterFlag::Quiet) != !(flags & ConverterFlag::Verbose), - "ShaderTools::AbstractConverter::setFeatures(): can't have both Quiet and Verbose set", ); + CORRADE_ASSERT(!(flags >= (ConverterFlag::Quiet|ConverterFlag::Verbose)), + "ShaderTools::AbstractConverter::setFlags(): can't have both Quiet and Verbose set", ); + CORRADE_ASSERT((features() & ConverterFeature::Preprocess) || !(flags & ConverterFlag::PreprocessOnly), + "ShaderTools::AbstractConverter::setFlags(): PreprocessOnly not supported by the implementation", ); _flags = flags; doSetFlags(flags); } @@ -122,6 +125,20 @@ void AbstractConverter::setOutputFormat(const Format format) { return setOutputFormat(format, {}); } +void AbstractConverter::setDefinitions(const Containers::ArrayView> definitions) { + CORRADE_ASSERT(features() & ConverterFeature::Preprocess, + "ShaderTools::AbstractConverter::setDefinitions(): feature not supported", ); + doSetDefinitions(definitions); +} + +void AbstractConverter::setDefinitions(std::initializer_list> definitions) { + return setDefinitions(Containers::arrayView(definitions)); +} + +void AbstractConverter::doSetDefinitions(Containers::ArrayView>) { + CORRADE_ASSERT_UNREACHABLE("ShaderTools::AbstractConverter::setDefinitions(): feature advertised but not implemented", ); +} + std::pair AbstractConverter::validateData(const Stage stage, const Containers::ArrayView data) { CORRADE_ASSERT(features() & ConverterFeature::ValidateData, "ShaderTools::AbstractConverter::validateData(): feature not supported", {}); @@ -371,6 +388,8 @@ Containers::Array AbstractConverter::doConvertFileToData(const Stage stage Containers::Array AbstractConverter::linkDataToData(const Containers::ArrayView>> data) { CORRADE_ASSERT(features() >= ConverterFeature::LinkData, "ShaderTools::AbstractConverter::linkDataToData(): feature not supported", {}); + CORRADE_ASSERT(!(_flags & ConverterFlag::PreprocessOnly), + "ShaderTools::AbstractConverter::linkDataToData(): PreprocessOnly is not allowed in combination with linking", {}); CORRADE_ASSERT(!data.empty(), "ShaderTools::AbstractConverter::linkDataToData(): no data passed", {}); @@ -392,6 +411,8 @@ Containers::Array AbstractConverter::doLinkDataToData(Containers::ArrayVie bool AbstractConverter::linkDataToFile(const Containers::ArrayView>> data, const Containers::StringView to) { CORRADE_ASSERT(features() >= ConverterFeature::LinkData, "ShaderTools::AbstractConverter::linkDataToFile(): feature not supported", {}); + CORRADE_ASSERT(!(_flags & ConverterFlag::PreprocessOnly), + "ShaderTools::AbstractConverter::linkDataToFile(): PreprocessOnly is not allowed in combination with linking", {}); CORRADE_ASSERT(!data.empty(), "ShaderTools::AbstractConverter::linkDataToFile(): no data passed", {}); @@ -451,6 +472,8 @@ Containers::Array AbstractConverter::linkDataToDataUsingInputFileCallbacks bool AbstractConverter::linkFilesToFile(const Containers::ArrayView> from, const Containers::StringView to) { CORRADE_ASSERT(features() & (ConverterFeature::LinkFile|ConverterFeature::LinkData), "ShaderTools::AbstractConverter::linkFilesToFile(): feature not supported", {}); + CORRADE_ASSERT(!(_flags & ConverterFlag::PreprocessOnly), + "ShaderTools::AbstractConverter::linkFilesToFile(): PreprocessOnly is not allowed in combination with linking", {}); CORRADE_ASSERT(!from.empty(), "ShaderTools::AbstractConverter::linkFilesToFile(): no files passed", {}); @@ -539,6 +562,8 @@ bool AbstractConverter::doLinkFilesToFile(const Containers::ArrayView AbstractConverter::linkFilesToData(const Containers::ArrayView> from) { CORRADE_ASSERT(features() >= ConverterFeature::LinkData, "ShaderTools::AbstractConverter::linkFilesToData(): feature not supported", {}); + CORRADE_ASSERT(!(_flags & ConverterFlag::PreprocessOnly), + "ShaderTools::AbstractConverter::linkFilesToData(): PreprocessOnly is not allowed in combination with linking", {}); CORRADE_ASSERT(!from.empty(), "ShaderTools::AbstractConverter::linkFilesToData(): no files passed", {}); @@ -617,6 +642,7 @@ Debug& operator<<(Debug& debug, const ConverterFeature value) { _c(LinkData) _c(LinkFile) _c(InputFileCallback) + _c(Preprocess) #undef _c /* LCOV_EXCL_STOP */ } @@ -635,7 +661,9 @@ Debug& operator<<(Debug& debug, const ConverterFeatures value) { ConverterFeature::LinkData, /* Implied by LinkData, has to be after */ ConverterFeature::LinkFile, - ConverterFeature::InputFileCallback}); + ConverterFeature::InputFileCallback, + ConverterFeature::Preprocess + }); } Debug& operator<<(Debug& debug, const ConverterFlag value) { @@ -647,6 +675,7 @@ Debug& operator<<(Debug& debug, const ConverterFlag value) { _c(Quiet) _c(Verbose) _c(WarningAsError) + _c(PreprocessOnly) #undef _c /* LCOV_EXCL_STOP */ } @@ -658,7 +687,8 @@ Debug& operator<<(Debug& debug, const ConverterFlags value) { return Containers::enumSetDebugOutput(debug, value, "ShaderTools::ConverterFlags{}", { ConverterFlag::Quiet, ConverterFlag::Verbose, - ConverterFlag::WarningAsError + ConverterFlag::WarningAsError, + ConverterFlag::PreprocessOnly }); } diff --git a/src/Magnum/ShaderTools/AbstractConverter.h b/src/Magnum/ShaderTools/AbstractConverter.h index 85abd2109..002d5dd04 100644 --- a/src/Magnum/ShaderTools/AbstractConverter.h +++ b/src/Magnum/ShaderTools/AbstractConverter.h @@ -94,7 +94,13 @@ enum class ConverterFeature: UnsignedInt { * See @ref ShaderTools-AbstractConverter-usage-callbacks and particular * converter documentation for more information. */ - InputFileCallback = 1 << 6 + InputFileCallback = 1 << 6, + + /** + * Set preprocess definitions using @ref AbstractConverter::setDefinitions() + * and the @ref ConverterFlag::PreprocessOnly flag. + */ + Preprocess = 1 << 7 }; /** @@ -144,7 +150,17 @@ enum class ConverterFlag: UnsignedInt { * validation or conversion succeeds. With this flag set, it fails. * @see @ref ConverterFlag::Quiet */ - WarningAsError = 1 << 2 + WarningAsError = 1 << 2, + + /** + * Only run the preprocessor. Available only if the converter supports + * @ref ConverterFeature::Preprocess, not allowed in combination with + * @ref AbstractConverter::linkDataToData(), + * @ref AbstractConverter::linkDataToFile(), + * @ref AbstractConverter::linkFilesToFile() or + * @ref AbstractConverter::linkFilesToData(). + */ + PreprocessOnly = 1 << 3 }; /** @@ -574,6 +590,24 @@ class MAGNUM_SHADERTOOLS_EXPORT AbstractConverter: public PluginManager::Abstrac void setOutputFormat(Format format); #endif + /** + * @brief Set preprocessor definitions + * + * Available only if @ref ConverterFeature::Preprocess is supported. + * First string is macro name, second its value. If the second string + * is empty (but not @cpp nullptr @ce), it's the same as + * @cpp #define @ce without a value; if the second string is + * @cpp nullptr @ce, it's the same as @cpp #undef @ce. + * + * Calling this function replaces the previous set, calling it with an + * empty list will reset the definitions back to initial state. + * @see @ref ConverterFlag::PreprocessOnly + */ + void setDefinitions(Containers::ArrayView> definitions); + + /** @overload */ + void setDefinitions(std::initializer_list> definitions); + /** * @brief Validate a shader * @@ -662,7 +696,9 @@ class MAGNUM_SHADERTOOLS_EXPORT AbstractConverter: public PluginManager::Abstrac * * Available only if @ref ConverterFeature::LinkData is supported. On * failure the function prints an error message and returns - * @cpp nullptr @ce. + * @cpp nullptr @ce. Can't be called if + * @ref ConverterFlag::PreprocessOnly is set --- in that case + * @ref convertDataToData() has to be used instead. * @see @ref features() @ref linkDataToFile(), @ref linkFilesToFile() */ Containers::Array linkDataToData(Containers::ArrayView>> data); @@ -675,7 +711,9 @@ class MAGNUM_SHADERTOOLS_EXPORT AbstractConverter: public PluginManager::Abstrac * * Available only if @ref ConverterFeature::LinkData is supported. On * Returns @cpp true @ce on success, prints an error message and - * returns @cpp false @ce otherwise. + * returns @cpp false @ce otherwise. Can't be called if + * @ref ConverterFlag::PreprocessOnly is set --- in that case + * @ref convertDataToFile() has to be used instead. * @see @ref features(), @ref linkFilesToFile(), * @ref linkFilesToData(), @ref linkDataToData() */ @@ -690,7 +728,9 @@ class MAGNUM_SHADERTOOLS_EXPORT AbstractConverter: public PluginManager::Abstrac * Available only if @ref ConverterFeature::LinkFile or * @ref ConverterFeature::LinkData is supported. Returns @cpp true @ce * on success, prints an error message and returns @cpp false @ce - * otherwise. + * otherwise. Can't be called if @ref ConverterFlag::PreprocessOnly is + * set --- in that case @ref convertFileToFile() has to be used + * instead. * @see @ref features(), @ref linkFilesToData(), @ref linkDataToFile(), * @ref linkDataToData() */ @@ -704,7 +744,9 @@ class MAGNUM_SHADERTOOLS_EXPORT AbstractConverter: public PluginManager::Abstrac * * Available only if @ref ConverterFeature::LinkData is supported, On * failure the function prints an error message and returns - * @cpp nullptr @ce. + * @cpp nullptr @ce. Can't be called if + * @ref ConverterFlag::PreprocessOnly is set --- in that case + * @ref convertFileToData() has to be used instead. * @see @ref features(), @ref linkFilesToFile(), @ref linkDataToFile(), * @ref linkDataToData() */ @@ -862,6 +904,14 @@ class MAGNUM_SHADERTOOLS_EXPORT AbstractConverter: public PluginManager::Abstrac */ virtual void doSetOutputFormat(Format format, Containers::StringView version) = 0; + /** + * @brief Implementation for @ref setDefinitions() + * + * Has to be implemented if @ref ConverterFeature::Preprocess is + * supported. This function isn't expected to fail. + */ + virtual void doSetDefinitions(Containers::ArrayView> definitions); + /** * @brief Implementation for @ref validateData() * diff --git a/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp b/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp index fad87e037..a3cabc464 100644 --- a/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp +++ b/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp @@ -48,10 +48,16 @@ struct AbstractConverterTest: TestSuite::Tester { void setFlags(); void setFlagsBothQuietAndVerbose(); + void setFlagsPreprocessNotSupported(); + void setFlagsPreprocessOnlyNotAllowed(); void setFlagsNotImplemented(); void setInputOutputFormat(); + void setDefinitions(); + void setDefinitionsNotSupported(); + void setDefinitionsNotImplemented(); + void validateData(); void validateDataNotSupported(); void validateDataNotImplemented(); @@ -168,10 +174,16 @@ AbstractConverterTest::AbstractConverterTest() { &AbstractConverterTest::setFlags, &AbstractConverterTest::setFlagsBothQuietAndVerbose, + &AbstractConverterTest::setFlagsPreprocessNotSupported, + &AbstractConverterTest::setFlagsPreprocessOnlyNotAllowed, &AbstractConverterTest::setFlagsNotImplemented, &AbstractConverterTest::setInputOutputFormat, + &AbstractConverterTest::setDefinitions, + &AbstractConverterTest::setDefinitionsNotSupported, + &AbstractConverterTest::setDefinitionsNotImplemented, + &AbstractConverterTest::validateData, &AbstractConverterTest::validateDataNotSupported, &AbstractConverterTest::validateDataNotImplemented, @@ -294,7 +306,7 @@ void AbstractConverterTest::featuresNone() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { /* These aren't real features, so it should still complain */ - return ConverterFeature::InputFileCallback; + return ConverterFeature::InputFileCallback|ConverterFeature::Preprocess; } void doSetInputFormat(Format, Containers::StringView) override {} void doSetOutputFormat(Format, Containers::StringView) override {} @@ -347,6 +359,55 @@ void AbstractConverterTest::setFlagsBothQuietAndVerbose() { CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::setFlags(): can't have both Quiet and Verbose set\n"); } +void AbstractConverterTest::setFlagsPreprocessNotSupported() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ValidateData; + } + void doSetInputFormat(Format, Containers::StringView) override {} + void doSetOutputFormat(Format, Containers::StringView) override {} + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.setFlags(ConverterFlag::PreprocessOnly); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::setFlags(): PreprocessOnly not supported by the implementation\n"); +} + +void AbstractConverterTest::setFlagsPreprocessOnlyNotAllowed() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + /** @todo should Validate/Convert be enforced when Preprocess is + present? */ + return ConverterFeature::Preprocess|ConverterFeature::LinkData; + } + void doSetInputFormat(Format, Containers::StringView) override {} + void doSetOutputFormat(Format, Containers::StringView) override {} + } converter; + + converter.setFlags(ConverterFlag::PreprocessOnly); + + std::ostringstream out; + Error redirectError{&out}; + converter.linkDataToData({}); + converter.linkDataToFile({}, {}); + converter.linkFilesToFile({}, {}); + converter.linkFilesToData({}); + CORRADE_COMPARE(out.str(), + "ShaderTools::AbstractConverter::linkDataToData(): PreprocessOnly is not allowed in combination with linking\n" + "ShaderTools::AbstractConverter::linkDataToFile(): PreprocessOnly is not allowed in combination with linking\n" + "ShaderTools::AbstractConverter::linkFilesToFile(): PreprocessOnly is not allowed in combination with linking\n" + "ShaderTools::AbstractConverter::linkFilesToData(): PreprocessOnly is not allowed in combination with linking\n"); +} + void AbstractConverterTest::setFlagsNotImplemented() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { @@ -396,6 +457,67 @@ void AbstractConverterTest::setInputOutputFormat() { CORRADE_COMPARE(converter.outputVersion, ""); } +void AbstractConverterTest::setDefinitions() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::Preprocess|ConverterFeature::ValidateData; + } + void doSetInputFormat(Format, Containers::StringView) override {} + void doSetOutputFormat(Format, Containers::StringView) override {} + + void doSetDefinitions(Containers::ArrayView> definitions) override { + howManyIsThere = definitions.size(); + } + + std::size_t howManyIsThere = 0; + } converter; + + converter.setDefinitions({ + {"VULKAN", ""}, + {"LIGHT_COUNT", "3"}, + {"GL_ES", nullptr} + }); + CORRADE_COMPARE(converter.howManyIsThere, 3); +} + +void AbstractConverterTest::setDefinitionsNotSupported() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ValidateData; + } + void doSetInputFormat(Format, Containers::StringView) override {} + void doSetOutputFormat(Format, Containers::StringView) override {} + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.setDefinitions({}); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::setDefinitions(): feature not supported\n"); +} + +void AbstractConverterTest::setDefinitionsNotImplemented() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::Preprocess|ConverterFeature::ValidateData; + } + void doSetInputFormat(Format, Containers::StringView) override {} + void doSetOutputFormat(Format, Containers::StringView) override {} + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.setDefinitions({}); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::setDefinitions(): feature advertised but not implemented\n"); +} + void AbstractConverterTest::validateData() { struct: AbstractConverter { ConverterFeatures doFeatures() const override {