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
47QT_BEGIN_NAMESPACE
48
49namespace QSGShaderRewriter {
50
51struct 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
75const char *Tokenizer::NAMES[] = {
76 "Void",
77 "OpenBrace",
78 "CloseBrace",
79 "SemiColon",
80 "Identifier",
81 "Macro",
82 "Unspecified",
83 "EOF"
84};
85
86void Tokenizer::initialize(const char *input)
87{
88 stream = input;
89 pos = input;
90 identifier = input;
91}
92
93Tokenizer::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
170using namespace QSGShaderRewriter;
171
172QByteArray 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("main", tok.identifier, 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(1024);
194 result += QByteArray::fromRawData(input, 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, 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
238const 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
265int 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
300QT_END_NAMESPACE
301