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