• 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 "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::kGeometry: typeName = "Geometry"; break;
232         case SkSL::ProgramKind::kFragment: typeName = "Fragment"; break;
233         default: break;
234     }
235     SkDebugf("---- %s shader ----------------------------------------------------\n", typeName);
236 }
237 
238 }  // namespace GrShaderUtils
239