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