blob: ea77eb1de60c8020f45073ed616a6c6f24d1b5ea [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"
Brian Osmanac9be9d2019-05-01 10:29:34 -040010#include "src/gpu/GrShaderUtils.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050011#include "src/sksl/SkSLString.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
Brian Osman5e7fbfd2019-05-03 13:13:35 -0400201GrContextOptions::ShaderErrorHandler* DefaultShaderErrorHandler() {
202 class GrDefaultShaderErrorHandler : public GrContextOptions::ShaderErrorHandler {
203 public:
204 void compileError(const char* shader, const char* errors) override {
205 SkDebugf("Shader compilation error\n"
206 "------------------------\n");
Chris Dalton77912982019-12-16 11:18:13 -0700207 PrintLineByLine(shader);
Brian Osman5e7fbfd2019-05-03 13:13:35 -0400208 SkDebugf("Errors:\n%s\n", errors);
209 SkDEBUGFAIL("Shader compilation failed!");
210 }
211 };
212
213 static GrDefaultShaderErrorHandler gHandler;
214 return &gHandler;
215}
216
Robert Phillips797831c2020-11-20 22:35:55 +0000217void PrintShaderBanner(SkSL::Program::Kind programKind) {
218 const char* typeName = "Unknown";
219 switch (programKind) {
220 case SkSL::Program::kVertex_Kind: typeName = "Vertex"; break;
221 case SkSL::Program::kGeometry_Kind: typeName = "Geometry"; break;
222 case SkSL::Program::kFragment_Kind: typeName = "Fragment"; break;
223 default: break;
224 }
225 SkDebugf("---- %s shader ----------------------------------------------------\n", typeName);
226}
227
John Stilesa6841be2020-08-06 14:11:56 -0400228} // namespace GrShaderUtils