diff options
author | Sean Harmer <sean.harmer@kdab.com> | 2013-11-06 13:07:58 +0000 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-11-13 23:42:13 +0100 |
commit | 3afffa47feabc80e1bc20ffd2143a722a1c360a2 (patch) | |
tree | 1e8d5e367aefae0d25c67a1c5e82eeeb357203a6 /src/quick/scenegraph/util/qsgshadersourcebuilder.cpp | |
parent | 376844cc6f312d2ab8cf3191ec3e5535bfa850e0 (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.cpp | 307 |
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 |