From 5575226f6b25bcf507c3412677756e07ce671cef Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Mon, 9 Jan 2017 11:43:31 +0100 Subject: PathItem: add optional threaded triangulation Spending 300+ ms in polish for the tiger when running with the generic backend is not going to fly. Therefore, add an optional mode for asynchronous triangulation. Change-Id: Ida44c7b8a28d38fb11243a6657221f039c62e21b Reviewed-by: Andy Nichols --- .../quick/pathitem/content/pathitemgallery.qml | 1 - examples/quick/pathitem/content/tiger.qml | 2 + src/quick/items/qquickpathitem.cpp | 20 ++- src/quick/items/qquickpathitem_p.h | 5 + src/quick/items/qquickpathitem_p_p.h | 3 +- src/quick/items/qquickpathitemgenericrenderer.cpp | 197 ++++++++++++++++----- src/quick/items/qquickpathitemgenericrenderer_p.h | 70 ++++++-- src/quick/items/qquickpathitemnvprrenderer.cpp | 2 +- src/quick/items/qquickpathitemnvprrenderer_p.h | 2 +- src/quick/items/qquickpathitemsoftwarerenderer.cpp | 2 +- src/quick/items/qquickpathitemsoftwarerenderer_p.h | 2 +- 11 files changed, 245 insertions(+), 61 deletions(-) diff --git a/examples/quick/pathitem/content/pathitemgallery.qml b/examples/quick/pathitem/content/pathitemgallery.qml index 88fe5b646d..3ade189ffd 100644 --- a/examples/quick/pathitem/content/pathitemgallery.qml +++ b/examples/quick/pathitem/content/pathitemgallery.qml @@ -170,7 +170,6 @@ Rectangle { cellHeight: 300 delegate: pathGalleryDelegate model: pathGalleryModel - cacheBuffer: 1000 } } diff --git a/examples/quick/pathitem/content/tiger.qml b/examples/quick/pathitem/content/tiger.qml index 2013cc9f2b..7d275ba22f 100644 --- a/examples/quick/pathitem/content/tiger.qml +++ b/examples/quick/pathitem/content/tiger.qml @@ -53,6 +53,8 @@ import QtQuick 2.9 PathItem { id: pathItem + asynchronous: true + anchors.fill: parent scale: 0.4 diff --git a/src/quick/items/qquickpathitem.cpp b/src/quick/items/qquickpathitem.cpp index d7131b3833..964d997806 100644 --- a/src/quick/items/qquickpathitem.cpp +++ b/src/quick/items/qquickpathitem.cpp @@ -361,6 +361,7 @@ QQuickPathItemPrivate::QQuickPathItemPrivate() : componentComplete(true), vpChanged(false), rendererType(QQuickPathItem::UnknownRenderer), + async(false), renderer(nullptr) { } @@ -393,6 +394,23 @@ QQuickPathItem::RendererType QQuickPathItem::rendererType() const return d->rendererType; } +bool QQuickPathItem::asynchronous() const +{ + Q_D(const QQuickPathItem); + return d->async; +} + +void QQuickPathItem::setAsynchronous(bool async) +{ + Q_D(QQuickPathItem); + if (d->async != async) { + d->async = async; + emit asynchronousChanged(); + if (d->componentComplete) + d->_q_visualPathChanged(); + } +} + static QQuickVisualPath *vpe_at(QQmlListProperty *property, int index) { QQuickPathItemPrivate *d = QQuickPathItemPrivate::get(static_cast(property->object)); @@ -606,7 +624,7 @@ void QQuickPathItemPrivate::sync() dirty = 0; } - renderer->endSync(); + renderer->endSync(async); } // ***** gradient support ***** diff --git a/src/quick/items/qquickpathitem_p.h b/src/quick/items/qquickpathitem_p.h index 0c1d0f061b..7c962d01fc 100644 --- a/src/quick/items/qquickpathitem_p.h +++ b/src/quick/items/qquickpathitem_p.h @@ -263,6 +263,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickPathItem : public QQuickItem { Q_OBJECT Q_PROPERTY(RendererType renderer READ rendererType NOTIFY rendererChanged) + Q_PROPERTY(bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY asynchronousChanged) Q_PROPERTY(QQmlListProperty visualPaths READ visualPaths) Q_CLASSINFO("DefaultProperty", "visualPaths") @@ -280,6 +281,9 @@ public: RendererType rendererType() const; + bool asynchronous() const; + void setAsynchronous(bool async); + QQmlListProperty visualPaths(); protected: @@ -291,6 +295,7 @@ protected: Q_SIGNALS: void rendererChanged(); + void asynchronousChanged(); private: Q_DISABLE_COPY(QQuickPathItem) diff --git a/src/quick/items/qquickpathitem_p_p.h b/src/quick/items/qquickpathitem_p_p.h index 5e6400edc6..faf7b1e451 100644 --- a/src/quick/items/qquickpathitem_p_p.h +++ b/src/quick/items/qquickpathitem_p_p.h @@ -79,7 +79,7 @@ public: virtual void setStrokeStyle(int index, QQuickVisualPath::StrokeStyle strokeStyle, qreal dashOffset, const QVector &dashPattern) = 0; virtual void setFillGradient(int index, QQuickPathGradient *gradient) = 0; - virtual void endSync() = 0; + virtual void endSync(bool async) = 0; // Render thread, with gui blocked virtual void updateNode() = 0; @@ -144,6 +144,7 @@ public: bool componentComplete; bool vpChanged; QQuickPathItem::RendererType rendererType; + bool async; QQuickAbstractPathRenderer *renderer; QVector vp; }; diff --git a/src/quick/items/qquickpathitemgenericrenderer.cpp b/src/quick/items/qquickpathitemgenericrenderer.cpp index dbbc14d3c8..6e4c89742a 100644 --- a/src/quick/items/qquickpathitemgenericrenderer.cpp +++ b/src/quick/items/qquickpathitemgenericrenderer.cpp @@ -39,7 +39,9 @@ #include "qquickpathitemgenericrenderer_p.h" #include +#include #include +#include QT_BEGIN_NAMESPACE @@ -103,6 +105,8 @@ void QQuickPathItemGenericStrokeFillNode::activateMaterial(Material m) } // sync, and so triangulation too, happens on the gui thread +// - except when async is set, in which case triangulation is moved to worker threads + void QQuickPathItemGenericRenderer::beginSync(int totalCount) { if (m_vp.count() != totalCount) { @@ -194,9 +198,22 @@ void QQuickPathItemGenericRenderer::setFillGradient(int index, QQuickPathGradien d.syncDirty |= DirtyFillGradient; } -void QQuickPathItemGenericRenderer::endSync() +void QQuickPathItemFillRunnable::run() { - for (VisualPathData &d : m_vp) { + QQuickPathItemGenericRenderer::triangulateFill(path, fillColor, &fillVertices, &fillIndices); + emit done(this); +} + +void QQuickPathItemStrokeRunnable::run() +{ + QQuickPathItemGenericRenderer::triangulateStroke(path, pen, strokeColor, &strokeVertices, clipSize); + emit done(this); +} + +void QQuickPathItemGenericRenderer::endSync(bool async) +{ + for (int i = 0; i < m_vp.count(); ++i) { + VisualPathData &d(m_vp[i]); if (!d.syncDirty) continue; @@ -217,67 +234,145 @@ void QQuickPathItemGenericRenderer::endSync() } if (d.syncDirty & DirtyGeom) { - if (d.fillColor.a) - triangulateFill(&d); - if (d.strokeWidth >= 0.0f && d.strokeColor.a) - triangulateStroke(&d); + static QThreadPool threadPool; + static bool threadPoolReady = false; + if (async && !threadPoolReady) { + threadPoolReady = true; + const int idealCount = QThread::idealThreadCount(); + threadPool.setMaxThreadCount(idealCount > 0 ? idealCount * 2 : 4); + } + if (d.fillColor.a) { + d.path.setFillRule(d.fillRule); + if (async) { + QQuickPathItemFillRunnable *r = new QQuickPathItemFillRunnable; + r->setAutoDelete(false); + if (d.pendingFill) + d.pendingFill->orphaned = true; + d.pendingFill = r; + r->path = d.path; + r->fillColor = d.fillColor; + // Unlikely in practice but in theory m_vp could be + // resized. Therefore, capture 'i' instead of 'd'. + QObject::connect(r, &QQuickPathItemFillRunnable::done, qApp, [this, i](QQuickPathItemFillRunnable *r) { + if (!r->orphaned && i < m_vp.count()) { + VisualPathData &d(m_vp[i]); + d.fillVertices = r->fillVertices; + d.fillIndices = r->fillIndices; + d.pendingFill = nullptr; + d.effectiveDirty |= DirtyGeom; + maybeUpdateAsyncItem(); + } + r->deleteLater(); + }); + threadPool.start(r); + } else { + triangulateFill(d.path, d.fillColor, &d.fillVertices, &d.fillIndices); + } + } + if (d.strokeWidth >= 0.0f && d.strokeColor.a) { + if (async) { + QQuickPathItemStrokeRunnable *r = new QQuickPathItemStrokeRunnable; + r->setAutoDelete(false); + if (d.pendingStroke) + d.pendingStroke->orphaned = true; + d.pendingStroke = r; + r->path = d.path; + r->pen = d.pen; + r->strokeColor = d.strokeColor; + r->clipSize = QSize(m_item->width(), m_item->height()); + QObject::connect(r, &QQuickPathItemStrokeRunnable::done, qApp, [this, i](QQuickPathItemStrokeRunnable *r) { + if (!r->orphaned && i < m_vp.count()) { + VisualPathData &d(m_vp[i]); + d.strokeVertices = r->strokeVertices; + d.pendingStroke = nullptr; + d.effectiveDirty |= DirtyGeom; + maybeUpdateAsyncItem(); + } + r->deleteLater(); + }); + threadPool.start(r); + } else { + triangulateStroke(d.path, d.pen, d.strokeColor, &d.strokeVertices, + QSize(m_item->width(), m_item->height())); + } + } } } } -void QQuickPathItemGenericRenderer::triangulateFill(VisualPathData *d) +void QQuickPathItemGenericRenderer::maybeUpdateAsyncItem() { - d->path.setFillRule(d->fillRule); + for (const VisualPathData &d : qAsConst(m_vp)) { + if (d.pendingFill || d.pendingStroke) + return; + } + m_accDirty |= DirtyGeom; + m_item->update(); +} - const QVectorPath &vp = qtVectorPathForPath(d->path); +// the stroke/fill triangulation functions may be invoked either on the gui +// thread or some worker thread and must thus be self-contained. +void QQuickPathItemGenericRenderer::triangulateFill(const QPainterPath &path, + const Color4ub &fillColor, + VerticesType *fillVertices, + IndicesType *fillIndices) +{ + const QVectorPath &vp = qtVectorPathForPath(path); QTriangleSet ts = qTriangulate(vp, QTransform::fromScale(SCALE, SCALE)); const int vertexCount = ts.vertices.count() / 2; // just a qreal vector with x,y hence the / 2 - d->fillVertices.resize(vertexCount); - ColoredVertex *vdst = reinterpret_cast(d->fillVertices.data()); + fillVertices->resize(vertexCount); + ColoredVertex *vdst = reinterpret_cast(fillVertices->data()); const qreal *vsrc = ts.vertices.constData(); for (int i = 0; i < vertexCount; ++i) - vdst[i].set(vsrc[i * 2] / SCALE, vsrc[i * 2 + 1] / SCALE, d->fillColor); + vdst[i].set(vsrc[i * 2] / SCALE, vsrc[i * 2 + 1] / SCALE, fillColor); - d->fillIndices.resize(ts.indices.size()); - quint16 *idst = d->fillIndices.data(); + fillIndices->resize(ts.indices.size()); + quint16 *idst = fillIndices->data(); if (ts.indices.type() == QVertexIndexVector::UnsignedShort) { - memcpy(idst, ts.indices.data(), d->fillIndices.count() * sizeof(quint16)); + memcpy(idst, ts.indices.data(), fillIndices->count() * sizeof(quint16)); } else { const quint32 *isrc = (const quint32 *) ts.indices.data(); - for (int i = 0; i < d->fillIndices.count(); ++i) + for (int i = 0; i < fillIndices->count(); ++i) idst[i] = isrc[i]; } } -void QQuickPathItemGenericRenderer::triangulateStroke(VisualPathData *d) +void QQuickPathItemGenericRenderer::triangulateStroke(const QPainterPath &path, + const QPen &pen, + const Color4ub &strokeColor, + VerticesType *strokeVertices, + const QSize &clipSize) { - const QVectorPath &vp = qtVectorPathForPath(d->path); - - const QRectF clip(0, 0, m_item->width(), m_item->height()); + const QVectorPath &vp = qtVectorPathForPath(path); + const QRectF clip(QPointF(0, 0), clipSize); const qreal inverseScale = 1.0 / SCALE; - m_stroker.setInvScale(inverseScale); - if (d->pen.style() == Qt::SolidLine) { - m_stroker.process(vp, d->pen, clip, 0); + + QTriangulatingStroker stroker; + stroker.setInvScale(inverseScale); + + if (pen.style() == Qt::SolidLine) { + stroker.process(vp, pen, clip, 0); } else { - m_dashStroker.setInvScale(inverseScale); - m_dashStroker.process(vp, d->pen, clip, 0); - QVectorPath dashStroke(m_dashStroker.points(), m_dashStroker.elementCount(), - m_dashStroker.elementTypes(), 0); - m_stroker.process(dashStroke, d->pen, clip, 0); + QDashedStrokeProcessor dashStroker; + dashStroker.setInvScale(inverseScale); + dashStroker.process(vp, pen, clip, 0); + QVectorPath dashStroke(dashStroker.points(), dashStroker.elementCount(), + dashStroker.elementTypes(), 0); + stroker.process(dashStroke, pen, clip, 0); } - if (!m_stroker.vertexCount()) { - d->strokeVertices.clear(); + if (!stroker.vertexCount()) { + strokeVertices->clear(); return; } - const int vertexCount = m_stroker.vertexCount() / 2; // just a float vector with x,y hence the / 2 - d->strokeVertices.resize(vertexCount); - ColoredVertex *vdst = reinterpret_cast(d->strokeVertices.data()); - const float *vsrc = m_stroker.vertices(); + const int vertexCount = stroker.vertexCount() / 2; // just a float vector with x,y hence the / 2 + strokeVertices->resize(vertexCount); + ColoredVertex *vdst = reinterpret_cast(strokeVertices->data()); + const float *vsrc = stroker.vertices(); for (int i = 0; i < vertexCount; ++i) - vdst[i].set(vsrc[i * 2], vsrc[i * 2 + 1], d->strokeColor); + vdst[i].set(vsrc[i * 2], vsrc[i * 2 + 1], strokeColor); } void QQuickPathItemGenericRenderer::setRootNode(QQuickPathItemGenericNode *node) @@ -317,7 +412,7 @@ void QQuickPathItemGenericRenderer::updateNode() QQuickPathItemGenericNode *node = *nodePtr; if (m_accDirty & DirtyList) - d.effectiveDirty |= DirtyGeom; + d.effectiveDirty |= DirtyGeom | DirtyColor | DirtyFillGradient; if (!d.effectiveDirty) continue; @@ -361,25 +456,36 @@ void QQuickPathItemGenericRenderer::updateNode() m_accDirty = 0; } +void QQuickPathItemGenericRenderer::updateShadowDataInNode(VisualPathData *d, QQuickPathItemGenericStrokeFillNode *n) +{ + if (d->fillGradientActive) { + if (d->effectiveDirty & DirtyFillGradient) + n->m_fillGradient = d->fillGradient; + } +} + void QQuickPathItemGenericRenderer::updateFillNode(VisualPathData *d, QQuickPathItemGenericNode *node) { if (!node->m_fillNode) return; + // Make a copy of the data that will be accessed by the material on + // the render thread. This must be done even when we bail out below. QQuickPathItemGenericStrokeFillNode *n = node->m_fillNode; + updateShadowDataInNode(d, n); + QSGGeometry *g = &n->m_geometry; if (d->fillVertices.isEmpty()) { - g->allocate(0, 0); - n->markDirty(QSGNode::DirtyGeometry); + if (g->vertexCount() || g->indexCount()) { + g->allocate(0, 0); + n->markDirty(QSGNode::DirtyGeometry); + } return; } if (d->fillGradientActive) { n->activateMaterial(QQuickPathItemGenericStrokeFillNode::MatLinearGradient); if (d->effectiveDirty & DirtyFillGradient) { - // Make a copy of the data that will be accessed by the material on - // the render thread. - n->m_fillGradient = d->fillGradient; // Gradients are implemented via a texture-based material. n->markDirty(QSGNode::DirtyMaterial); // stop here if only the gradient changed; no need to touch the geometry @@ -414,14 +520,17 @@ void QQuickPathItemGenericRenderer::updateStrokeNode(VisualPathData *d, QQuickPa return; QQuickPathItemGenericStrokeFillNode *n = node->m_strokeNode; - n->markDirty(QSGNode::DirtyGeometry); - QSGGeometry *g = &n->m_geometry; if (d->strokeVertices.isEmpty()) { - g->allocate(0, 0); + if (g->vertexCount() || g->indexCount()) { + g->allocate(0, 0); + n->markDirty(QSGNode::DirtyGeometry); + } return; } + n->markDirty(QSGNode::DirtyGeometry); + if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyGeom)) { ColoredVertex *vdst = reinterpret_cast(g->vertexData()); for (int i = 0; i < g->vertexCount(); ++i) diff --git a/src/quick/items/qquickpathitemgenericrenderer_p.h b/src/quick/items/qquickpathitemgenericrenderer_p.h index 4454b14a13..a4ed090004 100644 --- a/src/quick/items/qquickpathitemgenericrenderer_p.h +++ b/src/quick/items/qquickpathitemgenericrenderer_p.h @@ -55,11 +55,14 @@ #include #include #include -#include +#include QT_BEGIN_NAMESPACE class QQuickPathItemGenericNode; +class QQuickPathItemGenericStrokeFillNode; +class QQuickPathItemFillRunnable; +class QQuickPathItemStrokeRunnable; class QQuickPathItemGenericRenderer : public QQuickAbstractPathRenderer { @@ -88,15 +91,29 @@ public: void setStrokeStyle(int index, QQuickVisualPath::StrokeStyle strokeStyle, qreal dashOffset, const QVector &dashPattern) override; void setFillGradient(int index, QQuickPathGradient *gradient) override; - void endSync() override; + void endSync(bool async) override; void updateNode() override; void setRootNode(QQuickPathItemGenericNode *node); struct Color4ub { unsigned char r, g, b, a; }; + typedef QVector VerticesType; + typedef QVector IndicesType; + + static void triangulateFill(const QPainterPath &path, + const Color4ub &fillColor, + VerticesType *fillVertices, + IndicesType *fillIndices); + static void triangulateStroke(const QPainterPath &path, + const QPen &pen, + const Color4ub &strokeColor, + VerticesType *strokeVertices, + const QSize &clipSize); private: + void maybeUpdateAsyncItem(); + struct VisualPathData { float strokeWidth; QPen pen; @@ -106,27 +123,60 @@ private: QPainterPath path; bool fillGradientActive; QQuickPathItemGradientCache::GradientDesc fillGradient; - QVector fillVertices; - QVector fillIndices; - QVector strokeVertices; + VerticesType fillVertices; + IndicesType fillIndices; + VerticesType strokeVertices; int syncDirty; int effectiveDirty = 0; + QQuickPathItemFillRunnable *pendingFill = nullptr; + QQuickPathItemStrokeRunnable *pendingStroke = nullptr; }; - void triangulateFill(VisualPathData *d); - void triangulateStroke(VisualPathData *d); - + void updateShadowDataInNode(VisualPathData *d, QQuickPathItemGenericStrokeFillNode *n); void updateFillNode(VisualPathData *d, QQuickPathItemGenericNode *node); void updateStrokeNode(VisualPathData *d, QQuickPathItemGenericNode *node); QQuickItem *m_item; QQuickPathItemGenericNode *m_rootNode; - QTriangulatingStroker m_stroker; - QDashedStrokeProcessor m_dashStroker; QVector m_vp; int m_accDirty; }; +class QQuickPathItemFillRunnable : public QObject, public QRunnable +{ + Q_OBJECT + +public: + void run() override; + + bool orphaned = false; + QPainterPath path; + QQuickPathItemGenericRenderer::Color4ub fillColor; + QQuickPathItemGenericRenderer::VerticesType fillVertices; + QQuickPathItemGenericRenderer::IndicesType fillIndices; + +Q_SIGNALS: + void done(QQuickPathItemFillRunnable *self); +}; + +class QQuickPathItemStrokeRunnable : public QObject, public QRunnable +{ + Q_OBJECT + +public: + void run() override; + + bool orphaned = false; + QPainterPath path; + QPen pen; + QQuickPathItemGenericRenderer::Color4ub strokeColor; + QQuickPathItemGenericRenderer::VerticesType strokeVertices; + QSize clipSize; + +Q_SIGNALS: + void done(QQuickPathItemStrokeRunnable *self); +}; + class QQuickPathItemGenericStrokeFillNode : public QSGGeometryNode { public: diff --git a/src/quick/items/qquickpathitemnvprrenderer.cpp b/src/quick/items/qquickpathitemnvprrenderer.cpp index 0c4357145a..9303f698ac 100644 --- a/src/quick/items/qquickpathitemnvprrenderer.cpp +++ b/src/quick/items/qquickpathitemnvprrenderer.cpp @@ -140,7 +140,7 @@ void QQuickPathItemNvprRenderer::setFillGradient(int index, QQuickPathGradient * m_accDirty |= DirtyFillGradient; } -void QQuickPathItemNvprRenderer::endSync() +void QQuickPathItemNvprRenderer::endSync(bool) { } diff --git a/src/quick/items/qquickpathitemnvprrenderer_p.h b/src/quick/items/qquickpathitemnvprrenderer_p.h index 067ecf7355..1617de17e6 100644 --- a/src/quick/items/qquickpathitemnvprrenderer_p.h +++ b/src/quick/items/qquickpathitemnvprrenderer_p.h @@ -90,7 +90,7 @@ public: void setStrokeStyle(int index, QQuickVisualPath::StrokeStyle strokeStyle, qreal dashOffset, const QVector &dashPattern) override; void setFillGradient(int index, QQuickPathGradient *gradient) override; - void endSync() override; + void endSync(bool async) override; void updateNode() override; diff --git a/src/quick/items/qquickpathitemsoftwarerenderer.cpp b/src/quick/items/qquickpathitemsoftwarerenderer.cpp index 6c3cf73a56..63d4f8f6d4 100644 --- a/src/quick/items/qquickpathitemsoftwarerenderer.cpp +++ b/src/quick/items/qquickpathitemsoftwarerenderer.cpp @@ -158,7 +158,7 @@ void QQuickPathItemSoftwareRenderer::setFillGradient(int index, QQuickPathGradie m_accDirty |= DirtyBrush; } -void QQuickPathItemSoftwareRenderer::endSync() +void QQuickPathItemSoftwareRenderer::endSync(bool) { } diff --git a/src/quick/items/qquickpathitemsoftwarerenderer_p.h b/src/quick/items/qquickpathitemsoftwarerenderer_p.h index 60b52e2def..38130d7301 100644 --- a/src/quick/items/qquickpathitemsoftwarerenderer_p.h +++ b/src/quick/items/qquickpathitemsoftwarerenderer_p.h @@ -82,7 +82,7 @@ public: void setStrokeStyle(int index, QQuickVisualPath::StrokeStyle strokeStyle, qreal dashOffset, const QVector &dashPattern) override; void setFillGradient(int index, QQuickPathGradient *gradient) override; - void endSync() override; + void endSync(bool async) override; void updateNode() override; -- cgit v1.2.3