xref: /third_party/skia/src/sksl/SkSLMain.cpp (revision cb93a386)
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
31void SkDebugf(const char format[], ...) {
32    va_list args;
33    va_start(args, format);
34    vfprintf(stderr, format, args);
35    va_end(args);
36}
37
38namespace SkOpts {
39    decltype(hash_fn) hash_fn = skslc_standalone::hash_fn;
40    decltype(interpret_skvm) interpret_skvm = skslc_standalone::interpret_skvm;
41}
42
43enum class ResultCode {
44    kSuccess = 0,
45    kCompileError = 1,
46    kInputError = 2,
47    kOutputError = 3,
48    kConfigurationError = 4,
49};
50
51static 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.
75static 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.
93static 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 */
241static 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 */
253ResultCode 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 */
502ResultCode 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
548int 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