blob: 17102faa23e1f088aca5098f8da4d1da0f77a92d [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 }
John Stilesbe0a9ca2020-09-18 14:42:58 -0400142 if (settingsText.consumeSuffix(" IncompleteShortIntPrecision")) {
143 static auto s_incompleteShortIntCaps = Factory::IncompleteShortIntPrecision();
Brian Osmand7e76592020-11-02 12:26:22 -0500144 *caps = s_incompleteShortIntCaps.get();
John Stilesbe0a9ca2020-09-18 14:42:58 -0400145 }
John Stileseaaa71b2020-10-08 22:18:10 -0400146 if (settingsText.consumeSuffix(" MustGuardDivisionEvenAfterExplicitZeroCheck")) {
147 static auto s_div0Caps = Factory::MustGuardDivisionEvenAfterExplicitZeroCheck();
Brian Osmand7e76592020-11-02 12:26:22 -0500148 *caps = s_div0Caps.get();
John Stileseaaa71b2020-10-08 22:18:10 -0400149 }
John Stiles0ed9f312020-09-16 17:46:37 -0400150 if (settingsText.consumeSuffix(" MustForceNegatedAtanParamToFloat")) {
151 static auto s_negativeAtanCaps = Factory::MustForceNegatedAtanParamToFloat();
Brian Osmand7e76592020-11-02 12:26:22 -0500152 *caps = s_negativeAtanCaps.get();
John Stiles0ed9f312020-09-16 17:46:37 -0400153 }
Chris Dalton155c33c2021-06-07 18:51:42 -0600154 if (settingsText.consumeSuffix(" MustForceNegatedLdexpParamToMultiply")) {
155 static auto s_negativeLdexpCaps =
156 Factory::MustForceNegatedLdexpParamToMultiply();
157 *caps = s_negativeLdexpCaps.get();
158 }
John Stiles0ed9f312020-09-16 17:46:37 -0400159 if (settingsText.consumeSuffix(" RemovePowWithConstantExponent")) {
160 static auto s_powCaps = Factory::RemovePowWithConstantExponent();
Brian Osmand7e76592020-11-02 12:26:22 -0500161 *caps = s_powCaps.get();
John Stiles0ed9f312020-09-16 17:46:37 -0400162 }
John Stilesbe0a9ca2020-09-18 14:42:58 -0400163 if (settingsText.consumeSuffix(" RewriteDoWhileLoops")) {
164 static auto s_rewriteLoopCaps = Factory::RewriteDoWhileLoops();
Brian Osmand7e76592020-11-02 12:26:22 -0500165 *caps = s_rewriteLoopCaps.get();
John Stilesbe0a9ca2020-09-18 14:42:58 -0400166 }
John Stiles85749c02021-03-23 17:12:03 -0400167 if (settingsText.consumeSuffix(" RewriteMatrixVectorMultiply")) {
168 static auto s_rewriteMatVecMulCaps = Factory::RewriteMatrixVectorMultiply();
169 *caps = s_rewriteMatVecMulCaps.get();
170 }
John Stiles5ee369f2021-05-21 17:27:57 -0400171 if (settingsText.consumeSuffix(" RewriteMatrixComparisons")) {
172 static auto s_rewriteMatrixComparisons = Factory::RewriteMatrixComparisons();
173 *caps = s_rewriteMatrixComparisons.get();
John Stiles36c57962021-05-20 18:00:39 -0400174 }
John Stilesc8846312020-09-18 12:03:42 -0400175 if (settingsText.consumeSuffix(" ShaderDerivativeExtensionString")) {
176 static auto s_derivativeCaps = Factory::ShaderDerivativeExtensionString();
Brian Osmand7e76592020-11-02 12:26:22 -0500177 *caps = s_derivativeCaps.get();
John Stilesc8846312020-09-18 12:03:42 -0400178 }
John Stiles0ed9f312020-09-16 17:46:37 -0400179 if (settingsText.consumeSuffix(" UnfoldShortCircuitAsTernary")) {
180 static auto s_ternaryCaps = Factory::UnfoldShortCircuitAsTernary();
Brian Osmand7e76592020-11-02 12:26:22 -0500181 *caps = s_ternaryCaps.get();
John Stiles0ed9f312020-09-16 17:46:37 -0400182 }
John Stiles72664be2020-09-16 17:43:11 -0400183 if (settingsText.consumeSuffix(" UsesPrecisionModifiers")) {
John Stiles0ed9f312020-09-16 17:46:37 -0400184 static auto s_precisionCaps = Factory::UsesPrecisionModifiers();
Brian Osmand7e76592020-11-02 12:26:22 -0500185 *caps = s_precisionCaps.get();
John Stiles72664be2020-09-16 17:43:11 -0400186 }
187 if (settingsText.consumeSuffix(" Version110")) {
John Stiles0ed9f312020-09-16 17:46:37 -0400188 static auto s_version110Caps = Factory::Version110();
Brian Osmand7e76592020-11-02 12:26:22 -0500189 *caps = s_version110Caps.get();
John Stiles72664be2020-09-16 17:43:11 -0400190 }
191 if (settingsText.consumeSuffix(" Version450Core")) {
John Stiles0ed9f312020-09-16 17:46:37 -0400192 static auto s_version450CoreCaps = Factory::Version450Core();
Brian Osmand7e76592020-11-02 12:26:22 -0500193 *caps = s_version450CoreCaps.get();
John Stiles72664be2020-09-16 17:43:11 -0400194 }
John Stiles9fdcc512021-08-04 13:27:05 -0400195 if (settingsText.consumeSuffix(" AllowNarrowingConversions")) {
196 settings->fAllowNarrowingConversions = true;
197 }
John Stiles72664be2020-09-16 17:43:11 -0400198 if (settingsText.consumeSuffix(" ForceHighPrecision")) {
199 settings->fForceHighPrecision = true;
200 }
John Stiles2195f942021-08-05 10:55:03 -0400201 if (settingsText.consumeSuffix(" NoES2Restrictions")) {
202 settings->fEnforceES2Restrictions = false;
203 }
John Stilesf9723132020-10-12 12:33:27 -0400204 if (settingsText.consumeSuffix(" NoInline")) {
205 settings->fInlineThreshold = 0;
206 }
John Stiles031a7672020-11-13 16:13:18 -0500207 if (settingsText.consumeSuffix(" InlineThresholdMax")) {
208 settings->fInlineThreshold = INT_MAX;
209 }
John Stiles72664be2020-09-16 17:43:11 -0400210 if (settingsText.consumeSuffix(" Sharpen")) {
211 settings->fSharpenTextures = true;
212 }
213
214 if (settingsText.empty()) {
215 break;
216 }
217 if (settingsText.length() == startingLength) {
218 printf("Unrecognized #pragma settings: %s\n", settingsText.c_str());
John Stilesa1e8fe32020-11-11 17:29:28 -0500219 return false;
John Stiles72664be2020-09-16 17:43:11 -0400220 }
221 }
222 }
223 }
John Stilesa1e8fe32020-11-11 17:29:28 -0500224
225 return true;
John Stiles72664be2020-09-16 17:43:11 -0400226}
227
ethannicholasb3058bd2016-07-01 08:22:01 -0700228/**
John Stilesa1e8fe32020-11-11 17:29:28 -0500229 * Displays a usage banner; used when the command line arguments don't make sense.
ethannicholasb3058bd2016-07-01 08:22:01 -0700230 */
John Stilesa1e8fe32020-11-11 17:29:28 -0500231static void show_usage() {
Brian Osman7b239052020-11-18 17:04:33 +0000232 printf("usage: skslc <input> <output> <flags>\n"
233 " skslc <worklist>\n"
John Stilesa1e8fe32020-11-11 17:29:28 -0500234 "\n"
235 "Allowed flags:\n"
236 "--settings: honor embedded /*#pragma settings*/ comments.\n"
237 "--nosettings: ignore /*#pragma settings*/ comments\n");
238}
239
240/**
241 * Handle a single input.
242 */
John Stilesa446ca12020-11-19 18:54:33 -0500243ResultCode processCommand(std::vector<SkSL::String>& args) {
John Stiles0ed9f312020-09-16 17:46:37 -0400244 bool honorSettings = true;
John Stilesa1e8fe32020-11-11 17:29:28 -0500245 if (args.size() == 4) {
246 // Handle four-argument case: `skslc in.sksl out.glsl --settings`
247 const SkSL::String& settingsArg = args[3];
248 if (settingsArg == "--settings") {
John Stiles0ed9f312020-09-16 17:46:37 -0400249 honorSettings = true;
John Stilesa1e8fe32020-11-11 17:29:28 -0500250 } else if (settingsArg == "--nosettings") {
John Stiles0ed9f312020-09-16 17:46:37 -0400251 honorSettings = false;
252 } else {
John Stilesa1e8fe32020-11-11 17:29:28 -0500253 printf("unrecognized flag: %s\n\n", settingsArg.c_str());
254 show_usage();
John Stilesa446ca12020-11-19 18:54:33 -0500255 return ResultCode::kInputError;
John Stiles0ed9f312020-09-16 17:46:37 -0400256 }
John Stilesa1e8fe32020-11-11 17:29:28 -0500257 } else if (args.size() != 3) {
258 show_usage();
John Stilesa446ca12020-11-19 18:54:33 -0500259 return ResultCode::kInputError;
ethannicholasb3058bd2016-07-01 08:22:01 -0700260 }
John Stiles0ed9f312020-09-16 17:46:37 -0400261
John Stilesdbd4e6f2021-02-16 13:29:15 -0500262 SkSL::ProgramKind kind;
John Stilesa1e8fe32020-11-11 17:29:28 -0500263 const SkSL::String& inputPath = args[1];
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400264 if (inputPath.ends_with(".vert")) {
John Stilesdbd4e6f2021-02-16 13:29:15 -0500265 kind = SkSL::ProgramKind::kVertex;
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400266 } else if (inputPath.ends_with(".frag") || inputPath.ends_with(".sksl")) {
John Stilesdbd4e6f2021-02-16 13:29:15 -0500267 kind = SkSL::ProgramKind::kFragment;
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400268 } else if (inputPath.ends_with(".rtb")) {
John Stiles2d8b8352021-06-16 11:33:13 -0400269 kind = SkSL::ProgramKind::kRuntimeBlender;
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400270 } else if (inputPath.ends_with(".rtcf")) {
Brian Osmancbb60bd2021-04-12 09:49:20 -0400271 kind = SkSL::ProgramKind::kRuntimeColorFilter;
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400272 } else if (inputPath.ends_with(".rts")) {
Brian Osmancbb60bd2021-04-12 09:49:20 -0400273 kind = SkSL::ProgramKind::kRuntimeShader;
ethannicholasb3058bd2016-07-01 08:22:01 -0700274 } else {
Brian Osman99ddd2a2021-08-27 11:21:12 -0400275 printf("input filename must end in '.vert', '.frag', '.rtb', '.rtcf', "
Brian Osmancbb60bd2021-04-12 09:49:20 -0400276 "'.rts', or '.sksl'\n");
John Stilesa446ca12020-11-19 18:54:33 -0500277 return ResultCode::kInputError;
ethannicholasb3058bd2016-07-01 08:22:01 -0700278 }
279
John Stilesa1e8fe32020-11-11 17:29:28 -0500280 std::ifstream in(inputPath);
John Stiles72664be2020-09-16 17:43:11 -0400281 SkSL::String text((std::istreambuf_iterator<char>(in)),
282 std::istreambuf_iterator<char>());
ethannicholasb3058bd2016-07-01 08:22:01 -0700283 if (in.rdstate()) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500284 printf("error reading '%s'\n", inputPath.c_str());
John Stilesa446ca12020-11-19 18:54:33 -0500285 return ResultCode::kInputError;
ethannicholasb3058bd2016-07-01 08:22:01 -0700286 }
John Stiles194b9b92020-09-15 15:37:24 -0400287
Ethan Nicholas941e7e22016-12-12 15:33:30 -0500288 SkSL::Program::Settings settings;
Brian Osmand7e76592020-11-02 12:26:22 -0500289 const SkSL::ShaderCapsClass* caps = &SkSL::standaloneCaps;
John Stiles0ed9f312020-09-16 17:46:37 -0400290 if (honorSettings) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500291 if (!detect_shader_settings(text, &settings, &caps)) {
John Stilesa446ca12020-11-19 18:54:33 -0500292 return ResultCode::kInputError;
John Stilesa1e8fe32020-11-11 17:29:28 -0500293 }
John Stiles0ed9f312020-09-16 17:46:37 -0400294 }
John Stilesa1e8fe32020-11-11 17:29:28 -0500295
Brian Salomond8d85b92021-07-07 09:41:17 -0400296 // This tells the compiler where the rt-flip uniform will live should it be required. For
297 // 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 -0400298 // leave them at their default invalid values, or if the offset overlaps another uniform.
299 settings.fRTFlipOffset = 16384;
Brian Salomond8d85b92021-07-07 09:41:17 -0400300 settings.fRTFlipSet = 0;
301 settings.fRTFlipBinding = 0;
302
John Stilesa1e8fe32020-11-11 17:29:28 -0500303 const SkSL::String& outputPath = args[2];
304 auto emitCompileError = [&](SkSL::FileOutputStream& out, const char* errorText) {
John Stilesa446ca12020-11-19 18:54:33 -0500305 // Overwrite the compiler output, if any, with an error message.
306 out.close();
307 SkSL::FileOutputStream errorStream(outputPath);
308 errorStream.writeText("### Compilation failed:\n\n");
309 errorStream.writeText(errorText);
310 errorStream.close();
311 // Also emit the error directly to stdout.
312 puts(errorText);
John Stilesa1e8fe32020-11-11 17:29:28 -0500313 };
314
John Stilesd6a5f4492021-02-11 15:46:11 -0500315 auto compileProgram = [&](const auto& writeFn) -> ResultCode {
John Stiles445df8d2020-11-20 14:45:00 -0500316 SkSL::FileOutputStream out(outputPath);
John Stilesd6a5f4492021-02-11 15:46:11 -0500317 SkSL::Compiler compiler(caps);
John Stiles445df8d2020-11-20 14:45:00 -0500318 if (!out.isValid()) {
319 printf("error writing '%s'\n", outputPath.c_str());
320 return ResultCode::kOutputError;
321 }
322 std::unique_ptr<SkSL::Program> program = compiler.convertProgram(kind, text, settings);
323 if (!program || !writeFn(compiler, *program, out)) {
324 emitCompileError(out, compiler.errorText().c_str());
325 return ResultCode::kCompileError;
326 }
327 if (!out.close()) {
328 printf("error writing '%s'\n", outputPath.c_str());
329 return ResultCode::kOutputError;
330 }
331 return ResultCode::kSuccess;
332 };
333
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400334 if (outputPath.ends_with(".spirv")) {
John Stiles445df8d2020-11-20 14:45:00 -0500335 return compileProgram(
John Stiles445df8d2020-11-20 14:45:00 -0500336 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
337 return compiler.toSPIRV(program, out);
338 });
Brian Osman99ddd2a2021-08-27 11:21:12 -0400339 } else if (outputPath.ends_with(".asm.frag") || outputPath.ends_with(".asm.vert")) {
John Stilesdda1d312020-11-20 16:28:50 -0500340 return compileProgram(
John Stilesdda1d312020-11-20 16:28:50 -0500341 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
342 // Compile program to SPIR-V assembly in a string-stream.
343 SkSL::StringStream assembly;
344 if (!compiler.toSPIRV(program, assembly)) {
345 return false;
346 }
347 // Convert the string-stream to a SPIR-V disassembly.
348 spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0);
349 const SkSL::String& spirv(assembly.str());
350 std::string disassembly;
351 if (!tools.Disassemble((const uint32_t*)spirv.data(),
352 spirv.size() / 4, &disassembly)) {
353 return false;
354 }
355 // Finally, write the disassembly to our output stream.
356 out.write(disassembly.data(), disassembly.size());
357 return true;
358 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400359 } else if (outputPath.ends_with(".glsl")) {
John Stiles445df8d2020-11-20 14:45:00 -0500360 return compileProgram(
John Stiles445df8d2020-11-20 14:45:00 -0500361 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
362 return compiler.toGLSL(program, out);
363 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400364 } else if (outputPath.ends_with(".metal")) {
John Stiles445df8d2020-11-20 14:45:00 -0500365 return compileProgram(
John Stiles445df8d2020-11-20 14:45:00 -0500366 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
367 return compiler.toMetal(program, out);
368 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400369 } else if (outputPath.ends_with(".skvm")) {
Brian Osman23f00d72020-12-02 09:27:10 -0500370 return compileProgram(
Brian Osman47726a12020-12-17 16:02:08 -0500371 [](SkSL::Compiler&, SkSL::Program& program, SkSL::OutputStream& out) {
Mike Klein4535e612020-12-22 11:46:57 -0600372 skvm::Builder builder{skvm::Features{}};
Brian Osman47726a12020-12-17 16:02:08 -0500373 if (!SkSL::testingOnly_ProgramToSkVMShader(program, &builder)) {
Brian Osman23f00d72020-12-02 09:27:10 -0500374 return false;
375 }
376
Brian Osman47726a12020-12-17 16:02:08 -0500377 std::unique_ptr<SkWStream> redirect = as_SkWStream(out);
378 builder.done().dump(redirect.get());
Brian Osman23f00d72020-12-02 09:27:10 -0500379 return true;
380 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400381 } else if (outputPath.ends_with(".stage")) {
John Stilesd6a5f4492021-02-11 15:46:11 -0500382 return compileProgram(
Brian Osman62b039b2021-02-08 13:49:53 -0500383 [](SkSL::Compiler&, SkSL::Program& program, SkSL::OutputStream& out) {
384 class Callbacks : public SkSL::PipelineStage::Callbacks {
385 public:
386 using String = SkSL::String;
387
Brian Osmana5ab63a2021-02-17 15:41:57 -0500388 String getMangledName(const char* name) override {
389 return String(name) + "_0";
390 }
391
Brian Osman62b039b2021-02-08 13:49:53 -0500392 String declareUniform(const SkSL::VarDeclaration* decl) override {
393 fOutput += decl->description();
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400394 return String(decl->var().name());
Brian Osman62b039b2021-02-08 13:49:53 -0500395 }
396
Brian Osman55761ae2021-02-09 16:52:53 -0500397 void defineFunction(const char* decl,
398 const char* body,
399 bool /*isMain*/) override {
400 fOutput += String(decl) + "{" + body + "}";
Brian Osman62b039b2021-02-08 13:49:53 -0500401 }
402
Brian Osman8e756f32021-02-10 10:19:27 -0500403 void defineStruct(const char* definition) override {
404 fOutput += definition;
405 }
406
Brian Osman5e603c32021-02-17 15:39:06 -0500407 void declareGlobal(const char* declaration) override {
408 fOutput += declaration;
409 }
410
John Stilesd9a56b92021-07-23 15:50:39 -0400411 String sampleShader(int index, String coords) override {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400412 return "child_" + SkSL::to_string(index) + ".eval(" + coords + ")";
John Stiles8050a4b2021-07-23 15:50:20 -0400413 }
414
415 String sampleColorFilter(int index, String color) override {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400416 return "child_" + SkSL::to_string(index) + ".eval(" + color + ")";
Brian Osman62b039b2021-02-08 13:49:53 -0500417 }
Brian Osman62b039b2021-02-08 13:49:53 -0500418
John Stiles2955c262021-07-23 15:51:05 -0400419 String sampleBlender(int index, String src, String dst) override {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400420 return "child_" + SkSL::to_string(index) + ".eval(" + src + ", " +
John Stiles2955c262021-07-23 15:51:05 -0400421 dst + ")";
422 }
423
Brian Osmanc9125aa2021-04-21 09:57:19 -0400424 String fOutput;
Brian Osman62b039b2021-02-08 13:49:53 -0500425 };
Brian Osman668496b2021-06-09 16:57:45 -0400426 // The .stage output looks almost like valid SkSL, but not quite.
427 // The PipelineStageGenerator bridges the gap between the SkSL in `program`,
428 // and the C++ FP builder API (see GrSkSLFP). In that API, children don't need
429 // to be declared (so they don't emit declarations here). Children are sampled
430 // by index, not name - so all children here are just "child_N".
431 // The input color and coords have names in the original SkSL (as parameters to
432 // main), but those are ignored here. References to those variables become
433 // "_coords" and "_inColor". At runtime, those variable names are irrelevant
434 // when the new SkSL is emitted inside the FP - references to those variables
435 // are replaced with strings from EmitArgs, and might be varyings or differently
436 // named parameters.
Brian Osman62b039b2021-02-08 13:49:53 -0500437 Callbacks callbacks;
John Stiles50d0d092021-06-09 17:24:31 -0400438 SkSL::PipelineStage::ConvertProgram(program, "_coords", "_inColor",
439 "_canvasColor", &callbacks);
Brian Osman62b039b2021-02-08 13:49:53 -0500440 out.writeString(GrShaderUtils::PrettyPrint(callbacks.fOutput));
441 return true;
442 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400443 } else if (outputPath.ends_with(".dehydrated.sksl")) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500444 SkSL::FileOutputStream out(outputPath);
Brian Osmand7e76592020-11-02 12:26:22 -0500445 SkSL::Compiler compiler(caps);
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400446 if (!out.isValid()) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500447 printf("error writing '%s'\n", outputPath.c_str());
John Stilesa446ca12020-11-19 18:54:33 -0500448 return ResultCode::kOutputError;
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400449 }
John Stilesa935c3f2021-02-25 10:35:49 -0500450 SkSL::LoadedModule module =
451 compiler.loadModule(kind, SkSL::Compiler::MakeModulePath(inputPath.c_str()),
452 /*base=*/nullptr, /*dehydrate=*/true);
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400453 SkSL::Dehydrator dehydrator;
Brian Osman0006ad02020-11-18 15:38:39 -0500454 dehydrator.write(*module.fSymbols);
455 dehydrator.write(module.fElements);
John Stilesa1e8fe32020-11-11 17:29:28 -0500456 SkSL::String baseName = base_name(inputPath, "", ".sksl");
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400457 SkSL::StringStream buffer;
458 dehydrator.finish(buffer);
459 const SkSL::String& data = buffer.str();
John Stilese1589a12020-10-08 13:56:46 -0400460 out.printf("static uint8_t SKSL_INCLUDE_%s[] = {", baseName.c_str());
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400461 for (size_t i = 0; i < data.length(); ++i) {
John Stilese1589a12020-10-08 13:56:46 -0400462 out.printf("%s%d,", dehydrator.prefixAtOffset(i), uint8_t(data[i]));
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400463 }
464 out.printf("};\n");
John Stilese1589a12020-10-08 13:56:46 -0400465 out.printf("static constexpr size_t SKSL_INCLUDE_%s_LENGTH = sizeof(SKSL_INCLUDE_%s);\n",
466 baseName.c_str(), baseName.c_str());
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400467 if (!out.close()) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500468 printf("error writing '%s'\n", outputPath.c_str());
John Stilesa446ca12020-11-19 18:54:33 -0500469 return ResultCode::kOutputError;
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400470 }
ethannicholas5961bc92016-10-12 06:39:56 -0700471 } else {
John Stilesa935c3f2021-02-25 10:35:49 -0500472 printf("expected output path to end with one of: .glsl, .metal, .spirv, .asm.frag, .skvm, "
Brian Osman99ddd2a2021-08-27 11:21:12 -0400473 ".stage, .asm.vert (got '%s')\n", outputPath.c_str());
John Stilesdda1d312020-11-20 16:28:50 -0500474 return ResultCode::kConfigurationError;
John Stilesa1e8fe32020-11-11 17:29:28 -0500475 }
John Stilesa446ca12020-11-19 18:54:33 -0500476 return ResultCode::kSuccess;
John Stilesa1e8fe32020-11-11 17:29:28 -0500477}
478
Brian Osman7b239052020-11-18 17:04:33 +0000479/**
480 * Processes multiple inputs in a single invocation of skslc.
481 */
John Stilesa446ca12020-11-19 18:54:33 -0500482ResultCode processWorklist(const char* worklistPath) {
Brian Osman7b239052020-11-18 17:04:33 +0000483 SkSL::String inputPath(worklistPath);
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400484 if (!inputPath.ends_with(".worklist")) {
Brian Osman7b239052020-11-18 17:04:33 +0000485 printf("expected .worklist file, found: %s\n\n", worklistPath);
486 show_usage();
John Stilesdda1d312020-11-20 16:28:50 -0500487 return ResultCode::kConfigurationError;
Brian Osman7b239052020-11-18 17:04:33 +0000488 }
489
490 // The worklist contains one line per argument to pass to skslc. When a blank line is reached,
491 // those arguments will be passed to `processCommand`.
John Stilesa446ca12020-11-19 18:54:33 -0500492 auto resultCode = ResultCode::kSuccess;
Brian Osman7b239052020-11-18 17:04:33 +0000493 std::vector<SkSL::String> args = {"skslc"};
494 std::ifstream in(worklistPath);
495 for (SkSL::String line; std::getline(in, line); ) {
496 if (in.rdstate()) {
497 printf("error reading '%s'\n", worklistPath);
John Stilesa446ca12020-11-19 18:54:33 -0500498 return ResultCode::kInputError;
Brian Osman7b239052020-11-18 17:04:33 +0000499 }
500
501 if (!line.empty()) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500502 // We found an argument. Remember it.
Brian Osman7b239052020-11-18 17:04:33 +0000503 args.push_back(std::move(line));
John Stilesa1e8fe32020-11-11 17:29:28 -0500504 } else {
Brian Osman7b239052020-11-18 17:04:33 +0000505 // We found a blank line. If we have any arguments stored up, process them as a command.
506 if (!args.empty()) {
John Stilesa446ca12020-11-19 18:54:33 -0500507 ResultCode outcome = processCommand(args);
508 resultCode = std::max(resultCode, outcome);
John Stilesa1e8fe32020-11-11 17:29:28 -0500509
510 // Clear every argument except the first ("skslc").
511 args.resize(1);
512 }
513 }
514 }
515
Brian Osman7b239052020-11-18 17:04:33 +0000516 // If the worklist ended with a list of arguments but no blank line, process those now.
John Stilesa1e8fe32020-11-11 17:29:28 -0500517 if (args.size() > 1) {
John Stilesa446ca12020-11-19 18:54:33 -0500518 ResultCode outcome = processCommand(args);
519 resultCode = std::max(resultCode, outcome);
John Stilesa1e8fe32020-11-11 17:29:28 -0500520 }
521
John Stilesa446ca12020-11-19 18:54:33 -0500522 // Return the "worst" status we encountered. For our purposes, compilation errors are the least
523 // serious, because they are expected to occur in unit tests. Other types of errors are not
524 // expected at all during a build.
525 return resultCode;
Brian Osman7b239052020-11-18 17:04:33 +0000526}
527
528int main(int argc, const char** argv) {
529 if (argc == 2) {
530 // Worklists are the only two-argument case for skslc, and we don't intend to support
531 // nested worklists, so we can process them here.
John Stilesa446ca12020-11-19 18:54:33 -0500532 return (int)processWorklist(argv[1]);
Brian Osman7b239052020-11-18 17:04:33 +0000533 } else {
534 // Process non-worklist inputs.
535 std::vector<SkSL::String> args;
536 for (int index=0; index<argc; ++index) {
537 args.push_back(argv[index]);
538 }
539
John Stilesa446ca12020-11-19 18:54:33 -0500540 return (int)processCommand(args);
Brian Osman7b239052020-11-18 17:04:33 +0000541 }
ethannicholasb3058bd2016-07-01 08:22:01 -0700542}