1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include <QtCore/QByteArray> |
41 | #include <QtCore/QString> |
42 | #include <QtGui/QSurfaceFormat> |
43 | |
44 | // Duct Tape tokenizer for the purpose of parsing and rewriting |
45 | // shader source code |
46 | |
47 | QT_BEGIN_NAMESPACE |
48 | |
49 | namespace QSGShaderRewriter { |
50 | |
51 | struct Tokenizer { |
52 | |
53 | enum Token { |
54 | Token_Void, |
55 | Token_OpenBrace, |
56 | Token_CloseBrace, |
57 | Token_SemiColon, |
58 | Token_Identifier, |
59 | Token_Macro, |
60 | Token_Unspecified, |
61 | |
62 | Token_EOF |
63 | }; |
64 | |
65 | static const char *NAMES[]; |
66 | |
67 | void initialize(const char *input); |
68 | Token next(); |
69 | |
70 | const char *stream; |
71 | const char *pos; |
72 | const char *identifier; |
73 | }; |
74 | |
75 | const char *Tokenizer::NAMES[] = { |
76 | "Void" , |
77 | "OpenBrace" , |
78 | "CloseBrace" , |
79 | "SemiColon" , |
80 | "Identifier" , |
81 | "Macro" , |
82 | "Unspecified" , |
83 | "EOF" |
84 | }; |
85 | |
86 | void Tokenizer::initialize(const char *input) |
87 | { |
88 | stream = input; |
89 | pos = input; |
90 | identifier = input; |
91 | } |
92 | |
93 | Tokenizer::Token Tokenizer::next() |
94 | { |
95 | while (*pos != 0) { |
96 | char c = *pos++; |
97 | switch (c) { |
98 | case '/': |
99 | |
100 | if (*pos == '/') { |
101 | // '//' comment |
102 | ++pos; |
103 | while (*pos != 0 && *pos != '\n') ++pos; |
104 | if (*pos != 0) ++pos; // skip the newline |
105 | |
106 | } else if (*pos == '*') { |
107 | // /* */ comment |
108 | ++pos; |
109 | while (*pos != 0 && *pos != '*' && pos[1] != '/') ++pos; |
110 | if (*pos != 0) pos += 2; |
111 | } |
112 | break; |
113 | |
114 | case '#': { |
115 | while (*pos != 0) { |
116 | if (*pos == '\n') { |
117 | ++pos; |
118 | break; |
119 | } else if (*pos == '\\') { |
120 | ++pos; |
121 | while (*pos != 0 && (*pos == ' ' || *pos == '\t')) |
122 | ++pos; |
123 | if (*pos != 0 && (*pos == '\n' || (*pos == '\r' && pos[1] == '\n'))) |
124 | pos+=2; |
125 | } else { |
126 | ++pos; |
127 | } |
128 | } |
129 | break; |
130 | } |
131 | |
132 | case 'v': { |
133 | if (*pos == 'o' && pos[1] == 'i' && pos[2] == 'd') { |
134 | pos += 3; |
135 | return Token_Void; |
136 | } |
137 | Q_FALLTHROUGH(); |
138 | } |
139 | |
140 | case ';': return Token_SemiColon; |
141 | case 0: return Token_EOF; |
142 | case '{': return Token_OpenBrace; |
143 | case '}': return Token_CloseBrace; |
144 | |
145 | case ' ': |
146 | case '\n': |
147 | case '\r': break; |
148 | default: |
149 | // Identifier... |
150 | if ((c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' ) || c == '_') { |
151 | identifier = pos - 1; |
152 | while (*pos != 0 && ((*pos >= 'a' && *pos <= 'z') |
153 | || (*pos >= 'A' && *pos <= 'Z') |
154 | || *pos == '_' |
155 | || (*pos >= '0' && *pos <= '9'))) { |
156 | ++pos; |
157 | } |
158 | return Token_Identifier; |
159 | } else { |
160 | return Token_Unspecified; |
161 | } |
162 | } |
163 | } |
164 | |
165 | return Token_EOF; |
166 | } |
167 | |
168 | } |
169 | |
170 | using namespace QSGShaderRewriter; |
171 | |
172 | QByteArray qsgShaderRewriter_insertZAttributes(const char *input, QSurfaceFormat::OpenGLContextProfile profile) |
173 | { |
174 | Tokenizer tok; |
175 | tok.initialize(input); |
176 | |
177 | Tokenizer::Token lt = tok.next(); |
178 | Tokenizer::Token t = tok.next(); |
179 | |
180 | // First find "void main() { ... " |
181 | const char* voidPos = input; |
182 | while (t != Tokenizer::Token_EOF) { |
183 | if (lt == Tokenizer::Token_Void && t == Tokenizer::Token_Identifier) { |
184 | if (qstrncmp(str1: "main" , str2: tok.identifier, len: 4) == 0) |
185 | break; |
186 | } |
187 | voidPos = tok.pos - 4; |
188 | lt = t; |
189 | t = tok.next(); |
190 | } |
191 | |
192 | QByteArray result; |
193 | result.reserve(asize: 1024); |
194 | result += QByteArray::fromRawData(input, size: voidPos - input); |
195 | switch (profile) { |
196 | case QSurfaceFormat::NoProfile: |
197 | case QSurfaceFormat::CompatibilityProfile: |
198 | result += "attribute highp float _qt_order;\n" |
199 | "uniform highp float _qt_zRange;\n" ; |
200 | break; |
201 | |
202 | case QSurfaceFormat::CoreProfile: |
203 | result += "in float _qt_order;\n" |
204 | "uniform float _qt_zRange;\n" ; |
205 | break; |
206 | } |
207 | |
208 | // Find first brace '{' |
209 | while (t != Tokenizer::Token_EOF && t != Tokenizer::Token_OpenBrace) t = tok.next(); |
210 | int braceDepth = 1; |
211 | t = tok.next(); |
212 | |
213 | // Find matching brace and insert our code there... |
214 | while (t != Tokenizer::Token_EOF) { |
215 | switch (t) { |
216 | case Tokenizer::Token_CloseBrace: |
217 | braceDepth--; |
218 | if (braceDepth == 0) { |
219 | result += QByteArray::fromRawData(voidPos, size: tok.pos - 1 - voidPos) |
220 | + " gl_Position.z = (gl_Position.z * _qt_zRange + _qt_order) * gl_Position.w;\n" |
221 | + QByteArray(tok.pos - 1); |
222 | return result; |
223 | } |
224 | break; |
225 | case Tokenizer::Token_OpenBrace: |
226 | ++braceDepth; |
227 | break; |
228 | default: |
229 | break; |
230 | } |
231 | t = tok.next(); |
232 | } |
233 | return QByteArray(); |
234 | } |
235 | |
236 | #ifdef QSGSHADERREWRITER_STANDALONE |
237 | |
238 | const char *selftest = |
239 | "#define highp lowp stuff \n" |
240 | "#define multiline \\ \n" |
241 | " continue defining multiline \n" |
242 | " \n" |
243 | "attribute highp vec4 qt_Position; \n" |
244 | "attribute highp vec2 qt_TexCoord; \n" |
245 | " \n" |
246 | "uniform highp mat4 qt_Matrix; \n" |
247 | " \n" |
248 | "varying lowp vec2 vTexCoord; \n" |
249 | " \n" |
250 | "// commented out main(){} \n" |
251 | "/* commented out main() { } again */ \n" |
252 | "/* \n" |
253 | " multline comment with main() { } \n" |
254 | " */ \n" |
255 | " \n" |
256 | "void main() { \n" |
257 | " gl_Position = qt_Matrix * qt_Position; \n" |
258 | " vTexCoord = qt_TexCoord; \n" |
259 | " if (gl_Position < 0) { \n" |
260 | " vTexCoord.y = -vTexCoord.y; \n" |
261 | " } \n" |
262 | "} \n" |
263 | "" ; |
264 | |
265 | int main(int argc, char **argv) |
266 | { |
267 | QCoreApplication app(argc, argv); |
268 | |
269 | QString fileName; |
270 | QStringList args = app.arguments(); |
271 | |
272 | QByteArray content; |
273 | |
274 | for (int i=0; i<args.length(); ++i) { |
275 | const QString &a = args.at(i); |
276 | if (a == QLatin1String("--file" ) && i < args.length() - 1) { |
277 | qDebug() << "Reading file: " << args.at(i); |
278 | QFile file(args.at(++i)); |
279 | if (!file.open(QFile::ReadOnly)) { |
280 | qDebug() << "Error: failed to open file," << file.errorString(); |
281 | return 1; |
282 | } |
283 | content = file.readAll(); |
284 | } else if (a == QLatin1String("--selftest" )) { |
285 | qDebug() << "doing a selftest" ; |
286 | content = QByteArray(selftest); |
287 | } else if (a == QLatin1String("--help" ) || a == QLatin1String("-h" )) { |
288 | qDebug() << "usage:" << endl |
289 | << " --file [name] A vertex shader file to rewrite" << endl; |
290 | } |
291 | } |
292 | |
293 | QByteArray rewritten = qsgShaderRewriter_insertZAttributes(content); |
294 | |
295 | qDebug() << "Rewritten to:" ; |
296 | qDebug() << rewritten.constData(); |
297 | } |
298 | #endif |
299 | |
300 | QT_END_NAMESPACE |
301 | |