From 32c3de3d02193c994c701f085cd64d864563bac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20R=C3=B8dal?= Date: Thu, 17 Sep 2009 10:52:05 +0200 Subject: Optimized GL2 engine to use scissor clipping more aggressively. There's no reason to stop using a scissor clip when a more complex clip is set. Instead, we can use a combination of scissoring and depth clipping to represent the final clip. When intersecting with a new clip path, if the clip path is a rectangle we simply intersect it against the scissor clip, and otherwise we intersect its bounding rect against the scissor clip and write the actual path to the depth buffer. The patch simplifies the logic in clip() quite a bit, except in the UniteClip case in which we don't care about performance anyways. It also fixes a bug which could cause rendering errors if the stencil buffer contains junk before painting. Reviewed-by: Gunnar Sletta --- .../gl2paintengineex/qpaintengineex_opengl2.cpp | 163 ++++++++---------- .../gl2paintengineex/qpaintengineex_opengl2_p.h | 4 +- tests/auto/qgl/tst_qgl.cpp | 184 ++++++++++++++++++++- 3 files changed, 255 insertions(+), 96 deletions(-) diff --git a/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp b/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp index ba2a0ea..fb493e6 100644 --- a/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp +++ b/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp @@ -750,6 +750,7 @@ void QGL2PaintEngineEx::beginNativePainting() #endif d->lastTexture = GLuint(-1); + d->dirtyStencilRegion = QRect(0, 0, d->width, d->height); d->resetGLState(); d->needsSync = true; @@ -894,12 +895,12 @@ void QGL2PaintEngineExPrivate::fillStencilWithVertexArray(QGL2PEXVertexArray& ve // qDebug("QGL2PaintEngineExPrivate::fillStencilWithVertexArray()"); glStencilMask(0xFFFF); // Enable stencil writes - if (stencilBufferDirty) { + if (dirtyStencilRegion.intersects(currentScissorBounds)) { // Clear the stencil buffer to zeros glDisable(GL_STENCIL_TEST); glClearStencil(0); // Clear to zero glClear(GL_STENCIL_BUFFER_BIT); - stencilBufferDirty = false; + dirtyStencilRegion -= currentScissorBounds; } glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // Disable color writes @@ -1419,13 +1420,14 @@ bool QGL2PaintEngineEx::begin(QPaintDevice *pdev) d->brushUniformsDirty = true; d->matrixDirty = true; d->compositionModeDirty = true; - d->stencilBufferDirty = true; d->simpleShaderDepthUniformDirty = true; d->depthUniformDirty = true; d->opacityUniformDirty = true; d->needsSync = true; d->use_system_clip = !systemClip().isEmpty(); + d->dirtyStencilRegion = QRect(0, 0, d->width, d->height); + // Calling begin paint should make the correct context current. So, any // code which calls into GL or otherwise needs a current context *must* // go after beginPaint: @@ -1521,19 +1523,26 @@ void QGL2PaintEngineExPrivate::updateDepthScissorTest() else glDisable(GL_DEPTH_TEST); - if (q->state()->scissorTestEnabled) { - QRect bounds = q->state()->rectangleClip; - if (bounds.isNull() || !q->painter()->hasClipping()) { - if (use_system_clip) - bounds = systemClip.boundingRect(); - else - bounds = QRect(0, 0, width, height); - } + QRect bounds = q->state()->rectangleClip; + if (!q->state()->clipEnabled) { + if (use_system_clip) + bounds = systemClip.boundingRect(); + else + bounds = QRect(0, 0, width, height); + } else { + if (use_system_clip) + bounds = bounds.intersected(systemClip.boundingRect()); + else + bounds = bounds.intersected(QRect(0, 0, width, height)); + } + currentScissorBounds = bounds; + + if (bounds == QRect(0, 0, width, height)) { + glDisable(GL_SCISSOR_TEST); + } else { glEnable(GL_SCISSOR_TEST); setScissor(bounds); - } else { - glDisable(GL_SCISSOR_TEST); } } @@ -1573,7 +1582,6 @@ void QGL2PaintEngineExPrivate::writeClip(const QVectorPath &path, uint depth) if (matrixDirty) updateMatrix(); - if (q->state()->needsDepthBufferClear) { glDepthMask(true); glClearDepth(rawDepth(2)); @@ -1624,90 +1632,81 @@ void QGL2PaintEngineEx::clip(const QVectorPath &path, Qt::ClipOperation op) ensureActive(); - if (op == Qt::ReplaceClip && !d->hasClipOperations()) + if (op == Qt::ReplaceClip) { op = Qt::IntersectClip; + if (d->hasClipOperations()) { + d->systemStateChanged(); + state()->canRestoreClip = false; + } + } if (!path.isEmpty() && op == Qt::IntersectClip && (path.shape() == QVectorPath::RectangleHint)) { const QPointF* const points = reinterpret_cast(path.points()); QRectF rect(points[0], points[2]); if (state()->matrix.type() <= QTransform::TxScale) { - rect = state()->matrix.mapRect(rect); - - if ((d->use_system_clip && rect.contains(d->systemClip.boundingRect())) - || rect.contains(QRect(0, 0, d->width, d->height))) - return; - - if (state()->rectangleClip.isValid()) { - state()->rectangleClip = state()->rectangleClip.intersected(rect.toRect()); - - state()->hasRectangleClip = true; - state()->scissorTestEnabled = true; - - glEnable(GL_SCISSOR_TEST); - d->setScissor(state()->rectangleClip); - - return; - } + state()->rectangleClip = state()->rectangleClip.intersected(state()->matrix.mapRect(rect).toRect()); + d->updateDepthScissorTest(); + return; } } - if (!state()->hasRectangleClip) - state()->rectangleClip = QRect(); - - if (state()->rectangleClip.isValid() && op != Qt::NoClip && op != Qt::ReplaceClip) { - QPainterPath path; - path.addRect(state()->rectangleClip); - path = state()->matrix.inverted().map(path); - - state()->rectangleClip = QRect(); - d->updateDepthScissorTest(); - - glDepthFunc(GL_ALWAYS); - - state()->maxDepth = 4; - d->writeClip(qtVectorPathForPath(path), state()->maxDepth); - state()->currentDepth = 3; - state()->depthTestEnabled = true; - - glDepthFunc(GL_LESS); - glEnable(GL_DEPTH_TEST); - } + const QRect pathRect = state()->matrix.mapRect(path.controlPointRect()).toAlignedRect(); switch (op) { case Qt::NoClip: if (d->use_system_clip) { - glEnable(GL_DEPTH_TEST); state()->depthTestEnabled = true; state()->currentDepth = 0; } else { - glDisable(GL_DEPTH_TEST); state()->depthTestEnabled = false; } + state()->rectangleClip = QRect(0, 0, d->width, d->height); state()->canRestoreClip = false; + d->updateDepthScissorTest(); break; case Qt::IntersectClip: + state()->rectangleClip = state()->rectangleClip.intersected(pathRect); + d->updateDepthScissorTest(); ++state()->maxDepth; d->writeClip(path, state()->maxDepth); state()->currentDepth = state()->maxDepth - 1; state()->depthTestEnabled = true; break; - case Qt::ReplaceClip: - d->systemStateChanged(); - state()->rectangleClip = QRect(); - state()->maxDepth = 4; - glDepthFunc(GL_ALWAYS); - d->writeClip(path, state()->maxDepth); - state()->currentDepth = 3; - state()->canRestoreClip = false; - state()->depthTestEnabled = true; - break; - case Qt::UniteClip: + case Qt::UniteClip: { + if (state()->rectangleClip.isValid()) { + ++state()->maxDepth; + + QPainterPath path; + path.addRect(state()->rectangleClip); + + // flush the existing clip rectangle to the depth buffer + d->writeClip(qtVectorPathForPath(state()->matrix.inverted().map(path)), state()->maxDepth); + } + + QRect oldRectangleClip = state()->rectangleClip; + + state()->rectangleClip = state()->rectangleClip.united(pathRect); + d->updateDepthScissorTest(); + + QRegion extendRegion = QRegion(state()->rectangleClip) - oldRectangleClip; + glDepthFunc(GL_ALWAYS); + if (!extendRegion.isEmpty()) { + QPainterPath extendPath; + extendPath.addRegion(extendRegion); + + // first clear the depth buffer in the extended region + d->writeClip(qtVectorPathForPath(state()->matrix.inverted().map(extendPath)), 0); + } + + // now write the clip path d->writeClip(path, state()->maxDepth); state()->canRestoreClip = false; + state()->currentDepth = state()->maxDepth - 1; state()->depthTestEnabled = true; break; + } } glDepthFunc(GL_LESS); @@ -1739,39 +1738,22 @@ void QGL2PaintEngineExPrivate::systemStateChanged() } } - glDisable(GL_DEPTH_TEST); q->state()->depthTestEnabled = false; - q->state()->scissorTestEnabled = false; q->state()->needsDepthBufferClear = true; - q->state()->hasRectangleClip = false; - - glDisable(GL_SCISSOR_TEST); q->state()->currentDepth = 1; q->state()->maxDepth = 4; - q->state()->rectangleClip = QRect(0, 0, width, height); + q->state()->rectangleClip = use_system_clip ? systemClip.boundingRect() : QRect(0, 0, width, height); + updateDepthScissorTest(); if (use_system_clip) { if (systemClip.numRects() == 1) { - QRect bounds = systemClip.boundingRect(); - if (bounds == QRect(0, 0, width, height)) { + if (q->state()->rectangleClip == QRect(0, 0, width, height)) { use_system_clip = false; return; } - - q->state()->rectangleClip = bounds; - q->state()->scissorTestEnabled = true; - updateDepthScissorTest(); } else { - q->state()->rectangleClip = QRect(); - q->state()->scissorTestEnabled = true; - updateDepthScissorTest(); - - QTransform transform = q->state()->matrix; - q->state()->matrix = QTransform(); - q->transformChanged(); - q->state()->needsDepthBufferClear = false; glDepthMask(true); @@ -1783,17 +1765,13 @@ void QGL2PaintEngineExPrivate::systemStateChanged() path.addRegion(systemClip); glDepthFunc(GL_ALWAYS); - writeClip(qtVectorPathForPath(path), 2); + writeClip(qtVectorPathForPath(q->state()->matrix.inverted().map(path)), 2); glDepthFunc(GL_LESS); glEnable(GL_DEPTH_TEST); q->state()->depthTestEnabled = true; - - q->state()->matrix = transform; - q->transformChanged(); } - q->state()->currentDepth = 1; simpleShaderDepthUniformDirty = true; depthUniformDirty = true; } @@ -1870,18 +1848,15 @@ QOpenGL2PaintEngineState::QOpenGL2PaintEngineState(QOpenGL2PaintEngineState &oth maxDepth = other.maxDepth; canRestoreClip = other.canRestoreClip; rectangleClip = other.rectangleClip; - hasRectangleClip = other.hasRectangleClip; } QOpenGL2PaintEngineState::QOpenGL2PaintEngineState() { needsDepthBufferClear = true; depthTestEnabled = false; - scissorTestEnabled = false; currentDepth = 1; maxDepth = 4; canRestoreClip = true; - hasRectangleClip = false; } QOpenGL2PaintEngineState::~QOpenGL2PaintEngineState() diff --git a/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h b/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h index 8b09f41..049994f 100644 --- a/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h +++ b/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h @@ -217,11 +217,13 @@ public: bool brushUniformsDirty; bool simpleShaderMatrixUniformDirty; bool shaderMatrixUniformDirty; - bool stencilBufferDirty; bool depthUniformDirty; bool simpleShaderDepthUniformDirty; bool opacityUniformDirty; + QRegion dirtyStencilRegion; + QRect currentScissorBounds; + const QBrush* currentBrush; // May not be the state's brush! GLfloat inverseScale; diff --git a/tests/auto/qgl/tst_qgl.cpp b/tests/auto/qgl/tst_qgl.cpp index 21f7359..999d119 100644 --- a/tests/auto/qgl/tst_qgl.cpp +++ b/tests/auto/qgl/tst_qgl.cpp @@ -81,8 +81,9 @@ private slots: void stackedFBOs(); void colormap(); void fboFormat(); - void testDontCrashOnDanglingResources(); + void replaceClipping(); + void clipTest(); }; tst_QGL::tst_QGL() @@ -1539,5 +1540,186 @@ void tst_QGL::testDontCrashOnDanglingResources() widget->hide(); } +class ReplaceClippingGLWidget : public QGLWidget +{ +public: + void paint(QPainter *painter) + { + painter->fillRect(rect(), Qt::white); + + QPainterPath path; + path.addRect(0, 0, 100, 100); + path.addRect(50, 50, 100, 100); + + painter->setClipRect(0, 0, 150, 150); + painter->fillPath(path, Qt::red); + + painter->translate(150, 150); + painter->setClipRect(0, 0, 150, 150); + painter->fillPath(path, Qt::red); + } + +protected: + void paintEvent(QPaintEvent*) + { + // clear the stencil with junk + glStencilMask(0xFFFF); + glClearStencil(0xFFFF); + glDisable(GL_STENCIL_TEST); + glDisable(GL_SCISSOR_TEST); + glClear(GL_STENCIL_BUFFER_BIT); + + QPainter painter(this); + paint(&painter); + } +}; + +void tst_QGL::replaceClipping() +{ + ReplaceClippingGLWidget glw; + glw.resize(300, 300); + glw.show(); + +#ifdef Q_WS_X11 + qt_x11_wait_for_window_manager(&glw); +#endif + QTest::qWait(200); + + QImage reference(300, 300, QImage::Format_RGB32); + QPainter referencePainter(&reference); + glw.paint(&referencePainter); + referencePainter.end(); + + const QImage widgetFB = glw.grabFrameBuffer(false).convertToFormat(QImage::Format_RGB32); + + QCOMPARE(widgetFB, reference); +} + +class ClipTestGLWidget : public QGLWidget +{ +public: + void paint(QPainter *painter) + { + painter->fillRect(rect(), Qt::white); + painter->setClipRect(10, 10, width()-20, height()-20); + painter->fillRect(rect(), Qt::cyan); + + painter->save(); + painter->setClipRect(10, 10, 100, 100, Qt::IntersectClip); + + painter->fillRect(rect(), Qt::blue); + + painter->save(); + painter->setClipRect(10, 10, 50, 50, Qt::IntersectClip); + painter->fillRect(rect(), Qt::red); + painter->restore(); + painter->fillRect(0, 0, 40, 40, Qt::white); + painter->save(); + + painter->setClipRect(0, 0, 35, 35, Qt::IntersectClip); + painter->fillRect(rect(), Qt::black); + painter->restore(); + + painter->fillRect(0, 0, 30, 30, Qt::magenta); + + painter->save(); + painter->setClipRect(60, 10, 50, 50, Qt::ReplaceClip); + painter->fillRect(rect(), Qt::green); + painter->restore(); + + painter->save(); + painter->setClipRect(0, 60, 60, 25, Qt::IntersectClip); + painter->setClipRect(60, 60, 50, 25, Qt::UniteClip); + painter->fillRect(rect(), Qt::yellow); + painter->restore(); + + painter->restore(); + + painter->translate(100, 100); + + { + QPainterPath path; + path.addRect(10, 10, 100, 100); + path.addRect(10, 10, 10, 10); + painter->setClipPath(path, Qt::IntersectClip); + } + + painter->fillRect(rect(), Qt::blue); + + painter->save(); + { + QPainterPath path; + path.addRect(10, 10, 50, 50); + path.addRect(10, 10, 10, 10); + painter->setClipPath(path, Qt::IntersectClip); + } + painter->fillRect(rect(), Qt::red); + painter->restore(); + painter->fillRect(0, 0, 40, 40, Qt::white); + painter->save(); + + { + QPainterPath path; + path.addRect(0, 0, 35, 35); + path.addRect(10, 10, 10, 10); + painter->setClipPath(path, Qt::IntersectClip); + } + painter->fillRect(rect(), Qt::black); + painter->restore(); + + painter->fillRect(0, 0, 30, 30, Qt::magenta); + + painter->save(); + { + QPainterPath path; + path.addRect(60, 10, 50, 50); + path.addRect(10, 10, 10, 10); + painter->setClipPath(path, Qt::ReplaceClip); + } + painter->fillRect(rect(), Qt::green); + painter->restore(); + + painter->save(); + { + QPainterPath path; + path.addRect(0, 60, 60, 25); + path.addRect(10, 10, 10, 10); + painter->setClipPath(path, Qt::IntersectClip); + } + painter->setClipRect(60, 60, 50, 25, Qt::UniteClip); + painter->fillRect(rect(), Qt::yellow); + painter->restore(); + } + +protected: + void paintEvent(QPaintEvent*) + { + QPainter painter(this); + paint(&painter); + } +}; + +void tst_QGL::clipTest() +{ + ClipTestGLWidget glw; + glw.resize(220, 220); + glw.show(); + +#ifdef Q_WS_X11 + qt_x11_wait_for_window_manager(&glw); +#endif + QTest::qWait(200); + + QImage reference(glw.size(), QImage::Format_RGB32); + QPainter referencePainter(&reference); + glw.paint(&referencePainter); + referencePainter.end(); + + const QImage widgetFB = glw.grabFrameBuffer(false).convertToFormat(QImage::Format_RGB32); + + QCOMPARE(widgetFB, reference); +} + + QTEST_MAIN(tst_QGL) #include "tst_qgl.moc" -- cgit v0.12