xref: /aosp_15_r20/external/skia/src/utils/SkShaderUtils.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2019 Google LLC
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/utils/SkShaderUtils.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkString.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTArray.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkStringUtils.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLProgramSettings.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLString.h"
15*c8dee2aaSAndroid Build Coastguard Worker 
16*c8dee2aaSAndroid Build Coastguard Worker #include <cstddef>
17*c8dee2aaSAndroid Build Coastguard Worker 
18*c8dee2aaSAndroid Build Coastguard Worker using namespace skia_private;
19*c8dee2aaSAndroid Build Coastguard Worker 
20*c8dee2aaSAndroid Build Coastguard Worker namespace SkShaderUtils {
21*c8dee2aaSAndroid Build Coastguard Worker 
22*c8dee2aaSAndroid Build Coastguard Worker class GLSLPrettyPrint {
23*c8dee2aaSAndroid Build Coastguard Worker public:
GLSLPrettyPrint()24*c8dee2aaSAndroid Build Coastguard Worker     GLSLPrettyPrint() {}
25*c8dee2aaSAndroid Build Coastguard Worker 
prettify(const std::string & string)26*c8dee2aaSAndroid Build Coastguard Worker     std::string prettify(const std::string& string) {
27*c8dee2aaSAndroid Build Coastguard Worker         fTabs = 0;
28*c8dee2aaSAndroid Build Coastguard Worker         fFreshline = true;
29*c8dee2aaSAndroid Build Coastguard Worker 
30*c8dee2aaSAndroid Build Coastguard Worker         // If a string breaks while in the middle 'parse until' we need to continue parsing on the
31*c8dee2aaSAndroid Build Coastguard Worker         // next string
32*c8dee2aaSAndroid Build Coastguard Worker         fInParseUntilNewline = false;
33*c8dee2aaSAndroid Build Coastguard Worker         fInParseUntil = false;
34*c8dee2aaSAndroid Build Coastguard Worker 
35*c8dee2aaSAndroid Build Coastguard Worker         int parensDepth = 0;
36*c8dee2aaSAndroid Build Coastguard Worker 
37*c8dee2aaSAndroid Build Coastguard Worker         // setup pretty state
38*c8dee2aaSAndroid Build Coastguard Worker         fIndex = 0;
39*c8dee2aaSAndroid Build Coastguard Worker         fLength = string.length();
40*c8dee2aaSAndroid Build Coastguard Worker         fInput = string.c_str();
41*c8dee2aaSAndroid Build Coastguard Worker 
42*c8dee2aaSAndroid Build Coastguard Worker         while (fLength > fIndex) {
43*c8dee2aaSAndroid Build Coastguard Worker             /* The heart and soul of our prettification algorithm.  The rules should hopefully
44*c8dee2aaSAndroid Build Coastguard Worker              * be self explanatory.  For '#' and '//' tokens, we parse until we reach a newline.
45*c8dee2aaSAndroid Build Coastguard Worker              *
46*c8dee2aaSAndroid Build Coastguard Worker              * For long style comments like this one, we search for the ending token.  We also
47*c8dee2aaSAndroid Build Coastguard Worker              * preserve whitespace in these comments WITH THE CAVEAT that we do the newlines
48*c8dee2aaSAndroid Build Coastguard Worker              * ourselves.  This allows us to remain in control of line numbers, and matching
49*c8dee2aaSAndroid Build Coastguard Worker              * tabs.  Existing tabs in the input string are copied over too, but this will look
50*c8dee2aaSAndroid Build Coastguard Worker              * funny.
51*c8dee2aaSAndroid Build Coastguard Worker              *
52*c8dee2aaSAndroid Build Coastguard Worker              * '{' and '}' are handled in basically the same way.  We add a newline if we aren't
53*c8dee2aaSAndroid Build Coastguard Worker              * on a fresh line, dirty the line, then add a second newline, i.e. braces are always
54*c8dee2aaSAndroid Build Coastguard Worker              * on their own lines indented properly.
55*c8dee2aaSAndroid Build Coastguard Worker              *
56*c8dee2aaSAndroid Build Coastguard Worker              * '(' and ')' are basically ignored, except as a sign that we need to ignore ';', since
57*c8dee2aaSAndroid Build Coastguard Worker              * we want to keep for loops on a single line.
58*c8dee2aaSAndroid Build Coastguard Worker              *
59*c8dee2aaSAndroid Build Coastguard Worker              * ';' means add a new line.  If the previous character was a '}', we make sure that the
60*c8dee2aaSAndroid Build Coastguard Worker              * semicolon comes directly after the brace, not on a newline.
61*c8dee2aaSAndroid Build Coastguard Worker              *
62*c8dee2aaSAndroid Build Coastguard Worker              * ',' doesn't add a new line, but does have special handling to ensure it is on the
63*c8dee2aaSAndroid Build Coastguard Worker              * same line as a '}', much like the semicolon.
64*c8dee2aaSAndroid Build Coastguard Worker              *
65*c8dee2aaSAndroid Build Coastguard Worker              * '\t' and '\n' are ignored in general parsing for backwards compatibility with
66*c8dee2aaSAndroid Build Coastguard Worker              * existing shader code.  We also have a special case for handling whitespace at the
67*c8dee2aaSAndroid Build Coastguard Worker              * beginning of fresh lines.
68*c8dee2aaSAndroid Build Coastguard Worker              *
69*c8dee2aaSAndroid Build Coastguard Worker              * Otherwise, just add the new character to the pretty string, indenting if
70*c8dee2aaSAndroid Build Coastguard Worker              * necessary.
71*c8dee2aaSAndroid Build Coastguard Worker              */
72*c8dee2aaSAndroid Build Coastguard Worker             if (fInParseUntilNewline) {
73*c8dee2aaSAndroid Build Coastguard Worker                 this->parseUntilNewline();
74*c8dee2aaSAndroid Build Coastguard Worker                 continue;
75*c8dee2aaSAndroid Build Coastguard Worker             }
76*c8dee2aaSAndroid Build Coastguard Worker             if (fInParseUntil) {
77*c8dee2aaSAndroid Build Coastguard Worker                 this->parseUntil(fInParseUntilToken);
78*c8dee2aaSAndroid Build Coastguard Worker                 continue;
79*c8dee2aaSAndroid Build Coastguard Worker             }
80*c8dee2aaSAndroid Build Coastguard Worker             if (this->hasToken("#") || this->hasToken("//")) {
81*c8dee2aaSAndroid Build Coastguard Worker                 this->parseUntilNewline();
82*c8dee2aaSAndroid Build Coastguard Worker                 continue;
83*c8dee2aaSAndroid Build Coastguard Worker             }
84*c8dee2aaSAndroid Build Coastguard Worker             if (this->hasToken("/*")) {
85*c8dee2aaSAndroid Build Coastguard Worker                 this->parseUntil("*/");
86*c8dee2aaSAndroid Build Coastguard Worker                 continue;
87*c8dee2aaSAndroid Build Coastguard Worker             }
88*c8dee2aaSAndroid Build Coastguard Worker             if (fInput[fIndex] == '{') {
89*c8dee2aaSAndroid Build Coastguard Worker                 this->newline();
90*c8dee2aaSAndroid Build Coastguard Worker                 this->appendChar('{');
91*c8dee2aaSAndroid Build Coastguard Worker                 fTabs++;
92*c8dee2aaSAndroid Build Coastguard Worker                 this->newline();
93*c8dee2aaSAndroid Build Coastguard Worker                 continue;
94*c8dee2aaSAndroid Build Coastguard Worker             }
95*c8dee2aaSAndroid Build Coastguard Worker             if (fInput[fIndex] == '}') {
96*c8dee2aaSAndroid Build Coastguard Worker                 fTabs--;
97*c8dee2aaSAndroid Build Coastguard Worker                 this->newline();
98*c8dee2aaSAndroid Build Coastguard Worker                 this->appendChar('}');
99*c8dee2aaSAndroid Build Coastguard Worker                 this->newline();
100*c8dee2aaSAndroid Build Coastguard Worker                 continue;
101*c8dee2aaSAndroid Build Coastguard Worker             }
102*c8dee2aaSAndroid Build Coastguard Worker             if (fFreshline && fInput[fIndex] == ';') {
103*c8dee2aaSAndroid Build Coastguard Worker                 this->undoNewlineAfter('}');
104*c8dee2aaSAndroid Build Coastguard Worker                 this->appendChar(fInput[fIndex]);
105*c8dee2aaSAndroid Build Coastguard Worker                 this->newline();
106*c8dee2aaSAndroid Build Coastguard Worker                 continue;
107*c8dee2aaSAndroid Build Coastguard Worker             }
108*c8dee2aaSAndroid Build Coastguard Worker             if (fFreshline && fInput[fIndex] == ',') {
109*c8dee2aaSAndroid Build Coastguard Worker                 this->undoNewlineAfter('}');
110*c8dee2aaSAndroid Build Coastguard Worker                 this->appendChar(fInput[fIndex]);
111*c8dee2aaSAndroid Build Coastguard Worker                 continue;
112*c8dee2aaSAndroid Build Coastguard Worker             }
113*c8dee2aaSAndroid Build Coastguard Worker             if (this->hasToken(")")) {
114*c8dee2aaSAndroid Build Coastguard Worker                 parensDepth--;
115*c8dee2aaSAndroid Build Coastguard Worker                 continue;
116*c8dee2aaSAndroid Build Coastguard Worker             }
117*c8dee2aaSAndroid Build Coastguard Worker             if (this->hasToken("(")) {
118*c8dee2aaSAndroid Build Coastguard Worker                 parensDepth++;
119*c8dee2aaSAndroid Build Coastguard Worker                 continue;
120*c8dee2aaSAndroid Build Coastguard Worker             }
121*c8dee2aaSAndroid Build Coastguard Worker             if (this->hasToken(")")) {
122*c8dee2aaSAndroid Build Coastguard Worker                 parensDepth--;
123*c8dee2aaSAndroid Build Coastguard Worker                 continue;
124*c8dee2aaSAndroid Build Coastguard Worker             }
125*c8dee2aaSAndroid Build Coastguard Worker             if (!parensDepth && this->hasToken(";")) {
126*c8dee2aaSAndroid Build Coastguard Worker                 this->newline();
127*c8dee2aaSAndroid Build Coastguard Worker                 continue;
128*c8dee2aaSAndroid Build Coastguard Worker             }
129*c8dee2aaSAndroid Build Coastguard Worker             if (fInput[fIndex] == '\t' || fInput[fIndex] == '\n' ||
130*c8dee2aaSAndroid Build Coastguard Worker                 (fFreshline && fInput[fIndex] == ' ')) {
131*c8dee2aaSAndroid Build Coastguard Worker                 fIndex++;
132*c8dee2aaSAndroid Build Coastguard Worker                 continue;
133*c8dee2aaSAndroid Build Coastguard Worker             }
134*c8dee2aaSAndroid Build Coastguard Worker 
135*c8dee2aaSAndroid Build Coastguard Worker             this->appendChar(fInput[fIndex]);
136*c8dee2aaSAndroid Build Coastguard Worker         }
137*c8dee2aaSAndroid Build Coastguard Worker 
138*c8dee2aaSAndroid Build Coastguard Worker         return fPretty;
139*c8dee2aaSAndroid Build Coastguard Worker     }
140*c8dee2aaSAndroid Build Coastguard Worker 
141*c8dee2aaSAndroid Build Coastguard Worker private:
appendChar(char c)142*c8dee2aaSAndroid Build Coastguard Worker     void appendChar(char c) {
143*c8dee2aaSAndroid Build Coastguard Worker         this->tabString();
144*c8dee2aaSAndroid Build Coastguard Worker         fPretty += fInput[fIndex++];
145*c8dee2aaSAndroid Build Coastguard Worker         fFreshline = false;
146*c8dee2aaSAndroid Build Coastguard Worker     }
147*c8dee2aaSAndroid Build Coastguard Worker 
148*c8dee2aaSAndroid Build Coastguard Worker     // hasToken automatically consumes the next token, if it is a match, and then tabs
149*c8dee2aaSAndroid Build Coastguard Worker     // if necessary, before inserting the token into the pretty string
hasToken(const char * token)150*c8dee2aaSAndroid Build Coastguard Worker     bool hasToken(const char* token) {
151*c8dee2aaSAndroid Build Coastguard Worker         size_t i = fIndex;
152*c8dee2aaSAndroid Build Coastguard Worker         for (size_t j = 0; token[j] && fLength > i; i++, j++) {
153*c8dee2aaSAndroid Build Coastguard Worker             if (token[j] != fInput[i]) {
154*c8dee2aaSAndroid Build Coastguard Worker                 return false;
155*c8dee2aaSAndroid Build Coastguard Worker             }
156*c8dee2aaSAndroid Build Coastguard Worker         }
157*c8dee2aaSAndroid Build Coastguard Worker         this->tabString();
158*c8dee2aaSAndroid Build Coastguard Worker         fIndex = i;
159*c8dee2aaSAndroid Build Coastguard Worker         fPretty.append(token);
160*c8dee2aaSAndroid Build Coastguard Worker         fFreshline = false;
161*c8dee2aaSAndroid Build Coastguard Worker         return true;
162*c8dee2aaSAndroid Build Coastguard Worker     }
163*c8dee2aaSAndroid Build Coastguard Worker 
parseUntilNewline()164*c8dee2aaSAndroid Build Coastguard Worker     void parseUntilNewline() {
165*c8dee2aaSAndroid Build Coastguard Worker         while (fLength > fIndex) {
166*c8dee2aaSAndroid Build Coastguard Worker             if (fInput[fIndex] == '\n') {
167*c8dee2aaSAndroid Build Coastguard Worker                 fIndex++;
168*c8dee2aaSAndroid Build Coastguard Worker                 this->newline();
169*c8dee2aaSAndroid Build Coastguard Worker                 fInParseUntilNewline = false;
170*c8dee2aaSAndroid Build Coastguard Worker                 break;
171*c8dee2aaSAndroid Build Coastguard Worker             }
172*c8dee2aaSAndroid Build Coastguard Worker             fPretty += fInput[fIndex++];
173*c8dee2aaSAndroid Build Coastguard Worker             fInParseUntilNewline = true;
174*c8dee2aaSAndroid Build Coastguard Worker         }
175*c8dee2aaSAndroid Build Coastguard Worker     }
176*c8dee2aaSAndroid Build Coastguard Worker 
177*c8dee2aaSAndroid Build Coastguard Worker     // this code assumes it is not actually searching for a newline.  If you need to search for a
178*c8dee2aaSAndroid Build Coastguard Worker     // newline, then use the function above.  If you do search for a newline with this function
179*c8dee2aaSAndroid Build Coastguard Worker     // it will consume the entire string and the output will certainly not be prettified
parseUntil(const char * token)180*c8dee2aaSAndroid Build Coastguard Worker     void parseUntil(const char* token) {
181*c8dee2aaSAndroid Build Coastguard Worker         while (fLength > fIndex) {
182*c8dee2aaSAndroid Build Coastguard Worker             // For embedded newlines,  this code will make sure to embed the newline in the
183*c8dee2aaSAndroid Build Coastguard Worker             // pretty string, increase the linecount, and tab out the next line to the appropriate
184*c8dee2aaSAndroid Build Coastguard Worker             // place
185*c8dee2aaSAndroid Build Coastguard Worker             if (fInput[fIndex] == '\n') {
186*c8dee2aaSAndroid Build Coastguard Worker                 this->newline();
187*c8dee2aaSAndroid Build Coastguard Worker                 this->tabString();
188*c8dee2aaSAndroid Build Coastguard Worker                 fIndex++;
189*c8dee2aaSAndroid Build Coastguard Worker             }
190*c8dee2aaSAndroid Build Coastguard Worker             if (this->hasToken(token)) {
191*c8dee2aaSAndroid Build Coastguard Worker                 fInParseUntil = false;
192*c8dee2aaSAndroid Build Coastguard Worker                 break;
193*c8dee2aaSAndroid Build Coastguard Worker             }
194*c8dee2aaSAndroid Build Coastguard Worker             fFreshline = false;
195*c8dee2aaSAndroid Build Coastguard Worker             fPretty += fInput[fIndex++];
196*c8dee2aaSAndroid Build Coastguard Worker             fInParseUntil = true;
197*c8dee2aaSAndroid Build Coastguard Worker             fInParseUntilToken = token;
198*c8dee2aaSAndroid Build Coastguard Worker         }
199*c8dee2aaSAndroid Build Coastguard Worker     }
200*c8dee2aaSAndroid Build Coastguard Worker 
201*c8dee2aaSAndroid Build Coastguard Worker     // We only tab if on a newline, otherwise consider the line tabbed
tabString()202*c8dee2aaSAndroid Build Coastguard Worker     void tabString() {
203*c8dee2aaSAndroid Build Coastguard Worker         if (fFreshline) {
204*c8dee2aaSAndroid Build Coastguard Worker             for (int t = 0; t < fTabs; t++) {
205*c8dee2aaSAndroid Build Coastguard Worker                 fPretty += '\t';
206*c8dee2aaSAndroid Build Coastguard Worker             }
207*c8dee2aaSAndroid Build Coastguard Worker         }
208*c8dee2aaSAndroid Build Coastguard Worker     }
209*c8dee2aaSAndroid Build Coastguard Worker 
210*c8dee2aaSAndroid Build Coastguard Worker     // newline is really a request to add a newline, if we are on a fresh line there is no reason
211*c8dee2aaSAndroid Build Coastguard Worker     // to add another newline
newline()212*c8dee2aaSAndroid Build Coastguard Worker     void newline() {
213*c8dee2aaSAndroid Build Coastguard Worker         if (!fFreshline) {
214*c8dee2aaSAndroid Build Coastguard Worker             fFreshline = true;
215*c8dee2aaSAndroid Build Coastguard Worker             fPretty += '\n';
216*c8dee2aaSAndroid Build Coastguard Worker         }
217*c8dee2aaSAndroid Build Coastguard Worker     }
218*c8dee2aaSAndroid Build Coastguard Worker 
219*c8dee2aaSAndroid Build Coastguard Worker     // undoNewlineAfter() attempts to undo the effects of newline(), if the last character before
220*c8dee2aaSAndroid Build Coastguard Worker     // the newline matches `c`.
undoNewlineAfter(char c)221*c8dee2aaSAndroid Build Coastguard Worker     void undoNewlineAfter(char c) {
222*c8dee2aaSAndroid Build Coastguard Worker         if (fFreshline) {
223*c8dee2aaSAndroid Build Coastguard Worker             if (fPretty.size() >= 2 && fPretty.rbegin()[0] == '\n' && fPretty.rbegin()[1] == c) {
224*c8dee2aaSAndroid Build Coastguard Worker                 fFreshline = false;
225*c8dee2aaSAndroid Build Coastguard Worker                 fPretty.pop_back();
226*c8dee2aaSAndroid Build Coastguard Worker             }
227*c8dee2aaSAndroid Build Coastguard Worker         }
228*c8dee2aaSAndroid Build Coastguard Worker     }
229*c8dee2aaSAndroid Build Coastguard Worker 
230*c8dee2aaSAndroid Build Coastguard Worker     bool fFreshline;
231*c8dee2aaSAndroid Build Coastguard Worker     int fTabs;
232*c8dee2aaSAndroid Build Coastguard Worker     size_t fIndex, fLength;
233*c8dee2aaSAndroid Build Coastguard Worker     const char* fInput;
234*c8dee2aaSAndroid Build Coastguard Worker     std::string fPretty;
235*c8dee2aaSAndroid Build Coastguard Worker 
236*c8dee2aaSAndroid Build Coastguard Worker     // Some helpers for parseUntil when we go over a string length
237*c8dee2aaSAndroid Build Coastguard Worker     bool fInParseUntilNewline;
238*c8dee2aaSAndroid Build Coastguard Worker     bool fInParseUntil;
239*c8dee2aaSAndroid Build Coastguard Worker     const char* fInParseUntilToken;
240*c8dee2aaSAndroid Build Coastguard Worker };
241*c8dee2aaSAndroid Build Coastguard Worker 
PrettyPrint(const std::string & string)242*c8dee2aaSAndroid Build Coastguard Worker std::string PrettyPrint(const std::string& string) {
243*c8dee2aaSAndroid Build Coastguard Worker     GLSLPrettyPrint pp;
244*c8dee2aaSAndroid Build Coastguard Worker     return pp.prettify(string);
245*c8dee2aaSAndroid Build Coastguard Worker }
246*c8dee2aaSAndroid Build Coastguard Worker 
VisitLineByLine(const std::string & text,const std::function<void (int lineNumber,const char * lineText)> & visitFn)247*c8dee2aaSAndroid Build Coastguard Worker void VisitLineByLine(const std::string& text,
248*c8dee2aaSAndroid Build Coastguard Worker                      const std::function<void(int lineNumber, const char* lineText)>& visitFn) {
249*c8dee2aaSAndroid Build Coastguard Worker     TArray<SkString> lines;
250*c8dee2aaSAndroid Build Coastguard Worker     SkStrSplit(text.c_str(), "\n", kStrict_SkStrSplitMode, &lines);
251*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 0; i < lines.size(); ++i) {
252*c8dee2aaSAndroid Build Coastguard Worker         visitFn(i + 1, lines[i].c_str());
253*c8dee2aaSAndroid Build Coastguard Worker     }
254*c8dee2aaSAndroid Build Coastguard Worker }
255*c8dee2aaSAndroid Build Coastguard Worker 
BuildShaderErrorMessage(const char * shader,const char * errors)256*c8dee2aaSAndroid Build Coastguard Worker std::string BuildShaderErrorMessage(const char* shader, const char* errors) {
257*c8dee2aaSAndroid Build Coastguard Worker     std::string abortText{"Shader compilation error\n"
258*c8dee2aaSAndroid Build Coastguard Worker                           "------------------------\n"};
259*c8dee2aaSAndroid Build Coastguard Worker     VisitLineByLine(shader, [&](int lineNumber, const char* lineText) {
260*c8dee2aaSAndroid Build Coastguard Worker         SkSL::String::appendf(&abortText, "%4i\t%s\n", lineNumber, lineText);
261*c8dee2aaSAndroid Build Coastguard Worker     });
262*c8dee2aaSAndroid Build Coastguard Worker     SkSL::String::appendf(&abortText, "Errors:\n%s", errors);
263*c8dee2aaSAndroid Build Coastguard Worker     return abortText;
264*c8dee2aaSAndroid Build Coastguard Worker }
265*c8dee2aaSAndroid Build Coastguard Worker 
PrintShaderBanner(SkSL::ProgramKind programKind)266*c8dee2aaSAndroid Build Coastguard Worker void PrintShaderBanner(SkSL::ProgramKind programKind) {
267*c8dee2aaSAndroid Build Coastguard Worker     const char* typeName = "Unknown";
268*c8dee2aaSAndroid Build Coastguard Worker     if (SkSL::ProgramConfig::IsVertex(programKind)) {
269*c8dee2aaSAndroid Build Coastguard Worker         typeName = "Vertex";
270*c8dee2aaSAndroid Build Coastguard Worker     } else if (SkSL::ProgramConfig::IsFragment(programKind)) {
271*c8dee2aaSAndroid Build Coastguard Worker         typeName = "Fragment";
272*c8dee2aaSAndroid Build Coastguard Worker     }
273*c8dee2aaSAndroid Build Coastguard Worker     SkDebugf("---- %s shader ----------------------------------------------------\n", typeName);
274*c8dee2aaSAndroid Build Coastguard Worker }
275*c8dee2aaSAndroid Build Coastguard Worker 
276*c8dee2aaSAndroid Build Coastguard Worker }  // namespace SkShaderUtils
277