diff --git a/src/Magnum/ShaderTools/AbstractConverter.cpp b/src/Magnum/ShaderTools/AbstractConverter.cpp index 35fa24c2d..c59f9cb97 100644 --- a/src/Magnum/ShaderTools/AbstractConverter.cpp +++ b/src/Magnum/ShaderTools/AbstractConverter.cpp @@ -94,7 +94,7 @@ void AbstractConverter::setInputFileCallback(Containers::Optional 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(!data.empty(), + "ShaderTools::AbstractConverter::linkDataToData(): no data passed", {}); + + /* Cast to a non-void type for more convenience */ + Containers::Array out = doLinkDataToData(Containers::arrayCast>>(data)); + CORRADE_ASSERT(!out.deleter(), + "ShaderTools::AbstractConverter::linkDataToData(): implementation is not allowed to use a custom Array deleter", {}); + return out; +} + +Containers::Array AbstractConverter::linkDataToData(const std::initializer_list>> data) { + return linkDataToData(Containers::arrayView(data)); +} + +Containers::Array AbstractConverter::doLinkDataToData(Containers::ArrayView>>) { + CORRADE_ASSERT_UNREACHABLE("ShaderTools::AbstractConverter::linkDataToData(): feature advertised but not implemented", {}); +} + +bool AbstractConverter::linkDataToFile(const Containers::ArrayView>> data, const Containers::StringView to) { + CORRADE_ASSERT(features() >= ConverterFeature::LinkData, + "ShaderTools::AbstractConverter::linkDataToFile(): feature not supported", {}); + CORRADE_ASSERT(!data.empty(), + "ShaderTools::AbstractConverter::linkDataToFile(): no data passed", {}); + + /** @todo this needs expansion once output callbacks are supported as well */ + + /* Cast to a non-void type for more convenience */ + Containers::Array out = doLinkDataToData(Containers::arrayCast>>(data)); + if(!out) return false; + + if(!Utility::Directory::write(to, out)) { + Error{} << "ShaderTools::AbstractConverter::linkDataToFile(): cannot write to file" << to; + return false; + } + + return true; +} + +bool AbstractConverter::linkDataToFile(const std::initializer_list>> data, const Containers::StringView to) { + return linkDataToFile(Containers::arrayView(data), to); +} + +Containers::Array AbstractConverter::linkDataToDataUsingInputFileCallbacks(const char* const prefix, const Containers::ArrayView> from) { + Containers::Array>> data{Containers::NoInit, from.size()}; + + /* First load all files. Remember how many of these succeeded so we can + close them again after */ + std::size_t i; + for(i = 0; i != from.size(); ++i) { + const Containers::Optional> contents = _inputFileCallback(from[i].second, InputFileCallbackPolicy::LoadTemporary, _inputFileCallbackUserData); + if(!contents) break; + + data[i].first = from[i].first; + data[i].second = *contents; + } + + /* If all input files loaded successfully, process */ + Containers::Array out; + if(i == from.size()) out = doLinkDataToData(data); + + /* Close again all input files that loaded successfully */ + for(std::size_t ii = 0; ii != i; ++ii) + _inputFileCallback(from[ii].second, InputFileCallbackPolicy::Close, _inputFileCallbackUserData); + + /* Now that we have cleaned up correctly, it's time print the error message + if something didn't go well. IN this case doLinkDataToData() was not + called at all. */ + if(i != from.size()) { + Error{} << prefix << "cannot open file" << from[i].second; + return {}; + } + + /* Return the data. This could have failed too, but the error message got + printed by the implementation already */ + return out; +} + +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(!from.empty(), + "ShaderTools::AbstractConverter::linkFilesToFile(): no files passed", {}); + + /** @todo this needs expansion once output callbacks are supported as well */ + + /* If input file callbacks are not set or the converter supports handling + them directly, call into the implementation */ + if(!_inputFileCallback || (doFeatures() & ConverterFeature::InputFileCallback)) { + return doLinkFilesToFile(from, to); + + /* Otherwise, if converting data is supported, use the callback and pass + the data through to convertDataToData(). Mark the file as ready to be + closed once conversion is finished. */ + } else if(doFeatures() & ConverterFeature::LinkData) { + /* This needs to be duplicated here and in the doLinkFilesToFile() + implementation in order to support both following cases: + - plugins that don't support InputFileCallback but have their own + doLinkFilesToFile() implementation (callback needs to be used + here, because the base doLinkFilesToFile() implementation might + never get called) + - plugins that support InputFileCallback but want to delegate the + actual file loading to the default implementation (callback used + in the base doLinkFilesToFile() implementation, because this + branch is never taken in that case) */ + Containers::Array out = linkDataToDataUsingInputFileCallbacks("ShaderTools::AbstractConverter::linkFilesToFile():", from); + if(!out) return false; + + if(!Utility::Directory::write(to, out)) { + Error{} << "ShaderTools::AbstractConverter::linkFilesToFile(): cannot write to file" << to; + return false; + } + + return true; + + /* Shouldn't get here, the assert is fired already in setFileCallback() */ + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +bool AbstractConverter::linkFilesToFile(const std::initializer_list> from, const Containers::StringView to) { + return linkFilesToFile(Containers::arrayView(from), to); +} + +bool AbstractConverter::doLinkFilesToFile(const Containers::ArrayView> from, const Containers::StringView to) { + CORRADE_ASSERT(features() >= ConverterFeature::LinkData, "ShaderTools::AbstractConverter::linkFilesToFile(): feature advertised but not implemented", {}); + + /** @todo this needs expansion once output callbacks are supported as well */ + Containers::Array out; + + /* If callbacks are set, use them. This is the same implementation as in + convertFileToFile(), see the comment there for details. */ + if(_inputFileCallback) { + out = linkDataToDataUsingInputFileCallbacks("ShaderTools::AbstractConverter::linkFilesToFile():", from); + + /* Otherwise open the files directly */ + } else { + Containers::Array> fileData{from.size()}; + for(std::size_t i = 0; i != from.size(); ++i) { + if(!Utility::Directory::exists(from[i].second)) { + Error() << "ShaderTools::AbstractConverter::linkFilesToFile(): cannot open file" << from[i].second; + return {}; + } + + fileData[i] = Utility::Directory::read(from[i].second); + } + + /** @todo merge the allocations once we have an ArrayTuple */ + Containers::Array>> data{Containers::NoInit, from.size()}; + for(std::size_t i = 0; i != from.size(); ++i) { + data[i].first = from[i].first; + data[i].second = fileData[i]; + } + + out = doLinkDataToData(data); + } + + if(!out) return false; + + if(!Utility::Directory::write(to, out)) { + Error{} << "ShaderTools::AbstractConverter::linkFilesToFile(): cannot write to file" << to; + return false; + } + + return true; +} + +Containers::Array AbstractConverter::linkFilesToData(const Containers::ArrayView> from) { + CORRADE_ASSERT(features() >= ConverterFeature::LinkData, + "ShaderTools::AbstractConverter::linkFilesToData(): feature not supported", {}); + CORRADE_ASSERT(!from.empty(), + "ShaderTools::AbstractConverter::linkFilesToData(): no files passed", {}); + + Containers::Array out; + + /* If input file callbacks are not set or the converter supports handling + them directly, call into the implementation */ + if(!_inputFileCallback || (doFeatures() & ConverterFeature::InputFileCallback)) { + out = doLinkFilesToData(from); + + /* Otherwise use the callback and pass the data through to + convertDataToData(). Mark the file as ready to be closed once conversion + is finished. */ + } else { + /* This needs to be duplicated here and in the doConvertFileToData() + implementation in order to support both following cases: + - plugins that don't support InputFileCallback but have their own + doLinkFilesToData() implementation (callback needs to be used + here, because the base doLinkFilesToData() implementation might + never get called) + - plugins that support InputFileCallback but want to delegate the + actual file loading to the default implementation (callback used + in the base doLinkFilesToData() implementation, because this + branch is never taken in that case) */ + out = linkDataToDataUsingInputFileCallbacks("ShaderTools::AbstractConverter::linkFilesToData():", from); + } + + CORRADE_ASSERT(!out.deleter(), + "ShaderTools::AbstractConverter::linkFilesToData(): implementation is not allowed to use a custom Array deleter", {}); + return out; +} + +Containers::Array AbstractConverter::linkFilesToData(const std::initializer_list> from) { + return linkFilesToData(Containers::arrayView(from)); +} + +Containers::Array AbstractConverter::doLinkFilesToData(const Containers::ArrayView> from) { + /* If callbacks are set, use them. This is the same implementation as in + linkFilesToFile(), see the comment there for details. */ + if(_inputFileCallback) { + return linkDataToDataUsingInputFileCallbacks("ShaderTools::AbstractConverter::linkFilesToData():", from); + + /* Otherwise open the files directly */ + } else { + Containers::Array> fileData{from.size()}; + for(std::size_t i = 0; i != from.size(); ++i) { + if(!Utility::Directory::exists(from[i].second)) { + Error() << "ShaderTools::AbstractConverter::linkFilesToData(): cannot open file" << from[i].second; + return {}; + } + + fileData[i] = Utility::Directory::read(from[i].second); + } + + /** @todo merge the allocations once we have an ArrayTuple */ + Containers::Array>> data{Containers::NoInit, from.size()}; + for(std::size_t i = 0; i != from.size(); ++i) { + data[i].first = from[i].first; + data[i].second = fileData[i]; + } + + return doLinkDataToData(data); + } +} + Debug& operator<<(Debug& debug, const ConverterFeature value) { debug << "ShaderTools::ConverterFeature" << Debug::nospace; @@ -360,6 +596,8 @@ Debug& operator<<(Debug& debug, const ConverterFeature value) { _c(ValidateFile) _c(ConvertData) _c(ConvertFile) + _c(LinkData) + _c(LinkFile) _c(InputFileCallback) #undef _c /* LCOV_EXCL_STOP */ @@ -376,6 +614,9 @@ Debug& operator<<(Debug& debug, const ConverterFeatures value) { ConverterFeature::ConvertData, /* Implied by ConvertData, has to be after */ ConverterFeature::ConvertFile, + ConverterFeature::LinkData, + /* Implied by LinkData, has to be after */ + ConverterFeature::LinkFile, ConverterFeature::InputFileCallback}); } diff --git a/src/Magnum/ShaderTools/AbstractConverter.h b/src/Magnum/ShaderTools/AbstractConverter.h index ee7900356..78e8bce2c 100644 --- a/src/Magnum/ShaderTools/AbstractConverter.h +++ b/src/Magnum/ShaderTools/AbstractConverter.h @@ -70,6 +70,21 @@ enum class ConverterFeature: UnsignedInt { */ ConvertData = ConvertFile|(1 << 3), + /** + * Link shader files together and output a file with + * @ref AbstractConverter::linkFilesToFile() + */ + LinkFile = 1 << 4, + + /** + * Link shader data together and output a data with + * @ref AbstractConverter::linkDataToData() or any of the other + * @ref AbstractConverter::linkDataToFile(), + * @ref AbstractConverter::linkFilesToData() combinations. Implies + * @ref ConverterFeature::LinkFile. + */ + LinkData = LinkFile|(1 << 5), + /** * Specifying input file callbacks for additional files referenced from the * main file using @ref AbstractConverter::setInputFileCallback(). If the @@ -79,7 +94,7 @@ enum class ConverterFeature: UnsignedInt { * See @ref ShaderTools-AbstractConverter-usage-callbacks and particular * converter documentation for more information. */ - InputFileCallback = 1 << 4 + InputFileCallback = 1 << 6 }; /** @@ -151,7 +166,9 @@ enum class Stage: UnsignedInt { * Unspecified stage. When used in the * @ref AbstractConverter::validateFile(), * @ref AbstractConverter::convertFileToFile() "convertFileToFile()", - * @ref AbstractConverter::convertFileToData() "convertFileToData()" APIs, + * @ref AbstractConverter::convertFileToData() "convertFileToData()", + * @ref AbstractConverter::linkFilesToFile() "linkFilesToFile()" or + * @ref AbstractConverter::linkFilesToData() "linkFilesToData()" APIs, * particular plugins may attempt to detect the stage from filename, the * shader stage might also be encoded directly in certain formats. Leaving * the stage unspecified might limit validation and conversion @@ -204,16 +221,17 @@ linking. @subsection ShaderTools-AbstractConverter-usage-validation Shader validation -@subsection ShaderTools-AbstractConverter-usage-conversion Shader conversion +@subsection ShaderTools-AbstractConverter-usage-conversion Shader conversion and linking @subsection ShaderTools-AbstractConverter-usage-callbacks Loading shaders from memory, using file callbacks Besides loading shaders directly from the filesystem using @ref validateFile() -/ @ref convertFileToFile() like shown above, it's possible to use -@ref validateData(), @ref convertDataToData() and variants to load data from -memory. Note that the particular converter implementation has to support -@ref ConverterFeature::ValidateData / @ref ConverterFeature::ConvertData for -this method to work. +/ @ref convertFileToFile() / @ref linkFilesToFile() like shown above, it's +possible to use @ref validateData(), @ref convertDataToData(), +@ref linkDataToData() and variants to load data from memory. Note that the +particular converter implementation has to support +@ref ConverterFeature::ValidateData / @ref ConverterFeature::ConvertData / +@ref ConverterFeature::LinkData for this method to work. Textual shader sources sometimes @cpp #include @ce other sources and in that case you may want to intercept those references and load them in a custom way @@ -225,22 +243,25 @@ non-owning view on the loaded data or a @ref Corrade::Containers::NullOpt "Containers::NullOpt" to indicate the file loading failed. For example, validating a shader from compiled-in resources could look like below. Note that the input file callback affects -@ref validateFile() / @ref convertFileToFile() / @ref convertFileToData() as -well --- you don't have to load the top-level file manually and pass it to -@ref validateData() / @ref convertDataToData(), any converter supporting the +@ref validateFile() / @ref convertFileToFile() / @ref convertFileToData() / +@ref linkFilesToFile() / @ref linkFilesToData() as well --- you don't have to +load the top-level file manually and pass it to @ref validateData() / +@ref convertDataToData() / @ref linkDataToData(), any converter supporting the callback feature handles that correctly. @snippet MagnumShaderTools.cpp AbstractConverter-usage-callbacks For converters that don't support @ref ConverterFeature::InputFileCallback directly, the base @ref validateFile() / @ref convertFileToFile() / -@ref convertFileToData() implementations will use the file callback to pass -the loaded data through to @ref validateData() / @ref convertDataToData(), in -case the converter supports at least @ref ConverterFeature::ValidateData -/ @ref ConverterFeature::ConvertData. If the converter supports none of -@ref ConverterFeature::InputFileCallback, @ref ConverterFeature::ValidateData -or @ref ConverterFeature::ConvertData, @ref setInputFileCallback() doesn't -allow the callbacks to be set. +@ref convertFileToData() / @ref linkFilesToFile() / @ref linkFilesToData() +implementations will use the file callback to pass the loaded data through to +@ref validateData() / @ref convertDataToData() / @ref linkDataToData(), in case +the converter supports at least @ref ConverterFeature::ValidateData +/ @ref ConverterFeature::ConvertData / @ref ConverterFeature::LinkData. If the +converter supports none of @ref ConverterFeature::InputFileCallback, +@ref ConverterFeature::ValidateData, @ref ConverterFeature::ConvertData or +@ref ConverterFeature::LinkData, @ref setInputFileCallback() doesn't allow the +callbacks to be set. The input file callback signature is the same for @ref ShaderTools::AbstractConverter, @ref Trade::AbstractImporter and @@ -261,8 +282,9 @@ plugin module has been unloaded. The plugin needs to implement the @ref doFeatures() function and one or more of @ref doValidateData(), @ref doValidateFile(), @ref doConvertDataToData(), -@ref doConvertFileToData(), or @ref doConvertFileToFile() functions based on -what features are supported. +@ref doConvertFileToData(), @ref doConvertFileToFile(), +@ref doLinkDataToData(), @ref doLinkFilesToData() or @ref doLinkFilesToFile() +functions based on what features are supported. You don't need to do most of the redundant sanity checks, these things are checked by the implementation: @@ -275,6 +297,13 @@ checked by the implementation: called only if @ref ConverterFeature::ConvertData is supported. - The function @ref doConvertFileToFile() is called only if @ref ConverterFeature::ConvertFile is supported. +- Functions @ref doLinkDataToData() and @ref doLinkFilesToData() are + called only if @ref ConverterFeature::LinkData is supported. +- The function @ref doLinkFilesToFile() is called only if + @ref ConverterFeature::LinkFile is supported. +- Functions @ref doLinkDataToData(), @ref doLinkFilesToData() and + @ref doLinkFilesToFile() are called only if the data / file list passed is + non-empty. @m_class{m-block m-warning} @@ -355,11 +384,12 @@ class MAGNUM_SHADERTOOLS_EXPORT AbstractConverter: public PluginManager::Abstrac * @brief Set input file callback * * In case the converter supports @ref ConverterFeature::InputFileCallback, - * files opened through @ref validateFile(), @ref convertFileToData() - * and @ref convertFileToFile() will be loaded through the provided - * callback. Besides that, all external files referenced by the - * top-level file will be loaded through the callback function as well, - * usually on demand. The callback function gets a filename, + * files opened through @ref validateFile(), @ref convertFileToData(), + * @ref convertFileToFile(), @ref linkFilesToData() and + * @ref linkFilesToFile() will be loaded through the provided callback. + * Besides that, all external files referenced by the top-level file + * will be loaded through the callback function as well, usually on + * demand. The callback function gets a filename, * @ref InputFileCallbackPolicy and the @p userData pointer as input * and returns a non-owning view on the loaded data as output or a * @ref Corrade::Containers::NullOpt if loading failed --- because @@ -369,26 +399,31 @@ class MAGNUM_SHADERTOOLS_EXPORT AbstractConverter: public PluginManager::Abstrac * In case the converter doesn't support * @ref ConverterFeature::InputFileCallback but supports at least * @ref ConverterFeature::ValidateData / - * @ref ConverterFeature::ConvertData, a file opened through - * @ref validateFile(), @ref convertFileToData() or - * @ref convertFileToFile() will be internally loaded through the - * provided callback and then passed to @ref validateData() or - * @ref convertDataToData(). First the file is loaded with - * @ref InputFileCallbackPolicy::LoadTemporary passed to the callback, - * then the returned memory view is passed to @ref validateData() / - * @ref convertDataToData() (sidestepping the potential - * @ref validateFile() / @ref convertFileToFile() implementation of - * that particular converter) and after that the callback is called - * again with @ref InputFileCallbackPolicy::Close. In case you need a - * different behavior, use @ref validateData() / - * @ref convertDataToData() directly. + * @ref ConverterFeature::ConvertData / + * @ref ConverterFeature::LinkData, a file opened through + * @ref validateFile(), @ref convertFileToData(), + * @ref convertFileToFile(), @ref linkFilesToData() or + * @ref linkFilesToFile() will be internally loaded through the + * provided callback and then passed to @ref validateData(), + * @ref convertDataToData() or @ref linkDataToData(). First the file is + * loaded with @ref InputFileCallbackPolicy::LoadTemporary passed to + * the callback, then the returned memory view is passed to + * @ref validateData() / @ref convertDataToData() / + * @ref linkDataToData() (sidestepping the potential + * @ref validateFile() / @ref convertFileToFile() / + * @ref convertFileToData() / @ref linkFilesToFile() / + * @ref linkFilesToData() implementation of that particular converter) + * and after that the callback is called again with + * @ref InputFileCallbackPolicy::Close. In case you need a different + * behavior, use @ref validateData() / @ref convertDataToData() / + * @ref linkDataToData() directly. * * In case @p callback is @cpp nullptr @ce, the current callback (if * any) is reset. This function expects that the converter supports * either @ref ConverterFeature::InputFileCallback or at least one of * @ref ConverterFeature::ValidateData, - * @ref ConverterFeature::ConvertData. If a converter supports neither, - * callbacks can't be used. + * @ref ConverterFeature::ConvertData, @ref ConverterFeature::LinkData. + * If a converter supports neither, callbacks can't be used. * * Following is an example of setting up an input file callback for * fetching compiled-in resources from @ref Corrade::Utility::Resource. @@ -507,6 +542,62 @@ class MAGNUM_SHADERTOOLS_EXPORT AbstractConverter: public PluginManager::Abstrac */ Containers::Array convertFileToData(Stage stage, const Containers::StringView from); + /** + * @brief Link shader data together to a data + * + * Available only if @ref ConverterFeature::LinkData is supported. On + * failure the function prints an error message and returns + * @cpp nullptr @ce. + * @see @ref features() @ref linkDataToFile(), @ref linkFilesToFile() + */ + Containers::Array linkDataToData(Containers::ArrayView>> data); + + /** @overload */ + Containers::Array linkDataToData(std::initializer_list>> data); + + /** + * @brief Link shader data together to a file + * + * 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. + * @see @ref features(), @ref linkFilesToFile(), + * @ref linkFilesToData(), @ref linkDataToData() + */ + bool linkDataToFile(Containers::ArrayView>> data, Containers::StringView to); + + /** @overload */ + bool linkDataToFile(std::initializer_list>> data, Containers::StringView to); + + /** + * @brief Link shader files together to a file + * + * 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. + * @see @ref features(), @ref linkFilesToData(), @ref linkDataToFile(), + * @ref linkDataToData() + */ + bool linkFilesToFile(Containers::ArrayView> from, Containers::StringView to); + + /** @overload */ + bool linkFilesToFile(std::initializer_list> from, Containers::StringView to); + + /** + * @brief Link shader files together to a data + * + * Available only if @ref ConverterFeature::LinkData is supported, On + * failure the function prints an error message and returns + * @cpp nullptr @ce. + * @see @ref features(), @ref linkFilesToFile(), @ref linkDataToFile(), + * @ref linkDataToData() + */ + Containers::Array linkFilesToData(Containers::ArrayView> from); + + /** @overload */ + Containers::Array linkFilesToData(std::initializer_list> from); + protected: /** * @brief Implementation for @ref validateFile() @@ -559,6 +650,40 @@ class MAGNUM_SHADERTOOLS_EXPORT AbstractConverter: public PluginManager::Abstrac */ virtual Containers::Array doConvertFileToData(Stage stage, Containers::StringView from); + /** + * @brief Implementation for @ref linkFilesToFile() + * + * If @ref ConverterFeature::LinkData is supported, default + * implementation opens all files and calls @ref linkDataToData() with + * their contents It is allowed to call this function from your + * @ref doLinkFilesToFile() implementation --- in particular, this + * implementation will also correctly handle callbacks set through + * @ref setInputFileCallback(). + * + * This function is not called when file callbacks are set through + * @ref setInputFileCallback() and @ref ConverterFeature::InputFileCallback + * is not supported --- instead, file is loaded though the callback and + * data passed through to @ref doLinkDataToData(). + */ + virtual bool doLinkFilesToFile(Containers::ArrayView> from, Containers::StringView to); + + /** + * @brief Implementation for @ref linkFilesToData() + * + * Default implementation opens all files and calls + * @ref doLinkDataToData() with their contents --- you only need to + * implement this if you need to do extra work with file inputs. It is + * allowed to call this function from your @ref doLinkFilesToData() + * implementation --- in particular, this implementation will also + * correctly handle callbacks set through @ref setInputFileCallback(). + * + * This function is not called when file callbacks are set through + * @ref setInputFileCallback() and @ref ConverterFeature::InputFileCallback + * is not supported --- instead, file is loaded though the callback and + * data passed through to @ref doConvertDataToData(). + */ + virtual Containers::Array doLinkFilesToData(Containers::ArrayView> from); + private: /** * @brief Implementation for @ref features() @@ -618,6 +743,20 @@ class MAGNUM_SHADERTOOLS_EXPORT AbstractConverter: public PluginManager::Abstrac */ virtual Containers::Array doConvertDataToData(Stage stage, Containers::ArrayView data); + /* Used by linkFilesToFile(), doLinkFilesToFile(), linkFilesToData() + and doLinkFilesToData() */ + MAGNUM_SHADERTOOLS_LOCAL Containers::Array linkDataToDataUsingInputFileCallbacks(const char* prefix, Containers::ArrayView> from); + + /** + * @brief Implementation for @ref linkDataToData() + * + * Has to be implemented if @ref ConverterFeature::LinkData is + * supported. While @ref linkDataToData() uses a @cpp void @ce view in + * order to accept any type, this function gets it cast to + * @cpp char @ce for more convenience. + */ + virtual Containers::Array doLinkDataToData(Containers::ArrayView>> data); + ConverterFlags _flags; Containers::Optional>(*_inputFileCallback)(const std::string&, InputFileCallbackPolicy, void*){}; diff --git a/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp b/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp index 7068f48bf..3fb5bbbd7 100644 --- a/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp +++ b/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp @@ -85,6 +85,34 @@ struct AbstractConverterTest: TestSuite::Tester { void convertFileToDataNotImplemented(); void convertFileToDataCustomDeleter(); + void linkDataToData(); + void linkDataToDataNotSupported(); + void linkDataToDataNotImplemented(); + void linkDataToDataNoData(); + void linkDataToDataCustomDeleter(); + void linkDataToFileThroughData(); + void linkDataToFileThroughDataFailed(); + void linkDataToFileThroughDataNotWritable(); + void linkDataToFileNotSupported(); + void linkDataToFileNotImplemented(); + void linkDataToFileNoData(); + + void linkFilesToFile(); + void linkFilesToFileThroughData(); + void linkFilesToFileThroughDataNotFound(); + void linkFilesToFileThroughDataFailed(); + void linkFilesToFileThroughDataNotWritable(); + void linkFilesToFileNotSupported(); + void linkFilesToFileNotImplemented(); + void linkFilesToFileNoFile(); + void linkFilesToData(); + void linkFilesToDataAsData(); + void linkFilesToDataAsDataNotFound(); + void linkFilesToDataNotSupported(); + void linkFilesToDataNotImplemented(); + void linkFilesToDataNoFile(); + void linkFilesToDataCustomDeleter(); + void setInputFileCallback(); void setInputFileCallbackTemplate(); void setInputFileCallbackTemplateNull(); @@ -111,6 +139,19 @@ struct AbstractConverterTest: TestSuite::Tester { void setInputFileCallbackConvertFileToDataAsData(); void setInputFileCallbackConvertFileToDataAsDataFailed(); + void setInputFileCallbackLinkFilesToFileDirectly(); + void setInputFileCallbackLinkFilesToFileThroughBaseImplementation(); + void setInputFileCallbackLinkFilesToFileThroughBaseImplementationFailed(); + void setInputFileCallbackLinkFilesToFileAsData(); + void setInputFileCallbackLinkFilesToFileAsDataFailed(); + void setInputFileCallbackLinkFilesToFileAsDataNotWritable(); + + void setInputFileCallbackLinkFilesToDataDirectly(); + void setInputFileCallbackLinkFilesToDataThroughBaseImplementation(); + void setInputFileCallbackLinkFilesToDataThroughBaseImplementationFailed(); + void setInputFileCallbackLinkFilesToDataAsData(); + void setInputFileCallbackLinkFilesToDataAsDataFailed(); + void debugFeature(); void debugFeatures(); void debugFlag(); @@ -160,6 +201,34 @@ AbstractConverterTest::AbstractConverterTest() { &AbstractConverterTest::convertFileToDataNotImplemented, &AbstractConverterTest::convertFileToDataCustomDeleter, + &AbstractConverterTest::linkDataToData, + &AbstractConverterTest::linkDataToDataNotSupported, + &AbstractConverterTest::linkDataToDataNotImplemented, + &AbstractConverterTest::linkDataToDataNoData, + &AbstractConverterTest::linkDataToDataCustomDeleter, + &AbstractConverterTest::linkDataToFileThroughData, + &AbstractConverterTest::linkDataToFileThroughDataFailed, + &AbstractConverterTest::linkDataToFileThroughDataNotWritable, + &AbstractConverterTest::linkDataToFileNotSupported, + &AbstractConverterTest::linkDataToFileNotImplemented, + &AbstractConverterTest::linkDataToFileNoData, + + &AbstractConverterTest::linkFilesToFile, + &AbstractConverterTest::linkFilesToFileThroughData, + &AbstractConverterTest::linkFilesToFileThroughDataNotFound, + &AbstractConverterTest::linkFilesToFileThroughDataFailed, + &AbstractConverterTest::linkFilesToFileThroughDataNotWritable, + &AbstractConverterTest::linkFilesToFileNotSupported, + &AbstractConverterTest::linkFilesToFileNotImplemented, + &AbstractConverterTest::linkFilesToFileNoFile, + &AbstractConverterTest::linkFilesToData, + &AbstractConverterTest::linkFilesToDataAsData, + &AbstractConverterTest::linkFilesToDataAsDataNotFound, + &AbstractConverterTest::linkFilesToDataNotSupported, + &AbstractConverterTest::linkFilesToDataNotImplemented, + &AbstractConverterTest::linkFilesToDataNoFile, + &AbstractConverterTest::linkFilesToDataCustomDeleter, + &AbstractConverterTest::setInputFileCallback, &AbstractConverterTest::setInputFileCallbackTemplate, &AbstractConverterTest::setInputFileCallbackTemplateNull, @@ -186,6 +255,19 @@ AbstractConverterTest::AbstractConverterTest() { &AbstractConverterTest::setInputFileCallbackConvertFileToDataAsData, &AbstractConverterTest::setInputFileCallbackConvertFileToDataAsDataFailed, + &AbstractConverterTest::setInputFileCallbackLinkFilesToFileDirectly, + &AbstractConverterTest::setInputFileCallbackLinkFilesToFileThroughBaseImplementation, + &AbstractConverterTest::setInputFileCallbackLinkFilesToFileThroughBaseImplementationFailed, + &AbstractConverterTest::setInputFileCallbackLinkFilesToFileAsData, + &AbstractConverterTest::setInputFileCallbackLinkFilesToFileAsDataFailed, + &AbstractConverterTest::setInputFileCallbackLinkFilesToFileAsDataNotWritable, + + &AbstractConverterTest::setInputFileCallbackLinkFilesToDataDirectly, + &AbstractConverterTest::setInputFileCallbackLinkFilesToDataThroughBaseImplementation, + &AbstractConverterTest::setInputFileCallbackLinkFilesToDataThroughBaseImplementationFailed, + &AbstractConverterTest::setInputFileCallbackLinkFilesToDataAsData, + &AbstractConverterTest::setInputFileCallbackLinkFilesToDataAsDataFailed, + &AbstractConverterTest::debugFeature, &AbstractConverterTest::debugFeatures, &AbstractConverterTest::debugFlag, @@ -850,245 +932,1201 @@ void AbstractConverterTest::convertFileToDataCustomDeleter() { CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::convertFileToData(): implementation is not allowed to use a custom Array deleter\n"); } -void AbstractConverterTest::setInputFileCallback() { +void AbstractConverterTest::linkDataToData() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData; + return ConverterFeature::LinkData; } - void doSetInputFileCallback(Containers::Optional>(*)(const std::string&, InputFileCallbackPolicy, void*), void* userData) override { - *static_cast(userData) = 1337; - } - } converter; - - int a = 0; - auto lambda = [](const std::string&, InputFileCallbackPolicy, void*) { - return Containers::Optional>{}; - }; - converter.setInputFileCallback(lambda, &a); - CORRADE_COMPARE(converter.inputFileCallback(), lambda); - CORRADE_COMPARE(converter.inputFileCallbackUserData(), &a); - CORRADE_COMPARE(a, 1337); -} -void AbstractConverterTest::setInputFileCallbackTemplate() { - struct: AbstractConverter { - ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData; - } - void doSetInputFileCallback(Containers::Optional>(*)(const std::string&, InputFileCallbackPolicy, void*), void*) override { - called = true; + Containers::Array doLinkDataToData(Containers::ArrayView>> data) override { + CORRADE_COMPARE(data.size(), 2); + return Containers::array({ + data[0].first == Stage::Vertex ? data[0].second[0] : ' ', + data[1].first == Stage::Fragment ? data[1].second[0] : ' ' + }); } - - bool called = false; } converter; - int a = 0; - auto lambda = [](const std::string&, InputFileCallbackPolicy, int&) { - return Containers::Optional>{}; - }; - converter.setInputFileCallback(lambda, a); - CORRADE_VERIFY(converter.inputFileCallback()); - CORRADE_VERIFY(converter.inputFileCallbackUserData()); - CORRADE_VERIFY(converter.called); + CORRADE_VERIFY(true); /* so it picks up correct test case name */ - /* The data pointers should be wrapped, thus not the same */ - CORRADE_VERIFY(reinterpret_cast(converter.inputFileCallback()) != reinterpret_cast(static_cast>(*)(const std::string&, InputFileCallbackPolicy, int&)>(lambda))); - CORRADE_VERIFY(converter.inputFileCallbackUserData() != &a); + Containers::Array out = converter.linkDataToData({ + {Stage::Vertex, Containers::arrayView({'V', 'E'})}, + {Stage::Fragment, Containers::arrayView({'S', 'A'})} + }); + CORRADE_COMPARE_AS(out, Containers::arrayView({'V', 'S'}), + TestSuite::Compare::Container); } -void AbstractConverterTest::setInputFileCallbackTemplateNull() { +void AbstractConverterTest::linkDataToDataNotSupported() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData; - } - void doSetInputFileCallback(Containers::Optional>(*callback)(const std::string&, InputFileCallbackPolicy, void*), void* userData) override { - called = !callback && !userData; + return ConverterFeature::LinkFile; } - - bool called = false; } converter; - int a = 0; - converter.setInputFileCallback(static_cast>(*)(const std::string&, InputFileCallbackPolicy, int&)>(nullptr), a); - CORRADE_VERIFY(!converter.inputFileCallback()); - CORRADE_VERIFY(!converter.inputFileCallbackUserData()); - CORRADE_VERIFY(converter.called); + std::ostringstream out; + Error redirectError{&out}; + converter.linkDataToData({}); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkDataToData(): feature not supported\n"); } -void AbstractConverterTest::setInputFileCallbackTemplateConst() { +void AbstractConverterTest::linkDataToDataNotImplemented() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData; - } - void doSetInputFileCallback(Containers::Optional>(*)(const std::string&, InputFileCallbackPolicy, void*), void*) override { - called = true; + return ConverterFeature::LinkData; } - - bool called = false; } converter; - /* Just verify we can have const parameters */ - const int a = 0; - auto lambda = [](const std::string&, InputFileCallbackPolicy, const int&) { - return Containers::Optional>{}; - }; - converter.setInputFileCallback(lambda, a); - CORRADE_VERIFY(converter.inputFileCallback()); - CORRADE_VERIFY(converter.inputFileCallbackUserData()); - CORRADE_VERIFY(converter.called); + std::ostringstream out; + Error redirectError{&out}; + converter.linkDataToData({{}}); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkDataToData(): feature advertised but not implemented\n"); } -void AbstractConverterTest::setInputFileCallbackNotImplemented() { +void AbstractConverterTest::linkDataToDataNoData() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData; + return ConverterFeature::LinkData; } } converter; - int a; - auto lambda = [](const std::string&, InputFileCallbackPolicy, void*) { - return Containers::Optional>{}; - }; - converter.setInputFileCallback(lambda, &a); - CORRADE_COMPARE(converter.inputFileCallback(), lambda); - CORRADE_COMPARE(converter.inputFileCallbackUserData(), &a); - /* Should just work, no need to implement the function */ + std::ostringstream out; + Error redirectError{&out}; + converter.linkDataToData({}); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkDataToData(): no data passed\n"); } -void AbstractConverterTest::setInputFileCallbackNotSupported() { +void AbstractConverterTest::linkDataToDataCustomDeleter() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertFile; + return ConverterFeature::LinkData; + } + + Containers::Array doLinkDataToData(Containers::ArrayView>>) override { + return Containers::Array{nullptr, 0, [](char*, std::size_t){}}; } } converter; std::ostringstream out; Error redirectError{&out}; - - int a; - converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { - return Containers::Optional>{}; - }, &a); - CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::setInputFileCallback(): converter supports neither loading from data nor via callbacks, callbacks can't be used\n"); + converter.linkDataToData({{}}); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkDataToData(): implementation is not allowed to use a custom Array deleter\n"); } -void AbstractConverterTest::setInputFileCallbackValidateFileDirectly() { +void AbstractConverterTest::linkDataToFileThroughData() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ValidateFile|ConverterFeature::InputFileCallback; + return ConverterFeature::LinkData; } - std::pair doValidateFile(Stage, const Containers::StringView filename) override { - return {filename == "file.dat" && inputFileCallback() && inputFileCallbackUserData(), "it's what it is!"}; + Containers::Array doLinkDataToData(Containers::ArrayView>> data) override { + CORRADE_COMPARE(data.size(), 2); + return Containers::array({ + data[0].first == Stage::Vertex ? data[0].second[0] : ' ', + data[1].first == Stage::Fragment ? data[1].second[0] : ' ' + }); } + } converter; - std::pair doValidateData(Stage, Containers::ArrayView) override { - CORRADE_VERIFY(!"this should not be reached"); + const std::string filename = Utility::Directory::join(SHADERTOOLS_TEST_OUTPUT_DIR, "file.dat"); + + /* Remove previous file, if any */ + Utility::Directory::rm(filename); + CORRADE_VERIFY(!Utility::Directory::exists(filename)); + + CORRADE_VERIFY(converter.linkDataToFile({ + {Stage::Vertex, Containers::arrayView({'V', 'E'})}, + {Stage::Fragment, Containers::arrayView({'S', 'A'})} + }, filename)); + CORRADE_COMPARE_AS(filename, "VS", + TestSuite::Compare::FileToString); +} + +void AbstractConverterTest::linkDataToFileThroughDataFailed() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkData; + } + + Containers::Array doLinkDataToData(Containers::ArrayView>>) override { return {}; } } converter; - int a{}; - converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { - CORRADE_VERIFY(!"this should not be reached"); - return Containers::Optional>{}; - }, &a); + const std::string filename = Utility::Directory::join(SHADERTOOLS_TEST_OUTPUT_DIR, "file.dat"); - CORRADE_COMPARE(converter.validateFile({}, "file.dat"), std::make_pair(true, "it's what it is!")); + /* Remove previous file, if any */ + Utility::Directory::rm(filename); + CORRADE_VERIFY(!Utility::Directory::exists(filename)); + + /* Function should fail, no file should get written and no error output + should be printed (the base implementation assumes the plugin does it) */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.linkDataToFile({{}}, filename)); + CORRADE_VERIFY(!Utility::Directory::exists(filename)); + CORRADE_COMPARE(out.str(), ""); } -void AbstractConverterTest::setInputFileCallbackValidateFileThroughBaseImplementation() { +void AbstractConverterTest::linkDataToFileThroughDataNotWritable() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ValidateData|ConverterFeature::InputFileCallback; + return ConverterFeature::LinkData; } - std::pair doValidateFile(Stage stage, const Containers::StringView filename) override { - validateFileCalled = true; + Containers::Array doLinkDataToData(Containers::ArrayView>>) override { + return Containers::Array{1}; + } + } converter; - if(filename != "file.dat" || !inputFileCallback() || !inputFileCallbackUserData()) - return {}; + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.linkDataToFile({{}}, "/some/path/that/does/not/exist")); + CORRADE_COMPARE(out.str(), + "Utility::Directory::write(): can't open /some/path/that/does/not/exist\n" + "ShaderTools::AbstractConverter::linkDataToFile(): cannot write to file /some/path/that/does/not/exist\n"); +} - return AbstractConverter::doValidateFile(stage, filename); +void AbstractConverterTest::linkDataToFileNotSupported() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkFile; } + } converter; - std::pair doValidateData(Stage stage, Containers::ArrayView data) override { - return {stage == Stage::RayCallable && data.size() == 1 && data[0] == '\xb0', "yep!!"}; + std::ostringstream out; + Error redirectError{&out}; + converter.linkDataToFile({}, "file.dat"); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkDataToFile(): feature not supported\n"); +} + +void AbstractConverterTest::linkDataToFileNotImplemented() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkData; } + } converter; - bool validateFileCalled = false; + std::ostringstream out; + Error redirectError{&out}; + converter.linkDataToFile({{}}, "file.dat"); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkDataToData(): feature advertised but not implemented\n"); +} + +void AbstractConverterTest::linkDataToFileNoData() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkData; + } } converter; - struct State { - const char data = '\xb0'; - bool loaded = false; - bool closed = false; - } state; + std::ostringstream out; + Error redirectError{&out}; + converter.linkDataToFile({}, {}); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkDataToFile(): no data passed\n"); +} - converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { - if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) { - state.loaded = true; - return Containers::arrayView(&state.data, 1); +void AbstractConverterTest::linkFilesToFile() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkFile; } - if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) { - state.closed = true; - return {}; + bool doLinkFilesToFile(Containers::ArrayView> from, Containers::StringView to) override { + CORRADE_COMPARE(from.size(), 2); + Containers::Array first = Utility::Directory::read(from[0].second); + Containers::Array second = Utility::Directory::read(from[1].second); + CORRADE_VERIFY(first); + CORRADE_VERIFY(second); + return Utility::Directory::write(to, Containers::array({ + from[0].first == Stage::Vertex ? first[0] : ' ', + from[1].first == Stage::Fragment ? second[0] : ' ' + })); } + } converter; - CORRADE_VERIFY(!"this shouldn't be reached"); - return {}; - }, state); + const std::string filename = Utility::Directory::join(SHADERTOOLS_TEST_OUTPUT_DIR, "file.dat"); - CORRADE_COMPARE(converter.validateFile(Stage::RayCallable, "file.dat"), std::make_pair(true, "yep!!")); - CORRADE_VERIFY(converter.validateFileCalled); - CORRADE_VERIFY(state.loaded); - CORRADE_VERIFY(state.closed); + /* Remove previous file, if any */ + Utility::Directory::rm(filename); + CORRADE_VERIFY(!Utility::Directory::exists(filename)); + + CORRADE_VERIFY(converter.linkFilesToFile({ + {Stage::Vertex, Utility::Directory::join(SHADERTOOLS_TEST_DIR, "another.dat")}, + {Stage::Fragment, Utility::Directory::join(SHADERTOOLS_TEST_DIR, "file.dat")} + }, filename)); + CORRADE_COMPARE_AS(filename, "VS", + TestSuite::Compare::FileToString); } -void AbstractConverterTest::setInputFileCallbackValidateFileThroughBaseImplementationFailed() { +void AbstractConverterTest::linkFilesToFileThroughData() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ValidateData|ConverterFeature::InputFileCallback; + return ConverterFeature::LinkData; } - std::pair doValidateFile(Stage stage, const Containers::StringView filename) override { - validateFileCalled = true; - return AbstractConverter::doValidateFile(stage, filename); + Containers::Array doLinkDataToData(Containers::ArrayView>> data) override { + CORRADE_COMPARE(data.size(), 2); + return Containers::array({ + data[0].first == Stage::Vertex ? data[0].second[0] : ' ', + data[1].first == Stage::Fragment ? data[1].second[0] : ' ' + }); } - - bool validateFileCalled = false; } converter; - converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { - return Containers::Optional>{}; - }); + const std::string filename = Utility::Directory::join(SHADERTOOLS_TEST_OUTPUT_DIR, "file.dat"); - std::ostringstream out; - Error redirectError{&out}; + /* Remove previous file, if any */ + Utility::Directory::rm(filename); + CORRADE_VERIFY(!Utility::Directory::exists(filename)); - CORRADE_COMPARE(converter.validateFile({}, "file.dat"), std::make_pair(false, "")); - CORRADE_VERIFY(converter.validateFileCalled); - CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::validateFile(): cannot open file file.dat\n"); + CORRADE_VERIFY(converter.linkFilesToFile({ + {Stage::Vertex, Utility::Directory::join(SHADERTOOLS_TEST_DIR, "another.dat")}, + {Stage::Fragment, Utility::Directory::join(SHADERTOOLS_TEST_DIR, "file.dat")} + }, filename)); + CORRADE_COMPARE_AS(filename, "VS", + TestSuite::Compare::FileToString); } -void AbstractConverterTest::setInputFileCallbackValidateFileAsData() { +void AbstractConverterTest::linkFilesToFileThroughDataNotFound() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ValidateData; + return ConverterFeature::LinkData; } - std::pair doValidateFile(Stage, const Containers::StringView) override { + Containers::Array doLinkDataToData(Containers::ArrayView>>) override { CORRADE_VERIFY(!"this shouldn't be reached"); return {}; } + } converter; - std::pair doValidateData(Stage stage, Containers::ArrayView data) override { - return {stage == Stage::Fragment && data.size() == 1 && data[0] == '\xb0', "yep!!"}; + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.linkFilesToFile({ + {{}, Utility::Directory::join(SHADERTOOLS_TEST_DIR, "another.dat")}, + {{}, "nonexistent.bin"} + }, "file.dat")); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkFilesToFile(): cannot open file nonexistent.bin\n"); +} + +void AbstractConverterTest::linkFilesToFileThroughDataFailed() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkData; + } + + Containers::Array doLinkDataToData(Containers::ArrayView>>) override { + return {}; + } + } converter; + + const std::string filename = Utility::Directory::join(SHADERTOOLS_TEST_OUTPUT_DIR, "file.dat"); + + /* Remove previous file, if any */ + Utility::Directory::rm(filename); + CORRADE_VERIFY(!Utility::Directory::exists(filename)); + + /* Function should fail, no file should get written and no error output + should be printed (the base implementation assumes the plugin does it) */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.linkFilesToFile({ + {{}, Utility::Directory::join(SHADERTOOLS_TEST_DIR, "file.dat")} + }, filename)); + CORRADE_VERIFY(!Utility::Directory::exists(filename)); + CORRADE_COMPARE(out.str(), ""); +} + +void AbstractConverterTest::linkFilesToFileThroughDataNotWritable() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkData; + } + + Containers::Array doLinkDataToData(Containers::ArrayView>>) override { + return Containers::Array{1}; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.linkFilesToFile({ + {{}, Utility::Directory::join(SHADERTOOLS_TEST_DIR, "file.dat")} + }, "/some/path/that/does/not/exist")); + CORRADE_COMPARE(out.str(), + "Utility::Directory::write(): can't open /some/path/that/does/not/exist\n" + "ShaderTools::AbstractConverter::linkFilesToFile(): cannot write to file /some/path/that/does/not/exist\n"); +} + +void AbstractConverterTest::linkFilesToFileNotSupported() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ValidateData; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.linkFilesToFile({}, {}); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkFilesToFile(): feature not supported\n"); +} + +void AbstractConverterTest::linkFilesToFileNotImplemented() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkFile; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.linkFilesToFile({{}}, {}); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkFilesToFile(): feature advertised but not implemented\n"); +} + +void AbstractConverterTest::linkFilesToFileNoFile() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkFile; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.linkFilesToFile({}, {}); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkFilesToFile(): no files passed\n"); +} + +void AbstractConverterTest::linkFilesToData() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkData; + } + + Containers::Array doLinkFilesToData(Containers::ArrayView> from) override { + CORRADE_COMPARE(from.size(), 2); + Containers::Array first = Utility::Directory::read(from[0].second); + Containers::Array second = Utility::Directory::read(from[1].second); + CORRADE_VERIFY(first); + CORRADE_VERIFY(second); + return Containers::array({ + from[0].first == Stage::Vertex ? first[0] : ' ', + from[1].first == Stage::Fragment ? second[0] : ' ' + }); + } + } converter; + + CORRADE_VERIFY(true); /* so it picks up correct test case name */ + + Containers::Array out = converter.linkFilesToData({ + {Stage::Vertex, Utility::Directory::join(SHADERTOOLS_TEST_DIR, "another.dat")}, + {Stage::Fragment, Utility::Directory::join(SHADERTOOLS_TEST_DIR, "file.dat")} + }); + CORRADE_COMPARE_AS(out, Containers::arrayView({'V', 'S'}), + TestSuite::Compare::Container); +} + +void AbstractConverterTest::linkFilesToDataAsData() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkData; + } + + Containers::Array doLinkDataToData(Containers::ArrayView>> data) override { + CORRADE_COMPARE(data.size(), 2); + return Containers::array({ + data[0].first == Stage::Vertex ? data[0].second[0] : ' ', + data[1].first == Stage::Fragment ? data[1].second[0] : ' ' + }); + } + } converter; + + Containers::Array out = converter.linkFilesToData({ + {Stage::Vertex, Utility::Directory::join(SHADERTOOLS_TEST_DIR, "another.dat")}, + {Stage::Fragment, Utility::Directory::join(SHADERTOOLS_TEST_DIR, "file.dat")} + }); + CORRADE_COMPARE_AS(out, Containers::arrayView({'V', 'S'}), + TestSuite::Compare::Container); +} + +void AbstractConverterTest::linkFilesToDataAsDataNotFound() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkData; + } + + Containers::Array doLinkDataToData(Containers::ArrayView>>) override { + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.linkFilesToData({ + {{}, "nonexistent.bin"} + })); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkFilesToData(): cannot open file nonexistent.bin\n"); +} + +void AbstractConverterTest::linkFilesToDataNotSupported() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkFile; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.linkFilesToData({}); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkFilesToData(): feature not supported\n"); +} + +void AbstractConverterTest::linkFilesToDataNotImplemented() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkData; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.linkFilesToData({ + {{}, Utility::Directory::join(SHADERTOOLS_TEST_DIR, "file.dat")} + }); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkDataToData(): feature advertised but not implemented\n"); +} + +void AbstractConverterTest::linkFilesToDataNoFile() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkData; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.linkFilesToData({}); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkFilesToData(): no files passed\n"); +} + +void AbstractConverterTest::linkFilesToDataCustomDeleter() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::LinkData; + } + + Containers::Array doLinkFilesToData(Containers::ArrayView>) override { + return Containers::Array{nullptr, 0, [](char*, std::size_t){}}; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.linkFilesToData({ + {{}, "file.dat"} + }); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkFilesToData(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractConverterTest::setInputFileCallback() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData; + } + void doSetInputFileCallback(Containers::Optional>(*)(const std::string&, InputFileCallbackPolicy, void*), void* userData) override { + *static_cast(userData) = 1337; + } + } converter; + + int a = 0; + auto lambda = [](const std::string&, InputFileCallbackPolicy, void*) { + return Containers::Optional>{}; + }; + converter.setInputFileCallback(lambda, &a); + CORRADE_COMPARE(converter.inputFileCallback(), lambda); + CORRADE_COMPARE(converter.inputFileCallbackUserData(), &a); + CORRADE_COMPARE(a, 1337); +} + +void AbstractConverterTest::setInputFileCallbackTemplate() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData; + } + void doSetInputFileCallback(Containers::Optional>(*)(const std::string&, InputFileCallbackPolicy, void*), void*) override { + called = true; + } + + bool called = false; + } converter; + + int a = 0; + auto lambda = [](const std::string&, InputFileCallbackPolicy, int&) { + return Containers::Optional>{}; + }; + converter.setInputFileCallback(lambda, a); + CORRADE_VERIFY(converter.inputFileCallback()); + CORRADE_VERIFY(converter.inputFileCallbackUserData()); + CORRADE_VERIFY(converter.called); + + /* The data pointers should be wrapped, thus not the same */ + CORRADE_VERIFY(reinterpret_cast(converter.inputFileCallback()) != reinterpret_cast(static_cast>(*)(const std::string&, InputFileCallbackPolicy, int&)>(lambda))); + CORRADE_VERIFY(converter.inputFileCallbackUserData() != &a); +} + +void AbstractConverterTest::setInputFileCallbackTemplateNull() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData; + } + void doSetInputFileCallback(Containers::Optional>(*callback)(const std::string&, InputFileCallbackPolicy, void*), void* userData) override { + called = !callback && !userData; + } + + bool called = false; + } converter; + + int a = 0; + converter.setInputFileCallback(static_cast>(*)(const std::string&, InputFileCallbackPolicy, int&)>(nullptr), a); + CORRADE_VERIFY(!converter.inputFileCallback()); + CORRADE_VERIFY(!converter.inputFileCallbackUserData()); + CORRADE_VERIFY(converter.called); +} + +void AbstractConverterTest::setInputFileCallbackTemplateConst() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData; + } + void doSetInputFileCallback(Containers::Optional>(*)(const std::string&, InputFileCallbackPolicy, void*), void*) override { + called = true; + } + + bool called = false; + } converter; + + /* Just verify we can have const parameters */ + const int a = 0; + auto lambda = [](const std::string&, InputFileCallbackPolicy, const int&) { + return Containers::Optional>{}; + }; + converter.setInputFileCallback(lambda, a); + CORRADE_VERIFY(converter.inputFileCallback()); + CORRADE_VERIFY(converter.inputFileCallbackUserData()); + CORRADE_VERIFY(converter.called); +} + +void AbstractConverterTest::setInputFileCallbackNotImplemented() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData; + } + } converter; + + int a; + auto lambda = [](const std::string&, InputFileCallbackPolicy, void*) { + return Containers::Optional>{}; + }; + converter.setInputFileCallback(lambda, &a); + CORRADE_COMPARE(converter.inputFileCallback(), lambda); + CORRADE_COMPARE(converter.inputFileCallbackUserData(), &a); + /* Should just work, no need to implement the function */ +} + +void AbstractConverterTest::setInputFileCallbackNotSupported() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertFile; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + + int a; + converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { + return Containers::Optional>{}; + }, &a); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::setInputFileCallback(): converter supports neither loading from data nor via callbacks, callbacks can't be used\n"); +} + +void AbstractConverterTest::setInputFileCallbackValidateFileDirectly() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ValidateFile|ConverterFeature::InputFileCallback; + } + + std::pair doValidateFile(Stage, const Containers::StringView filename) override { + return {filename == "file.dat" && inputFileCallback() && inputFileCallbackUserData(), "it's what it is!"}; + } + + std::pair doValidateData(Stage, Containers::ArrayView) override { + CORRADE_VERIFY(!"this should not be reached"); + return {}; + } + } converter; + + int a{}; + converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { + CORRADE_VERIFY(!"this should not be reached"); + return Containers::Optional>{}; + }, &a); + + CORRADE_COMPARE(converter.validateFile({}, "file.dat"), std::make_pair(true, "it's what it is!")); +} + +void AbstractConverterTest::setInputFileCallbackValidateFileThroughBaseImplementation() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ValidateData|ConverterFeature::InputFileCallback; + } + + std::pair doValidateFile(Stage stage, const Containers::StringView filename) override { + validateFileCalled = true; + + if(filename != "file.dat" || !inputFileCallback() || !inputFileCallbackUserData()) + return {}; + + return AbstractConverter::doValidateFile(stage, filename); + } + + std::pair doValidateData(Stage stage, Containers::ArrayView data) override { + return {stage == Stage::RayCallable && data.size() == 1 && data[0] == '\xb0', "yep!!"}; + } + + bool validateFileCalled = false; + } converter; + + struct State { + const char data = '\xb0'; + bool loaded = false; + bool closed = false; + } state; + + converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) { + state.loaded = true; + return Containers::arrayView(&state.data, 1); + } + + if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) { + state.closed = true; + return {}; + } + + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + }, state); + + CORRADE_COMPARE(converter.validateFile(Stage::RayCallable, "file.dat"), std::make_pair(true, "yep!!")); + CORRADE_VERIFY(converter.validateFileCalled); + CORRADE_VERIFY(state.loaded); + CORRADE_VERIFY(state.closed); +} + +void AbstractConverterTest::setInputFileCallbackValidateFileThroughBaseImplementationFailed() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ValidateData|ConverterFeature::InputFileCallback; + } + + std::pair doValidateFile(Stage stage, const Containers::StringView filename) override { + validateFileCalled = true; + return AbstractConverter::doValidateFile(stage, filename); + } + + bool validateFileCalled = false; + } converter; + + converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { + return Containers::Optional>{}; + }); + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_COMPARE(converter.validateFile({}, "file.dat"), std::make_pair(false, "")); + CORRADE_VERIFY(converter.validateFileCalled); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::validateFile(): cannot open file file.dat\n"); +} + +void AbstractConverterTest::setInputFileCallbackValidateFileAsData() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ValidateData; + } + + std::pair doValidateFile(Stage, const Containers::StringView) override { + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + } + + std::pair doValidateData(Stage stage, Containers::ArrayView data) override { + return {stage == Stage::Fragment && data.size() == 1 && data[0] == '\xb0', "yep!!"}; + } + } converter; + + struct State { + const char data = '\xb0'; + bool loaded = false; + bool closed = false; + } state; + + converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) { + state.loaded = true; + return Containers::arrayView(&state.data, 1); + } + + if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) { + state.closed = true; + return {}; + } + + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + }, state); + + CORRADE_COMPARE(converter.validateFile(Stage::Fragment, "file.dat"), std::make_pair(true, "yep!!")); + CORRADE_VERIFY(state.loaded); + CORRADE_VERIFY(state.closed); +} + +void AbstractConverterTest::setInputFileCallbackValidateFileAsDataFailed() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ValidateData; + } + + std::pair doValidateFile(Stage, const Containers::StringView) override { + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + } + } converter; + + converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { + return Containers::Optional>{}; + }); + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_COMPARE(converter.validateFile({}, "file.dat"), std::make_pair(false, "")); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::validateFile(): cannot open file file.dat\n"); +} + +void AbstractConverterTest::setInputFileCallbackConvertFileToFileDirectly() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertFile|ConverterFeature::InputFileCallback; + } + + bool doConvertFileToFile(Stage stage, const Containers::StringView from, const Containers::StringView to) override { + return stage == Stage::Mesh && from == "file.dat" && to == "file.out" && inputFileCallback() && inputFileCallbackUserData(); + } + + Containers::Array doConvertDataToData(Stage, Containers::ArrayView) override { + CORRADE_VERIFY(!"this should not be reached"); + return {}; + } + } converter; + + int a{}; + converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { + CORRADE_VERIFY(!"this should not be reached"); + return Containers::Optional>{}; + }, &a); + + CORRADE_VERIFY(converter.convertFileToFile(Stage::Mesh, "file.dat", "file.out")); +} + +void AbstractConverterTest::setInputFileCallbackConvertFileToFileThroughBaseImplementation() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData|ConverterFeature::InputFileCallback; + } + + bool doConvertFileToFile(Stage stage, const Containers::StringView from, const Containers::StringView to) override { + convertFileToFileCalled = true; + + if(stage != Stage::Geometry || from != "file.dat" || !to.hasSuffix("file.out") || !inputFileCallback() || !inputFileCallbackUserData()) + return {}; + + return AbstractConverter::doConvertFileToFile(stage, from, to); + } + + Containers::Array doConvertDataToData(Stage stage, Containers::ArrayView data) override { + if(stage == Stage::Geometry && data.size() == 1 && data[0] == '\xb0') + return Containers::array({'y', 'e', 'p'}); + return {}; + } + + bool convertFileToFileCalled = false; + } converter; + + struct State { + const char data = '\xb0'; + bool loaded = false; + bool closed = false; + } state; + + converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) { + state.loaded = true; + return Containers::arrayView(&state.data, 1); + } + + if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) { + state.closed = true; + return {}; + } + + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + }, state); + + /* Remove previous file, if any */ + const std::string filename = Utility::Directory::join(SHADERTOOLS_TEST_OUTPUT_DIR, "file.out"); + Utility::Directory::rm(filename); + CORRADE_VERIFY(!Utility::Directory::exists(filename)); + + CORRADE_VERIFY(converter.convertFileToFile(Stage::Geometry, "file.dat", filename)); + CORRADE_VERIFY(converter.convertFileToFileCalled); + CORRADE_VERIFY(state.loaded); + CORRADE_VERIFY(state.closed); + CORRADE_COMPARE_AS(filename, "yep", TestSuite::Compare::FileToString); +} + +void AbstractConverterTest::setInputFileCallbackConvertFileToFileThroughBaseImplementationFailed() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData|ConverterFeature::InputFileCallback; + } + + bool doConvertFileToFile(Stage stage, const Containers::StringView from, const Containers::StringView to) override { + convertFileToFileCalled = true; + return AbstractConverter::doConvertFileToFile(stage, from, to); + } + + bool convertFileToFileCalled = false; + } converter; + + converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { + return Containers::Optional>{}; + }); + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_VERIFY(!converter.convertFileToFile({}, "file.dat", "/some/path/that/does/not/exist")); + CORRADE_VERIFY(converter.convertFileToFileCalled); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::convertFileToFile(): cannot open file file.dat\n"); +} + +void AbstractConverterTest::setInputFileCallbackConvertFileToFileAsData() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData; + } + + bool doConvertFileToFile(Stage, Containers::StringView, Containers::StringView) override { + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + } + + Containers::Array doConvertDataToData(Stage stage, Containers::ArrayView data) override { + if(stage == Stage::RayAnyHit && data.size() == 1 && data[0] == '\xb0') + return Containers::array({'y', 'e', 'p'}); + return {}; + } + } converter; + + struct State { + const char data = '\xb0'; + bool loaded = false; + bool closed = false; + } state; + + converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) { + state.loaded = true; + return Containers::arrayView(&state.data, 1); + } + + if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) { + state.closed = true; + return {}; + } + + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + }, state); + + /* Remove previous file, if any */ + const std::string filename = Utility::Directory::join(SHADERTOOLS_TEST_OUTPUT_DIR, "file.out"); + Utility::Directory::rm(filename); + CORRADE_VERIFY(!Utility::Directory::exists(filename)); + + CORRADE_VERIFY(converter.convertFileToFile(Stage::RayAnyHit, "file.dat", filename)); + CORRADE_VERIFY(state.loaded); + CORRADE_VERIFY(state.closed); + CORRADE_COMPARE_AS(filename, "yep", TestSuite::Compare::FileToString); +} + +void AbstractConverterTest::setInputFileCallbackConvertFileToFileAsDataFailed() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData; + } + + bool doConvertFileToFile(Stage, Containers::StringView, Containers::StringView) override { + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + } + } converter; + + converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { + return Containers::Optional>{}; + }); + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_VERIFY(!converter.convertFileToFile({}, "file.dat", "/some/path/that/does/not/exist")); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::convertFileToFile(): cannot open file file.dat\n"); +} + +void AbstractConverterTest::setInputFileCallbackConvertFileToFileAsDataNotWritable() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData; + } + + bool doConvertFileToFile(Stage, Containers::StringView, Containers::StringView) override { + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + } + + Containers::Array doConvertDataToData(Stage, Containers::ArrayView) override { + return Containers::Array{1}; + } + } converter; + + struct State { + const char data = '\xb0'; + bool loaded = false; + bool closed = false; + } state; + + converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) { + state.loaded = true; + return Containers::arrayView(&state.data, 1); + } + + if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) { + state.closed = true; + return {}; + } + + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + }, state); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertFileToFile({}, "file.dat", "/some/path/that/does/not/exist")); + CORRADE_VERIFY(state.loaded); + CORRADE_VERIFY(state.closed); + CORRADE_COMPARE(out.str(), + "Utility::Directory::write(): can't open /some/path/that/does/not/exist\n" + "ShaderTools::AbstractConverter::convertFileToFile(): cannot write to file /some/path/that/does/not/exist\n"); +} + +void AbstractConverterTest::setInputFileCallbackConvertFileToDataDirectly() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData|ConverterFeature::InputFileCallback; + } + + Containers::Array doConvertFileToData(Stage stage, Containers::StringView from) override { + if(stage == Stage::Compute && from == "file.dat" && inputFileCallback() && inputFileCallbackUserData()) + return Containers::array({'y', 'e', 'p'}); + return {}; + } + + Containers::Array doConvertDataToData(Stage, Containers::ArrayView) override { + CORRADE_VERIFY(!"this should not be reached"); + return {}; + } + } converter; + + int a{}; + converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { + CORRADE_VERIFY(!"this should not be reached"); + return Containers::Optional>{}; + }, &a); + + CORRADE_COMPARE_AS(converter.convertFileToData(Stage::Compute, "file.dat"), + Containers::arrayView({'y', 'e', 'p'}), + TestSuite::Compare::Container); +} + +void AbstractConverterTest::setInputFileCallbackConvertFileToDataThroughBaseImplementation() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData|ConverterFeature::InputFileCallback; + } + + Containers::Array doConvertFileToData(Stage stage, Containers::StringView from) override { + convertFileToDataCalled = true; + + if(stage != Stage::TessellationEvaluation || from != "file.dat" || !inputFileCallback() || !inputFileCallbackUserData()) + return {}; + + return AbstractConverter::doConvertFileToData(stage, from); + } + + Containers::Array doConvertDataToData(Stage stage, Containers::ArrayView data) override { + if(stage == Stage::TessellationEvaluation && data.size() == 1 && data[0] == '\xb0') + return Containers::array({'y', 'e', 'p'}); + return {}; + } + + bool convertFileToDataCalled = false; + } converter; + + struct State { + const char data = '\xb0'; + bool loaded = false; + bool closed = false; + } state; + + converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) { + state.loaded = true; + return Containers::arrayView(&state.data, 1); + } + + if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) { + state.closed = true; + return {}; + } + + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + }, state); + + CORRADE_COMPARE_AS(converter.convertFileToData(Stage::TessellationEvaluation, "file.dat"), + Containers::arrayView({'y', 'e', 'p'}), + TestSuite::Compare::Container); + CORRADE_VERIFY(converter.convertFileToDataCalled); + CORRADE_VERIFY(state.loaded); + CORRADE_VERIFY(state.closed); +} + +void AbstractConverterTest::setInputFileCallbackConvertFileToDataThroughBaseImplementationFailed() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData|ConverterFeature::InputFileCallback; + } + + Containers::Array doConvertFileToData(Stage stage, Containers::StringView from) override { + convertFileToDataCalled = true; + return AbstractConverter::doConvertFileToData(stage, from); + } + + bool convertFileToDataCalled = false; + } converter; + + converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { + return Containers::Optional>{}; + }); + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_VERIFY(!converter.convertFileToData({}, "file.dat")); + CORRADE_VERIFY(converter.convertFileToDataCalled); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::convertFileToData(): cannot open file file.dat\n"); +} + +void AbstractConverterTest::setInputFileCallbackConvertFileToDataAsData() { + struct: AbstractConverter { + ConverterFeatures doFeatures() const override { + return ConverterFeature::ConvertData; + } + + Containers::Array doConvertFileToData(Stage, Containers::StringView) override { + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + } + + Containers::Array doConvertDataToData(Stage stage, Containers::ArrayView data) override { + if(stage == Stage::RayGeneration && data.size() == 1 && data[0] == '\xb0') + return Containers::array({'y', 'e', 'p'}); + return {}; } } converter; @@ -1113,18 +2151,20 @@ void AbstractConverterTest::setInputFileCallbackValidateFileAsData() { return {}; }, state); - CORRADE_COMPARE(converter.validateFile(Stage::Fragment, "file.dat"), std::make_pair(true, "yep!!")); + CORRADE_COMPARE_AS(converter.convertFileToData(Stage::RayGeneration, "file.dat"), + Containers::arrayView({'y', 'e', 'p'}), + TestSuite::Compare::Container); CORRADE_VERIFY(state.loaded); CORRADE_VERIFY(state.closed); } -void AbstractConverterTest::setInputFileCallbackValidateFileAsDataFailed() { +void AbstractConverterTest::setInputFileCallbackConvertFileToDataAsDataFailed() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ValidateData; + return ConverterFeature::ConvertData; } - std::pair doValidateFile(Stage, const Containers::StringView) override { + Containers::Array doConvertFileToData(Stage, Containers::StringView) override { CORRADE_VERIFY(!"this shouldn't be reached"); return {}; } @@ -1137,18 +2177,18 @@ void AbstractConverterTest::setInputFileCallbackValidateFileAsDataFailed() { std::ostringstream out; Error redirectError{&out}; - CORRADE_COMPARE(converter.validateFile({}, "file.dat"), std::make_pair(false, "")); - CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::validateFile(): cannot open file file.dat\n"); + CORRADE_VERIFY(!converter.convertFileToData({}, "file.dat")); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::convertFileToData(): cannot open file file.dat\n"); } -void AbstractConverterTest::setInputFileCallbackConvertFileToFileDirectly() { +void AbstractConverterTest::setInputFileCallbackLinkFilesToFileDirectly() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertFile|ConverterFeature::InputFileCallback; + return ConverterFeature::LinkFile|ConverterFeature::InputFileCallback; } - bool doConvertFileToFile(Stage stage, const Containers::StringView from, const Containers::StringView to) override { - return stage == Stage::Mesh && from == "file.dat" && to == "file.out" && inputFileCallback() && inputFileCallbackUserData(); + bool doLinkFilesToFile(Containers::ArrayView> from, Containers::StringView to) override { + return from.size() == 2 && from[0].first == Stage::Vertex && from[0].second == "another.dat" && from[1].first == Stage::Fragment && from[1].second == "file.dat" && to == "file.out" && inputFileCallback() && inputFileCallbackUserData(); } Containers::Array doConvertDataToData(Stage, Containers::ArrayView) override { @@ -1163,47 +2203,55 @@ void AbstractConverterTest::setInputFileCallbackConvertFileToFileDirectly() { return Containers::Optional>{}; }, &a); - CORRADE_VERIFY(converter.convertFileToFile(Stage::Mesh, "file.dat", "file.out")); + CORRADE_VERIFY(converter.linkFilesToFile({ + {Stage::Vertex, "another.dat"}, + {Stage::Fragment, "file.dat"} + }, "file.out")); } -void AbstractConverterTest::setInputFileCallbackConvertFileToFileThroughBaseImplementation() { +void AbstractConverterTest::setInputFileCallbackLinkFilesToFileThroughBaseImplementation() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData|ConverterFeature::InputFileCallback; + return ConverterFeature::LinkData|ConverterFeature::InputFileCallback; } - bool doConvertFileToFile(Stage stage, const Containers::StringView from, const Containers::StringView to) override { - convertFileToFileCalled = true; + bool doLinkFilesToFile(Containers::ArrayView> from, Containers::StringView to) override { + linkFilesToFileCalled = true; - if(stage != Stage::Geometry || from != "file.dat" || !to.hasSuffix("file.out") || !inputFileCallback() || !inputFileCallbackUserData()) + if(from.size() != 2 || from[0].first != Stage::Vertex || from[0].second != "another.dat" || from[1].first != Stage::Fragment || from[1].second != "file.dat" || !to.hasSuffix("file.out") || !inputFileCallback() || !inputFileCallbackUserData()) return {}; - return AbstractConverter::doConvertFileToFile(stage, from, to); + return AbstractConverter::doLinkFilesToFile(from, to); } - Containers::Array doConvertDataToData(Stage stage, Containers::ArrayView data) override { - if(stage == Stage::Geometry && data.size() == 1 && data[0] == '\xb0') - return Containers::array({'y', 'e', 'p'}); - return {}; + Containers::Array doLinkDataToData(Containers::ArrayView>> data) override { + CORRADE_COMPARE(data.size(), 2); + return Containers::array({ + data[0].first == Stage::Vertex ? data[0].second[0] : ' ', + data[1].first == Stage::Fragment ? data[1].second[0] : ' ' + }); } - bool convertFileToFileCalled = false; + bool linkFilesToFileCalled = false; } converter; struct State { - const char data = '\xb0'; - bool loaded = false; - bool closed = false; + const char first[2]{'V', 'E'}; + const char second[2]{'S', 'A'}; + std::string operations; } state; converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { - if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) { - state.loaded = true; - return Containers::arrayView(&state.data, 1); + if(policy == InputFileCallbackPolicy::LoadTemporary) { + state.operations += "loaded " + filename + "\n"; + if(filename == "another.dat") + return Containers::arrayView(state.first); + if(filename == "file.dat") + return Containers::arrayView(state.second); } - if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) { - state.closed = true; + if(policy == InputFileCallbackPolicy::Close) { + state.operations += "closed " + filename + "\n"; return {}; } @@ -1216,71 +2264,113 @@ void AbstractConverterTest::setInputFileCallbackConvertFileToFileThroughBaseImpl Utility::Directory::rm(filename); CORRADE_VERIFY(!Utility::Directory::exists(filename)); - CORRADE_VERIFY(converter.convertFileToFile(Stage::Geometry, "file.dat", filename)); - CORRADE_VERIFY(converter.convertFileToFileCalled); - CORRADE_VERIFY(state.loaded); - CORRADE_VERIFY(state.closed); - CORRADE_COMPARE_AS(filename, "yep", TestSuite::Compare::FileToString); + CORRADE_VERIFY(converter.linkFilesToFile({ + {Stage::Vertex, "another.dat"}, + {Stage::Fragment, "file.dat"} + }, filename)); + CORRADE_VERIFY(converter.linkFilesToFileCalled); + CORRADE_COMPARE(state.operations, + "loaded another.dat\n" + "loaded file.dat\n" + "closed another.dat\n" + "closed file.dat\n"); + CORRADE_COMPARE_AS(filename, "VS", TestSuite::Compare::FileToString); } -void AbstractConverterTest::setInputFileCallbackConvertFileToFileThroughBaseImplementationFailed() { +void AbstractConverterTest::setInputFileCallbackLinkFilesToFileThroughBaseImplementationFailed() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData|ConverterFeature::InputFileCallback; + return ConverterFeature::LinkData|ConverterFeature::InputFileCallback; } - bool doConvertFileToFile(Stage stage, const Containers::StringView from, const Containers::StringView to) override { - convertFileToFileCalled = true; - return AbstractConverter::doConvertFileToFile(stage, from, to); + bool doLinkFilesToFile(Containers::ArrayView> from, Containers::StringView to) override { + linkFilesToFileCalled = true; + return AbstractConverter::doLinkFilesToFile(from, to); } - bool convertFileToFileCalled = false; + Containers::Array doLinkDataToData(Containers::ArrayView>>) override { + CORRADE_VERIFY(!"this shouldn't be called"); + return {}; + } + + bool linkFilesToFileCalled = false; } converter; - converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { - return Containers::Optional>{}; - }); + struct State { + const char data[1]{}; + std::string operations; + } state; + + converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(policy == InputFileCallbackPolicy::LoadTemporary) { + state.operations += "loaded " + filename + "\n"; + if(filename == "another.dat") + return Containers::arrayView(state.data); + /* This deliberately fails */ + if(filename == "file.dat") return {}; + } + + if(policy == InputFileCallbackPolicy::Close) { + state.operations += "closed " + filename + "\n"; + return {}; + } + + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + }, state); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!converter.convertFileToFile({}, "file.dat", "/some/path/that/does/not/exist")); - CORRADE_VERIFY(converter.convertFileToFileCalled); - CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::convertFileToFile(): cannot open file file.dat\n"); + CORRADE_VERIFY(!converter.linkFilesToFile({ + {Stage::Vertex, "another.dat"}, + {Stage::Fragment, "file.dat"} + }, "/some/path/that/does/not/exist")); + CORRADE_VERIFY(converter.linkFilesToFileCalled); + CORRADE_COMPARE(state.operations, + "loaded another.dat\n" + "loaded file.dat\n" /* this fails */ + "closed another.dat\n"); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkFilesToFile(): cannot open file file.dat\n"); } -void AbstractConverterTest::setInputFileCallbackConvertFileToFileAsData() { +void AbstractConverterTest::setInputFileCallbackLinkFilesToFileAsData() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData; + return ConverterFeature::LinkData; } - bool doConvertFileToFile(Stage, Containers::StringView, Containers::StringView) override { + bool doLinkFilesToFile(Containers::ArrayView>, Containers::StringView) override { CORRADE_VERIFY(!"this shouldn't be reached"); return {}; } - Containers::Array doConvertDataToData(Stage stage, Containers::ArrayView data) override { - if(stage == Stage::RayAnyHit && data.size() == 1 && data[0] == '\xb0') - return Containers::array({'y', 'e', 'p'}); - return {}; + Containers::Array doLinkDataToData(Containers::ArrayView>> data) override { + CORRADE_COMPARE(data.size(), 2); + return Containers::array({ + data[0].first == Stage::Vertex ? data[0].second[0] : ' ', + data[1].first == Stage::Fragment ? data[1].second[0] : ' ' + }); } } converter; struct State { - const char data = '\xb0'; - bool loaded = false; - bool closed = false; + const char first[2]{'V', 'E'}; + const char second[2]{'S', 'A'}; + std::string operations; } state; converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { - if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) { - state.loaded = true; - return Containers::arrayView(&state.data, 1); + if(policy == InputFileCallbackPolicy::LoadTemporary) { + state.operations += "loaded " + filename + "\n"; + if(filename == "another.dat") + return Containers::arrayView(state.first); + if(filename == "file.dat") + return Containers::arrayView(state.second); } - if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) { - state.closed = true; + if(policy == InputFileCallbackPolicy::Close) { + state.operations += "closed " + filename + "\n"; return {}; } @@ -1293,65 +2383,96 @@ void AbstractConverterTest::setInputFileCallbackConvertFileToFileAsData() { Utility::Directory::rm(filename); CORRADE_VERIFY(!Utility::Directory::exists(filename)); - CORRADE_VERIFY(converter.convertFileToFile(Stage::RayAnyHit, "file.dat", filename)); - CORRADE_VERIFY(state.loaded); - CORRADE_VERIFY(state.closed); - CORRADE_COMPARE_AS(filename, "yep", TestSuite::Compare::FileToString); + CORRADE_VERIFY(converter.linkFilesToFile({ + {Stage::Vertex, "another.dat"}, + {Stage::Fragment, "file.dat"} + }, filename)); + CORRADE_COMPARE(state.operations, + "loaded another.dat\n" + "loaded file.dat\n" + "closed another.dat\n" + "closed file.dat\n"); + CORRADE_COMPARE_AS(filename, "VS", TestSuite::Compare::FileToString); } -void AbstractConverterTest::setInputFileCallbackConvertFileToFileAsDataFailed() { +void AbstractConverterTest::setInputFileCallbackLinkFilesToFileAsDataFailed() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData; + return ConverterFeature::LinkData; } - bool doConvertFileToFile(Stage, Containers::StringView, Containers::StringView) override { + bool doLinkFilesToFile(Containers::ArrayView>, Containers::StringView) override { CORRADE_VERIFY(!"this shouldn't be reached"); return {}; } } converter; - converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { - return Containers::Optional>{}; - }); + struct State { + const char data[1]{}; + std::string operations; + } state; + + converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(policy == InputFileCallbackPolicy::LoadTemporary) { + state.operations += "loaded " + filename + "\n"; + if(filename == "another.dat") + return Containers::arrayView(state.data); + /* This deliberately fails */ + if(filename == "file.dat") return {}; + } + + if(policy == InputFileCallbackPolicy::Close) { + state.operations += "closed " + filename + "\n"; + return {}; + } + + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + }, state); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!converter.convertFileToFile({}, "file.dat", "/some/path/that/does/not/exist")); - CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::convertFileToFile(): cannot open file file.dat\n"); + CORRADE_VERIFY(!converter.linkFilesToFile({ + {Stage::Vertex, "another.dat"}, + {Stage::Fragment, "file.dat"} + }, "/some/path/that/does/not/exist")); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkFilesToFile(): cannot open file file.dat\n"); } -void AbstractConverterTest::setInputFileCallbackConvertFileToFileAsDataNotWritable() { +void AbstractConverterTest::setInputFileCallbackLinkFilesToFileAsDataNotWritable() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData; + return ConverterFeature::LinkData; } - bool doConvertFileToFile(Stage, Containers::StringView, Containers::StringView) override { + bool doLinkFilesToFile(Containers::ArrayView>, Containers::StringView) override { CORRADE_VERIFY(!"this shouldn't be reached"); return {}; } - Containers::Array doConvertDataToData(Stage, Containers::ArrayView) override { + Containers::Array doLinkDataToData(Containers::ArrayView>>) override { return Containers::Array{1}; } } converter; struct State { - const char data = '\xb0'; - bool loaded = false; - bool closed = false; + const char first[2]{'V', 'E'}; + const char second[2]{'S', 'A'}; + std::string operations; } state; converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { - if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) { - state.loaded = true; - return Containers::arrayView(&state.data, 1); + if(policy == InputFileCallbackPolicy::LoadTemporary) { + state.operations += "loaded " + filename + "\n"; + if(filename == "another.dat") + return Containers::arrayView(state.first); + if(filename == "file.dat") + return Containers::arrayView(state.second); } - if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) { - state.closed = true; + if(policy == InputFileCallbackPolicy::Close) { + state.operations += "closed " + filename + "\n"; return {}; } @@ -1361,27 +2482,33 @@ void AbstractConverterTest::setInputFileCallbackConvertFileToFileAsDataNotWritab std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!converter.convertFileToFile({}, "file.dat", "/some/path/that/does/not/exist")); - CORRADE_VERIFY(state.loaded); - CORRADE_VERIFY(state.closed); + CORRADE_VERIFY(!converter.linkFilesToFile({ + {Stage::Vertex, "another.dat"}, + {Stage::Fragment, "file.dat"} + }, "/some/path/that/does/not/exist")); + CORRADE_COMPARE(state.operations, + "loaded another.dat\n" + "loaded file.dat\n" + "closed another.dat\n" + "closed file.dat\n"); CORRADE_COMPARE(out.str(), "Utility::Directory::write(): can't open /some/path/that/does/not/exist\n" - "ShaderTools::AbstractConverter::convertFileToFile(): cannot write to file /some/path/that/does/not/exist\n"); + "ShaderTools::AbstractConverter::linkFilesToFile(): cannot write to file /some/path/that/does/not/exist\n"); } -void AbstractConverterTest::setInputFileCallbackConvertFileToDataDirectly() { +void AbstractConverterTest::setInputFileCallbackLinkFilesToDataDirectly() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData|ConverterFeature::InputFileCallback; + return ConverterFeature::LinkData|ConverterFeature::InputFileCallback; } - Containers::Array doConvertFileToData(Stage stage, Containers::StringView from) override { - if(stage == Stage::Compute && from == "file.dat" && inputFileCallback() && inputFileCallbackUserData()) + Containers::Array doLinkFilesToData(Containers::ArrayView> from) override { + if(from.size() == 2 && from[0].first == Stage::Vertex && from[0].second == "another.dat" && from[1].first == Stage::Fragment && from[1].second == "file.dat" && inputFileCallback() && inputFileCallbackUserData()) return Containers::array({'y', 'e', 'p'}); return {}; } - Containers::Array doConvertDataToData(Stage, Containers::ArrayView) override { + Containers::Array doLinkDataToData(Containers::ArrayView>>) override { CORRADE_VERIFY(!"this should not be reached"); return {}; } @@ -1393,49 +2520,56 @@ void AbstractConverterTest::setInputFileCallbackConvertFileToDataDirectly() { return Containers::Optional>{}; }, &a); - CORRADE_COMPARE_AS(converter.convertFileToData(Stage::Compute, "file.dat"), - Containers::arrayView({'y', 'e', 'p'}), + CORRADE_COMPARE_AS(converter.linkFilesToData({ + {Stage::Vertex, "another.dat"}, + {Stage::Fragment, "file.dat"} + }), Containers::arrayView({'y', 'e', 'p'}), TestSuite::Compare::Container); } -void AbstractConverterTest::setInputFileCallbackConvertFileToDataThroughBaseImplementation() { +void AbstractConverterTest::setInputFileCallbackLinkFilesToDataThroughBaseImplementation() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData|ConverterFeature::InputFileCallback; + return ConverterFeature::LinkData|ConverterFeature::InputFileCallback; } - Containers::Array doConvertFileToData(Stage stage, Containers::StringView from) override { - convertFileToDataCalled = true; + Containers::Array doLinkFilesToData(Containers::ArrayView> from) override { + linkFilesToDataCalled = true; - if(stage != Stage::TessellationEvaluation || from != "file.dat" || !inputFileCallback() || !inputFileCallbackUserData()) + if(from.size() != 2 || from[0].first != Stage::Vertex || from[0].second != "another.dat" || from[1].first != Stage::Fragment || from[1].second != "file.dat" || !inputFileCallback() || !inputFileCallbackUserData()) return {}; - return AbstractConverter::doConvertFileToData(stage, from); + return AbstractConverter::doLinkFilesToData(from); } - Containers::Array doConvertDataToData(Stage stage, Containers::ArrayView data) override { - if(stage == Stage::TessellationEvaluation && data.size() == 1 && data[0] == '\xb0') - return Containers::array({'y', 'e', 'p'}); - return {}; + Containers::Array doLinkDataToData(Containers::ArrayView>> data) override { + CORRADE_COMPARE(data.size(), 2); + return Containers::array({ + data[0].first == Stage::Vertex ? data[0].second[0] : ' ', + data[1].first == Stage::Fragment ? data[1].second[0] : ' ' + }); } - bool convertFileToDataCalled = false; + bool linkFilesToDataCalled = false; } converter; struct State { - const char data = '\xb0'; - bool loaded = false; - bool closed = false; + const char first[2]{'V', 'E'}; + const char second[2]{'S', 'A'}; + std::string operations; } state; converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { - if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) { - state.loaded = true; - return Containers::arrayView(&state.data, 1); + if(policy == InputFileCallbackPolicy::LoadTemporary) { + state.operations += "loaded " + filename + "\n"; + if(filename == "another.dat") + return Containers::arrayView(state.first); + if(filename == "file.dat") + return Containers::arrayView(state.second); } - if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) { - state.closed = true; + if(policy == InputFileCallbackPolicy::Close) { + state.operations += "closed " + filename + "\n"; return {}; } @@ -1443,72 +2577,108 @@ void AbstractConverterTest::setInputFileCallbackConvertFileToDataThroughBaseImpl return {}; }, state); - CORRADE_COMPARE_AS(converter.convertFileToData(Stage::TessellationEvaluation, "file.dat"), - Containers::arrayView({'y', 'e', 'p'}), + CORRADE_COMPARE_AS(converter.linkFilesToData({ + {Stage::Vertex, "another.dat"}, + {Stage::Fragment, "file.dat"} + }), Containers::arrayView({'V', 'S'}), TestSuite::Compare::Container); - CORRADE_VERIFY(converter.convertFileToDataCalled); - CORRADE_VERIFY(state.loaded); - CORRADE_VERIFY(state.closed); + CORRADE_VERIFY(converter.linkFilesToDataCalled); + CORRADE_COMPARE(state.operations, + "loaded another.dat\n" + "loaded file.dat\n" + "closed another.dat\n" + "closed file.dat\n"); } -void AbstractConverterTest::setInputFileCallbackConvertFileToDataThroughBaseImplementationFailed() { +void AbstractConverterTest::setInputFileCallbackLinkFilesToDataThroughBaseImplementationFailed() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData|ConverterFeature::InputFileCallback; + return ConverterFeature::LinkData|ConverterFeature::InputFileCallback; } - Containers::Array doConvertFileToData(Stage stage, Containers::StringView from) override { - convertFileToDataCalled = true; - return AbstractConverter::doConvertFileToData(stage, from); + Containers::Array doLinkFilesToData(Containers::ArrayView> from) override { + linkFilesToDataCalled = true; + return AbstractConverter::doLinkFilesToData(from); } - bool convertFileToDataCalled = false; + bool linkFilesToDataCalled = false; } converter; - converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { - return Containers::Optional>{}; - }); + struct State { + const char data[1]{}; + std::string operations; + } state; + + converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(policy == InputFileCallbackPolicy::LoadTemporary) { + state.operations += "loaded " + filename + "\n"; + if(filename == "another.dat") + return Containers::arrayView(state.data); + /* This deliberately fails */ + if(filename == "file.dat") return {}; + } + + if(policy == InputFileCallbackPolicy::Close) { + state.operations += "closed " + filename + "\n"; + return {}; + } + + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + }, state); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!converter.convertFileToData({}, "file.dat")); - CORRADE_VERIFY(converter.convertFileToDataCalled); - CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::convertFileToData(): cannot open file file.dat\n"); + CORRADE_VERIFY(!converter.linkFilesToData({ + {Stage::Vertex, "another.dat"}, + {Stage::Fragment, "file.dat"} + })); + CORRADE_VERIFY(converter.linkFilesToDataCalled); + CORRADE_COMPARE(state.operations, + "loaded another.dat\n" + "loaded file.dat\n" /* this fails */ + "closed another.dat\n"); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkFilesToData(): cannot open file file.dat\n"); } -void AbstractConverterTest::setInputFileCallbackConvertFileToDataAsData() { +void AbstractConverterTest::setInputFileCallbackLinkFilesToDataAsData() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData; + return ConverterFeature::LinkData; } - Containers::Array doConvertFileToData(Stage, Containers::StringView) override { + Containers::Array doLinkFilesToData(Containers::ArrayView>) override { CORRADE_VERIFY(!"this shouldn't be reached"); return {}; } - Containers::Array doConvertDataToData(Stage stage, Containers::ArrayView data) override { - if(stage == Stage::RayGeneration && data.size() == 1 && data[0] == '\xb0') - return Containers::array({'y', 'e', 'p'}); - return {}; + Containers::Array doLinkDataToData(Containers::ArrayView>> data) override { + CORRADE_COMPARE(data.size(), 2); + return Containers::array({ + data[0].first == Stage::Vertex ? data[0].second[0] : ' ', + data[1].first == Stage::Fragment ? data[1].second[0] : ' ' + }); } } converter; struct State { - const char data = '\xb0'; - bool loaded = false; - bool closed = false; + const char first[2]{'V', 'E'}; + const char second[2]{'S', 'A'}; + std::string operations; } state; converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { - if(filename == "file.dat" && policy == InputFileCallbackPolicy::LoadTemporary) { - state.loaded = true; - return Containers::arrayView(&state.data, 1); + if(policy == InputFileCallbackPolicy::LoadTemporary) { + state.operations += "loaded " + filename + "\n"; + if(filename == "another.dat") + return Containers::arrayView(state.first); + if(filename == "file.dat") + return Containers::arrayView(state.second); } - if(filename == "file.dat" && policy == InputFileCallbackPolicy::Close) { - state.closed = true; + if(policy == InputFileCallbackPolicy::Close) { + state.operations += "closed " + filename + "\n"; return {}; } @@ -1516,34 +2686,61 @@ void AbstractConverterTest::setInputFileCallbackConvertFileToDataAsData() { return {}; }, state); - CORRADE_COMPARE_AS(converter.convertFileToData(Stage::RayGeneration, "file.dat"), - Containers::arrayView({'y', 'e', 'p'}), + CORRADE_COMPARE_AS(converter.linkFilesToData({ + {Stage::Vertex, "another.dat"}, + {Stage::Fragment, "file.dat"} + }), Containers::arrayView({'V', 'S'}), TestSuite::Compare::Container); - CORRADE_VERIFY(state.loaded); - CORRADE_VERIFY(state.closed); + CORRADE_COMPARE(state.operations, + "loaded another.dat\n" + "loaded file.dat\n" + "closed another.dat\n" + "closed file.dat\n"); } -void AbstractConverterTest::setInputFileCallbackConvertFileToDataAsDataFailed() { +void AbstractConverterTest::setInputFileCallbackLinkFilesToDataAsDataFailed() { struct: AbstractConverter { ConverterFeatures doFeatures() const override { - return ConverterFeature::ConvertData; + return ConverterFeature::LinkData; } - Containers::Array doConvertFileToData(Stage, Containers::StringView) override { + Containers::Array doLinkFilesToData(Containers::ArrayView>) override { CORRADE_VERIFY(!"this shouldn't be reached"); return {}; } } converter; - converter.setInputFileCallback([](const std::string&, InputFileCallbackPolicy, void*) { - return Containers::Optional>{}; - }); + struct State { + const char data[1]{}; + std::string operations; + } state; + + converter.setInputFileCallback([](const std::string& filename, InputFileCallbackPolicy policy, State& state) -> Containers::Optional> { + if(policy == InputFileCallbackPolicy::LoadTemporary) { + state.operations += "loaded " + filename + "\n"; + if(filename == "another.dat") + return Containers::arrayView(state.data); + /* This deliberately fails */ + if(filename == "file.dat") return {}; + } + + if(policy == InputFileCallbackPolicy::Close) { + state.operations += "closed " + filename + "\n"; + return {}; + } + + CORRADE_VERIFY(!"this shouldn't be reached"); + return {}; + }, state); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!converter.convertFileToData({}, "file.dat")); - CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::convertFileToData(): cannot open file file.dat\n"); + CORRADE_VERIFY(!converter.linkFilesToData({ + {Stage::Vertex, "another.dat"}, + {Stage::Fragment, "file.dat"} + })); + CORRADE_COMPARE(out.str(), "ShaderTools::AbstractConverter::linkFilesToData(): cannot open file file.dat\n"); } void AbstractConverterTest::debugFeature() { diff --git a/src/Magnum/ShaderTools/Test/CMakeLists.txt b/src/Magnum/ShaderTools/Test/CMakeLists.txt index fb754cbcc..bfe4948f2 100644 --- a/src/Magnum/ShaderTools/Test/CMakeLists.txt +++ b/src/Magnum/ShaderTools/Test/CMakeLists.txt @@ -36,6 +36,6 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake corrade_add_test(ShaderToolsAbstractConverterTest AbstractConverterTest.cpp LIBRARIES MagnumShaderToolsTestLib - FILES file.dat) + FILES file.dat another.dat) target_include_directories(ShaderToolsAbstractConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/Magnum/ShaderTools/Test/another.dat b/src/Magnum/ShaderTools/Test/another.dat new file mode 100644 index 000000000..e34dd68f6 --- /dev/null +++ b/src/Magnum/ShaderTools/Test/another.dat @@ -0,0 +1 @@ +VRIPS