joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 1 | /* |
Brian Osman | ac9be9d | 2019-05-01 10:29:34 -0400 | [diff] [blame] | 2 | * Copyright 2019 Google LLC |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license that can be |
| 5 | * found in the LICENSE file. |
| 6 | */ |
Brian Osman | ac9be9d | 2019-05-01 10:29:34 -0400 | [diff] [blame] | 7 | |
| 8 | #include "include/core/SkString.h" |
Brian Osman | 5e7fbfd | 2019-05-03 13:13:35 -0400 | [diff] [blame] | 9 | #include "include/gpu/GrContextOptions.h" |
Ethan Nicholas | daed259 | 2021-03-04 14:30:25 -0500 | [diff] [blame] | 10 | #include "include/private/SkSLString.h" |
Brian Osman | ac9be9d | 2019-05-01 10:29:34 -0400 | [diff] [blame] | 11 | #include "src/gpu/GrShaderUtils.h" |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 12 | |
Brian Osman | ac9be9d | 2019-05-01 10:29:34 -0400 | [diff] [blame] | 13 | namespace GrShaderUtils { |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 14 | |
| 15 | class GLSLPrettyPrint { |
| 16 | public: |
| 17 | GLSLPrettyPrint() {} |
| 18 | |
Brian Osman | 6c431d5 | 2019-04-15 16:31:54 -0400 | [diff] [blame] | 19 | SkSL::String prettify(const SkSL::String& string) { |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 20 | fTabs = 0; |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 21 | fFreshline = true; |
| 22 | |
joshualitt | 43466a1 | 2015-02-13 17:18:27 -0800 | [diff] [blame] | 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 | |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 28 | int parensDepth = 0; |
joshualitt | 43466a1 | 2015-02-13 17:18:27 -0800 | [diff] [blame] | 29 | |
Brian Osman | 6c431d5 | 2019-04-15 16:31:54 -0400 | [diff] [blame] | 30 | // setup pretty state |
| 31 | fIndex = 0; |
| 32 | fLength = string.length(); |
| 33 | fInput = string.c_str(); |
joshualitt | 43466a1 | 2015-02-13 17:18:27 -0800 | [diff] [blame] | 34 | |
Brian Osman | 6c431d5 | 2019-04-15 16:31:54 -0400 | [diff] [blame] | 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]); |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 91 | } |
| 92 | } |
Brian Osman | 6c431d5 | 2019-04-15 16:31:54 -0400 | [diff] [blame] | 93 | |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 94 | return fPretty; |
| 95 | } |
Brian Salomon | e334c59 | 2017-05-15 11:00:58 -0400 | [diff] [blame] | 96 | |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 97 | private: |
| 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(); |
joshualitt | 43466a1 | 2015-02-13 17:18:27 -0800 | [diff] [blame] | 125 | fInParseUntilNewline = false; |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 126 | break; |
| 127 | } |
| 128 | fPretty.appendf("%c", fInput[fIndex++]); |
joshualitt | 43466a1 | 2015-02-13 17:18:27 -0800 | [diff] [blame] | 129 | fInParseUntilNewline = true; |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 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 |
| 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)) { |
joshualitt | 43466a1 | 2015-02-13 17:18:27 -0800 | [diff] [blame] | 147 | fInParseUntil = false; |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 148 | break; |
| 149 | } |
| 150 | fFreshline = false; |
| 151 | fPretty.appendf("%c", fInput[fIndex++]); |
joshualitt | 43466a1 | 2015-02-13 17:18:27 -0800 | [diff] [blame] | 152 | fInParseUntil = true; |
| 153 | fInParseUntilToken = token; |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 154 | } |
| 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"); |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 172 | } |
| 173 | } |
| 174 | |
Brian Osman | 6c431d5 | 2019-04-15 16:31:54 -0400 | [diff] [blame] | 175 | bool fFreshline; |
| 176 | int fTabs; |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 177 | size_t fIndex, fLength; |
joshualitt | 43466a1 | 2015-02-13 17:18:27 -0800 | [diff] [blame] | 178 | const char* fInput; |
Brian Osman | 93ba0a4 | 2017-08-14 14:48:10 -0400 | [diff] [blame] | 179 | SkSL::String fPretty; |
joshualitt | 43466a1 | 2015-02-13 17:18:27 -0800 | [diff] [blame] | 180 | |
| 181 | // Some helpers for parseUntil when we go over a string length |
| 182 | bool fInParseUntilNewline; |
| 183 | bool fInParseUntil; |
| 184 | const char* fInParseUntilToken; |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 185 | }; |
| 186 | |
Brian Osman | 6c431d5 | 2019-04-15 16:31:54 -0400 | [diff] [blame] | 187 | SkSL::String PrettyPrint(const SkSL::String& string) { |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 188 | GLSLPrettyPrint pp; |
Brian Osman | 6c431d5 | 2019-04-15 16:31:54 -0400 | [diff] [blame] | 189 | return pp.prettify(string); |
joshualitt | bab82ed | 2014-08-08 09:41:42 -0700 | [diff] [blame] | 190 | } |
| 191 | |
Chris Dalton | 7791298 | 2019-12-16 11:18:13 -0700 | [diff] [blame] | 192 | void VisitLineByLine(const SkSL::String& text, |
| 193 | const std::function<void(int lineNumber, const char* lineText)>& visitFn) { |
Brian Osman | ac9be9d | 2019-05-01 10:29:34 -0400 | [diff] [blame] | 194 | SkTArray<SkString> lines; |
Brian Osman | 1e2d4a1 | 2019-06-06 15:25:44 -0400 | [diff] [blame] | 195 | SkStrSplit(text.c_str(), "\n", kStrict_SkStrSplitMode, &lines); |
Brian Osman | ac9be9d | 2019-05-01 10:29:34 -0400 | [diff] [blame] | 196 | for (int i = 0; i < lines.count(); ++i) { |
Chris Dalton | 7791298 | 2019-12-16 11:18:13 -0700 | [diff] [blame] | 197 | visitFn(i + 1, lines[i].c_str()); |
Brian Osman | ac9be9d | 2019-05-01 10:29:34 -0400 | [diff] [blame] | 198 | } |
| 199 | } |
| 200 | |
John Stiles | 2ef627a | 2021-03-23 10:25:02 -0400 | [diff] [blame] | 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 | |
Brian Osman | 5e7fbfd | 2019-05-03 13:13:35 -0400 | [diff] [blame] | 211 | GrContextOptions::ShaderErrorHandler* DefaultShaderErrorHandler() { |
| 212 | class GrDefaultShaderErrorHandler : public GrContextOptions::ShaderErrorHandler { |
| 213 | public: |
| 214 | void compileError(const char* shader, const char* errors) override { |
John Stiles | 422f630 | 2021-04-21 13:56:01 -0400 | [diff] [blame] | 215 | SkSL::String message = BuildShaderErrorMessage(shader, errors); |
| 216 | VisitLineByLine(message, [](int, const char* lineText) { |
| 217 | SkDebugf("%s\n", lineText); |
| 218 | }); |
Brian Osman | 5e7fbfd | 2019-05-03 13:13:35 -0400 | [diff] [blame] | 219 | SkDEBUGFAIL("Shader compilation failed!"); |
| 220 | } |
| 221 | }; |
| 222 | |
| 223 | static GrDefaultShaderErrorHandler gHandler; |
| 224 | return &gHandler; |
| 225 | } |
| 226 | |
John Stiles | dbd4e6f | 2021-02-16 13:29:15 -0500 | [diff] [blame] | 227 | void PrintShaderBanner(SkSL::ProgramKind programKind) { |
Robert Phillips | 797831c | 2020-11-20 22:35:55 +0000 | [diff] [blame] | 228 | const char* typeName = "Unknown"; |
| 229 | switch (programKind) { |
John Stiles | dbd4e6f | 2021-02-16 13:29:15 -0500 | [diff] [blame] | 230 | case SkSL::ProgramKind::kVertex: typeName = "Vertex"; break; |
| 231 | case SkSL::ProgramKind::kGeometry: typeName = "Geometry"; break; |
| 232 | case SkSL::ProgramKind::kFragment: typeName = "Fragment"; break; |
Robert Phillips | 797831c | 2020-11-20 22:35:55 +0000 | [diff] [blame] | 233 | default: break; |
| 234 | } |
| 235 | SkDebugf("---- %s shader ----------------------------------------------------\n", typeName); |
| 236 | } |
| 237 | |
John Stiles | a6841be | 2020-08-06 14:11:56 -0400 | [diff] [blame] | 238 | } // namespace GrShaderUtils |