• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #define SK_OPTS_NS skslc_standalone
9 #include "src/core/SkOpts.h"
10 #include "src/opts/SkChecksum_opts.h"
11 #include "src/opts/SkVM_opts.h"
12 
13 #include "src/gpu/GrShaderUtils.h"
14 #include "src/sksl/SkSLCompiler.h"
15 #include "src/sksl/SkSLDehydrator.h"
16 #include "src/sksl/SkSLFileOutputStream.h"
17 #include "src/sksl/SkSLStringStream.h"
18 #include "src/sksl/SkSLUtil.h"
19 #include "src/sksl/codegen/SkSLPipelineStageCodeGenerator.h"
20 #include "src/sksl/codegen/SkSLVMCodeGenerator.h"
21 #include "src/sksl/ir/SkSLUnresolvedFunction.h"
22 #include "src/sksl/ir/SkSLVarDeclarations.h"
23 
24 #include "spirv-tools/libspirv.hpp"
25 
26 #include <fstream>
27 #include <limits.h>
28 #include <stdarg.h>
29 #include <stdio.h>
30 
SkDebugf(const char format[],...)31 void SkDebugf(const char format[], ...) {
32     va_list args;
33     va_start(args, format);
34     vfprintf(stderr, format, args);
35     va_end(args);
36 }
37 
38 namespace SkOpts {
39     decltype(hash_fn) hash_fn = skslc_standalone::hash_fn;
40     decltype(interpret_skvm) interpret_skvm = skslc_standalone::interpret_skvm;
41 }
42 
43 enum class ResultCode {
44     kSuccess = 0,
45     kCompileError = 1,
46     kInputError = 2,
47     kOutputError = 3,
48     kConfigurationError = 4,
49 };
50 
as_SkWStream(SkSL::OutputStream & s)51 static std::unique_ptr<SkWStream> as_SkWStream(SkSL::OutputStream& s) {
52     struct Adapter : public SkWStream {
53     public:
54         Adapter(SkSL::OutputStream& out) : fOut(out), fBytesWritten(0) {}
55 
56         bool write(const void* buffer, size_t size) override {
57             fOut.write(buffer, size);
58             fBytesWritten += size;
59             return true;
60         }
61         void flush() override {}
62         size_t bytesWritten() const override { return fBytesWritten; }
63 
64     private:
65         SkSL::OutputStream& fOut;
66         size_t fBytesWritten;
67     };
68 
69     return std::make_unique<Adapter>(s);
70 }
71 
72 // Given the path to a file (e.g. src/gpu/effects/GrFooFragmentProcessor.fp) and the expected
73 // filename prefix and suffix (e.g. "Gr" and ".fp"), returns the "base name" of the
74 // file (in this case, 'FooFragmentProcessor'). If no match, returns the empty string.
base_name(const SkSL::String & fpPath,const char * prefix,const char * suffix)75 static SkSL::String base_name(const SkSL::String& fpPath, const char* prefix, const char* suffix) {
76     SkSL::String result;
77     const char* end = &*fpPath.end();
78     const char* fileName = end;
79     // back up until we find a slash
80     while (fileName != fpPath && '/' != *(fileName - 1) && '\\' != *(fileName - 1)) {
81         --fileName;
82     }
83     if (!strncmp(fileName, prefix, strlen(prefix)) &&
84         !strncmp(end - strlen(suffix), suffix, strlen(suffix))) {
85         result.append(fileName + strlen(prefix), end - fileName - strlen(prefix) - strlen(suffix));
86     }
87     return result;
88 }
89 
90 // Given a string containing an SkSL program, searches for a #pragma settings comment, like so:
91 //    /*#pragma settings Default Sharpen*/
92 // The passed-in Settings object will be updated accordingly. Any number of options can be provided.
detect_shader_settings(const SkSL::String & text,SkSL::Program::Settings * settings,const SkSL::ShaderCapsClass ** caps,std::unique_ptr<SkSL::SkVMDebugInfo> * debugInfo)93 static bool detect_shader_settings(const SkSL::String& text,
94                                    SkSL::Program::Settings* settings,
95                                    const SkSL::ShaderCapsClass** caps,
96                                    std::unique_ptr<SkSL::SkVMDebugInfo>* debugInfo) {
97     using Factory = SkSL::ShaderCapsFactory;
98 
99     // Find a matching comment and isolate the name portion.
100     static constexpr char kPragmaSettings[] = "/*#pragma settings ";
101     const char* settingsPtr = strstr(text.c_str(), kPragmaSettings);
102     if (settingsPtr != nullptr) {
103         // Subtract one here in order to preserve the leading space, which is necessary to allow
104         // consumeSuffix to find the first item.
105         settingsPtr += strlen(kPragmaSettings) - 1;
106 
107         const char* settingsEnd = strstr(settingsPtr, "*/");
108         if (settingsEnd != nullptr) {
109             SkSL::String settingsText{settingsPtr, size_t(settingsEnd - settingsPtr)};
110 
111             // Apply settings as requested. Since they can come in any order, repeat until we've
112             // consumed them all.
113             for (;;) {
114                 const size_t startingLength = settingsText.length();
115 
116                 if (settingsText.consumeSuffix(" AddAndTrueToLoopCondition")) {
117                     static auto s_addAndTrueCaps = Factory::AddAndTrueToLoopCondition();
118                     *caps = s_addAndTrueCaps.get();
119                 }
120                 if (settingsText.consumeSuffix(" CannotUseFractForNegativeValues")) {
121                     static auto s_negativeFractCaps = Factory::CannotUseFractForNegativeValues();
122                     *caps = s_negativeFractCaps.get();
123                 }
124                 if (settingsText.consumeSuffix(" CannotUseFragCoord")) {
125                     static auto s_noFragCoordCaps = Factory::CannotUseFragCoord();
126                     *caps = s_noFragCoordCaps.get();
127                 }
128                 if (settingsText.consumeSuffix(" CannotUseMinAndAbsTogether")) {
129                     static auto s_minAbsCaps = Factory::CannotUseMinAndAbsTogether();
130                     *caps = s_minAbsCaps.get();
131                 }
132                 if (settingsText.consumeSuffix(" Default")) {
133                     static auto s_defaultCaps = Factory::Default();
134                     *caps = s_defaultCaps.get();
135                 }
136                 if (settingsText.consumeSuffix(" EmulateAbsIntFunction")) {
137                     static auto s_emulateAbsIntCaps = Factory::EmulateAbsIntFunction();
138                     *caps = s_emulateAbsIntCaps.get();
139                 }
140                 if (settingsText.consumeSuffix(" FramebufferFetchSupport")) {
141                     static auto s_fbFetchSupport = Factory::FramebufferFetchSupport();
142                     *caps = s_fbFetchSupport.get();
143                 }
144                 if (settingsText.consumeSuffix(" IncompleteShortIntPrecision")) {
145                     static auto s_incompleteShortIntCaps = Factory::IncompleteShortIntPrecision();
146                     *caps = s_incompleteShortIntCaps.get();
147                 }
148                 if (settingsText.consumeSuffix(" MustGuardDivisionEvenAfterExplicitZeroCheck")) {
149                     static auto s_div0Caps = Factory::MustGuardDivisionEvenAfterExplicitZeroCheck();
150                     *caps = s_div0Caps.get();
151                 }
152                 if (settingsText.consumeSuffix(" MustForceNegatedAtanParamToFloat")) {
153                     static auto s_negativeAtanCaps = Factory::MustForceNegatedAtanParamToFloat();
154                     *caps = s_negativeAtanCaps.get();
155                 }
156                 if (settingsText.consumeSuffix(" MustForceNegatedLdexpParamToMultiply")) {
157                     static auto s_negativeLdexpCaps =
158                             Factory::MustForceNegatedLdexpParamToMultiply();
159                     *caps = s_negativeLdexpCaps.get();
160                 }
161                 if (settingsText.consumeSuffix(" RemovePowWithConstantExponent")) {
162                     static auto s_powCaps = Factory::RemovePowWithConstantExponent();
163                     *caps = s_powCaps.get();
164                 }
165                 if (settingsText.consumeSuffix(" RewriteDoWhileLoops")) {
166                     static auto s_rewriteLoopCaps = Factory::RewriteDoWhileLoops();
167                     *caps = s_rewriteLoopCaps.get();
168                 }
169                 if (settingsText.consumeSuffix(" RewriteSwitchStatements")) {
170                     static auto s_rewriteSwitchCaps = Factory::RewriteSwitchStatements();
171                     *caps = s_rewriteSwitchCaps.get();
172                 }
173                 if (settingsText.consumeSuffix(" RewriteMatrixVectorMultiply")) {
174                     static auto s_rewriteMatVecMulCaps = Factory::RewriteMatrixVectorMultiply();
175                     *caps = s_rewriteMatVecMulCaps.get();
176                 }
177                 if (settingsText.consumeSuffix(" RewriteMatrixComparisons")) {
178                     static auto s_rewriteMatrixComparisons = Factory::RewriteMatrixComparisons();
179                     *caps = s_rewriteMatrixComparisons.get();
180                 }
181                 if (settingsText.consumeSuffix(" ShaderDerivativeExtensionString")) {
182                     static auto s_derivativeCaps = Factory::ShaderDerivativeExtensionString();
183                     *caps = s_derivativeCaps.get();
184                 }
185                 if (settingsText.consumeSuffix(" UnfoldShortCircuitAsTernary")) {
186                     static auto s_ternaryCaps = Factory::UnfoldShortCircuitAsTernary();
187                     *caps = s_ternaryCaps.get();
188                 }
189                 if (settingsText.consumeSuffix(" UsesPrecisionModifiers")) {
190                     static auto s_precisionCaps = Factory::UsesPrecisionModifiers();
191                     *caps = s_precisionCaps.get();
192                 }
193                 if (settingsText.consumeSuffix(" Version110")) {
194                     static auto s_version110Caps = Factory::Version110();
195                     *caps = s_version110Caps.get();
196                 }
197                 if (settingsText.consumeSuffix(" Version450Core")) {
198                     static auto s_version450CoreCaps = Factory::Version450Core();
199                     *caps = s_version450CoreCaps.get();
200                 }
201                 if (settingsText.consumeSuffix(" AllowNarrowingConversions")) {
202                     settings->fAllowNarrowingConversions = true;
203                 }
204                 if (settingsText.consumeSuffix(" ForceHighPrecision")) {
205                     settings->fForceHighPrecision = true;
206                 }
207                 if (settingsText.consumeSuffix(" NoES2Restrictions")) {
208                     settings->fEnforceES2Restrictions = false;
209                 }
210                 if (settingsText.consumeSuffix(" NoInline")) {
211                     settings->fInlineThreshold = 0;
212                 }
213                 if (settingsText.consumeSuffix(" InlineThresholdMax")) {
214                     settings->fInlineThreshold = INT_MAX;
215                 }
216                 if (settingsText.consumeSuffix(" Sharpen")) {
217                     settings->fSharpenTextures = true;
218                 }
219                 if (settingsText.consumeSuffix(" SkVMDebugTrace")) {
220                     settings->fOptimize = false;
221                     *debugInfo = std::make_unique<SkSL::SkVMDebugInfo>();
222                 }
223 
224                 if (settingsText.empty()) {
225                     break;
226                 }
227                 if (settingsText.length() == startingLength) {
228                     printf("Unrecognized #pragma settings: %s\n", settingsText.c_str());
229                     return false;
230                 }
231             }
232         }
233     }
234 
235     return true;
236 }
237 
238 /**
239  * Displays a usage banner; used when the command line arguments don't make sense.
240  */
show_usage()241 static void show_usage() {
242     printf("usage: skslc <input> <output> <flags>\n"
243            "       skslc <worklist>\n"
244            "\n"
245            "Allowed flags:\n"
246            "--settings:   honor embedded /*#pragma settings*/ comments.\n"
247            "--nosettings: ignore /*#pragma settings*/ comments\n");
248 }
249 
250 /**
251  * Handle a single input.
252  */
processCommand(std::vector<SkSL::String> & args)253 ResultCode processCommand(std::vector<SkSL::String>& args) {
254     bool honorSettings = true;
255     if (args.size() == 4) {
256         // Handle four-argument case: `skslc in.sksl out.glsl --settings`
257         const SkSL::String& settingsArg = args[3];
258         if (settingsArg == "--settings") {
259             honorSettings = true;
260         } else if (settingsArg == "--nosettings") {
261             honorSettings = false;
262         } else {
263             printf("unrecognized flag: %s\n\n", settingsArg.c_str());
264             show_usage();
265             return ResultCode::kInputError;
266         }
267     } else if (args.size() != 3) {
268         show_usage();
269         return ResultCode::kInputError;
270     }
271 
272     SkSL::ProgramKind kind;
273     const SkSL::String& inputPath = args[1];
274     if (inputPath.ends_with(".vert")) {
275         kind = SkSL::ProgramKind::kVertex;
276     } else if (inputPath.ends_with(".frag") || inputPath.ends_with(".sksl")) {
277         kind = SkSL::ProgramKind::kFragment;
278     } else if (inputPath.ends_with(".rtb")) {
279         kind = SkSL::ProgramKind::kRuntimeBlender;
280     } else if (inputPath.ends_with(".rtcf")) {
281         kind = SkSL::ProgramKind::kRuntimeColorFilter;
282     } else if (inputPath.ends_with(".rts")) {
283         kind = SkSL::ProgramKind::kRuntimeShader;
284     } else {
285         printf("input filename must end in '.vert', '.frag', '.rtb', '.rtcf', "
286                "'.rts', or '.sksl'\n");
287         return ResultCode::kInputError;
288     }
289 
290     std::ifstream in(inputPath);
291     SkSL::String text((std::istreambuf_iterator<char>(in)),
292                        std::istreambuf_iterator<char>());
293     if (in.rdstate()) {
294         printf("error reading '%s'\n", inputPath.c_str());
295         return ResultCode::kInputError;
296     }
297 
298     SkSL::Program::Settings settings;
299     SkSL::StandaloneShaderCaps standaloneCaps;
300     const SkSL::ShaderCapsClass* caps = &standaloneCaps;
301     std::unique_ptr<SkSL::SkVMDebugInfo> debugInfo;
302     if (honorSettings) {
303         if (!detect_shader_settings(text, &settings, &caps, &debugInfo)) {
304             return ResultCode::kInputError;
305         }
306     }
307 
308     // This tells the compiler where the rt-flip uniform will live should it be required. For
309     // testing purposes we don't care where that is, but the compiler will report an error if we
310     // leave them at their default invalid values, or if the offset overlaps another uniform.
311     settings.fRTFlipOffset  = 16384;
312     settings.fRTFlipSet     = 0;
313     settings.fRTFlipBinding = 0;
314 
315     const SkSL::String& outputPath = args[2];
316     auto emitCompileError = [&](SkSL::FileOutputStream& out, const char* errorText) {
317         // Overwrite the compiler output, if any, with an error message.
318         out.close();
319         SkSL::FileOutputStream errorStream(outputPath);
320         errorStream.writeText("### Compilation failed:\n\n");
321         errorStream.writeText(errorText);
322         errorStream.close();
323         // Also emit the error directly to stdout.
324         puts(errorText);
325     };
326 
327     auto compileProgram = [&](const auto& writeFn) -> ResultCode {
328         SkSL::FileOutputStream out(outputPath);
329         SkSL::Compiler compiler(caps);
330         if (!out.isValid()) {
331             printf("error writing '%s'\n", outputPath.c_str());
332             return ResultCode::kOutputError;
333         }
334         std::unique_ptr<SkSL::Program> program = compiler.convertProgram(kind, text, settings);
335         if (!program || !writeFn(compiler, *program, out)) {
336             emitCompileError(out, compiler.errorText().c_str());
337             return ResultCode::kCompileError;
338         }
339         if (!out.close()) {
340             printf("error writing '%s'\n", outputPath.c_str());
341             return ResultCode::kOutputError;
342         }
343         return ResultCode::kSuccess;
344     };
345 
346     if (outputPath.ends_with(".spirv")) {
347         return compileProgram(
348                 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
349                     return compiler.toSPIRV(program, out);
350                 });
351     } else if (outputPath.ends_with(".asm.frag") || outputPath.ends_with(".asm.vert")) {
352         return compileProgram(
353                 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
354                     // Compile program to SPIR-V assembly in a string-stream.
355                     SkSL::StringStream assembly;
356                     if (!compiler.toSPIRV(program, assembly)) {
357                         return false;
358                     }
359                     // Convert the string-stream to a SPIR-V disassembly.
360                     spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0);
361                     const SkSL::String& spirv(assembly.str());
362                     std::string disassembly;
363                     if (!tools.Disassemble((const uint32_t*)spirv.data(),
364                                            spirv.size() / 4, &disassembly)) {
365                         return false;
366                     }
367                     // Finally, write the disassembly to our output stream.
368                     out.write(disassembly.data(), disassembly.size());
369                     return true;
370                 });
371     } else if (outputPath.ends_with(".glsl")) {
372         return compileProgram(
373                 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
374                     return compiler.toGLSL(program, out);
375                 });
376     } else if (outputPath.ends_with(".metal")) {
377         return compileProgram(
378                 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
379                     return compiler.toMetal(program, out);
380                 });
381     } else if (outputPath.ends_with(".skvm")) {
382         return compileProgram(
383                 [&](SkSL::Compiler&, SkSL::Program& program, SkSL::OutputStream& out) {
384                     skvm::Builder builder{skvm::Features{}};
385                     if (!SkSL::testingOnly_ProgramToSkVMShader(program, &builder,
386                                                                debugInfo.get())) {
387                         return false;
388                     }
389 
390                     std::unique_ptr<SkWStream> redirect = as_SkWStream(out);
391                     if (debugInfo) {
392                         debugInfo->dump(redirect.get());
393                     }
394                     builder.done().dump(redirect.get());
395                     return true;
396                 });
397     } else if (outputPath.ends_with(".stage")) {
398         return compileProgram(
399                 [](SkSL::Compiler&, SkSL::Program& program, SkSL::OutputStream& out) {
400                     class Callbacks : public SkSL::PipelineStage::Callbacks {
401                     public:
402                         using String = SkSL::String;
403 
404                         String getMangledName(const char* name) override {
405                             return String(name) + "_0";
406                         }
407 
408                         String declareUniform(const SkSL::VarDeclaration* decl) override {
409                             fOutput += decl->description();
410                             return String(decl->var().name());
411                         }
412 
413                         void defineFunction(const char* decl,
414                                             const char* body,
415                                             bool /*isMain*/) override {
416                             fOutput += String(decl) + "{" + body + "}";
417                         }
418 
419                         void declareFunction(const char* decl) override {
420                             fOutput += String(decl) + ";";
421                         }
422 
423                         void defineStruct(const char* definition) override {
424                             fOutput += definition;
425                         }
426 
427                         void declareGlobal(const char* declaration) override {
428                             fOutput += declaration;
429                         }
430 
431                         String sampleShader(int index, String coords) override {
432                             return "child_" + SkSL::to_string(index) + ".eval(" + coords + ")";
433                         }
434 
435                         String sampleColorFilter(int index, String color) override {
436                             return "child_" + SkSL::to_string(index) + ".eval(" + color + ")";
437                         }
438 
439                         String sampleBlender(int index, String src, String dst) override {
440                             return "child_" + SkSL::to_string(index) + ".eval(" + src + ", " +
441                                    dst + ")";
442                         }
443 
444                         String fOutput;
445                     };
446                     // The .stage output looks almost like valid SkSL, but not quite.
447                     // The PipelineStageGenerator bridges the gap between the SkSL in `program`,
448                     // and the C++ FP builder API (see GrSkSLFP). In that API, children don't need
449                     // to be declared (so they don't emit declarations here). Children are sampled
450                     // by index, not name - so all children here are just "child_N".
451                     // The input color and coords have names in the original SkSL (as parameters to
452                     // main), but those are ignored here. References to those variables become
453                     // "_coords" and "_inColor". At runtime, those variable names are irrelevant
454                     // when the new SkSL is emitted inside the FP - references to those variables
455                     // are replaced with strings from EmitArgs, and might be varyings or differently
456                     // named parameters.
457                     Callbacks callbacks;
458                     SkSL::PipelineStage::ConvertProgram(program, "_coords", "_inColor",
459                                                         "_canvasColor", &callbacks);
460                     out.writeString(GrShaderUtils::PrettyPrint(callbacks.fOutput));
461                     return true;
462                 });
463     } else if (outputPath.ends_with(".dehydrated.sksl")) {
464         SkSL::FileOutputStream out(outputPath);
465         SkSL::Compiler compiler(caps);
466         if (!out.isValid()) {
467             printf("error writing '%s'\n", outputPath.c_str());
468             return ResultCode::kOutputError;
469         }
470         SkSL::LoadedModule module =
471                 compiler.loadModule(kind, SkSL::Compiler::MakeModulePath(inputPath.c_str()),
472                                     /*base=*/nullptr, /*dehydrate=*/true);
473         SkSL::Dehydrator dehydrator;
474         dehydrator.write(*module.fSymbols);
475         dehydrator.write(module.fElements);
476         SkSL::String baseName = base_name(inputPath, "", ".sksl");
477         SkSL::StringStream buffer;
478         dehydrator.finish(buffer);
479         const SkSL::String& data = buffer.str();
480         out.printf("static uint8_t SKSL_INCLUDE_%s[] = {", baseName.c_str());
481         for (size_t i = 0; i < data.length(); ++i) {
482             out.printf("%s%d,", dehydrator.prefixAtOffset(i), uint8_t(data[i]));
483         }
484         out.printf("};\n");
485         out.printf("static constexpr size_t SKSL_INCLUDE_%s_LENGTH = sizeof(SKSL_INCLUDE_%s);\n",
486                    baseName.c_str(), baseName.c_str());
487         if (!out.close()) {
488             printf("error writing '%s'\n", outputPath.c_str());
489             return ResultCode::kOutputError;
490         }
491     } else {
492         printf("expected output path to end with one of: .glsl, .metal, .spirv, .asm.frag, .skvm, "
493                ".stage, .asm.vert (got '%s')\n", outputPath.c_str());
494         return ResultCode::kConfigurationError;
495     }
496     return ResultCode::kSuccess;
497 }
498 
499 /**
500  * Processes multiple inputs in a single invocation of skslc.
501  */
processWorklist(const char * worklistPath)502 ResultCode processWorklist(const char* worklistPath) {
503     SkSL::String inputPath(worklistPath);
504     if (!inputPath.ends_with(".worklist")) {
505         printf("expected .worklist file, found: %s\n\n", worklistPath);
506         show_usage();
507         return ResultCode::kConfigurationError;
508     }
509 
510     // The worklist contains one line per argument to pass to skslc. When a blank line is reached,
511     // those arguments will be passed to `processCommand`.
512     auto resultCode = ResultCode::kSuccess;
513     std::vector<SkSL::String> args = {"skslc"};
514     std::ifstream in(worklistPath);
515     for (SkSL::String line; std::getline(in, line); ) {
516         if (in.rdstate()) {
517             printf("error reading '%s'\n", worklistPath);
518             return ResultCode::kInputError;
519         }
520 
521         if (!line.empty()) {
522             // We found an argument. Remember it.
523             args.push_back(std::move(line));
524         } else {
525             // We found a blank line. If we have any arguments stored up, process them as a command.
526             if (!args.empty()) {
527                 ResultCode outcome = processCommand(args);
528                 resultCode = std::max(resultCode, outcome);
529 
530                 // Clear every argument except the first ("skslc").
531                 args.resize(1);
532             }
533         }
534     }
535 
536     // If the worklist ended with a list of arguments but no blank line, process those now.
537     if (args.size() > 1) {
538         ResultCode outcome = processCommand(args);
539         resultCode = std::max(resultCode, outcome);
540     }
541 
542     // Return the "worst" status we encountered. For our purposes, compilation errors are the least
543     // serious, because they are expected to occur in unit tests. Other types of errors are not
544     // expected at all during a build.
545     return resultCode;
546 }
547 
main(int argc,const char ** argv)548 int main(int argc, const char** argv) {
549     if (argc == 2) {
550         // Worklists are the only two-argument case for skslc, and we don't intend to support
551         // nested worklists, so we can process them here.
552         return (int)processWorklist(argv[1]);
553     } else {
554         // Process non-worklist inputs.
555         std::vector<SkSL::String> args;
556         for (int index=0; index<argc; ++index) {
557             args.push_back(argv[index]);
558         }
559 
560         return (int)processCommand(args);
561     }
562 }
563