1cb93a386Sopenharmony_ci/*
2cb93a386Sopenharmony_ci * Copyright 2019 Google LLC
3cb93a386Sopenharmony_ci *
4cb93a386Sopenharmony_ci * Use of this source code is governed by a BSD-style license that can be
5cb93a386Sopenharmony_ci * found in the LICENSE file.
6cb93a386Sopenharmony_ci */
7cb93a386Sopenharmony_ci
8cb93a386Sopenharmony_ci#include "include/core/SkString.h"
9cb93a386Sopenharmony_ci#include "include/gpu/GrContextOptions.h"
10cb93a386Sopenharmony_ci#include "include/private/SkSLString.h"
11cb93a386Sopenharmony_ci#include "src/gpu/GrShaderUtils.h"
12cb93a386Sopenharmony_ci
13cb93a386Sopenharmony_cinamespace GrShaderUtils {
14cb93a386Sopenharmony_ci
15cb93a386Sopenharmony_ciclass GLSLPrettyPrint {
16cb93a386Sopenharmony_cipublic:
17cb93a386Sopenharmony_ci    GLSLPrettyPrint() {}
18cb93a386Sopenharmony_ci
19cb93a386Sopenharmony_ci    SkSL::String prettify(const SkSL::String& string) {
20cb93a386Sopenharmony_ci        fTabs = 0;
21cb93a386Sopenharmony_ci        fFreshline = true;
22cb93a386Sopenharmony_ci
23cb93a386Sopenharmony_ci        // If a string breaks while in the middle 'parse until' we need to continue parsing on the
24cb93a386Sopenharmony_ci        // next string
25cb93a386Sopenharmony_ci        fInParseUntilNewline = false;
26cb93a386Sopenharmony_ci        fInParseUntil = false;
27cb93a386Sopenharmony_ci
28cb93a386Sopenharmony_ci        int parensDepth = 0;
29cb93a386Sopenharmony_ci
30cb93a386Sopenharmony_ci        // setup pretty state
31cb93a386Sopenharmony_ci        fIndex = 0;
32cb93a386Sopenharmony_ci        fLength = string.length();
33cb93a386Sopenharmony_ci        fInput = string.c_str();
34cb93a386Sopenharmony_ci
35cb93a386Sopenharmony_ci        while (fLength > fIndex) {
36cb93a386Sopenharmony_ci            /* the heart and soul of our prettification algorithm.  The rules should hopefully
37cb93a386Sopenharmony_ci             * be self explanatory.  For '#' and '//' tokens we parse until we reach a newline.
38cb93a386Sopenharmony_ci             *
39cb93a386Sopenharmony_ci             * For long style comments like this one, we search for the ending token.  We also
40cb93a386Sopenharmony_ci             * preserve whitespace in these comments WITH THE CAVEAT that we do the newlines
41cb93a386Sopenharmony_ci             * ourselves.  This allows us to remain in control of line numbers, and matching
42cb93a386Sopenharmony_ci             * tabs Existing tabs in the input string are copied over too, but this will look
43cb93a386Sopenharmony_ci             *  funny
44cb93a386Sopenharmony_ci             *
45cb93a386Sopenharmony_ci             * '{' and '}' are handled in basically the same way.  We add a newline if we aren't
46cb93a386Sopenharmony_ci             * on a fresh line, dirty the line, then add a second newline, ie braces are always
47cb93a386Sopenharmony_ci             * on their own lines indented properly.  The one funkiness here is structs print
48cb93a386Sopenharmony_ci             * with the semicolon on its own line.  Its not a problem for a glsl compiler though
49cb93a386Sopenharmony_ci             *
50cb93a386Sopenharmony_ci             * '(' and ')' are basically ignored, except as a sign we need to ignore ';' ala
51cb93a386Sopenharmony_ci             * in for loops.
52cb93a386Sopenharmony_ci             *
53cb93a386Sopenharmony_ci             * ';' means add a new line
54cb93a386Sopenharmony_ci             *
55cb93a386Sopenharmony_ci             * '\t' and '\n' are ignored in general parsing for backwards compatability with
56cb93a386Sopenharmony_ci             * existing shader code and we also have a special case for handling whitespace
57cb93a386Sopenharmony_ci             * at the beginning of fresh lines.
58cb93a386Sopenharmony_ci             *
59cb93a386Sopenharmony_ci             * Otherwise just add the new character to the pretty string, indenting if
60cb93a386Sopenharmony_ci             * necessary.
61cb93a386Sopenharmony_ci             */
62cb93a386Sopenharmony_ci            if (fInParseUntilNewline) {
63cb93a386Sopenharmony_ci                this->parseUntilNewline();
64cb93a386Sopenharmony_ci            } else if (fInParseUntil) {
65cb93a386Sopenharmony_ci                this->parseUntil(fInParseUntilToken);
66cb93a386Sopenharmony_ci            } else if (this->hasToken("#") || this->hasToken("//")) {
67cb93a386Sopenharmony_ci                this->parseUntilNewline();
68cb93a386Sopenharmony_ci            } else if (this->hasToken("/*")) {
69cb93a386Sopenharmony_ci                this->parseUntil("*/");
70cb93a386Sopenharmony_ci            } else if ('{' == fInput[fIndex]) {
71cb93a386Sopenharmony_ci                this->newline();
72cb93a386Sopenharmony_ci                this->appendChar('{');
73cb93a386Sopenharmony_ci                fTabs++;
74cb93a386Sopenharmony_ci                this->newline();
75cb93a386Sopenharmony_ci            } else if ('}' == fInput[fIndex]) {
76cb93a386Sopenharmony_ci                fTabs--;
77cb93a386Sopenharmony_ci                this->newline();
78cb93a386Sopenharmony_ci                this->appendChar('}');
79cb93a386Sopenharmony_ci                this->newline();
80cb93a386Sopenharmony_ci            } else if (this->hasToken(")")) {
81cb93a386Sopenharmony_ci                parensDepth--;
82cb93a386Sopenharmony_ci            } else if (this->hasToken("(")) {
83cb93a386Sopenharmony_ci                parensDepth++;
84cb93a386Sopenharmony_ci            } else if (!parensDepth && this->hasToken(";")) {
85cb93a386Sopenharmony_ci                this->newline();
86cb93a386Sopenharmony_ci            } else if ('\t' == fInput[fIndex] || '\n' == fInput[fIndex] ||
87cb93a386Sopenharmony_ci                        (fFreshline && ' ' == fInput[fIndex])) {
88cb93a386Sopenharmony_ci                fIndex++;
89cb93a386Sopenharmony_ci            } else {
90cb93a386Sopenharmony_ci                this->appendChar(fInput[fIndex]);
91cb93a386Sopenharmony_ci            }
92cb93a386Sopenharmony_ci        }
93cb93a386Sopenharmony_ci
94cb93a386Sopenharmony_ci        return fPretty;
95cb93a386Sopenharmony_ci    }
96cb93a386Sopenharmony_ci
97cb93a386Sopenharmony_ciprivate:
98cb93a386Sopenharmony_ci    void appendChar(char c) {
99cb93a386Sopenharmony_ci        this->tabString();
100cb93a386Sopenharmony_ci        fPretty.appendf("%c", fInput[fIndex++]);
101cb93a386Sopenharmony_ci        fFreshline = false;
102cb93a386Sopenharmony_ci    }
103cb93a386Sopenharmony_ci
104cb93a386Sopenharmony_ci    // hasToken automatically consumes the next token, if it is a match, and then tabs
105cb93a386Sopenharmony_ci    // if necessary, before inserting the token into the pretty string
106cb93a386Sopenharmony_ci    bool hasToken(const char* token) {
107cb93a386Sopenharmony_ci        size_t i = fIndex;
108cb93a386Sopenharmony_ci        for (size_t j = 0; token[j] && fLength > i; i++, j++) {
109cb93a386Sopenharmony_ci            if (token[j] != fInput[i]) {
110cb93a386Sopenharmony_ci                return false;
111cb93a386Sopenharmony_ci            }
112cb93a386Sopenharmony_ci        }
113cb93a386Sopenharmony_ci        this->tabString();
114cb93a386Sopenharmony_ci        fIndex = i;
115cb93a386Sopenharmony_ci        fPretty.append(token);
116cb93a386Sopenharmony_ci        fFreshline = false;
117cb93a386Sopenharmony_ci        return true;
118cb93a386Sopenharmony_ci    }
119cb93a386Sopenharmony_ci
120cb93a386Sopenharmony_ci    void parseUntilNewline() {
121cb93a386Sopenharmony_ci        while (fLength > fIndex) {
122cb93a386Sopenharmony_ci            if ('\n' == fInput[fIndex]) {
123cb93a386Sopenharmony_ci                fIndex++;
124cb93a386Sopenharmony_ci                this->newline();
125cb93a386Sopenharmony_ci                fInParseUntilNewline = false;
126cb93a386Sopenharmony_ci                break;
127cb93a386Sopenharmony_ci            }
128cb93a386Sopenharmony_ci            fPretty.appendf("%c", fInput[fIndex++]);
129cb93a386Sopenharmony_ci            fInParseUntilNewline = true;
130cb93a386Sopenharmony_ci        }
131cb93a386Sopenharmony_ci    }
132cb93a386Sopenharmony_ci
133cb93a386Sopenharmony_ci    // this code assumes it is not actually searching for a newline.  If you need to search for a
134cb93a386Sopenharmony_ci    // newline, then use the function above.  If you do search for a newline with this function
135cb93a386Sopenharmony_ci    // it will consume the entire string and the output will certainly not be prettified
136cb93a386Sopenharmony_ci    void parseUntil(const char* token) {
137cb93a386Sopenharmony_ci        while (fLength > fIndex) {
138cb93a386Sopenharmony_ci            // For embedded newlines,  this code will make sure to embed the newline in the
139cb93a386Sopenharmony_ci            // pretty string, increase the linecount, and tab out the next line to the appropriate
140cb93a386Sopenharmony_ci            // place
141cb93a386Sopenharmony_ci            if ('\n' == fInput[fIndex]) {
142cb93a386Sopenharmony_ci                this->newline();
143cb93a386Sopenharmony_ci                this->tabString();
144cb93a386Sopenharmony_ci                fIndex++;
145cb93a386Sopenharmony_ci            }
146cb93a386Sopenharmony_ci            if (this->hasToken(token)) {
147cb93a386Sopenharmony_ci                fInParseUntil = false;
148cb93a386Sopenharmony_ci                break;
149cb93a386Sopenharmony_ci            }
150cb93a386Sopenharmony_ci            fFreshline = false;
151cb93a386Sopenharmony_ci            fPretty.appendf("%c", fInput[fIndex++]);
152cb93a386Sopenharmony_ci            fInParseUntil = true;
153cb93a386Sopenharmony_ci            fInParseUntilToken = token;
154cb93a386Sopenharmony_ci        }
155cb93a386Sopenharmony_ci    }
156cb93a386Sopenharmony_ci
157cb93a386Sopenharmony_ci    // We only tab if on a newline, otherwise consider the line tabbed
158cb93a386Sopenharmony_ci    void tabString() {
159cb93a386Sopenharmony_ci        if (fFreshline) {
160cb93a386Sopenharmony_ci            for (int t = 0; t < fTabs; t++) {
161cb93a386Sopenharmony_ci                fPretty.append("\t");
162cb93a386Sopenharmony_ci            }
163cb93a386Sopenharmony_ci        }
164cb93a386Sopenharmony_ci    }
165cb93a386Sopenharmony_ci
166cb93a386Sopenharmony_ci    // newline is really a request to add a newline, if we are on a fresh line there is no reason
167cb93a386Sopenharmony_ci    // to add another newline
168cb93a386Sopenharmony_ci    void newline() {
169cb93a386Sopenharmony_ci        if (!fFreshline) {
170cb93a386Sopenharmony_ci            fFreshline = true;
171cb93a386Sopenharmony_ci            fPretty.append("\n");
172cb93a386Sopenharmony_ci        }
173cb93a386Sopenharmony_ci    }
174cb93a386Sopenharmony_ci
175cb93a386Sopenharmony_ci    bool fFreshline;
176cb93a386Sopenharmony_ci    int fTabs;
177cb93a386Sopenharmony_ci    size_t fIndex, fLength;
178cb93a386Sopenharmony_ci    const char* fInput;
179cb93a386Sopenharmony_ci    SkSL::String fPretty;
180cb93a386Sopenharmony_ci
181cb93a386Sopenharmony_ci    // Some helpers for parseUntil when we go over a string length
182cb93a386Sopenharmony_ci    bool fInParseUntilNewline;
183cb93a386Sopenharmony_ci    bool fInParseUntil;
184cb93a386Sopenharmony_ci    const char* fInParseUntilToken;
185cb93a386Sopenharmony_ci};
186cb93a386Sopenharmony_ci
187cb93a386Sopenharmony_ciSkSL::String PrettyPrint(const SkSL::String& string) {
188cb93a386Sopenharmony_ci    GLSLPrettyPrint pp;
189cb93a386Sopenharmony_ci    return pp.prettify(string);
190cb93a386Sopenharmony_ci}
191cb93a386Sopenharmony_ci
192cb93a386Sopenharmony_civoid VisitLineByLine(const SkSL::String& text,
193cb93a386Sopenharmony_ci                     const std::function<void(int lineNumber, const char* lineText)>& visitFn) {
194cb93a386Sopenharmony_ci    SkTArray<SkString> lines;
195cb93a386Sopenharmony_ci    SkStrSplit(text.c_str(), "\n", kStrict_SkStrSplitMode, &lines);
196cb93a386Sopenharmony_ci    for (int i = 0; i < lines.count(); ++i) {
197cb93a386Sopenharmony_ci        visitFn(i + 1, lines[i].c_str());
198cb93a386Sopenharmony_ci    }
199cb93a386Sopenharmony_ci}
200cb93a386Sopenharmony_ci
201cb93a386Sopenharmony_ciSkSL::String BuildShaderErrorMessage(const char* shader, const char* errors) {
202cb93a386Sopenharmony_ci    SkSL::String abortText{"Shader compilation error\n"
203cb93a386Sopenharmony_ci                           "------------------------\n"};
204cb93a386Sopenharmony_ci    VisitLineByLine(shader, [&](int lineNumber, const char* lineText) {
205cb93a386Sopenharmony_ci        abortText.appendf("%4i\t%s\n", lineNumber, lineText);
206cb93a386Sopenharmony_ci    });
207cb93a386Sopenharmony_ci    abortText.appendf("Errors:\n%s", errors);
208cb93a386Sopenharmony_ci    return abortText;
209cb93a386Sopenharmony_ci}
210cb93a386Sopenharmony_ci
211cb93a386Sopenharmony_ciGrContextOptions::ShaderErrorHandler* DefaultShaderErrorHandler() {
212cb93a386Sopenharmony_ci    class GrDefaultShaderErrorHandler : public GrContextOptions::ShaderErrorHandler {
213cb93a386Sopenharmony_ci    public:
214cb93a386Sopenharmony_ci        void compileError(const char* shader, const char* errors) override {
215cb93a386Sopenharmony_ci            SkSL::String message = BuildShaderErrorMessage(shader, errors);
216cb93a386Sopenharmony_ci            VisitLineByLine(message, [](int, const char* lineText) {
217cb93a386Sopenharmony_ci                SkDebugf("%s\n", lineText);
218cb93a386Sopenharmony_ci            });
219cb93a386Sopenharmony_ci            SkDEBUGFAIL("Shader compilation failed!");
220cb93a386Sopenharmony_ci        }
221cb93a386Sopenharmony_ci    };
222cb93a386Sopenharmony_ci
223cb93a386Sopenharmony_ci    static GrDefaultShaderErrorHandler gHandler;
224cb93a386Sopenharmony_ci    return &gHandler;
225cb93a386Sopenharmony_ci}
226cb93a386Sopenharmony_ci
227cb93a386Sopenharmony_civoid PrintShaderBanner(SkSL::ProgramKind programKind) {
228cb93a386Sopenharmony_ci    const char* typeName = "Unknown";
229cb93a386Sopenharmony_ci    switch (programKind) {
230cb93a386Sopenharmony_ci        case SkSL::ProgramKind::kVertex:   typeName = "Vertex";   break;
231cb93a386Sopenharmony_ci        case SkSL::ProgramKind::kFragment: typeName = "Fragment"; break;
232cb93a386Sopenharmony_ci        default: break;
233cb93a386Sopenharmony_ci    }
234cb93a386Sopenharmony_ci    SkDebugf("---- %s shader ----------------------------------------------------\n", typeName);
235cb93a386Sopenharmony_ci}
236cb93a386Sopenharmony_ci
237cb93a386Sopenharmony_ci}  // namespace GrShaderUtils
238