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