blob: 2ab727f1a54ba8ed01978ce8dd540be98a30a683 [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 Stiles85749c02021-03-23 17:12:03 -0400171 if (settingsText.consumeSuffix(" RewriteMatrixVectorMultiply")) {
172 static auto s_rewriteMatVecMulCaps = Factory::RewriteMatrixVectorMultiply();
173 *caps = s_rewriteMatVecMulCaps.get();
174 }
John Stiles5ee369f2021-05-21 17:27:57 -0400175 if (settingsText.consumeSuffix(" RewriteMatrixComparisons")) {
176 static auto s_rewriteMatrixComparisons = Factory::RewriteMatrixComparisons();
177 *caps = s_rewriteMatrixComparisons.get();
John Stiles36c57962021-05-20 18:00:39 -0400178 }
John Stilesc8846312020-09-18 12:03:42 -0400179 if (settingsText.consumeSuffix(" ShaderDerivativeExtensionString")) {
180 static auto s_derivativeCaps = Factory::ShaderDerivativeExtensionString();
Brian Osmand7e76592020-11-02 12:26:22 -0500181 *caps = s_derivativeCaps.get();
John Stilesc8846312020-09-18 12:03:42 -0400182 }
John Stiles0ed9f312020-09-16 17:46:37 -0400183 if (settingsText.consumeSuffix(" UnfoldShortCircuitAsTernary")) {
184 static auto s_ternaryCaps = Factory::UnfoldShortCircuitAsTernary();
Brian Osmand7e76592020-11-02 12:26:22 -0500185 *caps = s_ternaryCaps.get();
John Stiles0ed9f312020-09-16 17:46:37 -0400186 }
John Stiles72664be2020-09-16 17:43:11 -0400187 if (settingsText.consumeSuffix(" UsesPrecisionModifiers")) {
John Stiles0ed9f312020-09-16 17:46:37 -0400188 static auto s_precisionCaps = Factory::UsesPrecisionModifiers();
Brian Osmand7e76592020-11-02 12:26:22 -0500189 *caps = s_precisionCaps.get();
John Stiles72664be2020-09-16 17:43:11 -0400190 }
191 if (settingsText.consumeSuffix(" Version110")) {
John Stiles0ed9f312020-09-16 17:46:37 -0400192 static auto s_version110Caps = Factory::Version110();
Brian Osmand7e76592020-11-02 12:26:22 -0500193 *caps = s_version110Caps.get();
John Stiles72664be2020-09-16 17:43:11 -0400194 }
195 if (settingsText.consumeSuffix(" Version450Core")) {
John Stiles0ed9f312020-09-16 17:46:37 -0400196 static auto s_version450CoreCaps = Factory::Version450Core();
Brian Osmand7e76592020-11-02 12:26:22 -0500197 *caps = s_version450CoreCaps.get();
John Stiles72664be2020-09-16 17:43:11 -0400198 }
John Stiles9fdcc512021-08-04 13:27:05 -0400199 if (settingsText.consumeSuffix(" AllowNarrowingConversions")) {
200 settings->fAllowNarrowingConversions = true;
201 }
John Stiles72664be2020-09-16 17:43:11 -0400202 if (settingsText.consumeSuffix(" ForceHighPrecision")) {
203 settings->fForceHighPrecision = true;
204 }
John Stiles2195f942021-08-05 10:55:03 -0400205 if (settingsText.consumeSuffix(" NoES2Restrictions")) {
206 settings->fEnforceES2Restrictions = false;
207 }
John Stilesf9723132020-10-12 12:33:27 -0400208 if (settingsText.consumeSuffix(" NoInline")) {
209 settings->fInlineThreshold = 0;
210 }
John Stiles031a7672020-11-13 16:13:18 -0500211 if (settingsText.consumeSuffix(" InlineThresholdMax")) {
212 settings->fInlineThreshold = INT_MAX;
213 }
John Stiles72664be2020-09-16 17:43:11 -0400214 if (settingsText.consumeSuffix(" Sharpen")) {
215 settings->fSharpenTextures = true;
216 }
217
218 if (settingsText.empty()) {
219 break;
220 }
221 if (settingsText.length() == startingLength) {
222 printf("Unrecognized #pragma settings: %s\n", settingsText.c_str());
John Stilesa1e8fe32020-11-11 17:29:28 -0500223 return false;
John Stiles72664be2020-09-16 17:43:11 -0400224 }
225 }
226 }
227 }
John Stilesa1e8fe32020-11-11 17:29:28 -0500228
229 return true;
John Stiles72664be2020-09-16 17:43:11 -0400230}
231
ethannicholasb3058bd2016-07-01 08:22:01 -0700232/**
John Stilesa1e8fe32020-11-11 17:29:28 -0500233 * Displays a usage banner; used when the command line arguments don't make sense.
ethannicholasb3058bd2016-07-01 08:22:01 -0700234 */
John Stilesa1e8fe32020-11-11 17:29:28 -0500235static void show_usage() {
Brian Osman7b239052020-11-18 17:04:33 +0000236 printf("usage: skslc <input> <output> <flags>\n"
237 " skslc <worklist>\n"
John Stilesa1e8fe32020-11-11 17:29:28 -0500238 "\n"
239 "Allowed flags:\n"
240 "--settings: honor embedded /*#pragma settings*/ comments.\n"
241 "--nosettings: ignore /*#pragma settings*/ comments\n");
242}
243
244/**
245 * Handle a single input.
246 */
John Stilesa446ca12020-11-19 18:54:33 -0500247ResultCode processCommand(std::vector<SkSL::String>& args) {
John Stiles0ed9f312020-09-16 17:46:37 -0400248 bool honorSettings = true;
John Stilesa1e8fe32020-11-11 17:29:28 -0500249 if (args.size() == 4) {
250 // Handle four-argument case: `skslc in.sksl out.glsl --settings`
251 const SkSL::String& settingsArg = args[3];
252 if (settingsArg == "--settings") {
John Stiles0ed9f312020-09-16 17:46:37 -0400253 honorSettings = true;
John Stilesa1e8fe32020-11-11 17:29:28 -0500254 } else if (settingsArg == "--nosettings") {
John Stiles0ed9f312020-09-16 17:46:37 -0400255 honorSettings = false;
256 } else {
John Stilesa1e8fe32020-11-11 17:29:28 -0500257 printf("unrecognized flag: %s\n\n", settingsArg.c_str());
258 show_usage();
John Stilesa446ca12020-11-19 18:54:33 -0500259 return ResultCode::kInputError;
John Stiles0ed9f312020-09-16 17:46:37 -0400260 }
John Stilesa1e8fe32020-11-11 17:29:28 -0500261 } else if (args.size() != 3) {
262 show_usage();
John Stilesa446ca12020-11-19 18:54:33 -0500263 return ResultCode::kInputError;
ethannicholasb3058bd2016-07-01 08:22:01 -0700264 }
John Stiles0ed9f312020-09-16 17:46:37 -0400265
John Stilesdbd4e6f2021-02-16 13:29:15 -0500266 SkSL::ProgramKind kind;
John Stilesa1e8fe32020-11-11 17:29:28 -0500267 const SkSL::String& inputPath = args[1];
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400268 if (inputPath.ends_with(".vert")) {
John Stilesdbd4e6f2021-02-16 13:29:15 -0500269 kind = SkSL::ProgramKind::kVertex;
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400270 } else if (inputPath.ends_with(".frag") || inputPath.ends_with(".sksl")) {
John Stilesdbd4e6f2021-02-16 13:29:15 -0500271 kind = SkSL::ProgramKind::kFragment;
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400272 } else if (inputPath.ends_with(".rtb")) {
John Stiles2d8b8352021-06-16 11:33:13 -0400273 kind = SkSL::ProgramKind::kRuntimeBlender;
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400274 } else if (inputPath.ends_with(".rtcf")) {
Brian Osmancbb60bd2021-04-12 09:49:20 -0400275 kind = SkSL::ProgramKind::kRuntimeColorFilter;
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400276 } else if (inputPath.ends_with(".rts")) {
Brian Osmancbb60bd2021-04-12 09:49:20 -0400277 kind = SkSL::ProgramKind::kRuntimeShader;
ethannicholasb3058bd2016-07-01 08:22:01 -0700278 } else {
Brian Osman99ddd2a2021-08-27 11:21:12 -0400279 printf("input filename must end in '.vert', '.frag', '.rtb', '.rtcf', "
Brian Osmancbb60bd2021-04-12 09:49:20 -0400280 "'.rts', or '.sksl'\n");
John Stilesa446ca12020-11-19 18:54:33 -0500281 return ResultCode::kInputError;
ethannicholasb3058bd2016-07-01 08:22:01 -0700282 }
283
John Stilesa1e8fe32020-11-11 17:29:28 -0500284 std::ifstream in(inputPath);
John Stiles72664be2020-09-16 17:43:11 -0400285 SkSL::String text((std::istreambuf_iterator<char>(in)),
286 std::istreambuf_iterator<char>());
ethannicholasb3058bd2016-07-01 08:22:01 -0700287 if (in.rdstate()) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500288 printf("error reading '%s'\n", inputPath.c_str());
John Stilesa446ca12020-11-19 18:54:33 -0500289 return ResultCode::kInputError;
ethannicholasb3058bd2016-07-01 08:22:01 -0700290 }
John Stiles194b9b92020-09-15 15:37:24 -0400291
Ethan Nicholas941e7e22016-12-12 15:33:30 -0500292 SkSL::Program::Settings settings;
Brian Osmand7e76592020-11-02 12:26:22 -0500293 const SkSL::ShaderCapsClass* caps = &SkSL::standaloneCaps;
John Stiles0ed9f312020-09-16 17:46:37 -0400294 if (honorSettings) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500295 if (!detect_shader_settings(text, &settings, &caps)) {
John Stilesa446ca12020-11-19 18:54:33 -0500296 return ResultCode::kInputError;
John Stilesa1e8fe32020-11-11 17:29:28 -0500297 }
John Stiles0ed9f312020-09-16 17:46:37 -0400298 }
John Stilesa1e8fe32020-11-11 17:29:28 -0500299
Brian Salomond8d85b92021-07-07 09:41:17 -0400300 // This tells the compiler where the rt-flip uniform will live should it be required. For
301 // 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 -0400302 // leave them at their default invalid values, or if the offset overlaps another uniform.
303 settings.fRTFlipOffset = 16384;
Brian Salomond8d85b92021-07-07 09:41:17 -0400304 settings.fRTFlipSet = 0;
305 settings.fRTFlipBinding = 0;
306
John Stilesa1e8fe32020-11-11 17:29:28 -0500307 const SkSL::String& outputPath = args[2];
308 auto emitCompileError = [&](SkSL::FileOutputStream& out, const char* errorText) {
John Stilesa446ca12020-11-19 18:54:33 -0500309 // Overwrite the compiler output, if any, with an error message.
310 out.close();
311 SkSL::FileOutputStream errorStream(outputPath);
312 errorStream.writeText("### Compilation failed:\n\n");
313 errorStream.writeText(errorText);
314 errorStream.close();
315 // Also emit the error directly to stdout.
316 puts(errorText);
John Stilesa1e8fe32020-11-11 17:29:28 -0500317 };
318
John Stilesd6a5f4492021-02-11 15:46:11 -0500319 auto compileProgram = [&](const auto& writeFn) -> ResultCode {
John Stiles445df8d2020-11-20 14:45:00 -0500320 SkSL::FileOutputStream out(outputPath);
John Stilesd6a5f4492021-02-11 15:46:11 -0500321 SkSL::Compiler compiler(caps);
John Stiles445df8d2020-11-20 14:45:00 -0500322 if (!out.isValid()) {
323 printf("error writing '%s'\n", outputPath.c_str());
324 return ResultCode::kOutputError;
325 }
326 std::unique_ptr<SkSL::Program> program = compiler.convertProgram(kind, text, settings);
327 if (!program || !writeFn(compiler, *program, out)) {
328 emitCompileError(out, compiler.errorText().c_str());
329 return ResultCode::kCompileError;
330 }
331 if (!out.close()) {
332 printf("error writing '%s'\n", outputPath.c_str());
333 return ResultCode::kOutputError;
334 }
335 return ResultCode::kSuccess;
336 };
337
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400338 if (outputPath.ends_with(".spirv")) {
John Stiles445df8d2020-11-20 14:45:00 -0500339 return compileProgram(
John Stiles445df8d2020-11-20 14:45:00 -0500340 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
341 return compiler.toSPIRV(program, out);
342 });
Brian Osman99ddd2a2021-08-27 11:21:12 -0400343 } else if (outputPath.ends_with(".asm.frag") || outputPath.ends_with(".asm.vert")) {
John Stilesdda1d312020-11-20 16:28:50 -0500344 return compileProgram(
John Stilesdda1d312020-11-20 16:28:50 -0500345 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
346 // Compile program to SPIR-V assembly in a string-stream.
347 SkSL::StringStream assembly;
348 if (!compiler.toSPIRV(program, assembly)) {
349 return false;
350 }
351 // Convert the string-stream to a SPIR-V disassembly.
352 spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0);
353 const SkSL::String& spirv(assembly.str());
354 std::string disassembly;
355 if (!tools.Disassemble((const uint32_t*)spirv.data(),
356 spirv.size() / 4, &disassembly)) {
357 return false;
358 }
359 // Finally, write the disassembly to our output stream.
360 out.write(disassembly.data(), disassembly.size());
361 return true;
362 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400363 } else if (outputPath.ends_with(".glsl")) {
John Stiles445df8d2020-11-20 14:45:00 -0500364 return compileProgram(
John Stiles445df8d2020-11-20 14:45:00 -0500365 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
366 return compiler.toGLSL(program, out);
367 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400368 } else if (outputPath.ends_with(".metal")) {
John Stiles445df8d2020-11-20 14:45:00 -0500369 return compileProgram(
John Stiles445df8d2020-11-20 14:45:00 -0500370 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
371 return compiler.toMetal(program, out);
372 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400373 } else if (outputPath.ends_with(".skvm")) {
Brian Osman23f00d72020-12-02 09:27:10 -0500374 return compileProgram(
Brian Osman47726a12020-12-17 16:02:08 -0500375 [](SkSL::Compiler&, SkSL::Program& program, SkSL::OutputStream& out) {
Mike Klein4535e612020-12-22 11:46:57 -0600376 skvm::Builder builder{skvm::Features{}};
Brian Osman47726a12020-12-17 16:02:08 -0500377 if (!SkSL::testingOnly_ProgramToSkVMShader(program, &builder)) {
Brian Osman23f00d72020-12-02 09:27:10 -0500378 return false;
379 }
380
Brian Osman47726a12020-12-17 16:02:08 -0500381 std::unique_ptr<SkWStream> redirect = as_SkWStream(out);
382 builder.done().dump(redirect.get());
Brian Osman23f00d72020-12-02 09:27:10 -0500383 return true;
384 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400385 } else if (outputPath.ends_with(".stage")) {
John Stilesd6a5f4492021-02-11 15:46:11 -0500386 return compileProgram(
Brian Osman62b039b2021-02-08 13:49:53 -0500387 [](SkSL::Compiler&, SkSL::Program& program, SkSL::OutputStream& out) {
388 class Callbacks : public SkSL::PipelineStage::Callbacks {
389 public:
390 using String = SkSL::String;
391
Brian Osmana5ab63a2021-02-17 15:41:57 -0500392 String getMangledName(const char* name) override {
393 return String(name) + "_0";
394 }
395
Brian Osman62b039b2021-02-08 13:49:53 -0500396 String declareUniform(const SkSL::VarDeclaration* decl) override {
397 fOutput += decl->description();
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400398 return String(decl->var().name());
Brian Osman62b039b2021-02-08 13:49:53 -0500399 }
400
Brian Osman55761ae2021-02-09 16:52:53 -0500401 void defineFunction(const char* decl,
402 const char* body,
403 bool /*isMain*/) override {
404 fOutput += String(decl) + "{" + body + "}";
Brian Osman62b039b2021-02-08 13:49:53 -0500405 }
406
Brian Osman8e756f32021-02-10 10:19:27 -0500407 void defineStruct(const char* definition) override {
408 fOutput += definition;
409 }
410
Brian Osman5e603c32021-02-17 15:39:06 -0500411 void declareGlobal(const char* declaration) override {
412 fOutput += declaration;
413 }
414
John Stilesd9a56b92021-07-23 15:50:39 -0400415 String sampleShader(int index, String coords) override {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400416 return "child_" + SkSL::to_string(index) + ".eval(" + coords + ")";
John Stiles8050a4b2021-07-23 15:50:20 -0400417 }
418
419 String sampleColorFilter(int index, String color) override {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400420 return "child_" + SkSL::to_string(index) + ".eval(" + color + ")";
Brian Osman62b039b2021-02-08 13:49:53 -0500421 }
Brian Osman62b039b2021-02-08 13:49:53 -0500422
John Stiles2955c262021-07-23 15:51:05 -0400423 String sampleBlender(int index, String src, String dst) override {
Brian Osmancbfa34a2021-09-02 09:26:27 -0400424 return "child_" + SkSL::to_string(index) + ".eval(" + src + ", " +
John Stiles2955c262021-07-23 15:51:05 -0400425 dst + ")";
426 }
427
Brian Osmanc9125aa2021-04-21 09:57:19 -0400428 String fOutput;
Brian Osman62b039b2021-02-08 13:49:53 -0500429 };
Brian Osman668496b2021-06-09 16:57:45 -0400430 // The .stage output looks almost like valid SkSL, but not quite.
431 // The PipelineStageGenerator bridges the gap between the SkSL in `program`,
432 // and the C++ FP builder API (see GrSkSLFP). In that API, children don't need
433 // to be declared (so they don't emit declarations here). Children are sampled
434 // by index, not name - so all children here are just "child_N".
435 // The input color and coords have names in the original SkSL (as parameters to
436 // main), but those are ignored here. References to those variables become
437 // "_coords" and "_inColor". At runtime, those variable names are irrelevant
438 // when the new SkSL is emitted inside the FP - references to those variables
439 // are replaced with strings from EmitArgs, and might be varyings or differently
440 // named parameters.
Brian Osman62b039b2021-02-08 13:49:53 -0500441 Callbacks callbacks;
John Stiles50d0d092021-06-09 17:24:31 -0400442 SkSL::PipelineStage::ConvertProgram(program, "_coords", "_inColor",
443 "_canvasColor", &callbacks);
Brian Osman62b039b2021-02-08 13:49:53 -0500444 out.writeString(GrShaderUtils::PrettyPrint(callbacks.fOutput));
445 return true;
446 });
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400447 } else if (outputPath.ends_with(".dehydrated.sksl")) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500448 SkSL::FileOutputStream out(outputPath);
Brian Osmand7e76592020-11-02 12:26:22 -0500449 SkSL::Compiler compiler(caps);
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400450 if (!out.isValid()) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500451 printf("error writing '%s'\n", outputPath.c_str());
John Stilesa446ca12020-11-19 18:54:33 -0500452 return ResultCode::kOutputError;
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400453 }
John Stilesa935c3f2021-02-25 10:35:49 -0500454 SkSL::LoadedModule module =
455 compiler.loadModule(kind, SkSL::Compiler::MakeModulePath(inputPath.c_str()),
456 /*base=*/nullptr, /*dehydrate=*/true);
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400457 SkSL::Dehydrator dehydrator;
Brian Osman0006ad02020-11-18 15:38:39 -0500458 dehydrator.write(*module.fSymbols);
459 dehydrator.write(module.fElements);
John Stilesa1e8fe32020-11-11 17:29:28 -0500460 SkSL::String baseName = base_name(inputPath, "", ".sksl");
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400461 SkSL::StringStream buffer;
462 dehydrator.finish(buffer);
463 const SkSL::String& data = buffer.str();
John Stilese1589a12020-10-08 13:56:46 -0400464 out.printf("static uint8_t SKSL_INCLUDE_%s[] = {", baseName.c_str());
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400465 for (size_t i = 0; i < data.length(); ++i) {
John Stilese1589a12020-10-08 13:56:46 -0400466 out.printf("%s%d,", dehydrator.prefixAtOffset(i), uint8_t(data[i]));
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400467 }
468 out.printf("};\n");
John Stilese1589a12020-10-08 13:56:46 -0400469 out.printf("static constexpr size_t SKSL_INCLUDE_%s_LENGTH = sizeof(SKSL_INCLUDE_%s);\n",
470 baseName.c_str(), baseName.c_str());
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400471 if (!out.close()) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500472 printf("error writing '%s'\n", outputPath.c_str());
John Stilesa446ca12020-11-19 18:54:33 -0500473 return ResultCode::kOutputError;
Ethan Nicholasc18bb512020-07-28 14:46:53 -0400474 }
ethannicholas5961bc92016-10-12 06:39:56 -0700475 } else {
John Stilesa935c3f2021-02-25 10:35:49 -0500476 printf("expected output path to end with one of: .glsl, .metal, .spirv, .asm.frag, .skvm, "
Brian Osman99ddd2a2021-08-27 11:21:12 -0400477 ".stage, .asm.vert (got '%s')\n", outputPath.c_str());
John Stilesdda1d312020-11-20 16:28:50 -0500478 return ResultCode::kConfigurationError;
John Stilesa1e8fe32020-11-11 17:29:28 -0500479 }
John Stilesa446ca12020-11-19 18:54:33 -0500480 return ResultCode::kSuccess;
John Stilesa1e8fe32020-11-11 17:29:28 -0500481}
482
Brian Osman7b239052020-11-18 17:04:33 +0000483/**
484 * Processes multiple inputs in a single invocation of skslc.
485 */
John Stilesa446ca12020-11-19 18:54:33 -0500486ResultCode processWorklist(const char* worklistPath) {
Brian Osman7b239052020-11-18 17:04:33 +0000487 SkSL::String inputPath(worklistPath);
Ethan Nicholasd2e09602021-06-10 11:21:59 -0400488 if (!inputPath.ends_with(".worklist")) {
Brian Osman7b239052020-11-18 17:04:33 +0000489 printf("expected .worklist file, found: %s\n\n", worklistPath);
490 show_usage();
John Stilesdda1d312020-11-20 16:28:50 -0500491 return ResultCode::kConfigurationError;
Brian Osman7b239052020-11-18 17:04:33 +0000492 }
493
494 // The worklist contains one line per argument to pass to skslc. When a blank line is reached,
495 // those arguments will be passed to `processCommand`.
John Stilesa446ca12020-11-19 18:54:33 -0500496 auto resultCode = ResultCode::kSuccess;
Brian Osman7b239052020-11-18 17:04:33 +0000497 std::vector<SkSL::String> args = {"skslc"};
498 std::ifstream in(worklistPath);
499 for (SkSL::String line; std::getline(in, line); ) {
500 if (in.rdstate()) {
501 printf("error reading '%s'\n", worklistPath);
John Stilesa446ca12020-11-19 18:54:33 -0500502 return ResultCode::kInputError;
Brian Osman7b239052020-11-18 17:04:33 +0000503 }
504
505 if (!line.empty()) {
John Stilesa1e8fe32020-11-11 17:29:28 -0500506 // We found an argument. Remember it.
Brian Osman7b239052020-11-18 17:04:33 +0000507 args.push_back(std::move(line));
John Stilesa1e8fe32020-11-11 17:29:28 -0500508 } else {
Brian Osman7b239052020-11-18 17:04:33 +0000509 // We found a blank line. If we have any arguments stored up, process them as a command.
510 if (!args.empty()) {
John Stilesa446ca12020-11-19 18:54:33 -0500511 ResultCode outcome = processCommand(args);
512 resultCode = std::max(resultCode, outcome);
John Stilesa1e8fe32020-11-11 17:29:28 -0500513
514 // Clear every argument except the first ("skslc").
515 args.resize(1);
516 }
517 }
518 }
519
Brian Osman7b239052020-11-18 17:04:33 +0000520 // If the worklist ended with a list of arguments but no blank line, process those now.
John Stilesa1e8fe32020-11-11 17:29:28 -0500521 if (args.size() > 1) {
John Stilesa446ca12020-11-19 18:54:33 -0500522 ResultCode outcome = processCommand(args);
523 resultCode = std::max(resultCode, outcome);
John Stilesa1e8fe32020-11-11 17:29:28 -0500524 }
525
John Stilesa446ca12020-11-19 18:54:33 -0500526 // Return the "worst" status we encountered. For our purposes, compilation errors are the least
527 // serious, because they are expected to occur in unit tests. Other types of errors are not
528 // expected at all during a build.
529 return resultCode;
Brian Osman7b239052020-11-18 17:04:33 +0000530}
531
532int main(int argc, const char** argv) {
533 if (argc == 2) {
534 // Worklists are the only two-argument case for skslc, and we don't intend to support
535 // nested worklists, so we can process them here.
John Stilesa446ca12020-11-19 18:54:33 -0500536 return (int)processWorklist(argv[1]);
Brian Osman7b239052020-11-18 17:04:33 +0000537 } else {
538 // Process non-worklist inputs.
539 std::vector<SkSL::String> args;
540 for (int index=0; index<argc; ++index) {
541 args.push_back(argv[index]);
542 }
543
John Stilesa446ca12020-11-19 18:54:33 -0500544 return (int)processCommand(args);
Brian Osman7b239052020-11-18 17:04:33 +0000545 }
ethannicholasb3058bd2016-07-01 08:22:01 -0700546}