diff options
author | Florian Behrens <flb@zuehlke.com> | 2013-06-07 22:52:29 +0200 |
---|---|---|
committer | Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> | 2014-11-03 10:10:16 +0100 |
commit | 92ce89c1efdb7ddb589528901087e4ff50e55fe2 (patch) | |
tree | 1ae2cd745485dd41b9dabe26ce680548b7c6f665 /src | |
parent | 294c914eb6bb5e0200480400ba31f2049edb9b86 (diff) |
Insert #line statements in shader program.
This makes the GLSL compiler issue correct line numbers on errors
even in the presence of the other preprocessor lines inserted by
QOpenGLShaderProgram/QGLShaderProgram.
Change-Id: Ief4fc1dd1e94bb2b3a1ad13fbaf62186910a4994
Reviewed-by: Laszlo Agocs <laszlo.agocs@digia.com>
Reviewed-by: Sean Harmer <sean.harmer@kdab.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/gui/opengl/qopenglshaderprogram.cpp | 152 |
1 files changed, 125 insertions, 27 deletions
diff --git a/src/gui/opengl/qopenglshaderprogram.cpp b/src/gui/opengl/qopenglshaderprogram.cpp index 5ea7a10e0d..d64847510b 100644 --- a/src/gui/opengl/qopenglshaderprogram.cpp +++ b/src/gui/opengl/qopenglshaderprogram.cpp @@ -39,6 +39,7 @@ #include <QtCore/qfile.h> #include <QtCore/qvarlengtharray.h> #include <QtCore/qvector.h> +#include <QtCore/qregularexpression.h> #include <QtGui/qtransform.h> #include <QtGui/QColor> #include <QtGui/QSurfaceFormat> @@ -47,6 +48,8 @@ #include <QtGui/qopenglfunctions_4_0_core.h> #endif +#include <algorithm> + QT_BEGIN_NAMESPACE /*! @@ -401,6 +404,95 @@ static const char redefineHighp[] = "#endif\n"; #endif +struct QVersionDirectivePosition +{ + Q_DECL_CONSTEXPR QVersionDirectivePosition(int position = 0, int line = -1) + : position(position) + , line(line) + { + } + + Q_DECL_CONSTEXPR bool hasPosition() const + { + return position > 0; + } + + const int position; + const int line; +}; + +static QVersionDirectivePosition findVersionDirectivePosition(const char *source) +{ + Q_ASSERT(source); + + QString working = QString::fromUtf8(source); + + // According to the GLSL spec the #version directive must not be + // preceded by anything but whitespace and comments. + // In order to not get confused by #version directives within a + // multiline comment, we need to run a minimal preprocessor first. + enum { + Normal, + CommentStarting, + MultiLineComment, + SingleLineComment, + CommentEnding + } state = Normal; + + for (QChar *c = working.begin(); c != working.end(); ++c) { + switch (state) { + case Normal: + if (*c == QLatin1Char('/')) + state = CommentStarting; + break; + case CommentStarting: + if (*c == QLatin1Char('*')) + state = MultiLineComment; + else if (*c == QLatin1Char('/')) + state = SingleLineComment; + else + state = Normal; + break; + case MultiLineComment: + if (*c == QLatin1Char('*')) + state = CommentEnding; + else if (*c == QLatin1Char('#')) + *c = QLatin1Char('_'); + break; + case SingleLineComment: + if (*c == QLatin1Char('\n')) + state = Normal; + else if (*c == QLatin1Char('#')) + *c = QLatin1Char('_'); + break; + case CommentEnding: + if (*c == QLatin1Char('/')) { + state = Normal; + } else { + if (*c == QLatin1Char('#')) + *c = QLatin1Char('_'); + state = MultiLineComment; + } + break; + } + } + + // Search for #version directive + int splitPosition = 0; + int linePosition = 1; + + static const QRegularExpression pattern(QStringLiteral("^#\\s*version.*(\\n)?"), + QRegularExpression::MultilineOption + | QRegularExpression::OptimizeOnFirstUsageOption); + QRegularExpressionMatch match = pattern.match(working); + if (match.hasMatch()) { + splitPosition = match.capturedEnd(); + linePosition += int(std::count(working.begin(), working.begin() + splitPosition, QLatin1Char('\n'))); + } + + return QVersionDirectivePosition(splitPosition, linePosition); +} + /*! Sets the \a source code for this shader and compiles it. Returns \c true if the source was successfully compiled, false otherwise. @@ -410,26 +502,24 @@ static const char redefineHighp[] = bool QOpenGLShader::compileSourceCode(const char *source) { Q_D(QOpenGLShader); - if (d->shaderGuard && d->shaderGuard->id()) { - QVarLengthArray<const char *, 4> src; - QVarLengthArray<GLint, 4> srclen; - int headerLen = 0; - while (source && source[headerLen] == '#') { - // Skip #version and #extension directives at the start of - // the shader code. We need to insert the qualifierDefines - // and redefineHighp just after them. - if (qstrncmp(source + headerLen, "#version", 8) != 0 && - qstrncmp(source + headerLen, "#extension", 10) != 0) { - break; - } - while (source[headerLen] != '\0' && source[headerLen] != '\n') - ++headerLen; - if (source[headerLen] == '\n') - ++headerLen; - } - if (headerLen > 0) { - src.append(source); - srclen.append(GLint(headerLen)); + // This method breaks the shader code into two parts: + // 1. Up to and including an optional #version directive. + // 2. The rest. + // If a #version directive exists, qualifierDefines and redefineHighp + // are inserted after. Otherwise they are inserted right at the start. + // In both cases a #line directive is appended in order to compensate + // for line number changes in case of compiler errors. + + if (d->shaderGuard && d->shaderGuard->id() && source) { + const QVersionDirectivePosition versionDirectivePosition = findVersionDirectivePosition(source); + + QVarLengthArray<const char *, 5> sourceChunks; + QVarLengthArray<GLint, 5> sourceChunkLengths; + + if (versionDirectivePosition.hasPosition()) { + // Append source up to #version directive + sourceChunks.append(source); + sourceChunkLengths.append(GLint(versionDirectivePosition.position)); } // The precision qualifiers are useful on OpenGL/ES systems, @@ -442,20 +532,28 @@ bool QOpenGLShader::compileSourceCode(const char *source) || true #endif ) { - src.append(qualifierDefines); - srclen.append(GLint(sizeof(qualifierDefines) - 1)); + sourceChunks.append(qualifierDefines); + sourceChunkLengths.append(GLint(sizeof(qualifierDefines) - 1)); } #ifdef QOpenGL_REDEFINE_HIGHP if (d->shaderType == Fragment && !ctx_d->workaround_missingPrecisionQualifiers && QOpenGLContext::currentContext()->isOpenGLES()) { - src.append(redefineHighp); - srclen.append(GLint(sizeof(redefineHighp) - 1)); + sourceChunks.append(redefineHighp); + sourceChunkLengths.append(GLint(sizeof(redefineHighp) - 1)); } #endif - src.append(source + headerLen); - srclen.append(GLint(qstrlen(source + headerLen))); - d->glfuncs->glShaderSource(d->shaderGuard->id(), src.size(), src.data(), srclen.data()); + + // Append #line directive in order to compensate for text insertion + QByteArray lineDirective = QStringLiteral("#line %1\n").arg(versionDirectivePosition.line).toUtf8(); + sourceChunks.append(lineDirective.constData()); + sourceChunkLengths.append(GLint(lineDirective.length())); + + // Append rest of shader code + sourceChunks.append(source + versionDirectivePosition.position); + sourceChunkLengths.append(GLint(qstrlen(source + versionDirectivePosition.position))); + + d->glfuncs->glShaderSource(d->shaderGuard->id(), sourceChunks.size(), sourceChunks.data(), sourceChunkLengths.data()); return d->compile(this); } else { return false; |