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