1/****************************************************************************
2**
3** Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
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 "qsgshadersourcebuilder_p.h"
41
42#include <QtGui/qopenglcontext.h>
43#include <QtGui/qopenglshaderprogram.h>
44
45#include <QtCore/qdebug.h>
46#include <QtCore/qfile.h>
47
48QT_BEGIN_NAMESPACE
49
50namespace QSGShaderParser {
51
52struct Tokenizer {
53
54 enum Token {
55 Token_Invalid,
56 Token_Void,
57 Token_OpenBrace,
58 Token_CloseBrace,
59 Token_SemiColon,
60 Token_Identifier,
61 Token_Macro,
62 Token_Version,
63 Token_Extension,
64 Token_SingleLineComment,
65 Token_MultiLineCommentStart,
66 Token_MultiLineCommentEnd,
67 Token_NewLine,
68 Token_Unspecified,
69 Token_EOF
70 };
71
72 static const char *NAMES[];
73
74 void initialize(const char *input);
75 Token next();
76
77 const char *stream;
78 const char *pos;
79 const char *identifier;
80};
81
82const char *Tokenizer::NAMES[] = {
83 "Invalid",
84 "Void",
85 "OpenBrace",
86 "CloseBrace",
87 "SemiColon",
88 "Identifier",
89 "Macro",
90 "Version",
91 "Extension",
92 "SingleLineComment",
93 "MultiLineCommentStart",
94 "MultiLineCommentEnd",
95 "NewLine",
96 "Unspecified",
97 "EOF"
98};
99
100void Tokenizer::initialize(const char *input)
101{
102 stream = input;
103 pos = input;
104 identifier = input;
105}
106
107Tokenizer::Token Tokenizer::next()
108{
109 while (*pos != 0) {
110 char c = *pos++;
111 switch (c) {
112 case '/':
113 if (*pos == '/') {
114 // '//' comment
115 return Token_SingleLineComment;
116 } else if (*pos == '*') {
117 // /* */ comment
118 return Token_MultiLineCommentStart;
119 }
120 break;
121
122 case '*':
123 if (*pos == '/')
124 return Token_MultiLineCommentEnd;
125 Q_FALLTHROUGH();
126
127 case '\n':
128 return Token_NewLine;
129
130 case '\r':
131 if (*pos == '\n')
132 return Token_NewLine;
133 Q_FALLTHROUGH();
134
135 case '#': {
136 if (*pos == 'v' && pos[1] == 'e' && pos[2] == 'r' && pos[3] == 's'
137 && pos[4] == 'i' && pos[5] == 'o' && pos[6] == 'n') {
138 return Token_Version;
139 } else if (*pos == 'e' && pos[1] == 'x' && pos[2] == 't' && pos[3] == 'e'
140 && pos[4] == 'n' && pos[5] == 's' && pos[6] == 'i'&& pos[7] == 'o'
141 && pos[8] == 'n') {
142 return Token_Extension;
143 } else {
144 while (*pos != 0) {
145 if (*pos == '\n') {
146 ++pos;
147 break;
148 } else if (*pos == '\\') {
149 ++pos;
150 while (*pos != 0 && (*pos == ' ' || *pos == '\t'))
151 ++pos;
152 if (*pos != 0 && (*pos == '\n' || (*pos == '\r' && pos[1] == '\n')))
153 pos+=2;
154 } else {
155 ++pos;
156 }
157 }
158 }
159 break;
160 }
161
162 case ';':
163 return Token_SemiColon;
164
165 case 0:
166 return Token_EOF;
167
168 case '{':
169 return Token_OpenBrace;
170
171 case '}':
172 return Token_CloseBrace;
173
174 case ' ':
175 break;
176
177 case 'v': {
178 if (*pos == 'o' && pos[1] == 'i' && pos[2] == 'd') {
179 pos += 3;
180 return Token_Void;
181 }
182 Q_FALLTHROUGH();
183 }
184 default:
185 // Identifier...
186 if ((c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' ) || c == '_') {
187 identifier = pos - 1;
188 while (*pos != 0 && ((*pos >= 'a' && *pos <= 'z')
189 || (*pos >= 'A' && *pos <= 'Z')
190 || *pos == '_'
191 || (*pos >= '0' && *pos <= '9'))) {
192 ++pos;
193 }
194 return Token_Identifier;
195 } else {
196 return Token_Unspecified;
197 }
198 }
199 }
200
201 return Token_Invalid;
202}
203
204} // namespace QSGShaderParser
205
206using namespace QSGShaderParser;
207
208QSGShaderSourceBuilder::QSGShaderSourceBuilder()
209{
210}
211
212void QSGShaderSourceBuilder::initializeProgramFromFiles(QOpenGLShaderProgram *program,
213 const QString &vertexShader,
214 const QString &fragmentShader)
215{
216 Q_ASSERT(program);
217 program->removeAllShaders();
218
219 QSGShaderSourceBuilder builder;
220
221 builder.appendSourceFile(fileName: vertexShader);
222 program->addCacheableShaderFromSourceCode(type: QOpenGLShader::Vertex, source: builder.source());
223 builder.clear();
224
225 builder.appendSourceFile(fileName: fragmentShader);
226 program->addCacheableShaderFromSourceCode(type: QOpenGLShader::Fragment, source: builder.source());
227}
228
229QByteArray QSGShaderSourceBuilder::source() const
230{
231 return m_source;
232}
233
234void QSGShaderSourceBuilder::clear()
235{
236 m_source.clear();
237}
238
239void QSGShaderSourceBuilder::appendSource(const QByteArray &source)
240{
241 m_source += source;
242}
243
244void QSGShaderSourceBuilder::appendSourceFile(const QString &fileName)
245{
246 const QString resolvedFileName = resolveShaderPath(path: fileName);
247 QFile f(resolvedFileName);
248 if (!f.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
249 qWarning() << "Failed to find shader" << resolvedFileName;
250 return;
251 }
252 m_source += f.readAll();
253}
254
255void QSGShaderSourceBuilder::addDefinition(const QByteArray &definition)
256{
257 if (definition.isEmpty())
258 return;
259
260 Tokenizer tok;
261 const char *input = m_source.constData();
262 tok.initialize(input);
263
264 // First find #version, #extension's and "void main() { ... "
265 const char *versionPos = nullptr;
266 const char *extensionPos = nullptr;
267 bool inSingleLineComment = false;
268 bool inMultiLineComment = false;
269 bool foundVersionStart = false;
270 bool foundExtensionStart = false;
271
272 Tokenizer::Token lt = Tokenizer::Token_Unspecified;
273 Tokenizer::Token t = tok.next();
274 while (t != Tokenizer::Token_EOF) {
275 // Handle comment blocks
276 if (t == Tokenizer::Token_MultiLineCommentStart )
277 inMultiLineComment = true;
278 if (t == Tokenizer::Token_MultiLineCommentEnd)
279 inMultiLineComment = false;
280 if (t == Tokenizer::Token_SingleLineComment)
281 inSingleLineComment = true;
282 if (t == Tokenizer::Token_NewLine && inSingleLineComment && !inMultiLineComment)
283 inSingleLineComment = false;
284
285 // Have we found #version, #extension or void main()?
286 if (t == Tokenizer::Token_Version && !inSingleLineComment && !inMultiLineComment)
287 foundVersionStart = true;
288
289 if (t == Tokenizer::Token_Extension && !inSingleLineComment && !inMultiLineComment)
290 foundExtensionStart = true;
291
292 if (foundVersionStart && t == Tokenizer::Token_NewLine) {
293 versionPos = tok.pos;
294 foundVersionStart = false;
295 } else if (foundExtensionStart && t == Tokenizer::Token_NewLine) {
296 extensionPos = tok.pos;
297 foundExtensionStart = false;
298 } else if (lt == Tokenizer::Token_Void && t == Tokenizer::Token_Identifier) {
299 if (qstrncmp(str1: "main", str2: tok.identifier, len: 4) == 0)
300 break;
301 }
302
303 // Scan to next token
304 lt = t;
305 t = tok.next();
306 }
307
308 // Determine where to insert the definition.
309 // If we found #extension directives, insert after last one,
310 // else, if we found #version insert after #version
311 // otherwise, insert at beginning.
312 const char *insertionPos = extensionPos ? extensionPos : (versionPos ? versionPos : input);
313
314 // Construct a new shader string, inserting the definition
315 QByteArray newSource = QByteArray::fromRawData(input, size: insertionPos - input)
316 + "#define " + definition + '\n'
317 + QByteArray::fromRawData(insertionPos, size: m_source.size() - (insertionPos - input));
318 m_source = std::move(newSource);
319}
320
321void QSGShaderSourceBuilder::removeVersion()
322{
323 Tokenizer tok;
324 const char *input = m_source.constData();
325 tok.initialize(input);
326
327 // First find #version beginning and end (if present)
328 const char *versionStartPos = nullptr;
329 const char *versionEndPos = nullptr;
330 bool inSingleLineComment = false;
331 bool inMultiLineComment = false;
332 bool foundVersionStart = false;
333
334 Tokenizer::Token lt = Tokenizer::Token_Unspecified;
335 Tokenizer::Token t = tok.next();
336 while (t != Tokenizer::Token_EOF) {
337 // Handle comment blocks
338 if (t == Tokenizer::Token_MultiLineCommentStart )
339 inMultiLineComment = true;
340 if (t == Tokenizer::Token_MultiLineCommentEnd)
341 inMultiLineComment = false;
342 if (t == Tokenizer::Token_SingleLineComment)
343 inSingleLineComment = true;
344 if (t == Tokenizer::Token_NewLine && inSingleLineComment && !inMultiLineComment)
345 inSingleLineComment = false;
346
347 // Have we found #version, #extension or void main()?
348 if (t == Tokenizer::Token_Version && !inSingleLineComment && !inMultiLineComment) {
349 versionStartPos = tok.pos - 1;
350 foundVersionStart = true;
351 } else if (foundVersionStart && t == Tokenizer::Token_NewLine) {
352 versionEndPos = tok.pos;
353 break;
354 } else if (lt == Tokenizer::Token_Void && t == Tokenizer::Token_Identifier) {
355 if (qstrncmp(str1: "main", str2: tok.identifier, len: 4) == 0)
356 break;
357 }
358
359 // Scan to next token
360 lt = t;
361 t = tok.next();
362 }
363
364 if (versionStartPos == nullptr)
365 return;
366
367 // Construct a new shader string, inserting the definition
368 QByteArray newSource;
369 newSource.reserve(asize: m_source.size() - (versionEndPos - versionStartPos));
370 newSource += QByteArray::fromRawData(input, size: versionStartPos - input);
371 newSource += QByteArray::fromRawData(versionEndPos, size: m_source.size() - (versionEndPos - versionStartPos));
372
373 m_source = newSource;
374}
375
376QString QSGShaderSourceBuilder::resolveShaderPath(const QString &path) const
377{
378 if (contextProfile() != QSurfaceFormat::CoreProfile) {
379 return path;
380 } else {
381 int idx = path.lastIndexOf(c: QLatin1Char('.'));
382 QString resolvedPath;
383 if (idx != -1)
384 resolvedPath = path.leftRef(n: idx)
385 + QLatin1String("_core")
386 + path.rightRef(n: path.length() - idx);
387 return resolvedPath;
388 }
389}
390
391QSurfaceFormat::OpenGLContextProfile QSGShaderSourceBuilder::contextProfile() const
392{
393 QOpenGLContext *context = QOpenGLContext::currentContext();
394 QSurfaceFormat::OpenGLContextProfile profile = QSurfaceFormat::NoProfile;
395 if (context)
396 profile = context->format().profile();
397 return profile;
398}
399
400QT_END_NAMESPACE
401

source code of qtdeclarative/src/quick/scenegraph/util/qsgshadersourcebuilder.cpp