aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/util/qsgshadersourcebuilder.cpp
diff options
context:
space:
mode:
authorSean Harmer <sean.harmer@kdab.com>2013-11-06 13:07:58 +0000
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-11-13 23:42:13 +0100
commit3afffa47feabc80e1bc20ffd2143a722a1c360a2 (patch)
tree1e8d5e367aefae0d25c67a1c5e82eeeb357203a6 /src/quick/scenegraph/util/qsgshadersourcebuilder.cpp
parent376844cc6f312d2ab8cf3191ec3e5535bfa850e0 (diff)
Adapt Qt Quick 2 renderer to work with OpenGL Core Profile
The basic approach is to have the batched renderer create and bind a vertex array object if it detects we are using an OpenGL Core profile context. The VAO is bound for the duration of the QQ2 renderer's work cycle and unbound at the end so as to not interfere with any other VAO's a user may wish to use. All shaders have been copied and ported to be compliant with the GLSL 150 core specification which is the minimum for a Core profile context (OpenGL 3.2 Core). We are not using any newer features as yet so this will work anywhere we can get a Core profile context. The QSGShaderSourceBuilder class has been extended to resolve any requests for shaders to the same basefilename with "_core" appended prior to any file extension. This could be extended in the future to allow version, or GPU or platform specific shaders. The QSGShaderSourceBuilder has also been extended to allow it to insert #define definitions in the prologue of a shader. Any such definition is inserted: * After the last #extension directive (if any are found) * Otherwise after the #version directive (if found) * Otherwise at the start of the shader source This is required by the custom particle shaders which make extensive use of such #defines. In addition the mechanism used by the distance field glyph cache to extend the cache with new glyphs has been modified to work (and work more efficiently) when using a Core profile context. Rather than using a shader program and a buffer filling quad to blit the old texture into the new cache texture, we instead use the technique of framebuffer blitting. The existing fallback implementation using glTexSubImage2D() is still available if needed. The DECLARATIVE_EXAMPLE_MAIN macro has been extended to allow easy testing of any of the QtDeclarative examples with a core profile context. Just run the example with QT_QUICK_CORE_PROFILE=1 ./text for e.g. The only ones that may not work out of the box are those that provide GLSL shader source e.g. the customparticles or shader effect examples. These work fine if the shader source is adapted to GLSL 150 core. In the future it may be a good idea to expose some context property to QML that the user can use to determine what shader source variation to provide to Qt Quick. Along these lines it would also be very nice to allow the provision of shader source to ShaderEffect or CustomParticle from a separate source file just as we now do within Qt Quick. Task-number: QTBUG-32050 Change-Id: Ia6e9f06dbb8508af9ae03c6b60fb418b4cc9e41f Reviewed-by: Lars Knoll <lars.knoll@digia.com>
Diffstat (limited to 'src/quick/scenegraph/util/qsgshadersourcebuilder.cpp')
-rw-r--r--src/quick/scenegraph/util/qsgshadersourcebuilder.cpp307
1 files changed, 302 insertions, 5 deletions
diff --git a/src/quick/scenegraph/util/qsgshadersourcebuilder.cpp b/src/quick/scenegraph/util/qsgshadersourcebuilder.cpp
index c7dd1a9799..1a1963dbca 100644
--- a/src/quick/scenegraph/util/qsgshadersourcebuilder.cpp
+++ b/src/quick/scenegraph/util/qsgshadersourcebuilder.cpp
@@ -41,6 +41,7 @@
#include "qsgshadersourcebuilder_p.h"
+#include <QtGui/qopenglcontext.h>
#include <QtGui/qopenglshaderprogram.h>
#include <QtCore/qdebug.h>
@@ -48,6 +49,162 @@
QT_BEGIN_NAMESPACE
+namespace QSGShaderParser {
+
+struct Tokenizer {
+
+ enum Token {
+ Token_Invalid,
+ Token_Void,
+ Token_OpenBrace,
+ Token_CloseBrace,
+ Token_SemiColon,
+ Token_Identifier,
+ Token_Macro,
+ Token_Version,
+ Token_Extension,
+ Token_SingleLineComment,
+ Token_MultiLineCommentStart,
+ Token_MultiLineCommentEnd,
+ Token_NewLine,
+ Token_Unspecified,
+ Token_EOF
+ };
+
+ static const char *NAMES[];
+
+ void initialize(const char *input);
+ Token next();
+
+ const char *stream;
+ const char *pos;
+ const char *identifier;
+};
+
+const char *Tokenizer::NAMES[] = {
+ "Invalid",
+ "Void",
+ "OpenBrace",
+ "CloseBrace",
+ "SemiColon",
+ "Identifier",
+ "Macro",
+ "Version",
+ "Extension",
+ "SingleLineComment",
+ "MultiLineCommentStart",
+ "MultiLineCommentEnd",
+ "NewLine",
+ "Unspecified",
+ "EOF"
+};
+
+void Tokenizer::initialize(const char *input)
+{
+ stream = input;
+ pos = input;
+ identifier = input;
+}
+
+Tokenizer::Token Tokenizer::next()
+{
+ while (*pos != 0) {
+ char c = *pos++;
+ switch (c) {
+ case '/':
+ if (*pos == '/') {
+ // '//' comment
+ return Token_SingleLineComment;
+ } else if (*pos == '*') {
+ // /* */ comment
+ return Token_MultiLineCommentStart;
+ }
+ break;
+
+ case '*':
+ if (*pos == '/')
+ return Token_MultiLineCommentEnd;
+
+ case '\n':
+ return Token_NewLine;
+
+ case '\r':
+ if (*pos == '\n')
+ return Token_NewLine;
+
+ case '#': {
+ if (*pos == 'v' && pos[1] == 'e' && pos[2] == 'r' && pos[3] == 's'
+ && pos[4] == 'i' && pos[5] == 'o' && pos[6] == 'n') {
+ return Token_Version;
+ } else if (*pos == 'e' && pos[1] == 'x' && pos[2] == 't' && pos[3] == 'e'
+ && pos[4] == 'n' && pos[5] == 's' && pos[6] == 'i'&& pos[7] == 'o'
+ && pos[8] == 'n') {
+ return Token_Extension;
+ } else {
+ while (*pos != 0) {
+ if (*pos == '\n') {
+ ++pos;
+ break;
+ } else if (*pos == '\\') {
+ ++pos;
+ while (*pos != 0 && (*pos == ' ' || *pos == '\t'))
+ ++pos;
+ if (*pos != 0 && (*pos == '\n' || (*pos == '\r' && pos[1] == '\n')))
+ pos+=2;
+ } else {
+ ++pos;
+ }
+ }
+ }
+ break;
+ }
+
+ case ';':
+ return Token_SemiColon;
+
+ case 0:
+ return Token_EOF;
+
+ case '{':
+ return Token_OpenBrace;
+
+ case '}':
+ return Token_CloseBrace;
+
+ case ' ':
+ break;
+
+ case 'v': {
+ if (*pos == 'o' && pos[1] == 'i' && pos[2] == 'd') {
+ pos += 3;
+ return Token_Void;
+ }
+ // Fall-thru
+ }
+ default:
+ // Identifier...
+ if ((c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' ) || c == '_') {
+ identifier = pos - 1;
+ while (*pos != 0 && ((*pos >= 'a' && *pos <= 'z')
+ || (*pos >= 'A' && *pos <= 'Z')
+ || *pos == '_'
+ || (*pos >= '0' && *pos <= '9'))) {
+ ++pos;
+ }
+ return Token_Identifier;
+ } else {
+ return Token_Unspecified;
+ }
+ }
+ }
+
+ return Token_Invalid;
+}
+
+} // namespace QSGShaderParser
+
+using namespace QSGShaderParser;
+
QSGShaderSourceBuilder::QSGShaderSourceBuilder()
{
}
@@ -87,7 +244,7 @@ void QSGShaderSourceBuilder::appendSource(const QByteArray &source)
void QSGShaderSourceBuilder::appendSourceFile(const QString &fileName)
{
const QString resolvedFileName = resolveShaderPath(fileName);
- QFile f(fileName);
+ QFile f(resolvedFileName);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "Failed to find shader" << resolvedFileName;
return;
@@ -95,12 +252,152 @@ void QSGShaderSourceBuilder::appendSourceFile(const QString &fileName)
m_source += f.readAll();
}
+void QSGShaderSourceBuilder::addDefinition(const QByteArray &definition)
+{
+ if (definition.isEmpty())
+ return;
+
+ Tokenizer tok;
+ const char *input = m_source.constData();
+ tok.initialize(input);
+
+ // First find #version, #extension's and "void main() { ... "
+ const char *versionPos = 0;
+ const char *extensionPos = 0;
+ bool inSingleLineComment = false;
+ bool inMultiLineComment = false;
+ bool foundVersionStart = false;
+ bool foundExtensionStart = false;
+
+ Tokenizer::Token lt = Tokenizer::Token_Unspecified;
+ Tokenizer::Token t = tok.next();
+ while (t != Tokenizer::Token_EOF) {
+ // Handle comment blocks
+ if (t == Tokenizer::Token_MultiLineCommentStart )
+ inMultiLineComment = true;
+ if (t == Tokenizer::Token_MultiLineCommentEnd)
+ inMultiLineComment = false;
+ if (t == Tokenizer::Token_SingleLineComment)
+ inSingleLineComment = true;
+ if (t == Tokenizer::Token_NewLine && inSingleLineComment && !inMultiLineComment)
+ inSingleLineComment = false;
+
+ // Have we found #version, #extension or void main()?
+ if (t == Tokenizer::Token_Version && !inSingleLineComment && !inMultiLineComment)
+ foundVersionStart = true;
+
+ if (t == Tokenizer::Token_Extension && !inSingleLineComment && !inMultiLineComment)
+ foundExtensionStart = true;
+
+ if (foundVersionStart && t == Tokenizer::Token_NewLine) {
+ versionPos = tok.pos;
+ foundVersionStart = false;
+ } else if (foundExtensionStart && t == Tokenizer::Token_NewLine) {
+ extensionPos = tok.pos;
+ foundExtensionStart = false;
+ } else if (lt == Tokenizer::Token_Void && t == Tokenizer::Token_Identifier) {
+ if (qstrncmp("main", tok.identifier, 4) == 0)
+ break;
+ }
+
+ // Scan to next token
+ lt = t;
+ t = tok.next();
+ }
+
+ // Determine where to insert the definition.
+ // If we found #extension directives, insert after last one,
+ // else, if we found #version insert after #version
+ // otherwise, insert at beginning.
+ const char *insertionPos = extensionPos ? extensionPos : (versionPos ? versionPos : input);
+
+ // Construct a new shader string, inserting the definition
+ QByteArray newSource;
+ newSource.reserve(m_source.size() + definition.size() + 9);
+ newSource += QByteArray::fromRawData(input, insertionPos - input);
+ newSource += QByteArrayLiteral("#define ") + definition + QByteArrayLiteral("\n");
+ newSource += QByteArray::fromRawData(insertionPos, m_source.size() - (insertionPos - input));
+
+ m_source = newSource;
+}
+
+void QSGShaderSourceBuilder::removeVersion()
+{
+ Tokenizer tok;
+ const char *input = m_source.constData();
+ tok.initialize(input);
+
+ // First find #version beginning and end (if present)
+ const char *versionStartPos = 0;
+ const char *versionEndPos = 0;
+ bool inSingleLineComment = false;
+ bool inMultiLineComment = false;
+ bool foundVersionStart = false;
+
+ Tokenizer::Token lt = Tokenizer::Token_Unspecified;
+ Tokenizer::Token t = tok.next();
+ while (t != Tokenizer::Token_EOF) {
+ // Handle comment blocks
+ if (t == Tokenizer::Token_MultiLineCommentStart )
+ inMultiLineComment = true;
+ if (t == Tokenizer::Token_MultiLineCommentEnd)
+ inMultiLineComment = false;
+ if (t == Tokenizer::Token_SingleLineComment)
+ inSingleLineComment = true;
+ if (t == Tokenizer::Token_NewLine && inSingleLineComment && !inMultiLineComment)
+ inSingleLineComment = false;
+
+ // Have we found #version, #extension or void main()?
+ if (t == Tokenizer::Token_Version && !inSingleLineComment && !inMultiLineComment) {
+ versionStartPos = tok.pos - 1;
+ foundVersionStart = true;
+ } else if (foundVersionStart && t == Tokenizer::Token_NewLine) {
+ versionEndPos = tok.pos;
+ break;
+ } else if (lt == Tokenizer::Token_Void && t == Tokenizer::Token_Identifier) {
+ if (qstrncmp("main", tok.identifier, 4) == 0)
+ break;
+ }
+
+ // Scan to next token
+ lt = t;
+ t = tok.next();
+ }
+
+ if (versionStartPos == 0)
+ return;
+
+ // Construct a new shader string, inserting the definition
+ QByteArray newSource;
+ newSource.reserve(m_source.size() - (versionEndPos - versionStartPos));
+ newSource += QByteArray::fromRawData(input, versionStartPos - input);
+ newSource += QByteArray::fromRawData(versionEndPos, m_source.size() - (versionEndPos - versionStartPos));
+
+ m_source = newSource;
+}
+
QString QSGShaderSourceBuilder::resolveShaderPath(const QString &path) const
{
- // For now, just return the path unaltered.
- // TODO: Resolve to more specific filename based upon OpenGL profile and
- // version, platform, GPU type etc
- return path;
+ if (contextProfile() != QSurfaceFormat::CoreProfile) {
+ return path;
+ } else {
+ int idx = path.lastIndexOf(QStringLiteral("."));
+ QString resolvedPath;
+ if (idx != -1)
+ resolvedPath = path.left(idx)
+ + QStringLiteral("_core")
+ + path.right(path.length() - idx);
+ return resolvedPath;
+ }
+}
+
+QSurfaceFormat::OpenGLContextProfile QSGShaderSourceBuilder::contextProfile() const
+{
+ QOpenGLContext *context = QOpenGLContext::currentContext();
+ QSurfaceFormat::OpenGLContextProfile profile = QSurfaceFormat::NoProfile;
+ Q_ASSERT(context);
+ profile = context->format().profile();
+ return profile;
}
QT_END_NAMESPACE