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