diff options
Diffstat (limited to 'src/opengl/gl2paintengineex')
-rw-r--r-- | src/opengl/gl2paintengineex/qgl2pexvertexarray.cpp | 65 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qgl2pexvertexarray_p.h | 10 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qglcustomshaderstage.cpp | 21 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qglcustomshaderstage_p.h | 3 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qglengineshadermanager.cpp | 426 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qglengineshadermanager_p.h | 131 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qglengineshadersource_p.h | 16 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp | 445 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h | 32 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qtriangulatingstroker.cpp | 549 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qtriangulatingstroker_p.h | 156 |
11 files changed, 1396 insertions, 458 deletions
diff --git a/src/opengl/gl2paintengineex/qgl2pexvertexarray.cpp b/src/opengl/gl2paintengineex/qgl2pexvertexarray.cpp index 1fe3999..ee1a797 100644 --- a/src/opengl/gl2paintengineex/qgl2pexvertexarray.cpp +++ b/src/opengl/gl2paintengineex/qgl2pexvertexarray.cpp @@ -48,7 +48,7 @@ QT_BEGIN_NAMESPACE void QGL2PEXVertexArray::clear() { vertexArray.reset(); - vertexArrayStops.clear(); + vertexArrayStops.reset(); boundingRectDirty = true; } @@ -101,7 +101,7 @@ void QGL2PEXVertexArray::addPath(const QVectorPath &path, GLfloat curveInverseSc boundingRectDirty = false; } - if (!outline) + if (!outline && !path.isConvex()) addCentroid(path, 0); int lastMoveTo = vertexArray.size(); @@ -120,15 +120,14 @@ void QGL2PEXVertexArray::addPath(const QVectorPath &path, GLfloat curveInverseSc // qDebug("QVectorPath has element types"); for (int i=1; i<path.elementCount(); ++i) { - const QPainterPath::ElementType elementType = elements[i]; - switch (elementType) { + switch (elements[i]) { case QPainterPath::MoveToElement: if (!outline) addClosingLine(lastMoveTo); // qDebug("element[%d] is a MoveToElement", i); - vertexArrayStops.append(vertexArray.size()); + vertexArrayStops.add(vertexArray.size()); if (!outline) { - addCentroid(path, i); + if (!path.isConvex()) addCentroid(path, i); lastMoveTo = vertexArray.size(); } lineToArray(points[i].x(), points[i].y()); // Add the moveTo as a new vertex @@ -137,11 +136,22 @@ void QGL2PEXVertexArray::addPath(const QVectorPath &path, GLfloat curveInverseSc // qDebug("element[%d] is a LineToElement", i); lineToArray(points[i].x(), points[i].y()); break; - case QPainterPath::CurveToElement: -// qDebug("element[%d] is a CurveToElement", i); - curveToArray(points[i], points[i+1], points[i+2], curveInverseScale); - i+=2; - break; + case QPainterPath::CurveToElement: { + QBezier b = QBezier::fromPoints(*(((const QPointF *) points) + i - 1), + points[i], + points[i+1], + points[i+2]); + QRectF bounds = b.bounds(); + // threshold based on same algorithm as in qtriangulatingstroker.cpp + int threshold = qMin<float>(64, qMax(bounds.width(), bounds.height()) * 3.14f / (curveInverseScale * 6)); + if (threshold < 3) threshold = 3; + qreal one_over_threshold_minus_1 = 1.f / (threshold - 1); + for (int t=0; t<threshold; ++t) { + QPointF pt = b.pointAt(t * one_over_threshold_minus_1); + lineToArray(pt.x(), pt.y()); + } + i += 2; + break; } default: break; } @@ -150,7 +160,7 @@ void QGL2PEXVertexArray::addPath(const QVectorPath &path, GLfloat curveInverseSc if (!outline) addClosingLine(lastMoveTo); - vertexArrayStops.append(vertexArray.size()); + vertexArrayStops.add(vertexArray.size()); } void QGL2PEXVertexArray::lineToArray(const GLfloat x, const GLfloat y) @@ -167,35 +177,4 @@ void QGL2PEXVertexArray::lineToArray(const GLfloat x, const GLfloat y) minY = y; } -void QGL2PEXVertexArray::curveToArray(const QGLPoint &cp1, const QGLPoint &cp2, const QGLPoint &ep, GLfloat inverseScale) -{ - qreal inverseScaleHalf = inverseScale / 2; - - QBezier beziers[32]; - beziers[0] = QBezier::fromPoints(vertexArray.last(), cp1, cp2, ep); - QBezier *b = beziers; - while (b >= beziers) { - // check if we can pop the top bezier curve from the stack - qreal l = qAbs(b->x4 - b->x1) + qAbs(b->y4 - b->y1); - qreal d; - if (l > inverseScale) { - d = qAbs( (b->x4 - b->x1)*(b->y1 - b->y2) - (b->y4 - b->y1)*(b->x1 - b->x2) ) - + qAbs( (b->x4 - b->x1)*(b->y1 - b->y3) - (b->y4 - b->y1)*(b->x1 - b->x3) ); - d /= l; - } else { - d = qAbs(b->x1 - b->x2) + qAbs(b->y1 - b->y2) + - qAbs(b->x1 - b->x3) + qAbs(b->y1 - b->y3); - } - if (d < inverseScaleHalf || b == beziers + 31) { - // good enough, we pop it off and add the endpoint - lineToArray(b->x4, b->y4); - --b; - } else { - // split, second half of the polygon goes lower into the stack - b->split(b+1, b); - ++b; - } - } -} - QT_END_NAMESPACE diff --git a/src/opengl/gl2paintengineex/qgl2pexvertexarray_p.h b/src/opengl/gl2paintengineex/qgl2pexvertexarray_p.h index 719904f..98eaa91 100644 --- a/src/opengl/gl2paintengineex/qgl2pexvertexarray_p.h +++ b/src/opengl/gl2paintengineex/qgl2pexvertexarray_p.h @@ -108,23 +108,23 @@ public: void clear(); QGLPoint* data() {return vertexArray.data();} - QVector<int>& stops() {return vertexArrayStops;} + int *stops() const { return vertexArrayStops.data(); } + int stopCount() const { return vertexArrayStops.size(); } QGLRect boundingRect() const; + int vertexCount() const { return vertexArray.size(); } + void lineToArray(const GLfloat x, const GLfloat y); private: QDataBuffer<QGLPoint> vertexArray; - QVector<int> vertexArrayStops; + QDataBuffer<int> vertexArrayStops; GLfloat maxX; GLfloat maxY; GLfloat minX; GLfloat minY; bool boundingRectDirty; - - inline void curveToArray(const QGLPoint &cp1, const QGLPoint &cp2, const QGLPoint &ep, GLfloat inverseScale); - void addClosingLine(int index); void addCentroid(const QVectorPath &path, int subPathIndex); }; diff --git a/src/opengl/gl2paintengineex/qglcustomshaderstage.cpp b/src/opengl/gl2paintengineex/qglcustomshaderstage.cpp index ab2026c..b71a7b7 100644 --- a/src/opengl/gl2paintengineex/qglcustomshaderstage.cpp +++ b/src/opengl/gl2paintengineex/qglcustomshaderstage.cpp @@ -67,8 +67,10 @@ QGLCustomShaderStage::QGLCustomShaderStage() QGLCustomShaderStage::~QGLCustomShaderStage() { Q_D(QGLCustomShaderStage); - if (d->m_manager) - d->m_manager->removeCustomStage(this); + if (d->m_manager) { + d->m_manager->removeCustomStage(); + d->m_manager->sharedShaders->cleanupCustomStage(this); + } } void QGLCustomShaderStage::setUniformsDirty() @@ -85,6 +87,8 @@ bool QGLCustomShaderStage::setOnPainter(QPainter* p) qWarning("QGLCustomShaderStage::setOnPainter() - paint engine not OpenGL2"); return false; } + if (d->m_manager) + qWarning("Custom shader is already set on a painter"); QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx*>(p->paintEngine()); d->m_manager = QGL2PaintEngineExPrivate::shaderManagerForEngine(engine); @@ -108,12 +112,21 @@ void QGLCustomShaderStage::removeFromPainter(QPainter* p) // This should leave the program in a compiled/linked state // if the next custom shader stage is this one again. d->m_manager->setCustomStage(0); + d->m_manager = 0; } -const char* QGLCustomShaderStage::source() const +QByteArray QGLCustomShaderStage::source() const { Q_D(const QGLCustomShaderStage); - return d->m_source.constData(); + return d->m_source; +} + +// Called by the shader manager if another custom shader is attached or +// the manager is deleted +void QGLCustomShaderStage::setInactive() +{ + Q_D(QGLCustomShaderStage); + d->m_manager = 0; } void QGLCustomShaderStage::setSource(const QByteArray& s) diff --git a/src/opengl/gl2paintengineex/qglcustomshaderstage_p.h b/src/opengl/gl2paintengineex/qglcustomshaderstage_p.h index f8c13c5..e319389 100644 --- a/src/opengl/gl2paintengineex/qglcustomshaderstage_p.h +++ b/src/opengl/gl2paintengineex/qglcustomshaderstage_p.h @@ -74,8 +74,9 @@ public: bool setOnPainter(QPainter*); void removeFromPainter(QPainter*); - const char* source() const; + QByteArray source() const; + void setInactive(); protected: void setSource(const QByteArray&); diff --git a/src/opengl/gl2paintengineex/qglengineshadermanager.cpp b/src/opengl/gl2paintengineex/qglengineshadermanager.cpp index e22303d..8a8f483 100644 --- a/src/opengl/gl2paintengineex/qglengineshadermanager.cpp +++ b/src/opengl/gl2paintengineex/qglengineshadermanager.cpp @@ -66,7 +66,7 @@ QGLEngineSharedShaders *QGLEngineSharedShaders::shadersForContext(const QGLConte return p; } -const char* QGLEngineSharedShaders::qglEngineShaderSourceCode[] = { +const char* QGLEngineSharedShaders::qShaderSnippets[] = { 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, @@ -78,7 +78,6 @@ QGLEngineSharedShaders::QGLEngineSharedShaders(const QGLContext* context) , blitShaderProg(0) , simpleShaderProg(0) { - memset(compiledShaders, 0, sizeof(compiledShaders)); /* Rather than having the shader source array statically initialised, it is initialised @@ -86,10 +85,10 @@ QGLEngineSharedShaders::QGLEngineSharedShaders(const QGLContext* context) around without having to change the order of the glsl strings. It is hoped this will make future hard-to-find runtime bugs more obvious and generally give more solid code. */ - static bool qglEngineShaderSourceCodePopulated = false; - if (!qglEngineShaderSourceCodePopulated) { + static bool snippetsPopulated = false; + if (!snippetsPopulated) { - const char** code = qglEngineShaderSourceCode; // shortcut + const char** code = qShaderSnippets; // shortcut code[MainVertexShader] = qglslMainVertexShader; code[MainWithTexCoordsVertexShader] = qglslMainWithTexCoordsVertexShader; @@ -121,7 +120,7 @@ QGLEngineSharedShaders::QGLEngineSharedShaders(const QGLContext* context) code[ImageSrcFragmentShader] = qglslImageSrcFragmentShader; code[ImageSrcWithPatternFragmentShader] = qglslImageSrcWithPatternFragmentShader; code[NonPremultipliedImageSrcFragmentShader] = qglslNonPremultipliedImageSrcFragmentShader; - code[CustomImageSrcFragmentShader] = ""; // Supplied by app. + code[CustomImageSrcFragmentShader] = qglslCustomSrcFragmentShader; // Calls "customShader", which must be appended code[SolidBrushSrcFragmentShader] = qglslSolidBrushSrcFragmentShader; code[TextureBrushSrcFragmentShader] = qglslTextureBrushSrcFragmentShader; code[TextureBrushSrcWithPatternFragmentShader] = qglslTextureBrushSrcWithPatternFragmentShader; @@ -131,11 +130,13 @@ QGLEngineSharedShaders::QGLEngineSharedShaders(const QGLContext* context) code[ConicalGradientBrushSrcFragmentShader] = qglslConicalGradientBrushSrcFragmentShader; code[ShockingPinkSrcFragmentShader] = qglslShockingPinkSrcFragmentShader; + code[NoMaskFragmentShader] = ""; code[MaskFragmentShader] = qglslMaskFragmentShader; code[RgbMaskFragmentShaderPass1] = qglslRgbMaskFragmentShaderPass1; code[RgbMaskFragmentShaderPass2] = qglslRgbMaskFragmentShaderPass2; code[RgbMaskWithGammaFragmentShader] = ""; //### + code[NoCompositionModeFragmentShader] = ""; code[MultiplyCompositionModeFragmentShader] = ""; //### code[ScreenCompositionModeFragmentShader] = ""; //### code[OverlayCompositionModeFragmentShader] = ""; //### @@ -150,29 +151,36 @@ QGLEngineSharedShaders::QGLEngineSharedShaders(const QGLContext* context) #if defined(QT_DEBUG) // Check that all the elements have been filled: - for (int i = 0; i < TotalShaderCount; ++i) { - if (qglEngineShaderSourceCode[i] == 0) { - int enumIndex = staticMetaObject.indexOfEnumerator("ShaderName"); - QMetaEnum m = staticMetaObject.enumerator(enumIndex); - - qCritical() << "qglEngineShaderSourceCode: Source for" << m.valueToKey(i) - << "(shader" << i << ") missing!"; + for (int i = 0; i < TotalSnippetCount; ++i) { + if (qShaderSnippets[i] == 0) { + qFatal("Shader snippet for %s (#%d) is missing!", + snippetNameStr(SnippetName(i)).constData(), i); } } #endif - qglEngineShaderSourceCodePopulated = true; + snippetsPopulated = true; } + QGLShader* fragShader; + QGLShader* vertexShader; + QByteArray source; + // Compile up the simple shader: + source.clear(); + source.append(qShaderSnippets[MainVertexShader]); + source.append(qShaderSnippets[PositionOnlyVertexShader]); + vertexShader = new QGLShader(QGLShader::Vertex, context, this); + vertexShader->compileSourceCode(source); + + source.clear(); + source.append(qShaderSnippets[MainFragmentShader]); + source.append(qShaderSnippets[ShockingPinkSrcFragmentShader]); + fragShader = new QGLShader(QGLShader::Fragment, context, this); + fragShader->compileSourceCode(source); + simpleShaderProg = new QGLShaderProgram(context, this); - compileNamedShader(MainVertexShader, QGLShader::PartialVertexShader); - compileNamedShader(PositionOnlyVertexShader, QGLShader::PartialVertexShader); - compileNamedShader(MainFragmentShader, QGLShader::PartialFragmentShader); - compileNamedShader(ShockingPinkSrcFragmentShader, QGLShader::PartialFragmentShader); - simpleShaderProg->addShader(compiledShaders[MainVertexShader]); - simpleShaderProg->addShader(compiledShaders[PositionOnlyVertexShader]); - simpleShaderProg->addShader(compiledShaders[MainFragmentShader]); - simpleShaderProg->addShader(compiledShaders[ShockingPinkSrcFragmentShader]); + simpleShaderProg->addShader(vertexShader); + simpleShaderProg->addShader(fragShader); simpleShaderProg->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR); simpleShaderProg->link(); if (!simpleShaderProg->isLinked()) { @@ -181,152 +189,159 @@ QGLEngineSharedShaders::QGLEngineSharedShaders(const QGLContext* context) } // Compile the blit shader: + source.clear(); + source.append(qShaderSnippets[MainWithTexCoordsVertexShader]); + source.append(qShaderSnippets[UntransformedPositionVertexShader]); + vertexShader = new QGLShader(QGLShader::Vertex, context, this); + vertexShader->compileSourceCode(source); + + source.clear(); + source.append(qShaderSnippets[MainFragmentShader]); + source.append(qShaderSnippets[ImageSrcFragmentShader]); + fragShader = new QGLShader(QGLShader::Fragment, context, this); + fragShader->compileSourceCode(source); + blitShaderProg = new QGLShaderProgram(context, this); - compileNamedShader(MainWithTexCoordsVertexShader, QGLShader::PartialVertexShader); - compileNamedShader(UntransformedPositionVertexShader, QGLShader::PartialVertexShader); - compileNamedShader(MainFragmentShader, QGLShader::PartialFragmentShader); - compileNamedShader(ImageSrcFragmentShader, QGLShader::PartialFragmentShader); - blitShaderProg->addShader(compiledShaders[MainWithTexCoordsVertexShader]); - blitShaderProg->addShader(compiledShaders[UntransformedPositionVertexShader]); - blitShaderProg->addShader(compiledShaders[MainFragmentShader]); - blitShaderProg->addShader(compiledShaders[ImageSrcFragmentShader]); + blitShaderProg->addShader(vertexShader); + blitShaderProg->addShader(fragShader); blitShaderProg->bindAttributeLocation("textureCoordArray", QT_TEXTURE_COORDS_ATTR); blitShaderProg->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR); blitShaderProg->link(); if (!blitShaderProg->isLinked()) { qCritical() << "Errors linking blit shader:" - << blitShaderProg->log(); - } -} - -void QGLEngineSharedShaders::shaderDestroyed(QObject *shader) -{ - // Remove any shader programs which has this as the srcPixel shader: - for (int i = 0; i < cachedPrograms.size(); ++i) { - if (cachedPrograms.at(i).srcPixelFragShader == shader) { - delete cachedPrograms.at(i).program; - cachedPrograms.removeAt(i--); - } + << simpleShaderProg->log(); } - emit shaderProgNeedsChanging(); } -QGLShader *QGLEngineSharedShaders::compileNamedShader(ShaderName name, QGLShader::ShaderType type) +#if defined (QT_DEBUG) +QByteArray QGLEngineSharedShaders::snippetNameStr(SnippetName name) { - Q_ASSERT(name != CustomImageSrcFragmentShader); - Q_ASSERT(name < InvalidShaderName); - - if (compiledShaders[name]) - return compiledShaders[name]; - - QByteArray source = qglEngineShaderSourceCode[name]; - QGLShader *newShader = new QGLShader(type, ctxGuard.context(), this); - newShader->compile(source); - -#if defined(QT_DEBUG) - // Name the shader for easier debugging - QMetaEnum m = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("ShaderName")); - newShader->setObjectName(QLatin1String(m.valueToKey(name))); -#endif - - compiledShaders[name] = newShader; - return newShader; + QMetaEnum m = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("SnippetName")); + return QByteArray(m.valueToKey(name)); } - -QGLShader *QGLEngineSharedShaders::compileCustomShader(QGLCustomShaderStage *stage, QGLShader::ShaderType type) -{ - QByteArray source = stage->source(); - source += qglslCustomSrcFragmentShader; - - QGLShader *newShader = customShaderCache.object(source); - if (newShader) - return newShader; - - newShader = new QGLShader(type, ctxGuard.context(), this); - newShader->compile(source); - customShaderCache.insert(source, newShader); - - connect(newShader, SIGNAL(destroyed(QObject *)), - this, SLOT(shaderDestroyed(QObject *))); - -#if defined(QT_DEBUG) - // Name the shader for easier debugging - QMetaEnum m = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("ShaderName")); - newShader->setObjectName(QLatin1String(m.valueToKey(CustomImageSrcFragmentShader))); #endif - return newShader; -} - // The address returned here will only be valid until next time this function is called. QGLEngineShaderProg *QGLEngineSharedShaders::findProgramInCache(const QGLEngineShaderProg &prog) { for (int i = 0; i < cachedPrograms.size(); ++i) { - if (cachedPrograms[i] == prog) - return &cachedPrograms[i]; + QGLEngineShaderProg *cachedProg = cachedPrograms[i]; + if (*cachedProg == prog) { + // Move the program to the top of the list as a poor-man's cache algo + cachedPrograms.move(i, 0); + return cachedProg; + } } - cachedPrograms.append(prog); - QGLEngineShaderProg &cached = cachedPrograms.last(); + QByteArray source; + source.append(qShaderSnippets[prog.mainFragShader]); + source.append(qShaderSnippets[prog.srcPixelFragShader]); + if (prog.srcPixelFragShader == CustomImageSrcFragmentShader) + source.append(prog.customStageSource); + if (prog.compositionFragShader) + source.append(qShaderSnippets[prog.compositionFragShader]); + if (prog.maskFragShader) + source.append(qShaderSnippets[prog.maskFragShader]); + QGLShader* fragShader = new QGLShader(QGLShader::Fragment, ctxGuard.context(), this); + fragShader->compileSourceCode(source); + + source.clear(); + source.append(qShaderSnippets[prog.mainVertexShader]); + source.append(qShaderSnippets[prog.positionVertexShader]); + QGLShader* vertexShader = new QGLShader(QGLShader::Vertex, ctxGuard.context(), this); + vertexShader->compileSourceCode(source); + +#if defined(QT_DEBUG) + // Name the shaders for easier debugging + QByteArray description; + description.append("Fragment shader: main="); + description.append(snippetNameStr(prog.mainFragShader)); + description.append(", srcPixel="); + description.append(snippetNameStr(prog.srcPixelFragShader)); + if (prog.compositionFragShader) { + description.append(", composition="); + description.append(snippetNameStr(prog.compositionFragShader)); + } + if (prog.maskFragShader) { + description.append(", mask="); + description.append(snippetNameStr(prog.maskFragShader)); + } + fragShader->setObjectName(QString::fromLatin1(description)); + + description.clear(); + description.append("Vertex shader: main="); + description.append(snippetNameStr(prog.mainVertexShader)); + description.append(", position="); + description.append(snippetNameStr(prog.positionVertexShader)); + vertexShader->setObjectName(QString::fromLatin1(description)); +#endif + + QGLEngineShaderProg* newProg = new QGLEngineShaderProg(prog); // If the shader program's not found in the cache, create it now. - cached.program = new QGLShaderProgram(ctxGuard.context(), this); - cached.program->addShader(cached.mainVertexShader); - cached.program->addShader(cached.positionVertexShader); - cached.program->addShader(cached.mainFragShader); - cached.program->addShader(cached.srcPixelFragShader); - cached.program->addShader(cached.maskFragShader); - cached.program->addShader(cached.compositionFragShader); + newProg->program = new QGLShaderProgram(ctxGuard.context(), this); + newProg->program->addShader(vertexShader); + newProg->program->addShader(fragShader); // We have to bind the vertex attribute names before the program is linked: - cached.program->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR); - if (cached.useTextureCoords) - cached.program->bindAttributeLocation("textureCoordArray", QT_TEXTURE_COORDS_ATTR); - if (cached.useOpacityAttribute) - cached.program->bindAttributeLocation("opacityArray", QT_OPACITY_ATTR); - - cached.program->link(); - if (!cached.program->isLinked()) { + newProg->program->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR); + if (newProg->useTextureCoords) + newProg->program->bindAttributeLocation("textureCoordArray", QT_TEXTURE_COORDS_ATTR); + if (newProg->useOpacityAttribute) + newProg->program->bindAttributeLocation("opacityArray", QT_OPACITY_ATTR); + + newProg->program->link(); + if (!newProg->program->isLinked()) { QLatin1String none("none"); QLatin1String br("\n"); QString error; error = QLatin1String("Shader program failed to link,") #if defined(QT_DEBUG) + br - + QLatin1String(" Shaders Used:\n") - + QLatin1String(" mainVertexShader = ") - + (cached.mainVertexShader ? - cached.mainVertexShader->objectName() : none) + br - + QLatin1String(" positionVertexShader = ") - + (cached.positionVertexShader ? - cached.positionVertexShader->objectName() : none) + br - + QLatin1String(" mainFragShader = ") - + (cached.mainFragShader ? - cached.mainFragShader->objectName() : none) + br - + QLatin1String(" srcPixelFragShader = ") - + (cached.srcPixelFragShader ? - cached.srcPixelFragShader->objectName() : none) + br - + QLatin1String(" maskFragShader = ") - + (cached.maskFragShader ? - cached.maskFragShader->objectName() : none) + br - + QLatin1String(" compositionFragShader = ") - + (cached.compositionFragShader ? - cached.compositionFragShader->objectName() : none) + br + + QLatin1String(" Shaders Used:") + br + + QLatin1String(" ") + vertexShader->objectName() + QLatin1String(": ") + br + + QLatin1String(vertexShader->sourceCode()) + br + + QLatin1String(" ") + fragShader->objectName() + QLatin1String(": ") + br + + QLatin1String(fragShader->sourceCode()) + br #endif + QLatin1String(" Error Log:\n") - + QLatin1String(" ") + cached.program->log(); + + QLatin1String(" ") + newProg->program->log(); qWarning() << error; - delete cached.program; - cachedPrograms.removeLast(); - return 0; - } else { - // taking the address here is safe since - // cachePrograms isn't resized anywhere else - return &cached; + delete newProg; // Deletes the QGLShaderProgram in it's destructor + newProg = 0; } + else { + if (cachedPrograms.count() > 30) { + // The cache is full, so delete the last 5 programs in the list. + // These programs will be least used, as a program us bumped to + // the top of the list when it's used. + for (int i = 0; i < 5; ++i) { + delete cachedPrograms.last(); + cachedPrograms.removeLast(); + } + } + + cachedPrograms.insert(0, newProg); + } + + return newProg; } +void QGLEngineSharedShaders::cleanupCustomStage(QGLCustomShaderStage* stage) +{ + // Remove any shader programs which has this as the custom shader src: + for (int i = 0; i < cachedPrograms.size(); ++i) { + QGLEngineShaderProg *cachedProg = cachedPrograms[i]; + if (cachedProg->customStageSource == stage->source()) { + delete cachedProg; + cachedPrograms.removeAt(i); + i--; + } + } +} + + QGLEngineShaderManager::QGLEngineShaderManager(QGLContext* context) : ctx(context), shaderProgNeedsChanging(true), @@ -335,8 +350,7 @@ QGLEngineShaderManager::QGLEngineShaderManager(QGLContext* context) maskType(NoMask), compositionMode(QPainter::CompositionMode_SourceOver), customSrcStage(0), - currentShaderProg(0), - customShader(0) + currentShaderProg(0) { sharedShaders = QGLEngineSharedShaders::shadersForContext(context); connect(sharedShaders, SIGNAL(shaderProgNeedsChanging()), this, SLOT(shaderProgNeedsChangingSlot())); @@ -345,6 +359,7 @@ QGLEngineShaderManager::QGLEngineShaderManager(QGLContext* context) QGLEngineShaderManager::~QGLEngineShaderManager() { //### + removeCustomStage(); } uint QGLEngineShaderManager::getUniformLocation(Uniform id) @@ -436,21 +451,20 @@ void QGLEngineShaderManager::setCompositionMode(QPainter::CompositionMode mode) void QGLEngineShaderManager::setCustomStage(QGLCustomShaderStage* stage) { + if (customSrcStage) + removeCustomStage(); customSrcStage = stage; - customShader = 0; // Will be compiled from 'customSrcStage' later. shaderProgNeedsChanging = true; } -void QGLEngineShaderManager::removeCustomStage(QGLCustomShaderStage* stage) +void QGLEngineShaderManager::removeCustomStage() { - Q_UNUSED(stage); // Currently we only support one at a time... - + if (customSrcStage) + customSrcStage->setInactive(); customSrcStage = 0; - customShader = 0; shaderProgNeedsChanging = true; } - QGLShaderProgram* QGLEngineShaderManager::currentProgram() { return currentShaderProg->program; @@ -476,28 +490,27 @@ bool QGLEngineShaderManager::useCorrectShaderProg() return false; bool useCustomSrc = customSrcStage != 0; - if (useCustomSrc && srcPixelType != QGLEngineShaderManager::ImageSrc) { + if (useCustomSrc && srcPixelType != QGLEngineShaderManager::ImageSrc && srcPixelType != Qt::TexturePattern) { useCustomSrc = false; qWarning("QGLEngineShaderManager - Ignoring custom shader stage for non image src"); } QGLEngineShaderProg requiredProgram; - requiredProgram.program = 0; bool texCoords = false; // Choose vertex shader shader position function (which typically also sets // varyings) and the source pixel (srcPixel) fragment shader function: - QGLEngineSharedShaders::ShaderName positionVertexShaderName = QGLEngineSharedShaders::InvalidShaderName; - QGLEngineSharedShaders::ShaderName srcPixelFragShaderName = QGLEngineSharedShaders::InvalidShaderName; + requiredProgram.positionVertexShader = QGLEngineSharedShaders::InvalidSnippetName; + requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::InvalidSnippetName; bool isAffine = brushTransform.isAffine(); if ( (srcPixelType >= Qt::Dense1Pattern) && (srcPixelType <= Qt::DiagCrossPattern) ) { if (isAffine) - positionVertexShaderName = QGLEngineSharedShaders::AffinePositionWithPatternBrushVertexShader; + requiredProgram.positionVertexShader = QGLEngineSharedShaders::AffinePositionWithPatternBrushVertexShader; else - positionVertexShaderName = QGLEngineSharedShaders::PositionWithPatternBrushVertexShader; + requiredProgram.positionVertexShader = QGLEngineSharedShaders::PositionWithPatternBrushVertexShader; - srcPixelFragShaderName = QGLEngineSharedShaders::PatternBrushSrcFragmentShader; + requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::PatternBrushSrcFragmentShader; } else switch (srcPixelType) { default: @@ -505,177 +518,162 @@ bool QGLEngineShaderManager::useCorrectShaderProg() qFatal("QGLEngineShaderManager::useCorrectShaderProg() - Qt::NoBrush style is set"); break; case QGLEngineShaderManager::ImageSrc: - srcPixelFragShaderName = QGLEngineSharedShaders::ImageSrcFragmentShader; - positionVertexShaderName = QGLEngineSharedShaders::PositionOnlyVertexShader; + requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::ImageSrcFragmentShader; + requiredProgram.positionVertexShader = QGLEngineSharedShaders::PositionOnlyVertexShader; texCoords = true; break; case QGLEngineShaderManager::NonPremultipliedImageSrc: - srcPixelFragShaderName = QGLEngineSharedShaders::NonPremultipliedImageSrcFragmentShader; - positionVertexShaderName = QGLEngineSharedShaders::PositionOnlyVertexShader; + requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::NonPremultipliedImageSrcFragmentShader; + requiredProgram.positionVertexShader = QGLEngineSharedShaders::PositionOnlyVertexShader; texCoords = true; break; case QGLEngineShaderManager::PatternSrc: - srcPixelFragShaderName = QGLEngineSharedShaders::ImageSrcWithPatternFragmentShader; - positionVertexShaderName = QGLEngineSharedShaders::PositionOnlyVertexShader; + requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::ImageSrcWithPatternFragmentShader; + requiredProgram.positionVertexShader = QGLEngineSharedShaders::PositionOnlyVertexShader; texCoords = true; break; case QGLEngineShaderManager::TextureSrcWithPattern: - srcPixelFragShaderName = QGLEngineSharedShaders::TextureBrushSrcWithPatternFragmentShader; - positionVertexShaderName = isAffine ? QGLEngineSharedShaders::AffinePositionWithTextureBrushVertexShader + requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::TextureBrushSrcWithPatternFragmentShader; + requiredProgram.positionVertexShader = isAffine ? QGLEngineSharedShaders::AffinePositionWithTextureBrushVertexShader : QGLEngineSharedShaders::PositionWithTextureBrushVertexShader; break; case Qt::SolidPattern: - srcPixelFragShaderName = QGLEngineSharedShaders::SolidBrushSrcFragmentShader; - positionVertexShaderName = QGLEngineSharedShaders::PositionOnlyVertexShader; + requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::SolidBrushSrcFragmentShader; + requiredProgram.positionVertexShader = QGLEngineSharedShaders::PositionOnlyVertexShader; break; case Qt::LinearGradientPattern: - srcPixelFragShaderName = QGLEngineSharedShaders::LinearGradientBrushSrcFragmentShader; - positionVertexShaderName = isAffine ? QGLEngineSharedShaders::AffinePositionWithLinearGradientBrushVertexShader + requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::LinearGradientBrushSrcFragmentShader; + requiredProgram.positionVertexShader = isAffine ? QGLEngineSharedShaders::AffinePositionWithLinearGradientBrushVertexShader : QGLEngineSharedShaders::PositionWithLinearGradientBrushVertexShader; break; case Qt::ConicalGradientPattern: - srcPixelFragShaderName = QGLEngineSharedShaders::ConicalGradientBrushSrcFragmentShader; - positionVertexShaderName = isAffine ? QGLEngineSharedShaders::AffinePositionWithConicalGradientBrushVertexShader + requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::ConicalGradientBrushSrcFragmentShader; + requiredProgram.positionVertexShader = isAffine ? QGLEngineSharedShaders::AffinePositionWithConicalGradientBrushVertexShader : QGLEngineSharedShaders::PositionWithConicalGradientBrushVertexShader; break; case Qt::RadialGradientPattern: - srcPixelFragShaderName = QGLEngineSharedShaders::RadialGradientBrushSrcFragmentShader; - positionVertexShaderName = isAffine ? QGLEngineSharedShaders::AffinePositionWithRadialGradientBrushVertexShader + requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::RadialGradientBrushSrcFragmentShader; + requiredProgram.positionVertexShader = isAffine ? QGLEngineSharedShaders::AffinePositionWithRadialGradientBrushVertexShader : QGLEngineSharedShaders::PositionWithRadialGradientBrushVertexShader; break; case Qt::TexturePattern: - srcPixelFragShaderName = QGLEngineSharedShaders::TextureBrushSrcFragmentShader; - positionVertexShaderName = isAffine ? QGLEngineSharedShaders::AffinePositionWithTextureBrushVertexShader + requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::TextureBrushSrcFragmentShader; + requiredProgram.positionVertexShader = isAffine ? QGLEngineSharedShaders::AffinePositionWithTextureBrushVertexShader : QGLEngineSharedShaders::PositionWithTextureBrushVertexShader; break; }; - requiredProgram.positionVertexShader = sharedShaders->compileNamedShader(positionVertexShaderName, QGLShader::PartialVertexShader); + if (useCustomSrc) { - if (!customShader) - customShader = sharedShaders->compileCustomShader(customSrcStage, QGLShader::PartialFragmentShader); - requiredProgram.srcPixelFragShader = customShader; - } else { - requiredProgram.srcPixelFragShader = sharedShaders->compileNamedShader(srcPixelFragShaderName, QGLShader::PartialFragmentShader); + requiredProgram.srcPixelFragShader = QGLEngineSharedShaders::CustomImageSrcFragmentShader; + requiredProgram.customStageSource = customSrcStage->source(); } const bool hasCompose = compositionMode > QPainter::CompositionMode_Plus; const bool hasMask = maskType != QGLEngineShaderManager::NoMask; // Choose fragment shader main function: - QGLEngineSharedShaders::ShaderName mainFragShaderName; - if (opacityMode == AttributeOpacity) { Q_ASSERT(!hasCompose && !hasMask); - mainFragShaderName = QGLEngineSharedShaders::MainFragmentShader_ImageArrays; + requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_ImageArrays; } else { bool useGlobalOpacity = (opacityMode == UniformOpacity); if (hasCompose && hasMask && useGlobalOpacity) - mainFragShaderName = QGLEngineSharedShaders::MainFragmentShader_CMO; + requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_CMO; if (hasCompose && hasMask && !useGlobalOpacity) - mainFragShaderName = QGLEngineSharedShaders::MainFragmentShader_CM; + requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_CM; if (!hasCompose && hasMask && useGlobalOpacity) - mainFragShaderName = QGLEngineSharedShaders::MainFragmentShader_MO; + requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_MO; if (!hasCompose && hasMask && !useGlobalOpacity) - mainFragShaderName = QGLEngineSharedShaders::MainFragmentShader_M; + requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_M; if (hasCompose && !hasMask && useGlobalOpacity) - mainFragShaderName = QGLEngineSharedShaders::MainFragmentShader_CO; + requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_CO; if (hasCompose && !hasMask && !useGlobalOpacity) - mainFragShaderName = QGLEngineSharedShaders::MainFragmentShader_C; + requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_C; if (!hasCompose && !hasMask && useGlobalOpacity) - mainFragShaderName = QGLEngineSharedShaders::MainFragmentShader_O; + requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader_O; if (!hasCompose && !hasMask && !useGlobalOpacity) - mainFragShaderName = QGLEngineSharedShaders::MainFragmentShader; + requiredProgram.mainFragShader = QGLEngineSharedShaders::MainFragmentShader; } - requiredProgram.mainFragShader = sharedShaders->compileNamedShader(mainFragShaderName, QGLShader::PartialFragmentShader); - if (hasMask) { - QGLEngineSharedShaders::ShaderName maskShaderName = QGLEngineSharedShaders::InvalidShaderName; if (maskType == PixelMask) { - maskShaderName = QGLEngineSharedShaders::MaskFragmentShader; + requiredProgram.maskFragShader = QGLEngineSharedShaders::MaskFragmentShader; texCoords = true; } else if (maskType == SubPixelMaskPass1) { - maskShaderName = QGLEngineSharedShaders::RgbMaskFragmentShaderPass1; + requiredProgram.maskFragShader = QGLEngineSharedShaders::RgbMaskFragmentShaderPass1; texCoords = true; } else if (maskType == SubPixelMaskPass2) { - maskShaderName = QGLEngineSharedShaders::RgbMaskFragmentShaderPass2; + requiredProgram.maskFragShader = QGLEngineSharedShaders::RgbMaskFragmentShaderPass2; texCoords = true; } else if (maskType == SubPixelWithGammaMask) { - maskShaderName = QGLEngineSharedShaders::RgbMaskWithGammaFragmentShader; + requiredProgram.maskFragShader = QGLEngineSharedShaders::RgbMaskWithGammaFragmentShader; texCoords = true; } else { qCritical("QGLEngineShaderManager::useCorrectShaderProg() - Unknown mask type"); } - - requiredProgram.maskFragShader = sharedShaders->compileNamedShader(maskShaderName, QGLShader::PartialFragmentShader); } else { - requiredProgram.maskFragShader = 0; + requiredProgram.maskFragShader = QGLEngineSharedShaders::NoMaskFragmentShader; } if (hasCompose) { - QGLEngineSharedShaders::ShaderName compositionShaderName = QGLEngineSharedShaders::InvalidShaderName; switch (compositionMode) { case QPainter::CompositionMode_Multiply: - compositionShaderName = QGLEngineSharedShaders::MultiplyCompositionModeFragmentShader; + requiredProgram.compositionFragShader = QGLEngineSharedShaders::MultiplyCompositionModeFragmentShader; break; case QPainter::CompositionMode_Screen: - compositionShaderName = QGLEngineSharedShaders::ScreenCompositionModeFragmentShader; + requiredProgram.compositionFragShader = QGLEngineSharedShaders::ScreenCompositionModeFragmentShader; break; case QPainter::CompositionMode_Overlay: - compositionShaderName = QGLEngineSharedShaders::OverlayCompositionModeFragmentShader; + requiredProgram.compositionFragShader = QGLEngineSharedShaders::OverlayCompositionModeFragmentShader; break; case QPainter::CompositionMode_Darken: - compositionShaderName = QGLEngineSharedShaders::DarkenCompositionModeFragmentShader; + requiredProgram.compositionFragShader = QGLEngineSharedShaders::DarkenCompositionModeFragmentShader; break; case QPainter::CompositionMode_Lighten: - compositionShaderName = QGLEngineSharedShaders::LightenCompositionModeFragmentShader; + requiredProgram.compositionFragShader = QGLEngineSharedShaders::LightenCompositionModeFragmentShader; break; case QPainter::CompositionMode_ColorDodge: - compositionShaderName = QGLEngineSharedShaders::ColorDodgeCompositionModeFragmentShader; + requiredProgram.compositionFragShader = QGLEngineSharedShaders::ColorDodgeCompositionModeFragmentShader; break; case QPainter::CompositionMode_ColorBurn: - compositionShaderName = QGLEngineSharedShaders::ColorBurnCompositionModeFragmentShader; + requiredProgram.compositionFragShader = QGLEngineSharedShaders::ColorBurnCompositionModeFragmentShader; break; case QPainter::CompositionMode_HardLight: - compositionShaderName = QGLEngineSharedShaders::HardLightCompositionModeFragmentShader; + requiredProgram.compositionFragShader = QGLEngineSharedShaders::HardLightCompositionModeFragmentShader; break; case QPainter::CompositionMode_SoftLight: - compositionShaderName = QGLEngineSharedShaders::SoftLightCompositionModeFragmentShader; + requiredProgram.compositionFragShader = QGLEngineSharedShaders::SoftLightCompositionModeFragmentShader; break; case QPainter::CompositionMode_Difference: - compositionShaderName = QGLEngineSharedShaders::DifferenceCompositionModeFragmentShader; + requiredProgram.compositionFragShader = QGLEngineSharedShaders::DifferenceCompositionModeFragmentShader; break; case QPainter::CompositionMode_Exclusion: - compositionShaderName = QGLEngineSharedShaders::ExclusionCompositionModeFragmentShader; + requiredProgram.compositionFragShader = QGLEngineSharedShaders::ExclusionCompositionModeFragmentShader; break; default: qWarning("QGLEngineShaderManager::useCorrectShaderProg() - Unsupported composition mode"); } - requiredProgram.compositionFragShader = sharedShaders->compileNamedShader(compositionShaderName, QGLShader::PartialFragmentShader); } else { - requiredProgram.compositionFragShader = 0; + requiredProgram.compositionFragShader = QGLEngineSharedShaders::NoCompositionModeFragmentShader; } - // Choose vertex shader main function - QGLEngineSharedShaders::ShaderName mainVertexShaderName = QGLEngineSharedShaders::InvalidShaderName; + // Choose vertex shader main function if (opacityMode == AttributeOpacity) { Q_ASSERT(texCoords); - mainVertexShaderName = QGLEngineSharedShaders::MainWithTexCoordsAndOpacityVertexShader; + requiredProgram.mainVertexShader = QGLEngineSharedShaders::MainWithTexCoordsAndOpacityVertexShader; } else if (texCoords) { - mainVertexShaderName = QGLEngineSharedShaders::MainWithTexCoordsVertexShader; + requiredProgram.mainVertexShader = QGLEngineSharedShaders::MainWithTexCoordsVertexShader; } else { - mainVertexShaderName = QGLEngineSharedShaders::MainVertexShader; + requiredProgram.mainVertexShader = QGLEngineSharedShaders::MainVertexShader; } - requiredProgram.mainVertexShader = sharedShaders->compileNamedShader(mainVertexShaderName, QGLShader::PartialVertexShader); requiredProgram.useTextureCoords = texCoords; requiredProgram.useOpacityAttribute = (opacityMode == AttributeOpacity); - // At this point, requiredProgram is fully populated so try to find the program in the cache currentShaderProg = sharedShaders->findProgramInCache(requiredProgram); if (currentShaderProg) { - currentShaderProg->program->enable(); + currentShaderProg->program->bind(); if (useCustomSrc) customSrcStage->setUniforms(currentShaderProg->program); } diff --git a/src/opengl/gl2paintengineex/qglengineshadermanager_p.h b/src/opengl/gl2paintengineex/qglengineshadermanager_p.h index 291d24c..50c1432 100644 --- a/src/opengl/gl2paintengineex/qglengineshadermanager_p.h +++ b/src/opengl/gl2paintengineex/qglengineshadermanager_p.h @@ -234,32 +234,6 @@ QT_BEGIN_NAMESPACE QT_MODULE(OpenGL) -struct QGLEngineShaderProg -{ - QGLShader* mainVertexShader; - QGLShader* positionVertexShader; - QGLShader* mainFragShader; - QGLShader* srcPixelFragShader; - QGLShader* maskFragShader; // Can be null for no mask - QGLShader* compositionFragShader; // Can be null for GL-handled mode - QGLShaderProgram* program; - - QVector<uint> uniformLocations; - - bool useTextureCoords; - bool useOpacityAttribute; - - bool operator==(const QGLEngineShaderProg& other) { - // We don't care about the program - return ( mainVertexShader == other.mainVertexShader && - positionVertexShader == other.positionVertexShader && - mainFragShader == other.mainFragShader && - srcPixelFragShader == other.srcPixelFragShader && - maskFragShader == other.maskFragShader && - compositionFragShader == other.compositionFragShader - ); - } -}; /* struct QGLEngineCachedShaderProg @@ -280,15 +254,19 @@ static const GLuint QT_VERTEX_COORDS_ATTR = 0; static const GLuint QT_TEXTURE_COORDS_ATTR = 1; static const GLuint QT_OPACITY_ATTR = 2; +class QGLEngineShaderProg; + class QGLEngineSharedShaders : public QObject { Q_OBJECT public: - enum ShaderName { + + enum SnippetName { MainVertexShader, MainWithTexCoordsVertexShader, MainWithTexCoordsAndOpacityVertexShader, + // UntransformedPositionVertexShader must be first in the list: UntransformedPositionVertexShader, PositionOnlyVertexShader, PositionWithPatternBrushVertexShader, @@ -302,6 +280,7 @@ public: AffinePositionWithRadialGradientBrushVertexShader, AffinePositionWithTextureBrushVertexShader, + // MainFragmentShader_CMO must be first in the list: MainFragmentShader_CMO, MainFragmentShader_CM, MainFragmentShader_MO, @@ -312,6 +291,7 @@ public: MainFragmentShader, MainFragmentShader_ImageArrays, + // ImageSrcFragmentShader must be first in the list:: ImageSrcFragmentShader, ImageSrcWithPatternFragmentShader, NonPremultipliedImageSrcFragmentShader, @@ -325,11 +305,15 @@ public: ConicalGradientBrushSrcFragmentShader, ShockingPinkSrcFragmentShader, + // NoMaskFragmentShader must be first in the list: + NoMaskFragmentShader, MaskFragmentShader, RgbMaskFragmentShaderPass1, RgbMaskFragmentShaderPass2, RgbMaskWithGammaFragmentShader, + // NoCompositionModeFragmentShader must be first in the list: + NoCompositionModeFragmentShader, MultiplyCompositionModeFragmentShader, ScreenCompositionModeFragmentShader, OverlayCompositionModeFragmentShader, @@ -342,37 +326,88 @@ public: DifferenceCompositionModeFragmentShader, ExclusionCompositionModeFragmentShader, - TotalShaderCount, InvalidShaderName + TotalSnippetCount, InvalidSnippetName }; +#if defined (QT_DEBUG) + Q_ENUMS(SnippetName) + static QByteArray snippetNameStr(SnippetName snippetName); +#endif - QGLEngineSharedShaders(const QGLContext *context); +/* + // These allow the ShaderName enum to be used as a cache key + const int mainVertexOffset = 0; + const int positionVertexOffset = (1<<2) - PositionOnlyVertexShader; + const int mainFragOffset = (1<<6) - MainFragmentShader_CMO; + const int srcPixelOffset = (1<<10) - ImageSrcFragmentShader; + const int maskOffset = (1<<14) - NoMaskShader; + const int compositionOffset = (1 << 16) - MultiplyCompositionModeFragmentShader; +*/ - QGLShader *compileNamedShader(ShaderName name, QGLShader::ShaderType type); + QGLEngineSharedShaders(const QGLContext *context); QGLShaderProgram *simpleProgram() { return simpleShaderProg; } QGLShaderProgram *blitProgram() { return blitShaderProg; } // Compile the program if it's not already in the cache, return the item in the cache. QGLEngineShaderProg *findProgramInCache(const QGLEngineShaderProg &prog); // Compile the custom shader if it's not already in the cache, return the item in the cache. - QGLShader *compileCustomShader(QGLCustomShaderStage *stage, QGLShader::ShaderType type); static QGLEngineSharedShaders *shadersForContext(const QGLContext *context); + // Ideally, this would be static and cleanup all programs in all contexts which + // contain the custom code. Currently it is just a hint and we rely on deleted + // custom shaders being cleaned up by being kicked out of the cache when it's + // full. + void cleanupCustomStage(QGLCustomShaderStage* stage); + signals: void shaderProgNeedsChanging(); -private slots: - void shaderDestroyed(QObject *shader); - private: QGLSharedResourceGuard ctxGuard; QGLShaderProgram *blitShaderProg; QGLShaderProgram *simpleShaderProg; - QList<QGLEngineShaderProg> cachedPrograms; - QCache<QByteArray, QGLShader> customShaderCache; - QGLShader* compiledShaders[TotalShaderCount]; + QList<QGLEngineShaderProg*> cachedPrograms; - static const char* qglEngineShaderSourceCode[TotalShaderCount]; + static const char* qShaderSnippets[TotalSnippetCount]; +}; + + +class QGLEngineShaderProg +{ +public: + QGLEngineShaderProg() : program(0) {} + + ~QGLEngineShaderProg() { + if (program) + delete program; + } + + QGLEngineSharedShaders::SnippetName mainVertexShader; + QGLEngineSharedShaders::SnippetName positionVertexShader; + QGLEngineSharedShaders::SnippetName mainFragShader; + QGLEngineSharedShaders::SnippetName srcPixelFragShader; + QGLEngineSharedShaders::SnippetName maskFragShader; + QGLEngineSharedShaders::SnippetName compositionFragShader; + + QByteArray customStageSource; //TODO: Decent cache key for custom stages + QGLShaderProgram* program; + + QVector<uint> uniformLocations; + + bool useTextureCoords; + bool useOpacityAttribute; + + bool operator==(const QGLEngineShaderProg& other) { + // We don't care about the program + return ( mainVertexShader == other.mainVertexShader && + positionVertexShader == other.positionVertexShader && + mainFragShader == other.mainFragShader && + srcPixelFragShader == other.srcPixelFragShader && + maskFragShader == other.maskFragShader && + compositionFragShader == other.compositionFragShader && + customStageSource == other.customStageSource + ); + } }; class Q_OPENGL_EXPORT QGLEngineShaderManager : public QObject @@ -426,7 +461,7 @@ public: void setMaskType(MaskType); void setCompositionMode(QPainter::CompositionMode); void setCustomStage(QGLCustomShaderStage* stage); - void removeCustomStage(QGLCustomShaderStage* stage); + void removeCustomStage(); uint getUniformLocation(Uniform id); @@ -437,19 +472,7 @@ public: QGLShaderProgram* simpleProgram(); // Used to draw into e.g. stencil buffers QGLShaderProgram* blitProgram(); // Used to blit a texture into the framebuffer -/* - // These allow the ShaderName enum to be used as a cache key - const int mainVertexOffset = 0; - const int positionVertexOffset = (1<<2) - PositionOnlyVertexShader; - const int mainFragOffset = (1<<6) - MainFragmentShader_CMO; - const int srcPixelOffset = (1<<10) - ImageSrcFragmentShader; - const int maskOffset = (1<<14) - NoMaskShader; - const int compositionOffset = (1 << 16) - MultiplyCompositionModeFragmentShader; -*/ - -#if defined (QT_DEBUG) - Q_ENUMS(ShaderName) -#endif + QGLEngineSharedShaders* sharedShaders; private slots: void shaderProgNeedsChangingSlot() { shaderProgNeedsChanging = true; } @@ -466,9 +489,7 @@ private: QPainter::CompositionMode compositionMode; QGLCustomShaderStage* customSrcStage; - QGLEngineShaderProg* currentShaderProg; - QGLEngineSharedShaders *sharedShaders; - QGLShader *customShader; + QGLEngineShaderProg* currentShaderProg; }; QT_END_NAMESPACE diff --git a/src/opengl/gl2paintengineex/qglengineshadersource_p.h b/src/opengl/gl2paintengineex/qglengineshadersource_p.h index 3eef808..2407979 100644 --- a/src/opengl/gl2paintengineex/qglengineshadersource_p.h +++ b/src/opengl/gl2paintengineex/qglengineshadersource_p.h @@ -258,7 +258,7 @@ static const char* const qglslPositionWithTextureBrushVertexShader = "\ uniform mediump vec2 halfViewportSize; \ uniform highp vec2 invertedTextureSize; \ uniform highp mat3 brushTransform; \ - varying highp vec2 brushTextureCoords; \ + varying highp vec2 textureCoords; \ void setPosition(void) { \ gl_Position = pmvMatrix * vertexCoordsArray;\ gl_Position.xy = gl_Position.xy / gl_Position.w; \ @@ -267,7 +267,7 @@ static const char* const qglslPositionWithTextureBrushVertexShader = "\ mediump float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \ gl_Position.xy = gl_Position.xy * invertedHTexCoordsZ; \ gl_Position.w = invertedHTexCoordsZ; \ - brushTextureCoords.xy = (hTexCoords.xy * invertedTextureSize) * gl_Position.w; \ + textureCoords.xy = (hTexCoords.xy * invertedTextureSize) * gl_Position.w; \ }"; static const char* const qglslAffinePositionWithTextureBrushVertexShader @@ -278,26 +278,26 @@ static const char* const qglslAffinePositionWithTextureBrushVertexShader // we emulate GL_REPEAT by only taking the fractional part of the texture coords. // TODO: Special case POT textures which don't need this emulation static const char* const qglslTextureBrushSrcFragmentShader = "\ - varying highp vec2 brushTextureCoords; \ + varying highp vec2 textureCoords; \ uniform lowp sampler2D brushTexture; \ lowp vec4 srcPixel() { \ - return texture2D(brushTexture, fract(brushTextureCoords)); \ + return texture2D(brushTexture, fract(textureCoords)); \ }"; #else static const char* const qglslTextureBrushSrcFragmentShader = "\ - varying highp vec2 brushTextureCoords; \ + varying highp vec2 textureCoords; \ uniform lowp sampler2D brushTexture; \ lowp vec4 srcPixel() { \ - return texture2D(brushTexture, brushTextureCoords); \ + return texture2D(brushTexture, textureCoords); \ }"; #endif static const char* const qglslTextureBrushSrcWithPatternFragmentShader = "\ - varying highp vec2 brushTextureCoords; \ + varying highp vec2 textureCoords; \ uniform lowp vec4 patternColor; \ uniform lowp sampler2D brushTexture; \ lowp vec4 srcPixel() { \ - return patternColor * (1.0 - texture2D(brushTexture, brushTextureCoords).r); \ + return patternColor * (1.0 - texture2D(brushTexture, textureCoords).r); \ }"; // Solid Fill Brush diff --git a/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp b/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp index 13efbda..fb9bcb4 100644 --- a/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp +++ b/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp @@ -62,6 +62,8 @@ and use the correct program when we really need it. */ +// #define QT_OPENGL_CACHE_AS_VBOS + #include "qpaintengineex_opengl2_p.h" #include <string.h> //for memcpy @@ -81,6 +83,8 @@ #include "qglengineshadermanager_p.h" #include "qgl2pexvertexarray_p.h" +#include "qtriangulatingstroker_p.h" + #include <QDebug> QT_BEGIN_NAMESPACE @@ -156,8 +160,8 @@ QGLTextureGlyphCache::QGLTextureGlyphCache(QGLContext *context, QFontEngineGlyph , m_height(0) { glGenFramebuffers(1, &m_fbo); - connect(QGLSignalProxy::instance(), SIGNAL(aboutToDestroyContext(const QGLContext *)), - SLOT(contextDestroyed(const QGLContext *))); + connect(QGLSignalProxy::instance(), SIGNAL(aboutToDestroyContext(const QGLContext*)), + SLOT(contextDestroyed(const QGLContext*))); } QGLTextureGlyphCache::~QGLTextureGlyphCache() @@ -244,7 +248,7 @@ void QGLTextureGlyphCache::resizeTextureData(int width, int height) glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, vertexCoordinateArray); glVertexAttribPointer(QT_TEXTURE_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, textureCoordinateArray); - pex->shaderManager->blitProgram()->enable(); + pex->shaderManager->blitProgram()->bind(); pex->shaderManager->blitProgram()->setUniformValue("imageTexture", QT_IMAGE_TEXTURE_UNIT); pex->shaderManager->setDirty(); @@ -342,6 +346,13 @@ extern QImage qt_imageForBrush(int brushStyle, bool invert); QGL2PaintEngineExPrivate::~QGL2PaintEngineExPrivate() { delete shaderManager; + + while (pathCaches.size()) { + QVectorPath::CacheEntry *e = *(pathCaches.constBegin()); + e->cleanup(e->engine, e->data); + e->data = 0; + e->engine = 0; + } } void QGL2PaintEngineExPrivate::updateTextureFilter(GLenum target, GLenum wrapMode, bool smoothPixmapTransform, GLuint id) @@ -393,7 +404,7 @@ void QGL2PaintEngineExPrivate::setBrush(const QBrush* brush) void QGL2PaintEngineExPrivate::useSimpleShader() { - shaderManager->simpleProgram()->enable(); + shaderManager->simpleProgram()->bind(); shaderManager->setDirty(); if (matrixDirty) @@ -533,7 +544,7 @@ void QGL2PaintEngineExPrivate::updateBrushUniforms() shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::PatternColor), col); } - QSizeF invertedTextureSize(1.0 / texPixmap.width(), 1.0 * textureInvertedY / texPixmap.height()); + QSizeF invertedTextureSize(1.0 / texPixmap.width(), 1.0 / texPixmap.height()); shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::InvertedTextureSize), invertedTextureSize); QVector2D halfViewportSize(width*0.5, height*0.5); @@ -548,7 +559,11 @@ void QGL2PaintEngineExPrivate::updateBrushUniforms() QTransform translate(1, 0, 0, 1, -translationPoint.x(), -translationPoint.y()); QTransform gl_to_qt(1, 0, 0, -1, 0, height); - QTransform inv_matrix = gl_to_qt * (brushQTransform * matrix).inverted() * translate; + QTransform inv_matrix; + if (style == Qt::TexturePattern && textureInvertedY == -1) + inv_matrix = gl_to_qt * (QTransform(1, 0, 0, -1, 0, currentBrush->texture().height()) * brushQTransform * matrix).inverted() * translate; + else + inv_matrix = gl_to_qt * (brushQTransform * matrix).inverted() * translate; shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::BrushTransform), inv_matrix); shaderManager->currentProgram()->setUniformValue(location(QGLEngineShaderManager::BrushTexture), QT_BRUSH_TEXTURE_UNIT); @@ -572,62 +587,42 @@ void QGL2PaintEngineExPrivate::updateMatrix() // // We expand out the multiplication to save the cost of a full 4x4 // matrix multiplication as most of the components are trivial. - const QTransform& transform = q->state()->matrix; - if (mode == TextDrawingMode) { - // Text drawing mode is only used for non-scaling transforms - pmvMatrix[0][0] = 2.0 / width; - pmvMatrix[0][1] = 0.0; - pmvMatrix[0][2] = 0.0; - pmvMatrix[0][3] = 0.0; - pmvMatrix[1][0] = 0.0; - pmvMatrix[1][1] = -2.0 / height; - pmvMatrix[1][2] = 0.0; - pmvMatrix[1][3] = 0.0; - pmvMatrix[2][0] = 0.0; - pmvMatrix[2][1] = 0.0; - pmvMatrix[2][2] = -1.0; - pmvMatrix[2][3] = 0.0; - pmvMatrix[3][0] = pmvMatrix[0][0] * qRound(transform.dx()) - 1.0; - pmvMatrix[3][1] = pmvMatrix[1][1] * qRound(transform.dy()) + 1.0; - pmvMatrix[3][2] = 0.0; - pmvMatrix[3][3] = 1.0; - - inverseScale = 1; - } else { - qreal wfactor = 2.0 / width; - qreal hfactor = -2.0 / height; - - pmvMatrix[0][0] = wfactor * transform.m11() - transform.m13(); - pmvMatrix[0][1] = hfactor * transform.m12() + transform.m13(); - pmvMatrix[0][2] = 0.0; - pmvMatrix[0][3] = transform.m13(); - pmvMatrix[1][0] = wfactor * transform.m21() - transform.m23(); - pmvMatrix[1][1] = hfactor * transform.m22() + transform.m23(); - pmvMatrix[1][2] = 0.0; - pmvMatrix[1][3] = transform.m23(); - pmvMatrix[2][0] = 0.0; - pmvMatrix[2][1] = 0.0; - pmvMatrix[2][2] = -1.0; - pmvMatrix[2][3] = 0.0; - pmvMatrix[3][0] = wfactor * transform.dx() - transform.m33(); - pmvMatrix[3][1] = hfactor * transform.dy() + transform.m33(); - pmvMatrix[3][2] = 0.0; - pmvMatrix[3][3] = transform.m33(); - - // 1/10000 == 0.0001, so we have good enough res to cover curves - // that span the entire widget... - inverseScale = qMax(1 / qMax( qMax(qAbs(transform.m11()), qAbs(transform.m22())), - qMax(qAbs(transform.m12()), qAbs(transform.m21())) ), - qreal(0.0001)); - } + qreal wfactor = 2.0 / width; + qreal hfactor = -2.0 / height; + + pmvMatrix[0][0] = wfactor * transform.m11() - transform.m13(); + pmvMatrix[0][1] = hfactor * transform.m12() + transform.m13(); + pmvMatrix[0][2] = 0.0; + pmvMatrix[0][3] = transform.m13(); + pmvMatrix[1][0] = wfactor * transform.m21() - transform.m23(); + pmvMatrix[1][1] = hfactor * transform.m22() + transform.m23(); + pmvMatrix[1][2] = 0.0; + pmvMatrix[1][3] = transform.m23(); + pmvMatrix[2][0] = 0.0; + pmvMatrix[2][1] = 0.0; + pmvMatrix[2][2] = -1.0; + pmvMatrix[2][3] = 0.0; + pmvMatrix[3][0] = wfactor * transform.dx() - transform.m33(); + pmvMatrix[3][1] = hfactor * transform.dy() + transform.m33(); + pmvMatrix[3][2] = 0.0; + pmvMatrix[3][3] = transform.m33(); + + // 1/10000 == 0.0001, so we have good enough res to cover curves + // that span the entire widget... + inverseScale = qMax(1 / qMax( qMax(qAbs(transform.m11()), qAbs(transform.m22())), + qMax(qAbs(transform.m12()), qAbs(transform.m21())) ), + qreal(0.0001)); matrixDirty = false; // The actual data has been updated so both shader program's uniforms need updating simpleShaderMatrixUniformDirty = true; shaderMatrixUniformDirty = true; + + dasher.setInvScale(inverseScale); + stroker.setInvScale(inverseScale); } @@ -800,17 +795,12 @@ void QGL2PaintEngineExPrivate::transferMode(EngineMode newMode) lastTexture = GLuint(-1); } - if (mode == TextDrawingMode) - matrixDirty = true; - if (newMode == TextDrawingMode) { glEnableVertexAttribArray(QT_VERTEX_COORDS_ATTR); glEnableVertexAttribArray(QT_TEXTURE_COORDS_ATTR); glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, vertexCoordinateArray.data()); glVertexAttribPointer(QT_TEXTURE_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, textureCoordinateArray.data()); - - matrixDirty = true; } if (newMode == ImageDrawingMode) { @@ -838,28 +828,30 @@ void QGL2PaintEngineExPrivate::transferMode(EngineMode newMode) mode = newMode; } -void QGL2PaintEngineExPrivate::drawOutline(const QVectorPath& path) +struct QGL2PEVectorPathCache { - transferMode(BrushDrawingMode); - - // Might need to call updateMatrix to re-calculate inverseScale - if (matrixDirty) - updateMatrix(); - - vertexCoordinateArray.clear(); - vertexCoordinateArray.addPath(path, inverseScale); - - if (path.hasImplicitClose()) { - // Close the path's outline - vertexCoordinateArray.lineToArray(path.points()[0], path.points()[1]); - vertexCoordinateArray.stops().last() += 1; - } +#ifdef QT_OPENGL_CACHE_AS_VBOS + GLuint vbo; +#else + float *vertices; +#endif + int vertexCount; + GLenum primitiveType; + qreal iscale; +}; - prepareForDraw(currentBrush->isOpaque()); - drawVertexArrays(vertexCoordinateArray, GL_LINE_STRIP); +void qopengl2paintengine_cleanup_vectorpath(QPaintEngineEx *engine, void *data) +{ + QGL2PEVectorPathCache *c = (QGL2PEVectorPathCache *) data; +#ifdef QT_OPENGL_CACHE_AS_VBOS + QGL2PaintEngineExPrivate *d = QGL2PaintEngineExPrivate::getData((QGL2PaintEngineEx *) engine); + d->unusedVBOSToClean << c->vbo; +#else + qFree(c->vertices); +#endif + delete c; } - // Assumes everything is configured for the brush you want to use void QGL2PaintEngineExPrivate::fill(const QVectorPath& path) { @@ -876,13 +868,75 @@ void QGL2PaintEngineExPrivate::fill(const QVectorPath& path) QGLRect rect(points[0].x(), points[0].y(), points[2].x(), points[2].y()); prepareForDraw(currentBrush->isOpaque()); composite(rect); - } else if (path.shape() == QVectorPath::EllipseHint - || path.shape() == QVectorPath::ConvexPolygonHint) - { - vertexCoordinateArray.clear(); - vertexCoordinateArray.addPath(path, inverseScale, false); - prepareForDraw(currentBrush->isOpaque()); - drawVertexArrays(vertexCoordinateArray, GL_TRIANGLE_FAN); + } else if (path.isConvex()) { + + if (path.isCacheable()) { + QVectorPath::CacheEntry *data = path.lookupCacheData(q); + QGL2PEVectorPathCache *cache; + + if (data) { + cache = (QGL2PEVectorPathCache *) data->data; + // Check if scale factor is exceeded for curved paths and generate curves if so... + if (path.isCurved()) { + qreal scaleFactor = cache->iscale / inverseScale; + if (scaleFactor < 0.5 || scaleFactor > 2.0) { +#ifdef QT_OPENGL_CACHE_AS_VBOS + glDeleteBuffers(1, &cache->vbo); + cache->vbo = 0; +#else + qFree(cache->vertices); +#endif + cache->vertexCount = 0; + } + } + } else { + cache = new QGL2PEVectorPathCache; + cache->vertexCount = 0; + data = const_cast<QVectorPath &>(path).addCacheData(q, cache, qopengl2paintengine_cleanup_vectorpath); + } + + // Flatten the path at the current scale factor and fill it into the cache struct. + if (!cache->vertexCount) { + vertexCoordinateArray.clear(); + vertexCoordinateArray.addPath(path, inverseScale, false); + int vertexCount = vertexCoordinateArray.vertexCount(); + int floatSizeInBytes = vertexCount * 2 * sizeof(float); + cache->vertexCount = vertexCount; + cache->primitiveType = GL_TRIANGLE_FAN; + cache->iscale = inverseScale; +#ifdef QT_OPENGL_CACHE_AS_VBOS + glGenBuffers(1, &cache->vbo); + glBindBuffer(GL_ARRAY_BUFFER, cache->vbo); + glBufferData(GL_ARRAY_BUFFER, floatSizeInBytes, vertexCoordinateArray.data(), GL_STATIC_DRAW); +#else + cache->vertices = (float *) qMalloc(floatSizeInBytes); + memcpy(cache->vertices, vertexCoordinateArray.data(), floatSizeInBytes); +#endif + } + + prepareForDraw(currentBrush->isOpaque()); + glEnableVertexAttribArray(QT_VERTEX_COORDS_ATTR); +#ifdef QT_OPENGL_CACHE_AS_VBOS + glBindBuffer(GL_ARRAY_BUFFER, cache->vbo); + glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, false, 0, 0); +#else + glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, false, 0, cache->vertices); +#endif + glDrawArrays(cache->primitiveType, 0, cache->vertexCount); + + } else { + // printf(" - Marking path as cachable...\n"); + // Tag it for later so that if the same path is drawn twice, it is assumed to be static and thus cachable + // ### Remove before release... + static bool do_vectorpath_cache = qgetenv("QT_OPENGL_NO_PATH_CACHE").isEmpty(); + if (do_vectorpath_cache) + path.makeCacheable(); + vertexCoordinateArray.clear(); + vertexCoordinateArray.addPath(path, inverseScale, false); + prepareForDraw(currentBrush->isOpaque()); + drawVertexArrays(vertexCoordinateArray, GL_TRIANGLE_FAN); + } + } else { // The path is too complicated & needs the stencil technique vertexCoordinateArray.clear(); @@ -922,8 +976,15 @@ void QGL2PaintEngineExPrivate::fill(const QVectorPath& path) } -void QGL2PaintEngineExPrivate::fillStencilWithVertexArray(QGL2PEXVertexArray& vertexArray, bool useWindingFill) +void QGL2PaintEngineExPrivate::fillStencilWithVertexArray(const float *data, + int count, + int *stops, + int stopCount, + const QGLRect &bounds, + StencilFillMode mode) { + Q_ASSERT(count || stops); + // qDebug("QGL2PaintEngineExPrivate::fillStencilWithVertexArray()"); glStencilMask(0xff); // Enable stencil writes @@ -955,19 +1016,20 @@ void QGL2PaintEngineExPrivate::fillStencilWithVertexArray(QGL2PEXVertexArray& ve } #endif - if (useWindingFill) { + if (mode == WindingFillMode) { + Q_ASSERT(stops && !count); if (q->state()->clipTestEnabled) { // Flatten clip values higher than current clip, and set high bit to match current clip glStencilFunc(GL_LEQUAL, GL_STENCIL_HIGH_BIT | q->state()->currentClip, ~GL_STENCIL_HIGH_BIT); glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE); - composite(vertexArray.boundingRect()); + composite(bounds); glStencilFunc(GL_EQUAL, GL_STENCIL_HIGH_BIT, GL_STENCIL_HIGH_BIT); } else if (!stencilClean) { // Clear stencil buffer within bounding rect glStencilFunc(GL_ALWAYS, 0, 0xff); glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); - composite(vertexArray.boundingRect()); + composite(bounds); } // Inc. for front-facing triangle @@ -975,19 +1037,43 @@ void QGL2PaintEngineExPrivate::fillStencilWithVertexArray(QGL2PEXVertexArray& ve // Dec. for back-facing "holes" glStencilOpSeparate(GL_BACK, GL_KEEP, GL_DECR_WRAP, GL_DECR_WRAP); glStencilMask(~GL_STENCIL_HIGH_BIT); - drawVertexArrays(vertexArray, GL_TRIANGLE_FAN); + drawVertexArrays(data, stops, stopCount, GL_TRIANGLE_FAN); if (q->state()->clipTestEnabled) { // Clear high bit of stencil outside of path glStencilFunc(GL_EQUAL, q->state()->currentClip, ~GL_STENCIL_HIGH_BIT); glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE); glStencilMask(GL_STENCIL_HIGH_BIT); - composite(vertexArray.boundingRect()); + composite(bounds); } - } else { + } else if (mode == OddEvenFillMode) { + glStencilMask(GL_STENCIL_HIGH_BIT); + glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); // Simply invert the stencil bit + drawVertexArrays(data, stops, stopCount, GL_TRIANGLE_FAN); + + } else { // TriStripStrokeFillMode + Q_ASSERT(count && !stops); // tristrips generated directly, so no vertexArray or stops glStencilMask(GL_STENCIL_HIGH_BIT); +#if 0 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); // Simply invert the stencil bit - drawVertexArrays(vertexArray, GL_TRIANGLE_FAN); + glEnableVertexAttribArray(QT_VERTEX_COORDS_ATTR); + glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, data); + glDrawArrays(GL_TRIANGLE_STRIP, 0, count); + glDisableVertexAttribArray(QT_VERTEX_COORDS_ATTR); +#else + + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + if (q->state()->clipTestEnabled) { + glStencilFunc(GL_LEQUAL, q->state()->currentClip | GL_STENCIL_HIGH_BIT, + ~GL_STENCIL_HIGH_BIT); + } else { + glStencilFunc(GL_ALWAYS, GL_STENCIL_HIGH_BIT, 0xff); + } + glEnableVertexAttribArray(QT_VERTEX_COORDS_ATTR); + glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, data); + glDrawArrays(GL_TRIANGLE_STRIP, 0, count); + glDisableVertexAttribArray(QT_VERTEX_COORDS_ATTR); +#endif } // Enable color writes & disable stencil writes @@ -1122,14 +1208,16 @@ void QGL2PaintEngineExPrivate::composite(const QGLRect& boundingRect) } // Draws the vertex array as a set of <vertexArrayStops.size()> triangle fans. -void QGL2PaintEngineExPrivate::drawVertexArrays(QGL2PEXVertexArray& vertexArray, GLenum primitive) +void QGL2PaintEngineExPrivate::drawVertexArrays(const float *data, int *stops, int stopCount, + GLenum primitive) { // Now setup the pointer to the vertex array: glEnableVertexAttribArray(QT_VERTEX_COORDS_ATTR); - glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, vertexArray.data()); + glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, GL_FALSE, 0, data); int previousStop = 0; - foreach(int stop, vertexArray.stops()) { + for (int i=0; i<stopCount; ++i) { + int stop = stops[i]; /* qDebug("Drawing triangle fan for vertecies %d -> %d:", previousStop, stop-1); for (int i=previousStop; i<stop; ++i) @@ -1180,14 +1268,36 @@ void QGL2PaintEngineEx::fill(const QVectorPath &path, const QBrush &brush) { Q_D(QGL2PaintEngineEx); - if (qbrush_style(brush) == Qt::NoBrush) + Qt::BrushStyle style = qbrush_style(brush); + if (style == Qt::NoBrush) return; if (!d->inRenderText) ensureActive(); + + QOpenGL2PaintEngineState *s = state(); + bool doOffset = !(s->renderHints & QPainter::Antialiasing) && + (style == Qt::SolidPattern) && + !d->multisamplingAlwaysEnabled; + + if (doOffset) { + d->temporaryTransform = s->matrix; + QTransform tx = QTransform::fromTranslate(.49, .49); + s->matrix = s->matrix * tx; + d->matrixDirty = true; + } + d->setBrush(&brush); d->fill(path); + + if (doOffset) { + s->matrix = d->temporaryTransform; + d->matrixDirty = true; + } } +extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); // qtransform.cpp + + void QGL2PaintEngineEx::stroke(const QVectorPath &path, const QPen &pen) { Q_D(QGL2PaintEngineEx); @@ -1197,23 +1307,95 @@ void QGL2PaintEngineEx::stroke(const QVectorPath &path, const QPen &pen) if (penStyle == Qt::NoPen || qbrush_style(penBrush) == Qt::NoBrush) return; + QOpenGL2PaintEngineState *s = state(); + if (pen.isCosmetic() && !qt_scaleForTransform(s->transform(), 0)) { + // QTriangulatingStroker class is not meant to support cosmetically sheared strokes. + QPaintEngineEx::stroke(path, pen); + return; + } + ensureActive(); - qreal penWidth = qpen_widthf(pen); - if ( (pen.isCosmetic() && (penStyle == Qt::SolidLine)) && (penWidth < 2.5f) ) - { - // We only handle solid, cosmetic pens with a width of 1 pixel - const QBrush& brush = pen.brush(); - d->setBrush(&brush); + bool doOffset = !(s->renderHints & QPainter::Antialiasing) && !d->multisamplingAlwaysEnabled; + if (doOffset) { + d->temporaryTransform = s->matrix; + QTransform tx = QTransform::fromTranslate(0.49, .49); + s->matrix = s->matrix * tx; + d->matrixDirty = true; + } - if (penWidth < 0.01f) - glLineWidth(1.0); - else - glLineWidth(penWidth); + bool opaque = penBrush.isOpaque() && s->opacity > 0.99; + d->setBrush(&penBrush); + d->transferMode(BrushDrawingMode); + + // updateMatrix() is responsible for setting the inverse scale on + // the strokers, so we need to call it here and not wait for + // prepareForDraw() down below. + d->updateMatrix(); + + if (penStyle == Qt::SolidLine) { + d->stroker.process(path, pen); + + } else { // Some sort of dash + d->dasher.process(path, pen); - d->drawOutline(path); - } else - return QPaintEngineEx::stroke(path, pen); + QVectorPath dashStroke(d->dasher.points(), + d->dasher.elementCount(), + d->dasher.elementTypes()); + d->stroker.process(dashStroke, pen); + } + + + QGLContext *ctx = d->ctx; + Q_UNUSED(ctx); + + if (opaque) { + d->prepareForDraw(opaque); + glEnableVertexAttribArray(QT_VERTEX_COORDS_ATTR); + glVertexAttribPointer(QT_VERTEX_COORDS_ATTR, 2, GL_FLOAT, false, 0, d->stroker.vertices()); + glDrawArrays(GL_TRIANGLE_STRIP, 0, d->stroker.vertexCount() / 2); + +// QBrush b(Qt::green); +// d->setBrush(&b); +// d->prepareForDraw(true); +// glDrawArrays(GL_LINE_STRIP, 0, d->stroker.vertexCount() / 2); + + glDisableVertexAttribArray(QT_VERTEX_COORDS_ATTR); + + } else { + qreal width = qpen_widthf(pen) / 2; + if (width == 0) + width = 0.5; + qreal extra = pen.joinStyle() == Qt::MiterJoin + ? qMax(pen.miterLimit() * width, width) + : width; + + if (pen.isCosmetic()) + extra = extra * d->inverseScale; + + QRectF bounds = path.controlPointRect().adjusted(-extra, -extra, extra, extra); + + d->fillStencilWithVertexArray(d->stroker.vertices(), d->stroker.vertexCount() / 2, + 0, 0, bounds, QGL2PaintEngineExPrivate::TriStripStrokeFillMode); + + glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE); + + // Pass when any bit is set, replace stencil value with 0 + glStencilFunc(GL_NOTEQUAL, 0, GL_STENCIL_HIGH_BIT); + d->prepareForDraw(false); + + // Stencil the brush onto the dest buffer + d->composite(bounds); + + glStencilMask(0); + + d->updateClipScissorTest(); + } + + if (doOffset) { + s->matrix = d->temporaryTransform; + d->matrixDirty = true; + } } void QGL2PaintEngineEx::penChanged() { } @@ -1336,20 +1518,21 @@ void QGL2PaintEngineEx::drawTextItem(const QPointF &p, const QTextItem &textItem const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); - bool drawCached = true; + QTransform::TransformationType txtype = s->matrix.type(); - if (s->matrix.type() > QTransform::TxTranslate) - drawCached = false; + float det = s->matrix.determinant(); + bool drawCached = txtype < QTransform::TxProject; - // don't try to cache huge fonts - if (ti.fontEngine->fontDef.pixelSize * qSqrt(s->matrix.determinant()) >= 64) + // don't try to cache huge fonts or vastly transformed fonts + const qreal pixelSize = ti.fontEngine->fontDef.pixelSize; + if (pixelSize * pixelSize * qAbs(det) >= 64 * 64 || det < 0.25f || det > 4.f) drawCached = false; QFontEngineGlyphCache::Type glyphType = ti.fontEngine->glyphFormat >= 0 ? QFontEngineGlyphCache::Type(ti.fontEngine->glyphFormat) : d->glyphCacheType; - if (d->inRenderText) + if (d->inRenderText || txtype > QTransform::TxTranslate) glyphType = QFontEngineGlyphCache::Raster_A8; if (glyphType == QFontEngineGlyphCache::Raster_RGBMask @@ -1371,7 +1554,6 @@ void QGL2PaintEngineExPrivate::drawCachedGlyphs(const QPointF &p, QFontEngineGly const QTextItemInt &ti) { Q_Q(QGL2PaintEngineEx); - QOpenGL2PaintEngineState *s = q->state(); QVarLengthArray<QFixedPoint> positions; QVarLengthArray<glyph_t> glyphs; @@ -1379,10 +1561,10 @@ void QGL2PaintEngineExPrivate::drawCachedGlyphs(const QPointF &p, QFontEngineGly ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); QGLTextureGlyphCache *cache = - (QGLTextureGlyphCache *) ti.fontEngine->glyphCache(ctx, s->matrix); + (QGLTextureGlyphCache *) ti.fontEngine->glyphCache(ctx, glyphType, QTransform()); if (!cache || cache->cacheType() != glyphType) { - cache = new QGLTextureGlyphCache(ctx, glyphType, s->matrix); + cache = new QGLTextureGlyphCache(ctx, glyphType, QTransform()); ti.fontEngine->setGlyphCache(ctx, cache); } @@ -1542,7 +1724,7 @@ void QGL2PaintEngineEx::drawPixmaps(const QDrawPixmaps::Data *drawingData, int d s = qFastSin(drawingData[i].rotation * Q_PI / 180); c = qFastCos(drawingData[i].rotation * Q_PI / 180); } - + qreal right = 0.5 * drawingData[i].scaleX * drawingData[i].source.width(); qreal bottom = 0.5 * drawingData[i].scaleY * drawingData[i].source.height(); QGLPoint bottomRight(right * c - bottom * s, right * s + bottom * c); @@ -1643,7 +1825,8 @@ bool QGL2PaintEngineEx::begin(QPaintDevice *pdev) d->device->beginPaint(); #if !defined(QT_OPENGL_ES_2) - bool success = qt_resolve_version_2_0_functions(d->ctx); + bool success = qt_resolve_version_2_0_functions(d->ctx) + && qt_resolve_buffer_extensions(d->ctx); Q_ASSERT(success); Q_UNUSED(success); #endif @@ -1663,13 +1846,18 @@ bool QGL2PaintEngineEx::begin(QPaintDevice *pdev) d->glyphCacheType = QFontEngineGlyphCache::Raster_A8; #if !defined(QT_OPENGL_ES_2) - if (!d->device->format().alpha() #if defined(Q_WS_WIN) - && qt_cleartype_enabled + if (qt_cleartype_enabled) #endif - ) { d->glyphCacheType = QFontEngineGlyphCache::Raster_RGBMask; - } +#endif + +#if defined(QT_OPENGL_ES_2) + // OpenGL ES can't switch MSAA off, so if the gl paint device is + // multisampled, it's always multisampled. + d->multisamplingAlwaysEnabled = d->device->format().sampleBuffers(); +#else + d->multisamplingAlwaysEnabled = false; #endif return true; @@ -1699,6 +1887,13 @@ bool QGL2PaintEngineEx::end() delete d->shaderManager; d->shaderManager = 0; +#ifdef QT_OPENGL_CACHE_AS_VBOS + if (!d->unusedVBOSToClean.isEmpty()) { + glDeleteBuffers(d->unusedVBOSToClean.size(), d->unusedVBOSToClean.constData()); + d->unusedVBOSToClean.clear(); + } +#endif + return false; } @@ -1989,7 +2184,7 @@ void QGL2PaintEngineExPrivate::systemStateChanged() q->state()->rectangleClip = use_system_clip ? systemClip.boundingRect() : QRect(0, 0, width, height); updateClipScissorTest(); - if (systemClip.numRects() == 1) { + if (systemClip.rectCount() == 1) { if (systemClip.boundingRect() == QRect(0, 0, width, height)) use_system_clip = false; #ifndef QT_GL_NO_SCISSOR_TEST diff --git a/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h b/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h index 5704a04..0084476 100644 --- a/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h +++ b/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h @@ -62,6 +62,7 @@ #include <private/qglpixmapfilter_p.h> #include <private/qfontengine_p.h> #include <private/qdatabuffer_p.h> +#include <private/qtriangulatingstroker_p.h> enum EngineMode { ImageDrawingMode, @@ -160,6 +161,12 @@ class QGL2PaintEngineExPrivate : public QPaintEngineExPrivate { Q_DECLARE_PUBLIC(QGL2PaintEngineEx) public: + enum StencilFillMode { + OddEvenFillMode, + WindingFillMode, + TriStripStrokeFillMode + }; + QGL2PaintEngineExPrivate(QGL2PaintEngineEx *q_ptr) : q(q_ptr), width(0), height(0), @@ -185,15 +192,24 @@ public: // fill, drawOutline, drawTexture & drawCachedGlyphs are the rendering entry points: void fill(const QVectorPath &path); - void drawOutline(const QVectorPath& path); void drawTexture(const QGLRect& dest, const QGLRect& src, const QSize &textureSize, bool opaque, bool pattern = false); void drawCachedGlyphs(const QPointF &p, QFontEngineGlyphCache::Type glyphType, const QTextItemInt &ti); - void drawVertexArrays(QGL2PEXVertexArray& vertexArray, GLenum primitive); + void drawVertexArrays(const float *data, int *stops, int stopCount, GLenum primitive); + void drawVertexArrays(QGL2PEXVertexArray &vertexArray, GLenum primitive) { + drawVertexArrays((const float *) vertexArray.data(), vertexArray.stops(), vertexArray.stopCount(), primitive); + } + // ^ draws whatever is in the vertex array void composite(const QGLRect& boundingRect); // ^ Composites the bounding rect onto dest buffer - void fillStencilWithVertexArray(QGL2PEXVertexArray& vertexArray, bool useWindingFill); + + void fillStencilWithVertexArray(const float *data, int count, int *stops, int stopCount, const QGLRect &bounds, StencilFillMode mode); + void fillStencilWithVertexArray(QGL2PEXVertexArray& vertexArray, bool useWindingFill) { + fillStencilWithVertexArray((const float *) vertexArray.data(), 0, vertexArray.stops(), vertexArray.stopCount(), + vertexArray.boundingRect(), + useWindingFill ? WindingFillMode : OddEvenFillMode); + } // ^ Calls drawVertexArrays to render into stencil buffer bool prepareForDraw(bool srcPixelsAreOpaque); @@ -205,6 +221,7 @@ public: void restoreDepthRangeForRenderText(); static QGLEngineShaderManager* shaderManagerForEngine(QGL2PaintEngineEx *engine) { return engine->d_func()->shaderManager; } + static QGL2PaintEngineExPrivate *getData(QGL2PaintEngineEx *engine) { return engine->d_func(); } QGL2PaintEngineEx* q; QGLPaintDevice* device; @@ -261,17 +278,26 @@ public: bool needsSync; bool inRenderText; + bool multisamplingAlwaysEnabled; GLfloat depthRange[2]; float textureInvertedY; + QTriangulatingStroker stroker; + QDashedStrokeProcessor dasher; + QTransform temporaryTransform; + QScopedPointer<QPixmapFilter> convolutionFilter; QScopedPointer<QPixmapFilter> colorizeFilter; QScopedPointer<QPixmapFilter> blurFilter; + QScopedPointer<QPixmapFilter> animationBlurFilter; QScopedPointer<QPixmapFilter> fastBlurFilter; QScopedPointer<QPixmapFilter> dropShadowFilter; QScopedPointer<QPixmapFilter> fastDropShadowFilter; + + QSet<QVectorPath::CacheEntry *> pathCaches; + QVector<GLuint> unusedVBOSToClean; }; QT_END_NAMESPACE diff --git a/src/opengl/gl2paintengineex/qtriangulatingstroker.cpp b/src/opengl/gl2paintengineex/qtriangulatingstroker.cpp new file mode 100644 index 0000000..6082f49 --- /dev/null +++ b/src/opengl/gl2paintengineex/qtriangulatingstroker.cpp @@ -0,0 +1,549 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtOpenGL module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtriangulatingstroker_p.h" +#include <qmath.h> + +QT_BEGIN_NAMESPACE + +#define CURVE_FLATNESS Q_PI / 8 + + + + +void QTriangulatingStroker::endCapOrJoinClosed(const qreal *start, const qreal *cur, + bool implicitClose, bool endsAtStart) +{ + if (endsAtStart) { + join(start + 2); + } else if (implicitClose) { + join(start); + lineTo(start); + join(start+2); + } else { + endCap(cur); + } + int count = m_vertices.size(); + m_vertices.add(m_vertices.at(count-2)); + m_vertices.add(m_vertices.at(count-1)); +} + + +void QTriangulatingStroker::process(const QVectorPath &path, const QPen &pen) +{ + const qreal *pts = path.points(); + const QPainterPath::ElementType *types = path.elements(); + int count = path.elementCount(); + if (count < 2) + return; + + float realWidth = qpen_widthf(pen); + if (realWidth == 0) + realWidth = 1; + + m_width = realWidth / 2; + + bool cosmetic = pen.isCosmetic(); + if (cosmetic) { + m_width = m_width * m_inv_scale; + } + + m_join_style = qpen_joinStyle(pen); + m_cap_style = qpen_capStyle(pen); + m_vertices.reset(); + m_miter_limit = pen.miterLimit() * qpen_widthf(pen); + + // The curvyness is based on the notion that I originally wanted + // roughly one line segment pr 4 pixels. This may seem little, but + // because we sample at constantly incrementing B(t) E [0<t<1], we + // will get longer segments where the curvature is small and smaller + // segments when the curvature is high. + // + // To get a rough idea of the length of each curve, I pretend that + // the curve is a 90 degree arc, whose radius is + // qMax(curveBounds.width, curveBounds.height). Based on this + // logic we can estimate the length of the outline edges based on + // the radius + a pen width and adjusting for scale factors + // depending on if the pen is cosmetic or not. + // + // The curvyness value of PI/14 was based on, + // arcLength=2*PI*r/4=PI/2 and splitting length into somewhere + // between 3 and 8 where 5 seemed to be give pretty good results + // hence: Q_PI/14. Lower divisors will give more detail at the + // direct cost of performance. + + // simplfy pens that are thin in device size (2px wide or less) + if (realWidth < 2.5 && (cosmetic || m_inv_scale == 1)) { + if (m_cap_style == Qt::RoundCap) + m_cap_style = Qt::SquareCap; + if (m_join_style == Qt::RoundJoin) + m_join_style = Qt::MiterJoin; + m_curvyness_add = 0.5; + m_curvyness_mul = CURVE_FLATNESS / m_inv_scale; + m_roundness = 1; + } else if (cosmetic) { + m_curvyness_add = realWidth / 2; + m_curvyness_mul = CURVE_FLATNESS; + m_roundness = qMax<int>(4, realWidth * CURVE_FLATNESS); + } else { + m_curvyness_add = m_width; + m_curvyness_mul = CURVE_FLATNESS / m_inv_scale; + m_roundness = qMax<int>(4, realWidth * m_curvyness_mul); + } + + // Over this level of segmentation, there doesn't seem to be any + // benefit, even for huge penWidth + if (m_roundness > 24) + m_roundness = 24; + + m_sin_theta = qFastSin(Q_PI / m_roundness); + m_cos_theta = qFastCos(Q_PI / m_roundness); + + const qreal *endPts = pts + (count<<1); + const qreal *startPts; + + Qt::PenCapStyle cap = m_cap_style; + + if (!types) { + startPts = pts; + + bool endsAtStart = startPts[0] == *(endPts-2) && startPts[1] == *(endPts-1); + + if (endsAtStart || path.hasImplicitClose()) + m_cap_style = Qt::FlatCap; + moveTo(pts); + m_cap_style = cap; + pts += 2; + lineTo(pts); + pts += 2; + while (pts < endPts) { + join(pts); + lineTo(pts); + pts += 2; + } + + endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart); + + } else { + bool endsAtStart; + while (pts < endPts) { + switch (*types) { + case QPainterPath::MoveToElement: { + if (pts != path.points()) + endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart); + + startPts = pts; + int end = (endPts - pts) / 2; + int i = 2; // Start looking to ahead since we never have two moveto's in a row + while (i<end && types[i] != QPainterPath::MoveToElement) { + ++i; + } + endsAtStart = startPts[0] == pts[i*2 - 2] && startPts[1] == pts[i*2 - 1]; + if (endsAtStart || path.hasImplicitClose()) + m_cap_style = Qt::FlatCap; + + moveTo(pts); + m_cap_style = cap; + pts+=2; + ++types; + break; } + case QPainterPath::LineToElement: + if (*(types - 1) != QPainterPath::MoveToElement) + join(pts); + lineTo(pts); + pts+=2; + ++types; + break; + case QPainterPath::CurveToElement: + if (*(types - 1) != QPainterPath::MoveToElement) + join(pts); + cubicTo(pts); + pts+=6; + types+=3; + break; + default: + Q_ASSERT(false); + break; + } + } + + endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart); + } +} + +void QTriangulatingStroker::moveTo(const qreal *pts) +{ + m_cx = pts[0]; + m_cy = pts[1]; + + float x2 = pts[2]; + float y2 = pts[3]; + normalVector(m_cx, m_cy, x2, y2, &m_nvx, &m_nvy); + + + // To acheive jumps we insert zero-area tringles. This is done by + // adding two identical points in both the end of previous strip + // and beginning of next strip + bool invisibleJump = m_vertices.size(); + + switch (m_cap_style) { + case Qt::FlatCap: + if (invisibleJump) { + m_vertices.add(m_cx + m_nvx); + m_vertices.add(m_cy + m_nvy); + } + break; + case Qt::SquareCap: { + float sx = m_cx - m_nvy; + float sy = m_cy + m_nvx; + if (invisibleJump) { + m_vertices.add(sx + m_nvx); + m_vertices.add(sy + m_nvy); + } + emitLineSegment(sx, sy, m_nvx, m_nvy); + break; } + case Qt::RoundCap: { + QVarLengthArray<float> points; + arcPoints(m_cx, m_cy, m_cx + m_nvx, m_cy + m_nvy, m_cx - m_nvx, m_cy - m_nvy, points); + m_vertices.resize(m_vertices.size() + points.size() + 2 * int(invisibleJump)); + int count = m_vertices.size(); + int front = 0; + int end = points.size() / 2; + while (front != end) { + m_vertices.at(--count) = points[2 * end - 1]; + m_vertices.at(--count) = points[2 * end - 2]; + --end; + if (front == end) + break; + m_vertices.at(--count) = points[2 * front + 1]; + m_vertices.at(--count) = points[2 * front + 0]; + ++front; + } + + if (invisibleJump) { + m_vertices.at(count - 1) = m_vertices.at(count + 1); + m_vertices.at(count - 2) = m_vertices.at(count + 0); + } + break; } + default: break; // ssssh gcc... + } + emitLineSegment(m_cx, m_cy, m_nvx, m_nvy); +} + +void QTriangulatingStroker::cubicTo(const qreal *pts) +{ + const QPointF *p = (const QPointF *) pts; + QBezier bezier = QBezier::fromPoints(*(p - 1), p[0], p[1], p[2]); + + QRectF bounds = bezier.bounds(); + float rad = qMax(bounds.width(), bounds.height()); + int threshold = qMin<float>(64, (rad + m_curvyness_add) * m_curvyness_mul); + if (threshold < 4) + threshold = 4; + qreal threshold_minus_1 = threshold - 1; + float vx, vy; + + float cx = m_cx, cy = m_cy; + float x, y; + + for (int i=1; i<threshold; ++i) { + qreal t = qreal(i) / threshold_minus_1; + QPointF p = bezier.pointAt(t); + x = p.x(); + y = p.y(); + + normalVector(cx, cy, x, y, &vx, &vy); + + emitLineSegment(x, y, vx, vy); + + cx = x; + cy = y; + } + + m_cx = cx; + m_cy = cy; + + m_nvx = vx; + m_nvy = vy; +} + +void QTriangulatingStroker::join(const qreal *pts) +{ + // Creates a join to the next segment (m_cx, m_cy) -> (pts[0], pts[1]) + normalVector(m_cx, m_cy, pts[0], pts[1], &m_nvx, &m_nvy); + + switch (m_join_style) { + case Qt::BevelJoin: + break; + case Qt::SvgMiterJoin: + case Qt::MiterJoin: { + // Find out on which side the join should be. + int count = m_vertices.size(); + float prevNvx = m_vertices.at(count - 2) - m_cx; + float prevNvy = m_vertices.at(count - 1) - m_cy; + float xprod = prevNvx * m_nvy - prevNvy * m_nvx; + float px, py, qx, qy; + + // If the segments are parallel, use bevel join. + if (qFuzzyIsNull(xprod)) + break; + + // Find the corners of the previous and next segment to join. + if (xprod < 0) { + px = m_vertices.at(count - 2); + py = m_vertices.at(count - 1); + qx = m_cx - m_nvx; + qy = m_cy - m_nvy; + } else { + px = m_vertices.at(count - 4); + py = m_vertices.at(count - 3); + qx = m_cx + m_nvx; + qy = m_cy + m_nvy; + } + + // Find intersection point. + float pu = px * prevNvx + py * prevNvy; + float qv = qx * m_nvx + qy * m_nvy; + float ix = (m_nvy * pu - prevNvy * qv) / xprod; + float iy = (prevNvx * qv - m_nvx * pu) / xprod; + + // Check that the distance to the intersection point is less than the miter limit. + if ((ix - px) * (ix - px) + (iy - py) * (iy - py) <= m_miter_limit * m_miter_limit) { + m_vertices.add(ix); + m_vertices.add(iy); + m_vertices.add(ix); + m_vertices.add(iy); + } + // else + // Do a plain bevel join if the miter limit is exceeded or if + // the lines are parallel. This is not what the raster + // engine's stroker does, but it is both faster and similar to + // what some other graphics API's do. + + break; } + case Qt::RoundJoin: { + QVarLengthArray<float> points; + int count = m_vertices.size(); + float prevNvx = m_vertices.at(count - 2) - m_cx; + float prevNvy = m_vertices.at(count - 1) - m_cy; + if (m_nvx * prevNvy - m_nvy * prevNvx < 0) { + arcPoints(0, 0, m_nvx, m_nvy, -prevNvx, -prevNvy, points); + for (int i = points.size() / 2; i > 0; --i) + emitLineSegment(m_cx, m_cy, points[2 * i - 2], points[2 * i - 1]); + } else { + arcPoints(0, 0, -prevNvx, -prevNvy, m_nvx, m_nvy, points); + for (int i = 0; i < points.size() / 2; ++i) + emitLineSegment(m_cx, m_cy, points[2 * i + 0], points[2 * i + 1]); + } + break; } + default: break; // gcc warn-- + } + + emitLineSegment(m_cx, m_cy, m_nvx, m_nvy); +} + +void QTriangulatingStroker::endCap(const qreal *) +{ + switch (m_cap_style) { + case Qt::FlatCap: + break; + case Qt::SquareCap: + emitLineSegment(m_cx + m_nvy, m_cy - m_nvx, m_nvx, m_nvy); + break; + case Qt::RoundCap: { + QVarLengthArray<float> points; + int count = m_vertices.size(); + arcPoints(m_cx, m_cy, m_vertices.at(count - 2), m_vertices.at(count - 1), m_vertices.at(count - 4), m_vertices.at(count - 3), points); + int front = 0; + int end = points.size() / 2; + while (front != end) { + m_vertices.add(points[2 * end - 2]); + m_vertices.add(points[2 * end - 1]); + --end; + if (front == end) + break; + m_vertices.add(points[2 * front + 0]); + m_vertices.add(points[2 * front + 1]); + ++front; + } + break; } + default: break; // to shut gcc up... + } +} + +void QTriangulatingStroker::arcPoints(float cx, float cy, float fromX, float fromY, float toX, float toY, QVarLengthArray<float> &points) +{ + float dx1 = fromX - cx; + float dy1 = fromY - cy; + float dx2 = toX - cx; + float dy2 = toY - cy; + + // while more than 180 degrees left: + while (dx1 * dy2 - dx2 * dy1 < 0) { + float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta; + float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta; + dx1 = tmpx; + dy1 = tmpy; + points.append(cx + dx1); + points.append(cy + dy1); + } + + // while more than 90 degrees left: + while (dx1 * dx2 + dy1 * dy2 < 0) { + float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta; + float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta; + dx1 = tmpx; + dy1 = tmpy; + points.append(cx + dx1); + points.append(cy + dy1); + } + + // while more than 0 degrees left: + while (dx1 * dy2 - dx2 * dy1 > 0) { + float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta; + float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta; + dx1 = tmpx; + dy1 = tmpy; + points.append(cx + dx1); + points.append(cy + dy1); + } + + // remove last point which was rotated beyond [toX, toY]. + if (!points.isEmpty()) + points.resize(points.size() - 2); +} + +static void qdashprocessor_moveTo(qreal x, qreal y, void *data) +{ + ((QDashedStrokeProcessor *) data)->addElement(QPainterPath::MoveToElement, x, y); +} + +static void qdashprocessor_lineTo(qreal x, qreal y, void *data) +{ + ((QDashedStrokeProcessor *) data)->addElement(QPainterPath::LineToElement, x, y); +} + +static void qdashprocessor_cubicTo(qreal, qreal, qreal, qreal, qreal, qreal, void *) +{ + Q_ASSERT(0); // The dasher should not produce curves... +} + +QDashedStrokeProcessor::QDashedStrokeProcessor() + : m_dash_stroker(0), m_inv_scale(1) +{ + m_dash_stroker.setMoveToHook(qdashprocessor_moveTo); + m_dash_stroker.setLineToHook(qdashprocessor_lineTo); + m_dash_stroker.setCubicToHook(qdashprocessor_cubicTo); +} + +void QDashedStrokeProcessor::process(const QVectorPath &path, const QPen &pen) +{ + + const qreal *pts = path.points(); + const QPainterPath::ElementType *types = path.elements(); + int count = path.elementCount(); + + m_points.reset(); + m_types.reset(); + + qreal width = qpen_widthf(pen); + if (width == 0) + width = 1; + + m_dash_stroker.setDashPattern(pen.dashPattern()); + m_dash_stroker.setStrokeWidth(pen.isCosmetic() ? width * m_inv_scale : width); + m_dash_stroker.setMiterLimit(pen.miterLimit()); + qreal curvyness = sqrt(width) * m_inv_scale / 8; + + if (count < 2) + return; + + const qreal *endPts = pts + (count<<1); + + m_dash_stroker.begin(this); + + if (!types) { + m_dash_stroker.moveTo(pts[0], pts[1]); + pts += 2; + while (pts < endPts) { + m_dash_stroker.lineTo(pts[0], pts[1]); + pts += 2; + } + } else { + while (pts < endPts) { + switch (*types) { + case QPainterPath::MoveToElement: + m_dash_stroker.moveTo(pts[0], pts[1]); + pts += 2; + ++types; + break; + case QPainterPath::LineToElement: + m_dash_stroker.lineTo(pts[0], pts[1]); + pts += 2; + ++types; + break; + case QPainterPath::CurveToElement: { + QBezier b = QBezier::fromPoints(*(((const QPointF *) pts) - 1), + *(((const QPointF *) pts)), + *(((const QPointF *) pts) + 1), + *(((const QPointF *) pts) + 2)); + QRectF bounds = b.bounds(); + int threshold = qMin<float>(64, qMax(bounds.width(), bounds.height()) * curvyness); + if (threshold < 4) + threshold = 4; + qreal threshold_minus_1 = threshold - 1; + for (int i=0; i<threshold; ++i) { + QPointF pt = b.pointAt(i / threshold_minus_1); + m_dash_stroker.lineTo(pt.x(), pt.y()); + } + pts += 6; + types += 3; + break; } + default: break; + } + } + } + + m_dash_stroker.end(); +} + +QT_END_NAMESPACE + diff --git a/src/opengl/gl2paintengineex/qtriangulatingstroker_p.h b/src/opengl/gl2paintengineex/qtriangulatingstroker_p.h new file mode 100644 index 0000000..2dba0ce --- /dev/null +++ b/src/opengl/gl2paintengineex/qtriangulatingstroker_p.h @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtOpenGL module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTRIANGULATINGSTROKER_P_H +#define QTRIANGULATINGSTROKER_P_H + +#include <private/qdatabuffer_p.h> +#include <qvarlengtharray.h> +#include <private/qvectorpath_p.h> +#include <private/qbezier_p.h> +#include <private/qnumeric_p.h> +#include <private/qmath_p.h> + +QT_BEGIN_NAMESPACE + +class QTriangulatingStroker +{ +public: + void process(const QVectorPath &path, const QPen &pen); + + inline int vertexCount() const { return m_vertices.size(); } + inline const float *vertices() const { return m_vertices.data(); } + + inline void setInvScale(qreal invScale) { m_inv_scale = invScale; } + +private: + inline void emitLineSegment(float x, float y, float nx, float ny); + void moveTo(const qreal *pts); + inline void lineTo(const qreal *pts); + void cubicTo(const qreal *pts); + void join(const qreal *pts); + inline void normalVector(float x1, float y1, float x2, float y2, float *nx, float *ny); + void endCap(const qreal *pts); + void arcPoints(float cx, float cy, float fromX, float fromY, float toX, float toY, QVarLengthArray<float> &points); + void endCapOrJoinClosed(const qreal *start, const qreal *cur, bool implicitClose, bool endsAtStart); + + + QDataBuffer<float> m_vertices; + + float m_cx, m_cy; // current points + float m_nvx, m_nvy; // normal vector... + float m_width; + qreal m_miter_limit; + + int m_roundness; // Number of line segments in a round join + qreal m_sin_theta; // sin(m_roundness / 360); + qreal m_cos_theta; // cos(m_roundness / 360); + qreal m_inv_scale; + float m_curvyness_mul; + float m_curvyness_add; + + Qt::PenJoinStyle m_join_style; + Qt::PenCapStyle m_cap_style; +}; + +class QDashedStrokeProcessor +{ +public: + QDashedStrokeProcessor(); + + void process(const QVectorPath &path, const QPen &pen); + + inline void addElement(QPainterPath::ElementType type, qreal x, qreal y) { + m_points.add(x); + m_points.add(y); + m_types.add(type); + } + + inline int elementCount() const { return m_types.size(); } + inline qreal *points() const { return m_points.data(); } + inline QPainterPath::ElementType *elementTypes() const { return m_types.data(); } + + inline void setInvScale(qreal invScale) { m_inv_scale = invScale; } + +private: + QDataBuffer<qreal> m_points; + QDataBuffer<QPainterPath::ElementType> m_types; + QDashStroker m_dash_stroker; + qreal m_inv_scale; +}; + +inline void QTriangulatingStroker::normalVector(float x1, float y1, float x2, float y2, + float *nx, float *ny) +{ + float dx = x2 - x1; + float dy = y2 - y1; + + float pw; + + if (dx == 0) + pw = m_width / qAbs(dy); + else if (dy == 0) + pw = m_width / qAbs(dx); + else + pw = m_width / sqrt(dx*dx + dy*dy); + + *nx = -dy * pw; + *ny = dx * pw; +} + +inline void QTriangulatingStroker::emitLineSegment(float x, float y, float vx, float vy) +{ + m_vertices.add(x + vx); + m_vertices.add(y + vy); + m_vertices.add(x - vx); + m_vertices.add(y - vy); +} + +void QTriangulatingStroker::lineTo(const qreal *pts) +{ + emitLineSegment(pts[0], pts[1], m_nvx, m_nvy); + m_cx = pts[0]; + m_cy = pts[1]; +} + +QT_END_NAMESPACE + +#endif |