You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

618 lines
30 KiB

/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/Arguments.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/Path.h>
#include <Corrade/Utility/String.h>
#include "Magnum/Implementation/converterUtilities.h"
#include "Magnum/Math/Functions.h"
#include "Magnum/ShaderTools/AbstractConverter.h"
#include "Magnum/ShaderTools/Stage.h"
#include "Magnum/ShaderTools/Implementation/spirv.h"
namespace Magnum {
/** @page magnum-shaderconverter Shader conversion utility
@brief Converts, compiles, optimizes and links shaders of different formats
@m_since_latest
@m_footernavigation
@m_keywords{magnum-shaderconverter shaderconverter}
This utility is built if `WITH_SHADERCONVERTER` is enabled when building
Magnum. To use this utility with CMake, you need to request the
`shaderconverter` component of the `Magnum` package and use the
`Magnum::shaderconverter` target for example in a custom command:
@code{.cmake}
find_package(Magnum REQUIRED shaderconverter)
add_custom_command(OUTPUT ... COMMAND Magnum::shaderconverter ...)
@endcode
See @ref building, @ref cmake and the @ref ShaderTools namespace for more
information.
@section magnum-shaderconverter-usage Usage
@code{.sh}
magnum-shaderconverter [-h|--help] [--validate] [--link]
[-C|--converter NAME]... [--plugin-dir DIR]
[-c|--converter-options key=val,key2=val2,]... [--info] [-q|--quiet]
[-v|--verbose] [--warning-as-error] [-E|--preprocess-only]
[-D|--define name=value]... [-U|--undefine name]... [-O|--optimize LEVEL]
[-g|--debug-info LEVEL] [--input-format glsl|spv|spvasm|hlsl|metal]...
[--output-format glsl|spv|spvasm|hlsl|metal]...
[--input-version VERSION]... [--output-version VERSION]...
[--] input... output
@endcode
Arguments:
- `input` --- input file(s)
- `output` --- output file; ignored if `--info` is present, disallowed for
`--validate`. If neither `--info`, `--validate` nor `--link` is present,
corresponds to the @ref ShaderTools::AbstractConverter::convertFileToFile()
function.
- `-h`, `--help` --- display this help message and exit
- `--validate` --- validate input. Corresponds to the
@ref ShaderTools::AbstractConverter::validateFile() function.
- `--link` --- link multiple input files together. Corresponds to the
@ref ShaderTools::AbstractConverter::linkFilesToFile() function.
- `-C`, `--converter CONVERTER` --- shader converter plugin(s)
- `--plugin-dir DIR` --- override base plugin dir
- `-c`, `--converter-options key=val,key2=val2,` --- configuration options
to pass to the converter(s)
- `--info` --- print SPIR-V module info and exit
- `-q`, `--quiet` --- quiet output from converter plugin(s). Corresponds to
the @ref ShaderTools::ConverterFlag::Quiet flag.
- `-v`, `--verbose` --- verbose output from converter plugin(s). Corresponds
to the @ref ShaderTools::ConverterFlag::Verbose flag.
- `--warning-as-error` --- treat warnings as errors. Corresponds to the
@ref ShaderTools::ConverterFlag::WarningAsError flag.
- `-E`, `--preprocess-only` --- preprocess the input file and exit.
Corresponds to the @ref ShaderTools::ConverterFlag::PreprocessOnly flag.
- `-D`, `--define name=value` --- define a preprocessor macro. Corresponds to
the @ref ShaderTools::AbstractConverter::setDefinitions() function.
- `-U`, `--undefine name` --- undefine a preprocessor macro. Corresponds to
the @ref ShaderTools::AbstractConverter::setDefinitions() function.
- `-O`, `--optimize LEVEL` --- optimization level to use. Corresponds to the
@ref ShaderTools::AbstractConverter::setOptimizationLevel() function.
- `-g`, `--debug-info LEVEL` --- debug info level to use. Corresponds to the
@ref ShaderTools::AbstractConverter::setDebugInfoLevel() function.
- `--input-format glsl|spv|spvasm|hlsl|metal` --- input format for each
converter
- `--output-format glsl|spv|spvasm|hlsl|metal` --- output format for each
converter
- `--input-version VERSION` --- input format version for each converter
- `--output-version VERSION` --- output format version for each converter
If `--validate` is given, the utility will validate the `input` file using
passed `--converter` (or @ref ShaderTools::AnyConverter "AnyShaderConverter" if
none is specified), print the validation log on output and exit with a non-zero
code if the validation fails. If `--link` is given, the utility will link all
files together using passed `--converter` (or
@ref ShaderTools::AnyConverter "AnyShaderConverter" if none is specified) and
save it to `output`. If neither is specified, the utility will convert the
`input` file using (one or more) passed `--converter` (or
@ref ShaderTools::AnyConverter "AnyShaderConverter" if none is specified) and
save it to `output`.
The `-c` / `--converter-options` argument accept a comma-separated list of
key/value pairs to set in the converter plugin configuration. If the `=`
character is omitted, it's equivalent to saying `key=true`; configuration
subgroups are delimited with `/`. It's possible to specify the `-C` /
`--converter` option (and correspondingly also `-c` / `--converter-options`,
`--input-format`, `--output-format`, `--input-version` and `--output-version`)
multiple times in order to chain more converters together. All converters in
the chain have to support the @ref ShaderTools::ConverterFeature::ConvertData
feature, if there's just one converter it's enough for it to support
@ref ShaderTools::ConverterFeature::ConvertFile. If no `-C` / `--converter` is
specified, @ref ShaderTools::AnyConverter "AnyShaderConverter" is used.
The `-D` / `--define`, `-U` / `--undefine`, `-O` / `--optimize`, `-g` /
`--debug-info`, `-E` / `--preprocess-only` arguments apply only to the first
converter. Split the conversion to multiple passes if you need to pass those to
converters later in the chain.
Values accepted by `-O` / `--optimize`, `-g` / `--debug-info`, `--input-format`,
`--output-format`, `--input-version` and `--output-version` are
converter-specific, see documentation of a particular converter for more
information.
@section magnum-shaderconverter-example Example usage
Validate a SPIR-V file for a Vulkan 1.1 target, using
@ref ShaderTools::SpirvToolsConverter "SpirvToolsShaderConverter" picked by
@ref ShaderTools::AnyConverter "AnyShaderConverter":
@code{.sh}
magnum-shaderconverter --validate --output-version vulkan1.1 shader.spv
@endcode
Converting a GLSL 4.10 file to a SPIR-V, supplying various preprocessor
definitions, treating warnings as errors and targeting OpenGL instead of the
(default) Vulkan, using @ref ShaderTools::GlslangConverter "GlslangShaderConverter"
picked again by @ref ShaderTools::AnyConverter "AnyShaderConverter":
@m_class{m-console-wrap}
@code{.sh}
magnum-shaderconverter phong.frag -DDIFFUSE_TEXTURE -DNORMAL_TEXTURE --input-version "410 core" --output-version opengl4.5 --warning-as-error phong.frag.spv
@endcode
*/
}
using namespace Corrade::Containers::Literals;
using namespace Magnum;
namespace {
ShaderTools::Stage spvExecutionModelToStage(const SpvExecutionModel model) {
switch(model) {
#define _c(model, stage) case SpvExecutionModel ## model: return ShaderTools::Stage::stage;
_c(Vertex, Vertex)
_c(Fragment, Fragment)
_c(Geometry, Geometry)
_c(TessellationControl, TessellationControl)
_c(TessellationEvaluation, TessellationEvaluation)
_c(GLCompute, Compute)
_c(RayGenerationKHR, RayGeneration)
_c(AnyHitKHR, RayAnyHit)
_c(ClosestHitKHR, RayClosestHit)
_c(MissKHR, RayMiss)
_c(IntersectionKHR, RayIntersection)
_c(CallableKHR, RayCallable)
_c(TaskNV, MeshTask)
_c(MeshNV, Mesh)
_c(Kernel, Kernel)
#undef _c
case SpvExecutionModelMax: break;
}
/* Encode unknown stages with the highest bit set. SpvExecutionModelMax is
0x7fffffff, so this shouldn't lead to any data loss. */
return ShaderTools::Stage((1u << 31)|model);
}
void printSpirvInfo(Containers::ArrayView<const UnsignedInt> data) {
while(Containers::Optional<ShaderTools::Implementation::SpirvEntrypoint> entrypoint = ShaderTools::Implementation::spirvNextEntrypoint(data)) {
Debug d;
d << "Entrypoint" << entrypoint->name << "(" << Debug::nospace << spvExecutionModelToStage(entrypoint->executionModel) << Debug::nospace << ")" << Debug::newline;
Containers::Array<ShaderTools::Implementation::SpirvEntrypointInterface> interface{ValueInit, entrypoint->interfaces.size()};
ShaderTools::Implementation::spirvEntrypointInterface(data, *entrypoint, interface);
for(const ShaderTools::Implementation::SpirvEntrypointInterface& i: interface) {
d << " ";
if(!i.storageClass) d << "(unknown)";
else if(*i.storageClass == SpvStorageClassInput)
d << "in";
else if(*i.storageClass == SpvStorageClassOutput)
d << "out";
else d << "SpvStorageClass(" << Debug::nospace << *i.storageClass << Debug::nospace << ")";
if(i.location) d << "(location=" << Debug::nospace << *i.location << Debug::nospace << ")";
d << Debug::newline;
}
}
}
}
int main(int argc, char** argv) {
Utility::Arguments args;
args.addArrayArgument("input").setHelp("input", "input file(s)")
.addArgument("output").setHelp("output", "output file; ignored if --info is present, disallowed for --validate")
.addBooleanOption("validate").setHelp("validate", "validate input")
.addBooleanOption("link").setHelp("link", "link multiple input files together")
.addArrayOption('C', "converter").setHelp("converter", "shader converter plugin(s)")
.addOption("plugin-dir").setHelp("plugin-dir", "override base plugin dir", "DIR")
.addArrayOption('c', "converter-options").setHelp("converter-options", "configuration options to pass to the converter(s)", "key=val,key2=val2,…")
.addBooleanOption("info").setHelp("info", "print SPIR-V module info and exit")
.addBooleanOption('q', "quiet").setHelp("quiet", "quiet output from converter plugin(s)")
.addBooleanOption('v', "verbose").setHelp("verbose", "verbose output from converter plugin(s)")
.addBooleanOption("warning-as-error").setHelp("warning-as-error", "treat warnings as errors")
.addBooleanOption('E', "preprocess-only").setHelp("preprocess-only", "preprocess the input file and exit")
.addArrayOption('D', "define").setHelp("define", "define a preprocessor macro", "name=value")
.addArrayOption('U', "undefine").setHelp("undefine", "undefine a preprocessor macro", "name")
.addOption('O', "optimize").setHelp("optimize", "optimization level to use", "LEVEL")
.addOption('g', "debug-info").setHelp("debug-info", "debug info level to use", "LEVEL")
/** @todo what the heck is the extension for wgsl and dxil?! */
.addArrayOption("input-format").setHelp("input-format", "input format for each converter", "glsl|spv|spvasm|hlsl|metal")
.addArrayOption("output-format").setHelp("output-format", "output format for each converter", "glsl|spv|spvasm|hlsl|metal")
.addArrayOption("input-version").setHelp("input-version", "input format version for each converter", "VERSION")
.addArrayOption("output-version").setHelp("output-version", "output format version for each converter", "VERSION")
.setParseErrorCallback([](const Utility::Arguments& args, Utility::Arguments::ParseError error, const std::string& key) {
/* If --info / --validate is passed, we don't need the output
argument */
if(error == Utility::Arguments::ParseError::MissingArgument &&
key == "output" && (args.isSet("info") || args.isSet("validate")))
return true;
/* Handle all other errors as usual */
return false;
})
.setGlobalHelp(R"(Converts, compiles, optimizes and links shaders of different formats.
If --info is given and the input looks like a SPIR-V binary, the utility prints
information about its entrypoints using builtin SPIR-V reflection capabilities.
If it's not a SPIR-V binary, it's converted to it first.
If --validate is given, the utility will validate the input file using passed
--converter (or AnyShaderConverter if none is specified), print the validation
log on output and exit with a non-zero code if the validation fails. If --link
is given, the utility will link all files together using passed --converter (or
AnyShaderConverter if none is specified) and save it to output. If neither is
specified, the utility will convert the input file using (one or more) passed
--converter and save it to output.
The -c / --converter-options argument accept a comma-separated list of
key/value pairs to set in the converter plugin configuration. If the =
character is omitted, it's equivalent to saying key=true; configuration
subgroups are delimited with /. It's possible to specify the -C / --converter
option (and correspondingly also -c / --converter-options, --input-format,
--output-format, --input-version and --output-version) multiple times in order
to chain more converters together. All converters in the chain have to support
the ConvertData feature, if there's just one converter it's enough for it to
support ConvertFile. If no -C / --converter is specified, AnyShaderConverter is
used.
The -D / --define, -U / --undefine, -O / --optimize, -g / --debug-info, -E /
--preprocess-only arguments apply only to the first converter. Split the
conversion to multiple passes if you need to pass those to converters later in
the chain.
Values accepted by -O / --optimize, -g / --debug-info, --input-format,
--output-format, --input-version and --output-version are converter-specific,
see documentation of a particular converter for more information.)")
.parse(argc, argv);
/* Generic checks */
if(!args.value<Containers::StringView>("output").isEmpty()) {
if(args.isSet("validate")) {
Error{} << "Output file shouldn't be set for --validate:" << args.value<Containers::StringView>("output");
return 1;
}
/* Not an error in this case, it should be possible to just append
--info to existing command line without having to remove anything.
But print a warning at least, it could also be a mistyped option. */
if(args.isSet("info"))
Warning{} << "Ignoring output file for --info:" << args.value<Containers::StringView>("output");
}
if(!args.isSet("link")) {
if(args.arrayValueCount("input") != 1) {
Error{} << "Multiple input files are allowed only for --link";
return 3;
}
}
if(args.isSet("validate") || args.isSet("link")) {
if(args.isSet("preprocess-only")) {
Error{} << "The --preprocess-only option isn't allowed for --validate or --link";
return 4;
}
if(args.arrayValueCount("converter") > 1) {
Error{} << "Cannot use multiple converters with --validate or --link";
return 5;
}
}
if(args.isSet("quiet") && args.isSet("verbose")) {
Error{} << "Can't set both --quiet and --verbose";
return 6;
}
if(args.isSet("quiet") && args.isSet("warning-as-error")) {
Error{} << "Can't set both --quiet and --warning-as-error";
return 6;
}
/* If we want just SPIR-V info and the input looks like a SPIR-V binary,
do that right away without going through any plugin. If it doesn't, we
try again after using a converter. */
if(args.isSet("info")) {
const Containers::Optional<Containers::Array<char>> data = Utility::Path::read(args.arrayValue("input", 0));
if(!data) {
Error{} << "Can't read" << args.arrayValue("input", 0);
return 24;
}
if(Containers::ArrayView<const UnsignedInt> spirv = ShaderTools::Implementation::spirvData(*data, data->size())) {
printSpirvInfo(spirv);
return 0;
}
}
/* Set up a converter manager */
PluginManager::Manager<ShaderTools::AbstractConverter> converterManager{
args.value("plugin-dir").empty() ? Containers::String{} :
Utility::Path::join(args.value("plugin-dir"), ShaderTools::AbstractConverter::pluginSearchPaths()[0])};
/* Data passed from one converter to another in case there's more than one */
Containers::Array<char> data;
/* If there's no converters, it'll be just one AnyShaderConverter. */
for(std::size_t i = 0, converterCount = args.arrayValueCount("converter"); i < Math::max(converterCount, std::size_t{1}); ++i) {
const std::string converterName = converterCount ?
args.arrayValue("converter", i) : "AnyShaderConverter";
Containers::Pointer<ShaderTools::AbstractConverter> converter = converterManager.loadAndInstantiate(converterName);
if(!converter) {
Debug{} << "Available converter plugins:" << Utility::String::join(converterManager.aliasList(), ", ");
return 7;
}
/* Set options if passed */
if(i < args.arrayValueCount("converter-options"))
Implementation::setOptions(*converter, "AnyShaderConverter", args.arrayValue("converter-options", i));
/* Parse format, if passed. If --info is desired, implicitly set the
output format to SPIR-V */
ShaderTools::Format inputFormat{}, outputFormat{};
if(args.isSet("info"))
outputFormat = ShaderTools::Format::Spirv;
auto parseFormat = [](Containers::StringView format) -> Containers::Optional<ShaderTools::Format> {
if(format == ""_s) return ShaderTools::Format::Unspecified;
if(format == "glsl"_s) return ShaderTools::Format::Glsl;
if(format == "spv"_s) return ShaderTools::Format::Spirv;
if(format == "spvasm"_s) return ShaderTools::Format::SpirvAssembly;
if(format == "hlsl"_s) return ShaderTools::Format::Hlsl;
if(format == "metal"_s) return ShaderTools::Format::Msl;
/** @todo wgsl and dxil once i figure out the extensions */
Error{} << "Unrecognized format" << format << Debug::nospace << ", expected glsl, spv, spvasm, hlsl or metal";
return {};
};
if(i < args.arrayValueCount("input-format")) {
if(const Containers::Optional<ShaderTools::Format> format = parseFormat(args.arrayValue<Containers::StringView>("input-format", i)))
inputFormat = *format;
else return 8;
}
if(i < args.arrayValueCount("output-format")) {
if(const Containers::Optional<ShaderTools::Format> format = parseFormat(args.arrayValue<Containers::StringView>("output-format", i)))
outputFormat = *format;
else return 9;
}
/* Get version, if passed */
Containers::StringView inputVersion{}, outputVersion{};
if(i < args.arrayValueCount("input-version"))
inputVersion = args.arrayValue<Containers::StringView>("input-version", i);
if(i < args.arrayValueCount("output-version"))
outputVersion = args.arrayValue<Containers::StringView>("output-version", i);
/* If not passed, these are set to Unspecified and "", which is the
default */
converter->setInputFormat(inputFormat, inputVersion);
converter->setOutputFormat(outputFormat, outputVersion);
ShaderTools::ConverterFlags flags;
/* Global flags, applied for all converters */
if(args.isSet("quiet")) flags |= ShaderTools::ConverterFlag::Quiet;
if(args.isSet("verbose")) flags |= ShaderTools::ConverterFlag::Verbose;
if(args.isSet("warning-as-error")) flags |= ShaderTools::ConverterFlag::WarningAsError;
/* Options and flags applied just for the first converter; setting up
file list for linking */
Containers::Array<std::pair<ShaderTools::Stage, Containers::StringView>> linkInputs;
if(i == 0) {
if((args.isSet("preprocess-only") || args.arrayValueCount("define") || args.arrayValueCount("undefine"))) {
if(!(converter->features() >= ShaderTools::ConverterFeature::Preprocess)) {
Error{} << "The -E / -D / -U options are set, but" << converterName << "doesn't support preprocessing";
return 10;
}
if(args.isSet("preprocess-only"))
flags |= ShaderTools::ConverterFlag::PreprocessOnly;
Containers::Array<std::pair<Containers::StringView, Containers::StringView>> definitions;
arrayReserve(definitions, args.arrayValueCount("define") + args.arrayValueCount("undefine"));
for(std::size_t j = 0; j != args.arrayValueCount("define"); ++j) {
const Containers::Array3<Containers::StringView> define =
args.arrayValue<Containers::StringView>("define", j).partition('=');
arrayAppend(definitions, InPlaceInit,
define[0], define[2]);
}
for(std::size_t j = 0; j != args.arrayValueCount("undefine"); ++j) {
arrayAppend(definitions, InPlaceInit,
args.arrayValue<Containers::StringView>("undefine", j), nullptr);
}
converter->setDefinitions(definitions);
}
if(!args.value<Containers::StringView>("optimize").isEmpty()) {
if(!(converter->features() >= ShaderTools::ConverterFeature::Optimize)) {
Error{} << "The -O option is set, but" << converterName << "doesn't support optimization";
return 11;
}
converter->setOptimizationLevel(args.value<Containers::StringView>("optimize"));
}
if(!args.value<Containers::StringView>("debug-info").isEmpty()) {
if(!(converter->features() >= ShaderTools::ConverterFeature::DebugInfo)) {
Error{} << "The -g option is set, but" << converterName << "doesn't support debug info";
return 12;
}
converter->setDebugInfoLevel(args.value<Containers::StringView>("debug-info"));
}
if(args.isSet("link")) {
arrayReserve(linkInputs, args.arrayValueCount("input"));
for(std::size_t j = 0; j != args.arrayValueCount("input"); ++j)
arrayAppend(linkInputs, InPlaceInit,
ShaderTools::Stage::Unspecified, args.arrayValue<Containers::StringView>("input", j));
}
}
converter->addFlags(flags);
/* If we want just SPIR-V info, convert to a SPIR-V and exit */
if(args.isSet("info")) {
/* The info exits right after, so this branch shouldn't get
re-entered again */
CORRADE_INTERNAL_ASSERT(i == 0);
if(!(converter->features() >= ShaderTools::ConverterFeature::ConvertData)) {
Error{} << converterName << "doesn't support data conversion";
return 18; /* same code as the same message below */
}
if(!(data = converter->convertFileToData(ShaderTools::Stage::Unspecified, args.arrayValue<Containers::StringView>("input", 0)))) {
Error{} << "Cannot convert" << args.arrayValue<Containers::StringView>("input", 0);
return 20; /* same code as the same message below */
}
Containers::ArrayView<const UnsignedInt> spirv = ShaderTools::Implementation::spirvData(data, data.size());
if(!spirv) {
Error{} << "The output is not a SPIR-V binary, can't print info";
return 23;
}
printSpirvInfo(spirv);
return 0;
}
/* If validating, do it just with the first passed converter and then
exit */
if(args.isSet("validate")) {
/* The validation exits right after, so this branch shouldn't get
re-entered again */
CORRADE_INTERNAL_ASSERT(i == 0);
if(!(converter->features() >= ShaderTools::ConverterFeature::ValidateFile)) {
Error{} << converterName << "doesn't support file validation";
return 13;
}
std::pair<bool, Containers::String> out = converter->validateFile(ShaderTools::Stage::Unspecified, args.arrayValue<Containers::StringView>("input", 0));
if(!out.first) {
if(args.isSet("verbose"))
Error{} << "Validation failed:";
if(!out.second.isEmpty()) Error{} << out.second;
} else if(!out.second.isEmpty()) {
if(args.isSet("verbose"))
Warning{} << "Validation succeeded with warnings:";
if(!out.second.isEmpty()) Warning{} << out.second;
} else if(args.isSet("verbose"))
Debug{} << "Validation passed";
return out.first ? 0 : 14;
}
/** @todo ability to specify the stage (need a configurationvalue parser for this) */
/* This is the first *and* last --converter, go from a file to a file */
if(i == 0 && converterCount <= 1) {
if(!(converter->features() >= ShaderTools::ConverterFeature::ConvertFile)) {
Error{} << converterName << "doesn't support file conversion";
return 15;
}
/* No verbose output for just one converter */
/* Linking */
if(args.isSet("link")) {
if(!converter->linkFilesToFile(linkInputs, args.value<Containers::StringView>("output"))) {
Error{} << "Cannot link" << args.arrayValue<Containers::StringView>("input", 0) << "and others to" << args.value<Containers::StringView>("output");
return 16;
}
/* Converting */
} else {
if(!converter->convertFileToFile(ShaderTools::Stage::Unspecified, args.arrayValue<Containers::StringView>("input", 0), args.value<Containers::StringView>("output"))) {
Error{} << "Cannot convert" << args.arrayValue<Containers::StringView>("input", 0) << "to" << args.value<Containers::StringView>("output");
return 17;
}
}
/* Otherwise we need to go through data */
} else {
if(!(converter->features() >= ShaderTools::ConverterFeature::ConvertData)) {
Error{} << converterName << "doesn't support data conversion";
return 18;
}
/* This is the first --converter and there are more, go from a file
to data */
if(i == 0 && converterCount > 1) {
if(args.isSet("verbose"))
Debug{} << "Processing (" << Debug::nospace << (i+1) << Debug::nospace << "/" << Debug::nospace << converterCount << Debug::nospace << ") with" << converterName << Debug::nospace << "...";
/* Linking */
if(args.isSet("link")) {
if(!(data = converter->linkFilesToData(linkInputs))) {
Error{} << "Cannot link" << args.arrayValue<Containers::StringView>("input", 0) << "and others to" << args.value<Containers::StringView>("output");
return 19;
}
/* Converting */
} else {
if(!(data = converter->convertFileToData(ShaderTools::Stage::Unspecified, args.arrayValue<Containers::StringView>("input", 0)))) {
Error{} << "Cannot convert" << args.arrayValue<Containers::StringView>("input", 0);
return 20;
}
}
/* This is neither first nor last --converter, go from data to
data */
} else if(i > 0 && i + 1 < converterCount) {
if(args.isSet("verbose"))
Debug{} << "Processing (" << Debug::nospace << (i+1) << Debug::nospace << "/" << Debug::nospace << converterCount << Debug::nospace << ") with" << converterName << Debug::nospace << "...";
CORRADE_INTERNAL_ASSERT(data);
/* Subsequent operations are always a conversion, not link */
if(!(data = converter->convertDataToData(ShaderTools::Stage::Unspecified, data))) {
Error{} << "Cannot convert shader data";
return 21;
}
/* This is the last --converter, output to a file and exit the
loop */
} else if(i + 1 >= converterCount) {
if(args.isSet("verbose"))
Debug{} << "Saving output with" << converterName << Debug::nospace << "...";
CORRADE_INTERNAL_ASSERT(data);
/* Subsequent operations are always a conversion, not link */
if(!converter->convertDataToFile(ShaderTools::Stage::Unspecified, data, args.value<Containers::StringView>("output"))) {
Error{} << "Cannot save file" << args.value<Containers::StringView>("output");
return 22;
}
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
}
}
}