blob: e3c290355152f83d5349d0141efacc60671f1841 [file] [log] [blame]
joshualittbab82ed2014-08-08 09:41:42 -07001/*
Brian Osmanac9be9d2019-05-01 10:29:34 -04002 * Copyright 2019 Google LLC
joshualittbab82ed2014-08-08 09:41:42 -07003 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
Brian Osmanac9be9d2019-05-01 10:29:34 -04007
8#include "include/core/SkString.h"
Brian Osman5e7fbfd2019-05-03 13:13:35 -04009#include "include/gpu/GrContextOptions.h"
Ethan Nicholasdaed2592021-03-04 14:30:25 -050010#include "include/private/SkSLString.h"
Brian Osmanac9be9d2019-05-01 10:29:34 -040011#include "src/gpu/GrShaderUtils.h"
joshualittbab82ed2014-08-08 09:41:42 -070012
Brian Osmanac9be9d2019-05-01 10:29:34 -040013namespace GrShaderUtils {
joshualittbab82ed2014-08-08 09:41:42 -070014
15class GLSLPrettyPrint {
16public:
17 GLSLPrettyPrint() {}
18
Brian Osman6c431d52019-04-15 16:31:54 -040019 SkSL::String prettify(const SkSL::String& string) {
joshualittbab82ed2014-08-08 09:41:42 -070020 fTabs = 0;
joshualittbab82ed2014-08-08 09:41:42 -070021 fFreshline = true;
22
joshualitt43466a12015-02-13 17:18:27 -080023 // 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
joshualittbab82ed2014-08-08 09:41:42 -070028 int parensDepth = 0;
joshualitt43466a12015-02-13 17:18:27 -080029
Brian Osman6c431d52019-04-15 16:31:54 -040030 // setup pretty state
31 fIndex = 0;
32 fLength = string.length();
33 fInput = string.c_str();
joshualitt43466a12015-02-13 17:18:27 -080034
Brian Osman6c431d52019-04-15 16:31:54 -040035 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]);
joshualittbab82ed2014-08-08 09:41:42 -070091 }
92 }
Brian Osman6c431d52019-04-15 16:31:54 -040093
joshualittbab82ed2014-08-08 09:41:42 -070094 return fPretty;
95 }
Brian Salomone334c592017-05-15 11:00:58 -040096
joshualittbab82ed2014-08-08 09:41:42 -070097private:
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
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
120 void parseUntilNewline() {
121 while (fLength > fIndex) {
122 if ('\n' == fInput[fIndex]) {
123 fIndex++;
124 this->newline();
joshualitt43466a12015-02-13 17:18:27 -0800125 fInParseUntilNewline = false;
joshualittbab82ed2014-08-08 09:41:42 -0700126 break;
127 }
128 fPretty.appendf("%c", fInput[fIndex++]);
joshualitt43466a12015-02-13 17:18:27 -0800129 fInParseUntilNewline = true;
joshualittbab82ed2014-08-08 09:41:42 -0700130 }
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
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)) {
joshualitt43466a12015-02-13 17:18:27 -0800147 fInParseUntil = false;
joshualittbab82ed2014-08-08 09:41:42 -0700148 break;
149 }
150 fFreshline = false;
151 fPretty.appendf("%c", fInput[fIndex++]);
joshualitt43466a12015-02-13 17:18:27 -0800152 fInParseUntil = true;
153 fInParseUntilToken = token;
joshualittbab82ed2014-08-08 09:41:42 -0700154 }
155 }
156
157 // We only tab if on a newline, otherwise consider the line tabbed
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
168 void newline() {
169 if (!fFreshline) {
170 fFreshline = true;
171 fPretty.append("\n");
joshualittbab82ed2014-08-08 09:41:42 -0700172 }
173 }
174
Brian Osman6c431d52019-04-15 16:31:54 -0400175 bool fFreshline;
176 int fTabs;
joshualittbab82ed2014-08-08 09:41:42 -0700177 size_t fIndex, fLength;
joshualitt43466a12015-02-13 17:18:27 -0800178 const char* fInput;
Brian Osman93ba0a42017-08-14 14:48:10 -0400179 SkSL::String fPretty;
joshualitt43466a12015-02-13 17:18:27 -0800180
181 // Some helpers for parseUntil when we go over a string length
182 bool fInParseUntilNewline;
183 bool fInParseUntil;
184 const char* fInParseUntilToken;
joshualittbab82ed2014-08-08 09:41:42 -0700185};
186
Brian Osman6c431d52019-04-15 16:31:54 -0400187SkSL::String PrettyPrint(const SkSL::String& string) {
joshualittbab82ed2014-08-08 09:41:42 -0700188 GLSLPrettyPrint pp;
Brian Osman6c431d52019-04-15 16:31:54 -0400189 return pp.prettify(string);
joshualittbab82ed2014-08-08 09:41:42 -0700190}
191
Chris Dalton77912982019-12-16 11:18:13 -0700192void VisitLineByLine(const SkSL::String& text,
193 const std::function<void(int lineNumber, const char* lineText)>& visitFn) {
Brian Osmanac9be9d2019-05-01 10:29:34 -0400194 SkTArray<SkString> lines;
Brian Osman1e2d4a12019-06-06 15:25:44 -0400195 SkStrSplit(text.c_str(), "\n", kStrict_SkStrSplitMode, &lines);
Brian Osmanac9be9d2019-05-01 10:29:34 -0400196 for (int i = 0; i < lines.count(); ++i) {
Chris Dalton77912982019-12-16 11:18:13 -0700197 visitFn(i + 1, lines[i].c_str());
Brian Osmanac9be9d2019-05-01 10:29:34 -0400198 }
199}
200
John Stiles2ef627a2021-03-23 10:25:02 -0400201SkSL::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
Brian Osman5e7fbfd2019-05-03 13:13:35 -0400211GrContextOptions::ShaderErrorHandler* DefaultShaderErrorHandler() {
212 class GrDefaultShaderErrorHandler : public GrContextOptions::ShaderErrorHandler {
213 public:
214 void compileError(const char* shader, const char* errors) override {
John Stiles422f6302021-04-21 13:56:01 -0400215 SkSL::String message = BuildShaderErrorMessage(shader, errors);
216 VisitLineByLine(message, [](int, const char* lineText) {
217 SkDebugf("%s\n", lineText);
218 });
Brian Osman5e7fbfd2019-05-03 13:13:35 -0400219 SkDEBUGFAIL("Shader compilation failed!");
220 }
221 };
222
223 static GrDefaultShaderErrorHandler gHandler;
224 return &gHandler;
225}
226
John Stilesdbd4e6f2021-02-16 13:29:15 -0500227void PrintShaderBanner(SkSL::ProgramKind programKind) {
Robert Phillips797831c2020-11-20 22:35:55 +0000228 const char* typeName = "Unknown";
229 switch (programKind) {
John Stilesdbd4e6f2021-02-16 13:29:15 -0500230 case SkSL::ProgramKind::kVertex: typeName = "Vertex"; break;
231 case SkSL::ProgramKind::kGeometry: typeName = "Geometry"; break;
232 case SkSL::ProgramKind::kFragment: typeName = "Fragment"; break;
Robert Phillips797831c2020-11-20 22:35:55 +0000233 default: break;
234 }
235 SkDebugf("---- %s shader ----------------------------------------------------\n", typeName);
236}
237
John Stilesa6841be2020-08-06 14:11:56 -0400238} // namespace GrShaderUtils