xref: /aosp_15_r20/external/skia/tools/sksl-minify/SkSLMinify.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2022 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkStringView.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkOpts.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLCompiler.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLFileOutputStream.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLLexer.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLModule.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLModuleLoader.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLProgramKind.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLProgramSettings.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLUtil.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLStructDefinition.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLSymbolTable.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/transform/SkSLTransform.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "src/utils/SkGetExecutablePath.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "src/utils/SkOSPath.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "tools/skslc/ProcessWorklist.h"
24*c8dee2aaSAndroid Build Coastguard Worker 
25*c8dee2aaSAndroid Build Coastguard Worker #include <cctype>
26*c8dee2aaSAndroid Build Coastguard Worker #include <forward_list>
27*c8dee2aaSAndroid Build Coastguard Worker #include <fstream>
28*c8dee2aaSAndroid Build Coastguard Worker #include <limits.h>
29*c8dee2aaSAndroid Build Coastguard Worker #include <stdarg.h>
30*c8dee2aaSAndroid Build Coastguard Worker #include <stdio.h>
31*c8dee2aaSAndroid Build Coastguard Worker 
32*c8dee2aaSAndroid Build Coastguard Worker static bool gUnoptimized = false;
33*c8dee2aaSAndroid Build Coastguard Worker static bool gStringify = false;
34*c8dee2aaSAndroid Build Coastguard Worker static SkSL::ProgramKind gProgramKind = SkSL::ProgramKind::kFragment;
35*c8dee2aaSAndroid Build Coastguard Worker 
SkDebugf(const char format[],...)36*c8dee2aaSAndroid Build Coastguard Worker void SkDebugf(const char format[], ...) {
37*c8dee2aaSAndroid Build Coastguard Worker     va_list args;
38*c8dee2aaSAndroid Build Coastguard Worker     va_start(args, format);
39*c8dee2aaSAndroid Build Coastguard Worker     vfprintf(stderr, format, args);
40*c8dee2aaSAndroid Build Coastguard Worker     va_end(args);
41*c8dee2aaSAndroid Build Coastguard Worker }
42*c8dee2aaSAndroid Build Coastguard Worker 
43*c8dee2aaSAndroid Build Coastguard Worker namespace SkOpts {
44*c8dee2aaSAndroid Build Coastguard Worker     size_t raster_pipeline_highp_stride = 1;
45*c8dee2aaSAndroid Build Coastguard Worker }
46*c8dee2aaSAndroid Build Coastguard Worker 
base_name(const std::string & path)47*c8dee2aaSAndroid Build Coastguard Worker static std::string base_name(const std::string& path) {
48*c8dee2aaSAndroid Build Coastguard Worker     size_t slashPos = path.find_last_of("/\\");
49*c8dee2aaSAndroid Build Coastguard Worker     return path.substr(slashPos == std::string::npos ? 0 : slashPos + 1);
50*c8dee2aaSAndroid Build Coastguard Worker }
51*c8dee2aaSAndroid Build Coastguard Worker 
remove_extension(const std::string & path)52*c8dee2aaSAndroid Build Coastguard Worker static std::string remove_extension(const std::string& path) {
53*c8dee2aaSAndroid Build Coastguard Worker     size_t dotPos = path.find_last_of('.');
54*c8dee2aaSAndroid Build Coastguard Worker     return path.substr(0, dotPos);
55*c8dee2aaSAndroid Build Coastguard Worker }
56*c8dee2aaSAndroid Build Coastguard Worker 
57*c8dee2aaSAndroid Build Coastguard Worker /**
58*c8dee2aaSAndroid Build Coastguard Worker  * Displays a usage banner; used when the command line arguments don't make sense.
59*c8dee2aaSAndroid Build Coastguard Worker  */
show_usage()60*c8dee2aaSAndroid Build Coastguard Worker static void show_usage() {
61*c8dee2aaSAndroid Build Coastguard Worker     printf("usage: sksl-minify <output> <input> [--frag|--vert|--compute|--shader|"
62*c8dee2aaSAndroid Build Coastguard Worker            "--colorfilter|--blender|--meshfrag|--meshvert] [dependencies...]\n");
63*c8dee2aaSAndroid Build Coastguard Worker }
64*c8dee2aaSAndroid Build Coastguard Worker 
stringize(const SkSL::Token & token,std::string_view text)65*c8dee2aaSAndroid Build Coastguard Worker static std::string_view stringize(const SkSL::Token& token, std::string_view text) {
66*c8dee2aaSAndroid Build Coastguard Worker     return text.substr(token.fOffset, token.fLength);
67*c8dee2aaSAndroid Build Coastguard Worker }
68*c8dee2aaSAndroid Build Coastguard Worker 
maybe_identifier(char c)69*c8dee2aaSAndroid Build Coastguard Worker static bool maybe_identifier(char c) {
70*c8dee2aaSAndroid Build Coastguard Worker     return std::isalnum(c) || c == '$' || c == '_';
71*c8dee2aaSAndroid Build Coastguard Worker }
72*c8dee2aaSAndroid Build Coastguard Worker 
is_plus_or_minus(char c)73*c8dee2aaSAndroid Build Coastguard Worker static bool is_plus_or_minus(char c) {
74*c8dee2aaSAndroid Build Coastguard Worker     return c == '+' || c == '-';
75*c8dee2aaSAndroid Build Coastguard Worker }
76*c8dee2aaSAndroid Build Coastguard Worker 
module_type_for_path(const char * path)77*c8dee2aaSAndroid Build Coastguard Worker static SkSL::ModuleType module_type_for_path(const char* path) {
78*c8dee2aaSAndroid Build Coastguard Worker     SkString filename = SkOSPath::Basename(path);
79*c8dee2aaSAndroid Build Coastguard Worker 
80*c8dee2aaSAndroid Build Coastguard Worker #define M(type) if (filename.equals(#type ".sksl")) { return SkSL::ModuleType::type; }
81*c8dee2aaSAndroid Build Coastguard Worker     SKSL_MODULE_LIST(M)
82*c8dee2aaSAndroid Build Coastguard Worker #undef M
83*c8dee2aaSAndroid Build Coastguard Worker 
84*c8dee2aaSAndroid Build Coastguard Worker     return SkSL::ModuleType::unknown;
85*c8dee2aaSAndroid Build Coastguard Worker }
86*c8dee2aaSAndroid Build Coastguard Worker 
compile_module_list(SkSpan<const std::string> paths,SkSL::ProgramKind kind)87*c8dee2aaSAndroid Build Coastguard Worker static std::forward_list<std::unique_ptr<const SkSL::Module>> compile_module_list(
88*c8dee2aaSAndroid Build Coastguard Worker         SkSpan<const std::string> paths, SkSL::ProgramKind kind) {
89*c8dee2aaSAndroid Build Coastguard Worker     std::forward_list<std::unique_ptr<const SkSL::Module>> modules;
90*c8dee2aaSAndroid Build Coastguard Worker 
91*c8dee2aaSAndroid Build Coastguard Worker     // If we are compiling a Runtime Effect...
92*c8dee2aaSAndroid Build Coastguard Worker     if (SkSL::ProgramConfig::IsRuntimeEffect(kind)) {
93*c8dee2aaSAndroid Build Coastguard Worker         // ... the parent modules still need to be compiled as Fragment programs.
94*c8dee2aaSAndroid Build Coastguard Worker         // If no modules are explicitly specified, we automatically include the built-in modules for
95*c8dee2aaSAndroid Build Coastguard Worker         // runtime effects (sksl_shared, sksl_public) so that casual users don't need to always
96*c8dee2aaSAndroid Build Coastguard Worker         // remember to specify these modules.
97*c8dee2aaSAndroid Build Coastguard Worker         if (paths.size() == 1) {
98*c8dee2aaSAndroid Build Coastguard Worker             const std::string minifyDir = SkOSPath::Dirname(SkGetExecutablePath().c_str()).c_str();
99*c8dee2aaSAndroid Build Coastguard Worker             std::string defaultRuntimeShaderPaths[] = {
100*c8dee2aaSAndroid Build Coastguard Worker                     minifyDir + SkOSPath::SEPARATOR + "sksl_public.sksl",
101*c8dee2aaSAndroid Build Coastguard Worker                     minifyDir + SkOSPath::SEPARATOR + "sksl_shared.sksl",
102*c8dee2aaSAndroid Build Coastguard Worker             };
103*c8dee2aaSAndroid Build Coastguard Worker             modules = compile_module_list(defaultRuntimeShaderPaths, SkSL::ProgramKind::kFragment);
104*c8dee2aaSAndroid Build Coastguard Worker         } else {
105*c8dee2aaSAndroid Build Coastguard Worker             // The parent modules were listed on the command line; we need to compile them as
106*c8dee2aaSAndroid Build Coastguard Worker             // fragment programs. The final module keeps the Runtime Shader program-kind.
107*c8dee2aaSAndroid Build Coastguard Worker             modules = compile_module_list(paths.subspan(1), SkSL::ProgramKind::kFragment);
108*c8dee2aaSAndroid Build Coastguard Worker             paths = paths.first(1);
109*c8dee2aaSAndroid Build Coastguard Worker         }
110*c8dee2aaSAndroid Build Coastguard Worker         // Set up the public type aliases so that Runtime Shader code with GLSL types works as-is.
111*c8dee2aaSAndroid Build Coastguard Worker         SkSL::ModuleLoader::Get().addPublicTypeAliases(modules.front().get());
112*c8dee2aaSAndroid Build Coastguard Worker     }
113*c8dee2aaSAndroid Build Coastguard Worker 
114*c8dee2aaSAndroid Build Coastguard Worker     // Load in each input as a module, from right to left.
115*c8dee2aaSAndroid Build Coastguard Worker     // Each module inherits the symbols from its parent module.
116*c8dee2aaSAndroid Build Coastguard Worker     SkSL::Compiler compiler;
117*c8dee2aaSAndroid Build Coastguard Worker     for (auto modulePath = paths.rbegin(); modulePath != paths.rend(); ++modulePath) {
118*c8dee2aaSAndroid Build Coastguard Worker         std::ifstream in(*modulePath);
119*c8dee2aaSAndroid Build Coastguard Worker         std::string moduleSource{std::istreambuf_iterator<char>(in),
120*c8dee2aaSAndroid Build Coastguard Worker                                  std::istreambuf_iterator<char>()};
121*c8dee2aaSAndroid Build Coastguard Worker         if (in.rdstate()) {
122*c8dee2aaSAndroid Build Coastguard Worker             printf("error reading '%s'\n", modulePath->c_str());
123*c8dee2aaSAndroid Build Coastguard Worker             return {};
124*c8dee2aaSAndroid Build Coastguard Worker         }
125*c8dee2aaSAndroid Build Coastguard Worker 
126*c8dee2aaSAndroid Build Coastguard Worker         const SkSL::Module* parent = modules.empty() ? SkSL::ModuleLoader::Get().rootModule()
127*c8dee2aaSAndroid Build Coastguard Worker                                                      : modules.front().get();
128*c8dee2aaSAndroid Build Coastguard Worker         std::unique_ptr<SkSL::Module> m =
129*c8dee2aaSAndroid Build Coastguard Worker                 compiler.compileModule(kind,
130*c8dee2aaSAndroid Build Coastguard Worker                                        module_type_for_path(modulePath->c_str()),
131*c8dee2aaSAndroid Build Coastguard Worker                                        std::move(moduleSource),
132*c8dee2aaSAndroid Build Coastguard Worker                                        parent,
133*c8dee2aaSAndroid Build Coastguard Worker                                        /*shouldInline=*/false);
134*c8dee2aaSAndroid Build Coastguard Worker         if (!m) {
135*c8dee2aaSAndroid Build Coastguard Worker             return {};
136*c8dee2aaSAndroid Build Coastguard Worker         }
137*c8dee2aaSAndroid Build Coastguard Worker         // We need to optimize every module in the chain. We rename private functions at global
138*c8dee2aaSAndroid Build Coastguard Worker         // scope, and we need to make sure there are no name collisions between nested modules.
139*c8dee2aaSAndroid Build Coastguard Worker         // (i.e., if module A claims names `$a` and `$b` at global scope, module B will need to
140*c8dee2aaSAndroid Build Coastguard Worker         // start at `$c`. The most straightforward way to handle this is to actually perform the
141*c8dee2aaSAndroid Build Coastguard Worker         // renames.)
142*c8dee2aaSAndroid Build Coastguard Worker         compiler.optimizeModuleBeforeMinifying(kind, *m, /*shrinkSymbols=*/!gUnoptimized);
143*c8dee2aaSAndroid Build Coastguard Worker         modules.push_front(std::move(m));
144*c8dee2aaSAndroid Build Coastguard Worker     }
145*c8dee2aaSAndroid Build Coastguard Worker     // Return all of the modules to transfer their ownership to the caller.
146*c8dee2aaSAndroid Build Coastguard Worker     return modules;
147*c8dee2aaSAndroid Build Coastguard Worker }
148*c8dee2aaSAndroid Build Coastguard Worker 
generate_minified_text(std::string_view inputPath,std::string_view text,SkSL::FileOutputStream & out)149*c8dee2aaSAndroid Build Coastguard Worker static bool generate_minified_text(std::string_view inputPath,
150*c8dee2aaSAndroid Build Coastguard Worker                                    std::string_view text,
151*c8dee2aaSAndroid Build Coastguard Worker                                    SkSL::FileOutputStream& out) {
152*c8dee2aaSAndroid Build Coastguard Worker     using TokenKind = SkSL::Token::Kind;
153*c8dee2aaSAndroid Build Coastguard Worker 
154*c8dee2aaSAndroid Build Coastguard Worker     SkSL::Lexer lexer;
155*c8dee2aaSAndroid Build Coastguard Worker     lexer.start(text);
156*c8dee2aaSAndroid Build Coastguard Worker 
157*c8dee2aaSAndroid Build Coastguard Worker     SkSL::Token token;
158*c8dee2aaSAndroid Build Coastguard Worker     std::string_view lastTokenText = " ";
159*c8dee2aaSAndroid Build Coastguard Worker     int lineWidth = 1;
160*c8dee2aaSAndroid Build Coastguard Worker     for (;;) {
161*c8dee2aaSAndroid Build Coastguard Worker         token = lexer.next();
162*c8dee2aaSAndroid Build Coastguard Worker         if (token.fKind == TokenKind::TK_END_OF_FILE) {
163*c8dee2aaSAndroid Build Coastguard Worker             break;
164*c8dee2aaSAndroid Build Coastguard Worker         }
165*c8dee2aaSAndroid Build Coastguard Worker         if (token.fKind == TokenKind::TK_LINE_COMMENT ||
166*c8dee2aaSAndroid Build Coastguard Worker             token.fKind == TokenKind::TK_BLOCK_COMMENT ||
167*c8dee2aaSAndroid Build Coastguard Worker             token.fKind == TokenKind::TK_WHITESPACE) {
168*c8dee2aaSAndroid Build Coastguard Worker             continue;
169*c8dee2aaSAndroid Build Coastguard Worker         }
170*c8dee2aaSAndroid Build Coastguard Worker         std::string_view thisTokenText = stringize(token, text);
171*c8dee2aaSAndroid Build Coastguard Worker         if (token.fKind == TokenKind::TK_INVALID) {
172*c8dee2aaSAndroid Build Coastguard Worker             printf("%.*s: unable to parse '%.*s' at offset %d\n",
173*c8dee2aaSAndroid Build Coastguard Worker                    (int)inputPath.size(), inputPath.data(),
174*c8dee2aaSAndroid Build Coastguard Worker                    (int)thisTokenText.size(), thisTokenText.data(),
175*c8dee2aaSAndroid Build Coastguard Worker                    token.fOffset);
176*c8dee2aaSAndroid Build Coastguard Worker             return false;
177*c8dee2aaSAndroid Build Coastguard Worker         }
178*c8dee2aaSAndroid Build Coastguard Worker         if (thisTokenText.empty()) {
179*c8dee2aaSAndroid Build Coastguard Worker             continue;
180*c8dee2aaSAndroid Build Coastguard Worker         }
181*c8dee2aaSAndroid Build Coastguard Worker         if (token.fKind == TokenKind::TK_FLOAT_LITERAL) {
182*c8dee2aaSAndroid Build Coastguard Worker             // We can reduce `3.0` to `3.` safely.
183*c8dee2aaSAndroid Build Coastguard Worker             if (skstd::contains(thisTokenText, '.')) {
184*c8dee2aaSAndroid Build Coastguard Worker                 while (thisTokenText.back() == '0' && thisTokenText.size() >= 3) {
185*c8dee2aaSAndroid Build Coastguard Worker                     thisTokenText.remove_suffix(1);
186*c8dee2aaSAndroid Build Coastguard Worker                 }
187*c8dee2aaSAndroid Build Coastguard Worker             }
188*c8dee2aaSAndroid Build Coastguard Worker             // We can reduce `0.5` to `.5` safely.
189*c8dee2aaSAndroid Build Coastguard Worker             if (skstd::starts_with(thisTokenText, "0.") && thisTokenText.size() >= 3) {
190*c8dee2aaSAndroid Build Coastguard Worker                 thisTokenText.remove_prefix(1);
191*c8dee2aaSAndroid Build Coastguard Worker             }
192*c8dee2aaSAndroid Build Coastguard Worker         }
193*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(!lastTokenText.empty());
194*c8dee2aaSAndroid Build Coastguard Worker         if (gStringify && lineWidth > 75) {
195*c8dee2aaSAndroid Build Coastguard Worker             // We're getting full-ish; wrap to a new line.
196*c8dee2aaSAndroid Build Coastguard Worker             out.writeText("\"\n\"");
197*c8dee2aaSAndroid Build Coastguard Worker             lineWidth = 1;
198*c8dee2aaSAndroid Build Coastguard Worker         }
199*c8dee2aaSAndroid Build Coastguard Worker 
200*c8dee2aaSAndroid Build Coastguard Worker         // Detect tokens with abutting alphanumeric characters side-by-side.
201*c8dee2aaSAndroid Build Coastguard Worker         bool adjacentIdentifiers =
202*c8dee2aaSAndroid Build Coastguard Worker                 maybe_identifier(lastTokenText.back()) && maybe_identifier(thisTokenText.front());
203*c8dee2aaSAndroid Build Coastguard Worker 
204*c8dee2aaSAndroid Build Coastguard Worker         // Detect potentially ambiguous preincrement/postincrement operators.
205*c8dee2aaSAndroid Build Coastguard Worker         // For instance, `x + ++y` and `x++ + y` require whitespace for differentiation.
206*c8dee2aaSAndroid Build Coastguard Worker         bool adjacentPlusOrMinus =
207*c8dee2aaSAndroid Build Coastguard Worker                 is_plus_or_minus(lastTokenText.back()) && is_plus_or_minus(thisTokenText.front());
208*c8dee2aaSAndroid Build Coastguard Worker 
209*c8dee2aaSAndroid Build Coastguard Worker         // Insert whitespace when it is necessary for program correctness.
210*c8dee2aaSAndroid Build Coastguard Worker         if (adjacentIdentifiers || adjacentPlusOrMinus) {
211*c8dee2aaSAndroid Build Coastguard Worker             out.writeText(" ");
212*c8dee2aaSAndroid Build Coastguard Worker             lineWidth++;
213*c8dee2aaSAndroid Build Coastguard Worker         }
214*c8dee2aaSAndroid Build Coastguard Worker         out.write(thisTokenText.data(), thisTokenText.size());
215*c8dee2aaSAndroid Build Coastguard Worker         lineWidth += thisTokenText.size();
216*c8dee2aaSAndroid Build Coastguard Worker         lastTokenText = thisTokenText;
217*c8dee2aaSAndroid Build Coastguard Worker     }
218*c8dee2aaSAndroid Build Coastguard Worker 
219*c8dee2aaSAndroid Build Coastguard Worker     return true;
220*c8dee2aaSAndroid Build Coastguard Worker }
221*c8dee2aaSAndroid Build Coastguard Worker 
find_boolean_flag(SkSpan<std::string> * args,std::string_view flagName)222*c8dee2aaSAndroid Build Coastguard Worker static bool find_boolean_flag(SkSpan<std::string>* args, std::string_view flagName) {
223*c8dee2aaSAndroid Build Coastguard Worker     size_t startingCount = args->size();
224*c8dee2aaSAndroid Build Coastguard Worker     auto iter = std::remove_if(args->begin(), args->end(),
225*c8dee2aaSAndroid Build Coastguard Worker                                [&](const std::string& a) { return a == flagName; });
226*c8dee2aaSAndroid Build Coastguard Worker     *args = args->subspan(0, std::distance(args->begin(), iter));
227*c8dee2aaSAndroid Build Coastguard Worker     return args->size() < startingCount;
228*c8dee2aaSAndroid Build Coastguard Worker }
229*c8dee2aaSAndroid Build Coastguard Worker 
has_overlapping_flags(SkSpan<const bool> flags)230*c8dee2aaSAndroid Build Coastguard Worker static bool has_overlapping_flags(SkSpan<const bool> flags) {
231*c8dee2aaSAndroid Build Coastguard Worker     // Returns true if more than one boolean is set.
232*c8dee2aaSAndroid Build Coastguard Worker     return std::count(flags.begin(), flags.end(), true) > 1;
233*c8dee2aaSAndroid Build Coastguard Worker }
234*c8dee2aaSAndroid Build Coastguard Worker 
process_command(SkSpan<std::string> args)235*c8dee2aaSAndroid Build Coastguard Worker static ResultCode process_command(SkSpan<std::string> args) {
236*c8dee2aaSAndroid Build Coastguard Worker     // Ignore the process name.
237*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!args.empty());
238*c8dee2aaSAndroid Build Coastguard Worker     args = args.subspan(1);
239*c8dee2aaSAndroid Build Coastguard Worker 
240*c8dee2aaSAndroid Build Coastguard Worker     // Process command line flags.
241*c8dee2aaSAndroid Build Coastguard Worker     gUnoptimized = find_boolean_flag(&args, "--unoptimized");
242*c8dee2aaSAndroid Build Coastguard Worker     gStringify = find_boolean_flag(&args, "--stringify");
243*c8dee2aaSAndroid Build Coastguard Worker     bool isFrag = find_boolean_flag(&args, "--frag");
244*c8dee2aaSAndroid Build Coastguard Worker     bool isVert = find_boolean_flag(&args, "--vert");
245*c8dee2aaSAndroid Build Coastguard Worker     bool isCompute = find_boolean_flag(&args, "--compute");
246*c8dee2aaSAndroid Build Coastguard Worker     bool isShader = find_boolean_flag(&args, "--shader");
247*c8dee2aaSAndroid Build Coastguard Worker     bool isPrivateShader = find_boolean_flag(&args, "--privshader");
248*c8dee2aaSAndroid Build Coastguard Worker     bool isColorFilter = find_boolean_flag(&args, "--colorfilter");
249*c8dee2aaSAndroid Build Coastguard Worker     bool isBlender = find_boolean_flag(&args, "--blender");
250*c8dee2aaSAndroid Build Coastguard Worker     bool isMeshFrag = find_boolean_flag(&args, "--meshfrag");
251*c8dee2aaSAndroid Build Coastguard Worker     bool isMeshVert = find_boolean_flag(&args, "--meshvert");
252*c8dee2aaSAndroid Build Coastguard Worker     if (has_overlapping_flags({isFrag, isVert, isCompute, isShader, isColorFilter,
253*c8dee2aaSAndroid Build Coastguard Worker                                isBlender, isMeshFrag, isMeshVert})) {
254*c8dee2aaSAndroid Build Coastguard Worker         show_usage();
255*c8dee2aaSAndroid Build Coastguard Worker         return ResultCode::kInputError;
256*c8dee2aaSAndroid Build Coastguard Worker     }
257*c8dee2aaSAndroid Build Coastguard Worker     if (isFrag) {
258*c8dee2aaSAndroid Build Coastguard Worker         gProgramKind = SkSL::ProgramKind::kFragment;
259*c8dee2aaSAndroid Build Coastguard Worker     } else if (isVert) {
260*c8dee2aaSAndroid Build Coastguard Worker         gProgramKind = SkSL::ProgramKind::kVertex;
261*c8dee2aaSAndroid Build Coastguard Worker     } else if (isCompute) {
262*c8dee2aaSAndroid Build Coastguard Worker         gProgramKind = SkSL::ProgramKind::kCompute;
263*c8dee2aaSAndroid Build Coastguard Worker     } else if (isColorFilter) {
264*c8dee2aaSAndroid Build Coastguard Worker         gProgramKind = SkSL::ProgramKind::kRuntimeColorFilter;
265*c8dee2aaSAndroid Build Coastguard Worker     } else if (isBlender) {
266*c8dee2aaSAndroid Build Coastguard Worker         gProgramKind = SkSL::ProgramKind::kRuntimeBlender;
267*c8dee2aaSAndroid Build Coastguard Worker     } else if (isMeshFrag) {
268*c8dee2aaSAndroid Build Coastguard Worker         gProgramKind = SkSL::ProgramKind::kMeshFragment;
269*c8dee2aaSAndroid Build Coastguard Worker     } else if (isMeshVert) {
270*c8dee2aaSAndroid Build Coastguard Worker         gProgramKind = SkSL::ProgramKind::kMeshVertex;
271*c8dee2aaSAndroid Build Coastguard Worker     } else if (isPrivateShader) {
272*c8dee2aaSAndroid Build Coastguard Worker         gProgramKind = SkSL::ProgramKind::kPrivateRuntimeShader;
273*c8dee2aaSAndroid Build Coastguard Worker     } else {
274*c8dee2aaSAndroid Build Coastguard Worker         // Default case, if no option is specified.
275*c8dee2aaSAndroid Build Coastguard Worker         gProgramKind = SkSL::ProgramKind::kRuntimeShader;
276*c8dee2aaSAndroid Build Coastguard Worker     }
277*c8dee2aaSAndroid Build Coastguard Worker 
278*c8dee2aaSAndroid Build Coastguard Worker     // We expect, at a minimum, an output path and one or more input paths.
279*c8dee2aaSAndroid Build Coastguard Worker     if (args.size() < 2) {
280*c8dee2aaSAndroid Build Coastguard Worker         show_usage();
281*c8dee2aaSAndroid Build Coastguard Worker         return ResultCode::kInputError;
282*c8dee2aaSAndroid Build Coastguard Worker     }
283*c8dee2aaSAndroid Build Coastguard Worker     const std::string& outputPath = args[0];
284*c8dee2aaSAndroid Build Coastguard Worker     SkSpan inputPaths = args.subspan(1);
285*c8dee2aaSAndroid Build Coastguard Worker 
286*c8dee2aaSAndroid Build Coastguard Worker     // Compile the original SkSL from the input path.
287*c8dee2aaSAndroid Build Coastguard Worker     std::forward_list<std::unique_ptr<const SkSL::Module>> modules =
288*c8dee2aaSAndroid Build Coastguard Worker             compile_module_list(inputPaths, gProgramKind);
289*c8dee2aaSAndroid Build Coastguard Worker     if (modules.empty()) {
290*c8dee2aaSAndroid Build Coastguard Worker         return ResultCode::kInputError;
291*c8dee2aaSAndroid Build Coastguard Worker     }
292*c8dee2aaSAndroid Build Coastguard Worker     const SkSL::Module* module = modules.front().get();
293*c8dee2aaSAndroid Build Coastguard Worker 
294*c8dee2aaSAndroid Build Coastguard Worker     // Emit the minified SkSL into our output path.
295*c8dee2aaSAndroid Build Coastguard Worker     SkSL::FileOutputStream out(outputPath.c_str());
296*c8dee2aaSAndroid Build Coastguard Worker     if (!out.isValid()) {
297*c8dee2aaSAndroid Build Coastguard Worker         printf("error writing '%s'\n", outputPath.c_str());
298*c8dee2aaSAndroid Build Coastguard Worker         return ResultCode::kOutputError;
299*c8dee2aaSAndroid Build Coastguard Worker     }
300*c8dee2aaSAndroid Build Coastguard Worker 
301*c8dee2aaSAndroid Build Coastguard Worker     std::string baseName = remove_extension(base_name(inputPaths.front()));
302*c8dee2aaSAndroid Build Coastguard Worker     if (gStringify) {
303*c8dee2aaSAndroid Build Coastguard Worker         out.printf("static constexpr char SKSL_MINIFIED_%s[] =\n\"", baseName.c_str());
304*c8dee2aaSAndroid Build Coastguard Worker     }
305*c8dee2aaSAndroid Build Coastguard Worker 
306*c8dee2aaSAndroid Build Coastguard Worker     // Generate the program text by getting the program's description.
307*c8dee2aaSAndroid Build Coastguard Worker     std::string text;
308*c8dee2aaSAndroid Build Coastguard Worker     for (const std::unique_ptr<SkSL::ProgramElement>& element : module->fElements) {
309*c8dee2aaSAndroid Build Coastguard Worker         if ((isMeshFrag || isMeshVert) && element->is<SkSL::StructDefinition>()) {
310*c8dee2aaSAndroid Build Coastguard Worker             std::string_view name = element->as<SkSL::StructDefinition>().type().name();
311*c8dee2aaSAndroid Build Coastguard Worker             if (name == "Attributes" || name == "Varyings") {
312*c8dee2aaSAndroid Build Coastguard Worker                 // Don't emit the Attributes or Varyings structs from a mesh program into the
313*c8dee2aaSAndroid Build Coastguard Worker                 // minified output; those are synthesized via the SkMeshSpecification.
314*c8dee2aaSAndroid Build Coastguard Worker                 continue;
315*c8dee2aaSAndroid Build Coastguard Worker             }
316*c8dee2aaSAndroid Build Coastguard Worker         }
317*c8dee2aaSAndroid Build Coastguard Worker         text += element->description();
318*c8dee2aaSAndroid Build Coastguard Worker     }
319*c8dee2aaSAndroid Build Coastguard Worker 
320*c8dee2aaSAndroid Build Coastguard Worker     // Eliminate whitespace and perform other basic simplifications via a lexer pass.
321*c8dee2aaSAndroid Build Coastguard Worker     if (!generate_minified_text(inputPaths.front(), text, out)) {
322*c8dee2aaSAndroid Build Coastguard Worker         return ResultCode::kInputError;
323*c8dee2aaSAndroid Build Coastguard Worker     }
324*c8dee2aaSAndroid Build Coastguard Worker 
325*c8dee2aaSAndroid Build Coastguard Worker     if (gStringify) {
326*c8dee2aaSAndroid Build Coastguard Worker         out.writeText("\";");
327*c8dee2aaSAndroid Build Coastguard Worker     }
328*c8dee2aaSAndroid Build Coastguard Worker     out.writeText("\n");
329*c8dee2aaSAndroid Build Coastguard Worker 
330*c8dee2aaSAndroid Build Coastguard Worker     if (!out.close()) {
331*c8dee2aaSAndroid Build Coastguard Worker         printf("error writing '%s'\n", outputPath.c_str());
332*c8dee2aaSAndroid Build Coastguard Worker         return ResultCode::kOutputError;
333*c8dee2aaSAndroid Build Coastguard Worker     }
334*c8dee2aaSAndroid Build Coastguard Worker 
335*c8dee2aaSAndroid Build Coastguard Worker     return ResultCode::kSuccess;
336*c8dee2aaSAndroid Build Coastguard Worker }
337*c8dee2aaSAndroid Build Coastguard Worker 
main(int argc,const char ** argv)338*c8dee2aaSAndroid Build Coastguard Worker int main(int argc, const char** argv) {
339*c8dee2aaSAndroid Build Coastguard Worker     if (argc == 2) {
340*c8dee2aaSAndroid Build Coastguard Worker         // Worklists are the only two-argument case for sksl-minify, and we don't intend to support
341*c8dee2aaSAndroid Build Coastguard Worker         // nested worklists, so we can process them here.
342*c8dee2aaSAndroid Build Coastguard Worker         return (int)ProcessWorklist(argv[1], process_command);
343*c8dee2aaSAndroid Build Coastguard Worker     } else {
344*c8dee2aaSAndroid Build Coastguard Worker         // Process non-worklist inputs.
345*c8dee2aaSAndroid Build Coastguard Worker         std::vector<std::string> args;
346*c8dee2aaSAndroid Build Coastguard Worker         for (int index=0; index<argc; ++index) {
347*c8dee2aaSAndroid Build Coastguard Worker             args.push_back(argv[index]);
348*c8dee2aaSAndroid Build Coastguard Worker         }
349*c8dee2aaSAndroid Build Coastguard Worker 
350*c8dee2aaSAndroid Build Coastguard Worker         return (int)process_command(args);
351*c8dee2aaSAndroid Build Coastguard Worker     }
352*c8dee2aaSAndroid Build Coastguard Worker }
353