blob: 26c9f77abda97de468798a20377655f8536db7c2 [file] [log] [blame]
ethannicholasb3058bd2016-07-01 08:22:01 -07001/*
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
Brian Osman41d90672020-10-05 14:18:49 -04008#define SK_OPTS_NS skslc_standalone
9#include "src/opts/SkChecksum_opts.h"
Brian Osman47726a12020-12-17 16:02:08 -050010#include "src/opts/SkVM_opts.h"
Brian Osman41d90672020-10-05 14:18:49 -040011
Brian Osman62b039b2021-02-08 13:49:53 -050012#include "src/gpu/GrShaderUtils.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050013#include "src/sksl/SkSLCompiler.h"
Ethan Nicholasc18bb512020-07-28 14:46:53 -040014#include "src/sksl/SkSLDehydrator.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050015#include "src/sksl/SkSLFileOutputStream.h"
Ethan Nicholasc18bb512020-07-28 14:46:53 -040016#include "src/sksl/SkSLIRGenerator.h"
17#include "src/sksl/SkSLStringStream.h"
John Stiles194b9b92020-09-15 15:37:24 -040018#include "src/sksl/SkSLUtil.h"
John Stiles3738ef52021-04-13 10:41:57 -040019#include "src/sksl/codegen/SkSLPipelineStageCodeGenerator.h"
20#include "src/sksl/codegen/SkSLVMCodeGenerator.h"
Ethan Nicholasc18bb512020-07-28 14:46:53 -040021#include "src/sksl/ir/SkSLUnresolvedFunction.h"
ethannicholasb3058bd2016-07-01 08:22:01 -070022
John Stilesdda1d312020-11-20 16:28:50 -050023#include "spirv-tools/libspirv.hpp"
24
Brian Osman41d90672020-10-05 14:18:49 -040025#include <fstream>
John Stiles031a7672020-11-13 16:13:18 -050026#include <limits.h>
Brian Osmana6ca9752020-10-02 13:41:21 -040027#include <stdarg.h>
28#include <stdio.h>
29
30void SkDebugf(const char format[], ...) {
31 va_list args;
32 va_start(args, format);
33 vfprintf(stderr, format, args);
34 va_end(args);
35}
36
Brian Osman41d90672020-10-05 14:18:49 -040037namespace SkOpts {
38 decltype(hash_fn) hash_fn = skslc_standalone::hash_fn;
Brian Osman47726a12020-12-17 16:02:08 -050039 decltype(interpret_skvm) interpret_skvm = skslc_standalone::interpret_skvm;
Brian Osman41d90672020-10-05 14:18:49 -040040}
41
John Stilesa446ca12020-11-19 18:54:33 -050042enum class ResultCode {
43 kSuccess = 0,
44 kCompileError = 1,
45 kInputError = 2,
46 kOutputError = 3,
John Stilesdda1d312020-11-20 16:28:50 -050047 kConfigurationError = 4,
John Stilesa446ca12020-11-19 18:54:33 -050048};
49
Brian Osman47726a12020-12-17 16:02:08 -050050static std::unique_ptr<SkWStream> as_SkWStream(SkSL::OutputStream& s) {
51 struct Adapter : public SkWStream {
52 public:
53 Adapter(SkSL::OutputStream& out) : fOut(out), fBytesWritten(0) {}
54
55 bool write(const void* buffer, size_t size) override {
56 fOut.write(buffer, size);
57 fBytesWritten += size;
58 return true;
59 }
60 void flush() override {}
61 size_t bytesWritten() const override { return fBytesWritten; }
62
63 private:
64 SkSL::OutputStream& fOut;
65 size_t fBytesWritten;
66 };
67
68 return std::make_unique<Adapter>(s);
69}
70
Ethan Nicholas762466e2017-06-29 10:03:38 -040071// Given the path to a file (e.g. src/gpu/effects/GrFooFragmentProcessor.fp) and the expected
72// filename prefix and suffix (e.g. "Gr" and ".fp"), returns the "base name" of the
73// file (in this case, 'FooFragmentProcessor'). If no match, returns the empty string.
John Stilesa1e8fe32020-11-11 17:29:28 -050074static SkSL::String base_name(const SkSL::String& fpPath, const char* prefix, const char* suffix) {
Ethan Nicholas762466e2017-06-29 10:03:38 -040075 SkSL::String result;
John Stilesa1e8fe32020-11-11 17:29:28 -050076 const char* end = &*fpPath.end();
Ethan Nicholas762466e2017-06-29 10:03:38 -040077 const char* fileName = end;
78 // back up until we find a slash
79 while (fileName != fpPath && '/' != *(fileName - 1) && '\\' != *(fileName - 1)) {
80 --fileName;
81 }
82 if (!strncmp(fileName, prefix, strlen(prefix)) &&
83 !strncmp(end - strlen(suffix), suffix, strlen(suffix))) {
84 result.append(fileName + strlen(prefix), end - fileName - strlen(prefix) - strlen(suffix));
85 }
86 return result;
87}
88
John Stiles72664be2020-09-16 17:43:11 -040089// Given a string containing an SkSL program, searches for a #pragma settings comment, like so:
90// /*#pragma settings Default Sharpen*/
91// The passed-in Settings object will be updated accordingly. Any number of options can be provided.
John Stilesa1e8fe32020-11-11 17:29:28 -050092static bool detect_shader_settings(const SkSL::String& text,
Brian Osmand7e76592020-11-02 12:26:22 -050093 SkSL::Program::Settings* settings,
94 const SkSL::ShaderCapsClass** caps) {
John Stiles0ed9f312020-09-16 17:46:37 -040095 using Factory = SkSL::ShaderCapsFactory;
96
John Stiles72664be2020-09-16 17:43:11 -040097 // Find a matching comment and isolate the name portion.
98 static constexpr char kPragmaSettings[] = "/*#pragma settings ";
99 const char* settingsPtr = strstr(text.c_str(), kPragmaSettings);
100 if (settingsPtr != nullptr) {
101 // Subtract one here in order to preserve the leading space, which is necessary to allow
102 // consumeSuffix to find the first item.
103 settingsPtr += strlen(kPragmaSettings) - 1;
104
105 const char* settingsEnd = strstr(settingsPtr, "*/");
106 if (settingsEnd != nullptr) {
107 SkSL::String settingsText{settingsPtr, size_t(settingsEnd - settingsPtr)};
108
109 // Apply settings as requested. Since they can come in any order, repeat until we've
110 // consumed them all.
111 for (;;) {
112 const size_t startingLength = settingsText.length();
113
John Stiles0ed9f312020-09-16 17:46:37 -0400114 if (settingsText.consumeSuffix(" AddAndTrueToLoopCondition")) {
115 static auto s_addAndTrueCaps = Factory::AddAndTrueToLoopCondition();
Brian Osmand7e76592020-11-02 12:26:22 -0500116 *caps = s_addAndTrueCaps.get();
John Stiles0ed9f312020-09-16 17:46:37 -0400117 }
John Stiles8f84cee2020-10-08 18:39:08 -0400118 if (settingsText.consumeSuffix(" BlendModesFailRandomlyForAllZeroVec")) {
119 static auto s_blendZeroCaps = Factory::BlendModesFailRandomlyForAllZeroVec();
Brian Osmand7e76592020-11-02 12:26:22 -0500120 *caps = s_blendZeroCaps.get();
John Stiles8f84cee2020-10-08 18:39:08 -0400121 }
John Stiles0ed9f312020-09-16 17:46:37 -0400122 if (settingsText.consumeSuffix(" CannotUseFractForNegativeValues")) {
123 static auto s_negativeFractCaps = Factory::CannotUseFractForNegativeValues();
Brian Osmand7e76592020-11-02 12:26:22 -0500124 *caps = s_negativeFractCaps.get();
John Stiles0ed9f312020-09-16 17:46:37 -0400125 }
John Stilesbe0a9ca2020-09-18 14:42:58 -0400126 if (settingsText.consumeSuffix(" CannotUseFragCoord")) {
127 static auto s_noFragCoordCaps = Factory::CannotUseFragCoord();
Brian Osmand7e76592020-11-02 12:26:22 -0500128 *caps = s_noFragCoordCaps.get();
John Stilesbe0a9ca2020-09-18 14:42:58 -0400129 }
John Stiles0ed9f312020-09-16 17:46:37 -0400130 if (settingsText.consumeSuffix(" CannotUseMinAndAbsTogether")) {
131 static auto s_minAbsCaps = Factory::CannotUseMinAndAbsTogether();
Brian Osmand7e76592020-11-02 12:26:22 -0500132 *caps = s_minAbsCaps.get();
John Stiles0ed9f312020-09-16 17:46:37 -0400133 }
John Stiles72664be2020-09-16 17:43:11 -0400134 if (settingsText.consumeSuffix(" Default")) {
John Stiles0ed9f312020-09-16 17:46:37 -0400135 static auto s_defaultCaps = Factory::Default();
Brian Osmand7e76592020-11-02 12:26:22 -0500136 *caps = s_defaultCaps.get();
John Stiles72664be2020-09-16 17:43:11 -0400137 }
John Stiles0ed9f312020-09-16 17:46:37 -0400138 if (settingsText.consumeSuffix(" EmulateAbsIntFunction")) {
139 static auto s_emulateAbsIntCaps = Factory::EmulateAbsIntFunction();
Brian Osmand7e76592020-11-02 12:26:22 -0500140 *caps = s_emulateAbsIntCaps.get();
John Stiles0ed9f312020-09-16 17:46:37 -0400141 }
Brian Osmana81e7e22021-09-15 10:28:27 -0400142 if (settingsText.consumeSuffix(" FramebufferFetchSupport")) {
143 static auto s_fbFetchSupport = Factory::FramebufferFetchSupport();
144 *caps = s_fbFetchSupport.get();
145 }
John Stilesbe0a9ca2020-09-18 14:42:58 -0400146 if (settingsText.consumeSuffix(" IncompleteShortIntPrecision")) {
147 static auto s_incompleteShortIntCaps = Factory::IncompleteShortIntPrecision();
Brian Osmand7e76592020-11-02 12:26:22 -0500148 *caps = s_incompleteShortIntCaps.get();
John Stilesbe0a9ca2020-09-18 14:42:58 -0400149 }
John Stileseaaa71b2020-10-08 22:18:10 -0400150 if (settingsText.consumeSuffix(" MustGuardDivisionEvenAfterExplicitZeroCheck")) {
151 static auto s_div0Caps = Factory::MustGuardDivisionEvenAfterExplicitZeroCheck();
Brian Osmand7e76592020-11-02 12:26:22 -0500152 *caps = s_div0Caps.get();
John Stileseaaa71b2020-10-08 22:18:10 -0400153 }
John Stiles0ed9f312020-09-16 17:46:37 -0400154 if (settingsText.consumeSuffix(" MustForceNegatedAtanParamToFloat")) {
155 static auto s_negativeAtanCaps = Factory::MustForceNegatedAtanParamToFloat();
Brian Osmand7e76592020-11-02 12:26:22 -0500156 *caps = s_negativeAtanCaps.get();
John Stiles0ed9f312020-09-16 17:46:37 -0400157 }
Chris Dalton155c33c2021-06-07 18:51:42 -0600158 if (settingsText.consumeSuffix(" MustForceNegatedLdexpParamToMultiply")) {
159 static auto s_negativeLdexpCaps =
160 Factory::MustForceNegatedLdexpParamToMultiply();
161 *caps = s_negativeLdexpCaps.get();
162 }
John Stiles0ed9f312020-09-16 17:46:37 -0400163 if (settingsText.consumeSuffix(" RemovePowWithConstantExponent")) {
164 static auto s_powCaps = Factory::RemovePowWithConstantExponent();
Brian Osmand7e76592020-11-02 12:26:22 -0500165 *caps = s_powCaps.get();
John Stiles0ed9f312020-09-16 17:46:37 -0400166 }
John Stilesbe0a9ca2020-09-18 14:42:58 -0400167 if (settingsText.consumeSuffix(" RewriteDoWhileLoops")) {
168 static auto s_rewriteLoopCaps = Factory::RewriteDoWhileLoops();
Brian Osmand7e76592020-11-02 12:26:22 -0500169 *caps = s_rewriteLoopCaps.get();
John Stilesbe0a9ca2020-09-18 14:42:58 -0400170 }
John Stiles6b888392021-09-24 11:33:54 -0400171 if (settingsText.consumeSuffix(" RewriteSwitchStatements")) {
172 static auto s_rewriteSwitchCaps = Factory::RewriteSwitchStatements();
173 *caps = s_rewriteSwitchCaps.get();
174 }
John Stiles85749c02021-03-23 17:12:03 -0400175 if (settingsText.consumeSuffix(" RewriteMatrixVectorMultiply")) {
176 static auto s_rewriteMatVecMulCaps = Factory::RewriteMatrixVectorMultiply();
177 *caps = s_rewriteMatVecMulCaps.get();
178 }
John Stiles5ee369f2021-05-21 17:27:57 -0400179 if (settingsText.consumeSuffix(" RewriteMatrixComparisons")) {
180 static auto s_rewriteMatrixComparisons = Factory::RewriteMatrixComparisons();
181 *caps = s_rewriteMatrixComparisons.get();
John Stiles36c57962021-05-20 18:00:39 -0400182 }
John Stilesc8846312020-09-18 12:03:42 -0400183 if (settingsText.consumeSuffix(" ShaderDerivativeExtensionString")) {
184 static auto s_derivativeCaps = Factory::ShaderDerivativeExtensionString();
Brian Osmand7e76592020-11-02 12:26:22 -0500185 *caps = s_derivativeCaps.get();
John Stilesc8846312020-09-18 12:03:42 -0400186 }
John Stiles0ed9f312020-09-16 17:46:37 -0400187 if (settingsText.consumeSuffix(" UnfoldShortCircuitAsTernary")) {
188 static auto s_ternaryCaps = Factory::UnfoldShortCircuitAsTernary();
Brian Osmand7e76592020-11-02 12:26:22 -0500189 *caps = s_ternaryCaps.get();
John Stiles0ed9f312020-09-16 17:46:37 -0400190 }
John Stiles72664be2020-09-16 17:43:11 -0400191 if (settingsText.consumeSuffix(" UsesPrecisionModifiers")) {
John Stiles0ed9f312020-09-16 17:46:37 -0400192 static auto s_precisionCaps = Factory::UsesPrecisionModifiers();
Brian Osmand7e76592020-11-02 12:26:22 -0500193 *caps = s_precisionCaps.get();
John Stiles72664be2020-09-16 17:43:11 -0400194 }
195 if (settingsText.consumeSuffix(" Version110")) {
John Stiles0ed9f312020-09-16 17:46:37 -0400196 static auto s_version110Caps = Factory::Version110();
Brian Osmand7e76592020-11-02 12:26:22 -0500197 *caps = s_version110Caps.get();
John Stiles72664be2020-09-16 17:43:11 -0400198 }
199 if (settingsText.consumeSuffix(" Version450Core")) {
John Stiles0ed9f312020-09-16 17:46:37 -0400200 static auto s_version450CoreCaps = Factory::Version450Core();
Brian Osmand7e76592020-11-02 12:26:22 -0500201 *caps = s_version450CoreCaps.get();
John Stiles72664be2020-09-16 17:43:11 -0400202 }
John Stiles9fdcc512021-08-04 13:27:05 -0400203 if (settingsText.consumeSuffix(" AllowNarrowingConversions")) {
204 settings->fAllowNarrowingConversions = true;
205 }
John Stiles72664be2020-09-16 17:43:11 -0400206 if (settingsText.consumeSuffix(" ForceHighPrecision")) {
207 settings->fForceHighPrecision = true;
208 }
John Stiles2195f942021-08-05 10:55:03 -0400209 if (settingsText.consumeSuffix(" NoES2Restrictions")) {
210 settings->fEnforceES2Restrictions = false;
211 }
John Stilesf9723132020-10-12 12:33:27 -0400212 if (settingsText.consumeSuffix(" NoInline")) {
213 settings->fInlineThreshold = 0;
214 }
John Stiles031a7672020-11-13 16:13:18 -0500215 if (settingsText.consumeSuffix(" InlineThresholdMax")) {
216 settings->fInlineThreshold = INT_MAX;
217 }
John Stiles72664be2020-09-16 17:43:11 -0400218 if (settingsText.consumeSuffix(" Sharpen")) {
219 settings->fSharpenTextures = true;
220 }
221
222 if (settingsText.empty()) {
223 break;
224 }
225 if (settingsText.length() == startingLength) {
226 printf("Unrecognized #pragma settings: %s\n", settingsText.c_str());
John Stilesa1e8fe32020-11-11 17:29:28 -0500227 return false;
John Stiles72664be2020-09-16 17:43:11 -0400228 }
229 }
230 }
231 }
John Stilesa1e8fe32020-11-11 17:29:28 -0500232
233 return true;
John Stiles72664be2020-09-16 17:43:11 -0400234}
235
ethannicholasb3058bd2016-07-01 08:22:01 -0700236/**
John Stilesa1e8fe32020-11-11 17:29:28 -0500237 * Displays a usage banner; used when the command line arguments don't make sense.
ethannicholasb3058bd2016-07-01 08:22:01 -0700238 */
John Stilesa1e8fe32020-11-11 17:29:28 -0500239static void show_usage() {
Brian Osman7b239052020-11-18 17:04:33 +0000240 printf("usage: skslc <input> <output> <flags>\n"
241 " skslc <worklist>\n"
John Stilesa1e8fe32020-11-11 17:29:28 -0500242 "\n"
243 "Allowed flags:\n"
244 "--settings: honor embedded /*#pragma settings*/ comments.\n"
245 "--nosettings: ignore /*#pragma settings*/ comments\n");
246}
247
248/**
249 * Handle a single input.
250 */
John Stilesa446ca12020-11-19 18:54:33 -0500251ResultCode processCommand(std::vector<SkSL::String>& args) {
John Stiles0ed9f312020-09-16 17:46:37 -0400252 bool honorSettings = true;
John Stilesa1e8fe32020-11-11 17:29:28 -0500253 if (args.size() == 4) {
254 // Handle four-argument case: `skslc in.sksl out.glsl --settings`
255 const SkSL::String& settingsArg = args[3];
256 if (settingsArg == "--settings") {
John Stiles0ed9f312020-09-16 17:46:37 -0400257 honorSettings = true;
John Stilesa1e8fe32020-11-11 17:29:28 -0500258 } else if (settingsArg == "--nosettings") {
John Stiles0ed9f312020-09-16 17:46:37 -0400259 honorSettings = false;
260 } else {
John Stilesa1e8fe32020-11-11 17:29:28 -0500261 printf("unrecognized flag: %s\n\n", settingsArg.c_str());
262 show_usage();
John Stilesa446ca12020-11-19 18:54:33 -0500263 return ResultCode::kInputError;
John Stiles0ed9f312020-09-16 17:46:37 -0400264 }
John Stilesa1e8fe32020-11-11 17:29:28 -0500265 } else if (args.size() != 3) {
266 show_usage();
John Stilesa446ca12020-11-19 18:54:33 -0500267 return ResultCode::kInputError;
ethannicholasb3058bd2016-07-01 08:22:01 -0700268 }
John Stiles0ed9f312020-09-16 17:46:37 -0400269
John Stilesdbd4e6f2021-02-16 13:29:15 -0500270 SkSL::ProgramKind kind;
John Stilesa1e8fe32020-11-11 17:29:28 -0500271 const SkSL::String& inputPath = args[1];
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400272 if (inputPath.ends_with(".vert")) {
John Stilesdbd4e6f2021-02-16 13:29:15 -0500273 kind = SkSL::ProgramKind::kVertex;
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400274 } else if (inputPath.ends_with(".frag") || inputPath.ends_with(".sksl")) {
John Stilesdbd4e6f2021-02-16 13:29:15 -0500275 kind = SkSL::ProgramKind::kFragment;
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400276 } else if (inputPath.ends_with(".rtb")) {
John Stiles2d8b8352021-06-16 11:33:13 -0400277 kind = SkSL::ProgramKind::kRuntimeBlender;
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400278 } else if (inputPath.ends_with(".rtcf")) {
Brian Osmancbb60bd2021-04-12 09:49:20 -0400279 kind = SkSL::ProgramKind::kRuntimeColorFilter;
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400280 } else if (inputPath.ends_with(".rts")) {
Brian Osmancbb60bd2021-04-12 09:49:20 -0400281 kind = SkSL::ProgramKind::kRuntimeShader;
ethannicholasb3058bd2016-07-01 08:22:01 -0700282 } else {
Brian Osman99ddd2a2021-08-27 11:21:12 -0400283 printf("input filename must end in '.vert', '.frag', '.rtb', '.rtcf', "
Brian Osmancbb60bd2021-04-12 09:49:20 -0400284 "'.rts', or '.sksl'\n");
John Stilesa446ca12020-11-19 18:54:33 -0500285 return ResultCode::kInputError;
ethannicholasb3058bd2016-07-01 08:22:01 -0700286 }
287
John Stilesa1e8fe32020-11-11 17:29:28 -0500288 std::ifstream in(inputPath);
John Stiles72664be2020-09-16 17:43:11 -0400289 SkSL::String text((std::istreambuf_iterator<char>(in)),
290 std::istreambuf_iterator<char>());
ethannicholasb3058bd2016-07-01 08:22:01 -0700291 if (in.rdstate()) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500292 printf("error reading '%s'\n", inputPath.c_str());
John Stilesa446ca12020-11-19 18:54:33 -0500293 return ResultCode::kInputError;
ethannicholasb3058bd2016-07-01 08:22:01 -0700294 }
John Stiles194b9b92020-09-15 15:37:24 -0400295
Ethan Nicholas941e7e22016-12-12 15:33:30 -0500296 SkSL::Program::Settings settings;
Brian Osmand7e76592020-11-02 12:26:22 -0500297 const SkSL::ShaderCapsClass* caps = &SkSL::standaloneCaps;
John Stiles0ed9f312020-09-16 17:46:37 -0400298 if (honorSettings) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500299 if (!detect_shader_settings(text, &settings, &caps)) {
John Stilesa446ca12020-11-19 18:54:33 -0500300 return ResultCode::kInputError;
John Stilesa1e8fe32020-11-11 17:29:28 -0500301 }
John Stiles0ed9f312020-09-16 17:46:37 -0400302 }
John Stilesa1e8fe32020-11-11 17:29:28 -0500303
Brian Salomond8d85b92021-07-07 09:41:17 -0400304 // This tells the compiler where the rt-flip uniform will live should it be required. For
305 // testing purposes we don't care where that is, but the compiler will report an error if we
John Stilesb05f03d2021-08-12 16:09:21 -0400306 // leave them at their default invalid values, or if the offset overlaps another uniform.
307 settings.fRTFlipOffset = 16384;
Brian Salomond8d85b92021-07-07 09:41:17 -0400308 settings.fRTFlipSet = 0;
309 settings.fRTFlipBinding = 0;
310
John Stilesa1e8fe32020-11-11 17:29:28 -0500311 const SkSL::String& outputPath = args[2];
312 auto emitCompileError = [&](SkSL::FileOutputStream& out, const char* errorText) {
John Stilesa446ca12020-11-19 18:54:33 -0500313 // Overwrite the compiler output, if any, with an error message.
314 out.close();
315 SkSL::FileOutputStream errorStream(outputPath);
316 errorStream.writeText("### Compilation failed:\n\n");
317 errorStream.writeText(errorText);
318 errorStream.close();
319 // Also emit the error directly to stdout.
320 puts(errorText);
John Stilesa1e8fe32020-11-11 17:29:28 -0500321 };
322
John Stilesd6a5f4492021-02-11 15:46:11 -0500323 auto compileProgram = [&](const auto& writeFn) -> ResultCode {
John Stiles445df8d2020-11-20 14:45:00 -0500324 SkSL::FileOutputStream out(outputPath);
John Stilesd6a5f4492021-02-11 15:46:11 -0500325 SkSL::Compiler compiler(caps);
John Stiles445df8d2020-11-20 14:45:00 -0500326 if (!out.isValid()) {
327 printf("error writing '%s'\n", outputPath.c_str());
328 return ResultCode::kOutputError;
329 }
330 std::unique_ptr<SkSL::Program> program = compiler.convertProgram(kind, text, settings);
331 if (!program || !writeFn(compiler, *program, out)) {
332 emitCompileError(out, compiler.errorText().c_str());
333 return ResultCode::kCompileError;
334 }
335 if (!out.close()) {
336 printf("error writing '%s'\n", outputPath.c_str());
337 return ResultCode::kOutputError;
338 }
339 return ResultCode::kSuccess;
340 };
341
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400342 if (outputPath.ends_with(".spirv")) {
John Stiles445df8d2020-11-20 14:45:00 -0500343 return compileProgram(
John Stiles445df8d2020-11-20 14:45:00 -0500344 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
345 return compiler.toSPIRV(program, out);
346 });
Brian Osman99ddd2a2021-08-27 11:21:12 -0400347 } else if (outputPath.ends_with(".asm.frag") || outputPath.ends_with(".asm.vert")) {
John Stilesdda1d312020-11-20 16:28:50 -0500348 return compileProgram(
John Stilesdda1d312020-11-20 16:28:50 -0500349 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
350 // Compile program to SPIR-V assembly in a string-stream.
351 SkSL::StringStream assembly;
352 if (!compiler.toSPIRV(program, assembly)) {
353 return false;
354 }
355 // Convert the string-stream to a SPIR-V disassembly.
356 spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0);
357 const SkSL::String& spirv(assembly.str());
358 std::string disassembly;
359 if (!tools.Disassemble((const uint32_t*)spirv.data(),
360 spirv.size() / 4, &disassembly)) {
361 return false;
362 }
363 // Finally, write the disassembly to our output stream.
364 out.write(disassembly.data(), disassembly.size());
365 return true;
366 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400367 } else if (outputPath.ends_with(".glsl")) {
John Stiles445df8d2020-11-20 14:45:00 -0500368 return compileProgram(
John Stiles445df8d2020-11-20 14:45:00 -0500369 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
370 return compiler.toGLSL(program, out);
371 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400372 } else if (outputPath.ends_with(".metal")) {
John Stiles445df8d2020-11-20 14:45:00 -0500373 return compileProgram(
John Stiles445df8d2020-11-20 14:45:00 -0500374 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
375 return compiler.toMetal(program, out);
376 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400377 } else if (outputPath.ends_with(".skvm")) {
Brian Osman23f00d72020-12-02 09:27:10 -0500378 return compileProgram(
Brian Osman47726a12020-12-17 16:02:08 -0500379 [](SkSL::Compiler&, SkSL::Program& program, SkSL::OutputStream& out) {
Mike Klein4535e612020-12-22 11:46:57 -0600380 skvm::Builder builder{skvm::Features{}};
Brian Osman47726a12020-12-17 16:02:08 -0500381 if (!SkSL::testingOnly_ProgramToSkVMShader(program, &builder)) {
Brian Osman23f00d72020-12-02 09:27:10 -0500382 return false;
383 }
384
Brian Osman47726a12020-12-17 16:02:08 -0500385 std::unique_ptr<SkWStream> redirect = as_SkWStream(out);
386 builder.done().dump(redirect.get());
Brian Osman23f00d72020-12-02 09:27:10 -0500387 return true;
388 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400389 } else if (outputPath.ends_with(".stage")) {
John Stilesd6a5f4492021-02-11 15:46:11 -0500390 return compileProgram(
Brian Osman62b039b2021-02-08 13:49:53 -0500391 [](SkSL::Compiler&, SkSL::Program& program, SkSL::OutputStream& out) {
392 class Callbacks : public SkSL::PipelineStage::Callbacks {
393 public:
394 using String = SkSL::String;
395
Brian Osmana5ab63a2021-02-17 15:41:57 -0500396 String getMangledName(const char* name) override {
397 return String(name) + "_0";
398 }
399
Brian Osman62b039b2021-02-08 13:49:53 -0500400 String declareUniform(const SkSL::VarDeclaration* decl) override {
401 fOutput += decl->description();
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400402 return String(decl->var().name());
Brian Osman62b039b2021-02-08 13:49:53 -0500403 }
404
Brian Osman55761ae2021-02-09 16:52:53 -0500405 void defineFunction(const char* decl,
406 const char* body,
407 bool /*isMain*/) override {
408 fOutput += String(decl) + "{" + body + "}";
Brian Osman62b039b2021-02-08 13:49:53 -0500409 }
410
John Stiles31e4c382021-09-30 15:20:44 -0400411 void declareFunction(const char* decl) override {
412 fOutput += String(decl) + ";";
413 }
414
Brian Osman8e756f32021-02-10 10:19:27 -0500415 void defineStruct(const char* definition) override {
416 fOutput += definition;
417 }
418
Brian Osman5e603c32021-02-17 15:39:06 -0500419 void declareGlobal(const char* declaration) override {
420 fOutput += declaration;
421 }
422
John Stilesd9a56b92021-07-23 15:50:39 -0400423 String sampleShader(int index, String coords) override {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400424 return "child_" + SkSL::to_string(index) + ".eval(" + coords + ")";
John Stiles8050a4b2021-07-23 15:50:20 -0400425 }
426
427 String sampleColorFilter(int index, String color) override {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400428 return "child_" + SkSL::to_string(index) + ".eval(" + color + ")";
Brian Osman62b039b2021-02-08 13:49:53 -0500429 }
Brian Osman62b039b2021-02-08 13:49:53 -0500430
John Stiles2955c262021-07-23 15:51:05 -0400431 String sampleBlender(int index, String src, String dst) override {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400432 return "child_" + SkSL::to_string(index) + ".eval(" + src + ", " +
John Stiles2955c262021-07-23 15:51:05 -0400433 dst + ")";
434 }
435
Brian Osmanc9125aa2021-04-21 09:57:19 -0400436 String fOutput;
Brian Osman62b039b2021-02-08 13:49:53 -0500437 };
Brian Osman668496b2021-06-09 16:57:45 -0400438 // The .stage output looks almost like valid SkSL, but not quite.
439 // The PipelineStageGenerator bridges the gap between the SkSL in `program`,
440 // and the C++ FP builder API (see GrSkSLFP). In that API, children don't need
441 // to be declared (so they don't emit declarations here). Children are sampled
442 // by index, not name - so all children here are just "child_N".
443 // The input color and coords have names in the original SkSL (as parameters to
444 // main), but those are ignored here. References to those variables become
445 // "_coords" and "_inColor". At runtime, those variable names are irrelevant
446 // when the new SkSL is emitted inside the FP - references to those variables
447 // are replaced with strings from EmitArgs, and might be varyings or differently
448 // named parameters.
Brian Osman62b039b2021-02-08 13:49:53 -0500449 Callbacks callbacks;
John Stiles50d0d092021-06-09 17:24:31 -0400450 SkSL::PipelineStage::ConvertProgram(program, "_coords", "_inColor",
451 "_canvasColor", &callbacks);
Brian Osman62b039b2021-02-08 13:49:53 -0500452 out.writeString(GrShaderUtils::PrettyPrint(callbacks.fOutput));
453 return true;
454 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400455 } else if (outputPath.ends_with(".dehydrated.sksl")) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500456 SkSL::FileOutputStream out(outputPath);
Brian Osmand7e76592020-11-02 12:26:22 -0500457 SkSL::Compiler compiler(caps);
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400458 if (!out.isValid()) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500459 printf("error writing '%s'\n", outputPath.c_str());
John Stilesa446ca12020-11-19 18:54:33 -0500460 return ResultCode::kOutputError;
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400461 }
John Stilesa935c3f2021-02-25 10:35:49 -0500462 SkSL::LoadedModule module =
463 compiler.loadModule(kind, SkSL::Compiler::MakeModulePath(inputPath.c_str()),
464 /*base=*/nullptr, /*dehydrate=*/true);
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400465 SkSL::Dehydrator dehydrator;
Brian Osman0006ad02020-11-18 15:38:39 -0500466 dehydrator.write(*module.fSymbols);
467 dehydrator.write(module.fElements);
John Stilesa1e8fe32020-11-11 17:29:28 -0500468 SkSL::String baseName = base_name(inputPath, "", ".sksl");
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400469 SkSL::StringStream buffer;
470 dehydrator.finish(buffer);
471 const SkSL::String& data = buffer.str();
John Stilese1589a12020-10-08 13:56:46 -0400472 out.printf("static uint8_t SKSL_INCLUDE_%s[] = {", baseName.c_str());
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400473 for (size_t i = 0; i < data.length(); ++i) {
John Stilese1589a12020-10-08 13:56:46 -0400474 out.printf("%s%d,", dehydrator.prefixAtOffset(i), uint8_t(data[i]));
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400475 }
476 out.printf("};\n");
John Stilese1589a12020-10-08 13:56:46 -0400477 out.printf("static constexpr size_t SKSL_INCLUDE_%s_LENGTH = sizeof(SKSL_INCLUDE_%s);\n",
478 baseName.c_str(), baseName.c_str());
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400479 if (!out.close()) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500480 printf("error writing '%s'\n", outputPath.c_str());
John Stilesa446ca12020-11-19 18:54:33 -0500481 return ResultCode::kOutputError;
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400482 }
ethannicholas5961bc92016-10-12 06:39:56 -0700483 } else {
John Stilesa935c3f2021-02-25 10:35:49 -0500484 printf("expected output path to end with one of: .glsl, .metal, .spirv, .asm.frag, .skvm, "
Brian Osman99ddd2a2021-08-27 11:21:12 -0400485 ".stage, .asm.vert (got '%s')\n", outputPath.c_str());
John Stilesdda1d312020-11-20 16:28:50 -0500486 return ResultCode::kConfigurationError;
John Stilesa1e8fe32020-11-11 17:29:28 -0500487 }
John Stilesa446ca12020-11-19 18:54:33 -0500488 return ResultCode::kSuccess;
John Stilesa1e8fe32020-11-11 17:29:28 -0500489}
490
Brian Osman7b239052020-11-18 17:04:33 +0000491/**
492 * Processes multiple inputs in a single invocation of skslc.
493 */
John Stilesa446ca12020-11-19 18:54:33 -0500494ResultCode processWorklist(const char* worklistPath) {
Brian Osman7b239052020-11-18 17:04:33 +0000495 SkSL::String inputPath(worklistPath);
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400496 if (!inputPath.ends_with(".worklist")) {
Brian Osman7b239052020-11-18 17:04:33 +0000497 printf("expected .worklist file, found: %s\n\n", worklistPath);
498 show_usage();
John Stilesdda1d312020-11-20 16:28:50 -0500499 return ResultCode::kConfigurationError;
Brian Osman7b239052020-11-18 17:04:33 +0000500 }
501
502 // The worklist contains one line per argument to pass to skslc. When a blank line is reached,
503 // those arguments will be passed to `processCommand`.
John Stilesa446ca12020-11-19 18:54:33 -0500504 auto resultCode = ResultCode::kSuccess;
Brian Osman7b239052020-11-18 17:04:33 +0000505 std::vector<SkSL::String> args = {"skslc"};
506 std::ifstream in(worklistPath);
507 for (SkSL::String line; std::getline(in, line); ) {
508 if (in.rdstate()) {
509 printf("error reading '%s'\n", worklistPath);
John Stilesa446ca12020-11-19 18:54:33 -0500510 return ResultCode::kInputError;
Brian Osman7b239052020-11-18 17:04:33 +0000511 }
512
513 if (!line.empty()) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500514 // We found an argument. Remember it.
Brian Osman7b239052020-11-18 17:04:33 +0000515 args.push_back(std::move(line));
John Stilesa1e8fe32020-11-11 17:29:28 -0500516 } else {
Brian Osman7b239052020-11-18 17:04:33 +0000517 // We found a blank line. If we have any arguments stored up, process them as a command.
518 if (!args.empty()) {
John Stilesa446ca12020-11-19 18:54:33 -0500519 ResultCode outcome = processCommand(args);
520 resultCode = std::max(resultCode, outcome);
John Stilesa1e8fe32020-11-11 17:29:28 -0500521
522 // Clear every argument except the first ("skslc").
523 args.resize(1);
524 }
525 }
526 }
527
Brian Osman7b239052020-11-18 17:04:33 +0000528 // If the worklist ended with a list of arguments but no blank line, process those now.
John Stilesa1e8fe32020-11-11 17:29:28 -0500529 if (args.size() > 1) {
John Stilesa446ca12020-11-19 18:54:33 -0500530 ResultCode outcome = processCommand(args);
531 resultCode = std::max(resultCode, outcome);
John Stilesa1e8fe32020-11-11 17:29:28 -0500532 }
533
John Stilesa446ca12020-11-19 18:54:33 -0500534 // Return the "worst" status we encountered. For our purposes, compilation errors are the least
535 // serious, because they are expected to occur in unit tests. Other types of errors are not
536 // expected at all during a build.
537 return resultCode;
Brian Osman7b239052020-11-18 17:04:33 +0000538}
539
540int main(int argc, const char** argv) {
541 if (argc == 2) {
542 // Worklists are the only two-argument case for skslc, and we don't intend to support
543 // nested worklists, so we can process them here.
John Stilesa446ca12020-11-19 18:54:33 -0500544 return (int)processWorklist(argv[1]);
Brian Osman7b239052020-11-18 17:04:33 +0000545 } else {
546 // Process non-worklist inputs.
547 std::vector<SkSL::String> args;
548 for (int index=0; index<argc; ++index) {
549 args.push_back(argv[index]);
550 }
551
John Stilesa446ca12020-11-19 18:54:33 -0500552 return (int)processCommand(args);
Brian Osman7b239052020-11-18 17:04:33 +0000553 }
ethannicholasb3058bd2016-07-01 08:22:01 -0700554}