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>
This commit is contained in:
Florian Behrens 2013-06-07 22:52:29 +02:00 committed by Giuseppe D'Angelo
parent 294c914eb6
commit 92ce89c1ef

View File

@ -39,6 +39,7 @@
#include <QtCore/qfile.h> #include <QtCore/qfile.h>
#include <QtCore/qvarlengtharray.h> #include <QtCore/qvarlengtharray.h>
#include <QtCore/qvector.h> #include <QtCore/qvector.h>
#include <QtCore/qregularexpression.h>
#include <QtGui/qtransform.h> #include <QtGui/qtransform.h>
#include <QtGui/QColor> #include <QtGui/QColor>
#include <QtGui/QSurfaceFormat> #include <QtGui/QSurfaceFormat>
@ -47,6 +48,8 @@
#include <QtGui/qopenglfunctions_4_0_core.h> #include <QtGui/qopenglfunctions_4_0_core.h>
#endif #endif
#include <algorithm>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
/*! /*!
@ -401,6 +404,95 @@ static const char redefineHighp[] =
"#endif\n"; "#endif\n";
#endif #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. Sets the \a source code for this shader and compiles it.
Returns \c true if the source was successfully compiled, false otherwise. 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) bool QOpenGLShader::compileSourceCode(const char *source)
{ {
Q_D(QOpenGLShader); Q_D(QOpenGLShader);
if (d->shaderGuard && d->shaderGuard->id()) { // This method breaks the shader code into two parts:
QVarLengthArray<const char *, 4> src; // 1. Up to and including an optional #version directive.
QVarLengthArray<GLint, 4> srclen; // 2. The rest.
int headerLen = 0; // If a #version directive exists, qualifierDefines and redefineHighp
while (source && source[headerLen] == '#') { // are inserted after. Otherwise they are inserted right at the start.
// Skip #version and #extension directives at the start of // In both cases a #line directive is appended in order to compensate
// the shader code. We need to insert the qualifierDefines // for line number changes in case of compiler errors.
// and redefineHighp just after them.
if (qstrncmp(source + headerLen, "#version", 8) != 0 && if (d->shaderGuard && d->shaderGuard->id() && source) {
qstrncmp(source + headerLen, "#extension", 10) != 0) { const QVersionDirectivePosition versionDirectivePosition = findVersionDirectivePosition(source);
break;
} QVarLengthArray<const char *, 5> sourceChunks;
while (source[headerLen] != '\0' && source[headerLen] != '\n') QVarLengthArray<GLint, 5> sourceChunkLengths;
++headerLen;
if (source[headerLen] == '\n') if (versionDirectivePosition.hasPosition()) {
++headerLen; // Append source up to #version directive
} sourceChunks.append(source);
if (headerLen > 0) { sourceChunkLengths.append(GLint(versionDirectivePosition.position));
src.append(source);
srclen.append(GLint(headerLen));
} }
// The precision qualifiers are useful on OpenGL/ES systems, // The precision qualifiers are useful on OpenGL/ES systems,
@ -442,20 +532,28 @@ bool QOpenGLShader::compileSourceCode(const char *source)
|| true || true
#endif #endif
) { ) {
src.append(qualifierDefines); sourceChunks.append(qualifierDefines);
srclen.append(GLint(sizeof(qualifierDefines) - 1)); sourceChunkLengths.append(GLint(sizeof(qualifierDefines) - 1));
} }
#ifdef QOpenGL_REDEFINE_HIGHP #ifdef QOpenGL_REDEFINE_HIGHP
if (d->shaderType == Fragment && !ctx_d->workaround_missingPrecisionQualifiers if (d->shaderType == Fragment && !ctx_d->workaround_missingPrecisionQualifiers
&& QOpenGLContext::currentContext()->isOpenGLES()) { && QOpenGLContext::currentContext()->isOpenGLES()) {
src.append(redefineHighp); sourceChunks.append(redefineHighp);
srclen.append(GLint(sizeof(redefineHighp) - 1)); sourceChunkLengths.append(GLint(sizeof(redefineHighp) - 1));
} }
#endif #endif
src.append(source + headerLen);
srclen.append(GLint(qstrlen(source + headerLen))); // Append #line directive in order to compensate for text insertion
d->glfuncs->glShaderSource(d->shaderGuard->id(), src.size(), src.data(), srclen.data()); 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); return d->compile(this);
} else { } else {
return false; return false;