Implement async mode for QuickShapes curve renderer backend

Async mode enables spinning off non-gui threads to perform the path
preprocessing, thus avoiding blocking the gui thread and enabling
better performance on multi-core CPUs.

Fixes: QTBUG-110958
Change-Id: I969f256a119d4be2dd4107954f4d5f8ceeac2edb
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Eirik Aavitsland 2023-10-31 10:11:17 +01:00
parent 608473399e
commit a7ec055cf2
3 changed files with 223 additions and 103 deletions

View File

@ -781,14 +781,13 @@ void QQuickShape::setPreferredRendererType(QQuickShape::RendererType preferredTy
emit preferredRendererTypeChanged();
}
/*!
\qmlproperty bool QtQuick.Shapes::Shape::asynchronous
When rendererType is \c Shape.GeometryRenderer, the input path is
triangulated on the CPU during the polishing phase of the Shape. This is
potentially expensive. To offload this work to separate worker threads,
set this property to \c true.
When rendererType is \c Shape.GeometryRenderer or \c Shape.CurveRenderer, a certain amount of
preprocessing of the input path is performed on the CPU during the polishing phase of the
Shape. This is potentially expensive. To offload this work to separate worker threads, set this
property to \c true.
When enabled, making a Shape visible will not wait for the content to
become available. Instead, the GUI/main thread is not blocked and the

View File

@ -4,6 +4,10 @@
#include "qquickshapecurverenderer_p.h"
#include "qquickshapecurverenderer_p_p.h"
#if QT_CONFIG(thread)
#include <QtCore/qthreadpool.h>
#endif
#include <QtGui/qvector2d.h>
#include <QtGui/qvector4d.h>
#include <QtGui/private/qtriangulator_p.h>
@ -16,8 +20,6 @@
#include <QtQuick/private/qsgcurveprocessor_p.h>
#include <QtQuick/qsgmaterial.h>
#include <QThread>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcShapeCurveRenderer, "qt.shape.curverenderer");
@ -124,8 +126,13 @@ protected:
};
}
QQuickShapeCurveRenderer::~QQuickShapeCurveRenderer() { }
QQuickShapeCurveRenderer::~QQuickShapeCurveRenderer()
{
for (const PathData &pd : std::as_const(m_paths)) {
if (pd.currentRunner)
pd.currentRunner->orphaned = true;
}
}
void QQuickShapeCurveRenderer::beginSync(int totalCount, bool *countChanged)
{
@ -252,86 +259,185 @@ void QQuickShapeCurveRenderer::setFillGradient(int index, QQuickShapeGradient *g
void QQuickShapeCurveRenderer::setAsyncCallback(void (*callback)(void *), void *data)
{
qCWarning(lcShapeCurveRenderer) << "Asynchronous creation not supported by CurveRenderer";
Q_UNUSED(callback);
Q_UNUSED(data);
m_asyncCallback = callback;
m_asyncCallbackData = data;
}
void QQuickShapeCurveRenderer::endSync(bool async)
{
Q_UNUSED(async);
bool didKickOffAsync = false;
for (PathData &pathData : m_paths) {
if (!pathData.m_dirty)
continue;
if (pathData.m_dirty == UniformsDirty) {
// Requires no curve node computation, gets handled directly in updateNode()
continue;
}
if (pathData.currentRunner) {
// Already performing async computing. New dirty flags will be handled in the next sync
// after the current computation is done and the item is updated
continue;
}
createRunner(&pathData);
#if QT_CONFIG(thread)
if (async) {
pathData.currentRunner->isAsync = true;
QThreadPool::globalInstance()->start(pathData.currentRunner);
didKickOffAsync = true;
} else
#endif
{
pathData.currentRunner->run();
}
}
if (async && !didKickOffAsync && m_asyncCallback)
m_asyncCallback(m_asyncCallbackData);
}
void QQuickShapeCurveRenderer::createRunner(PathData *pathData)
{
Q_ASSERT(!pathData->currentRunner);
QQuickShapeCurveRunnable *runner = new QQuickShapeCurveRunnable;
runner->setAutoDelete(false);
runner->pathData = *pathData;
runner->pathData.fillNodes.clear();
runner->pathData.strokeNodes.clear();
runner->pathData.currentRunner = nullptr;
pathData->currentRunner = runner;
pathData->m_dirty = 0;
QObject::connect(runner, &QQuickShapeCurveRunnable::done, qApp,
[this](QQuickShapeCurveRunnable *r) {
r->isDone = true;
if (r->orphaned) {
r->deleteLater(); // Renderer was destroyed
} else if (r->isAsync) {
maybeUpdateAsyncItem();
}
});
}
void QQuickShapeCurveRenderer::maybeUpdateAsyncItem()
{
for (const PathData &pd : std::as_const(m_paths)) {
if (pd.currentRunner && !pd.currentRunner->isDone)
return;
}
m_item->update();
if (m_asyncCallback)
m_asyncCallback(m_asyncCallbackData);
}
void QQuickShapeCurveRunnable::run()
{
QQuickShapeCurveRenderer::processPath(&pathData);
emit done(this);
}
void QQuickShapeCurveRenderer::updateNode()
{
if (!m_rootNode)
return;
auto updateUniforms = [](const PathData &pathData) {
for (auto &pathNode : std::as_const(pathData.fillNodes))
pathNode->setColor(pathData.fillColor);
for (auto &strokeNode : std::as_const(pathData.strokeNodes))
strokeNode->setColor(pathData.pen.color());
};
for (PathData &pathData : m_paths) {
if (pathData.currentRunner) {
if (!pathData.currentRunner->isDone)
continue;
const PathData &newData = pathData.currentRunner->pathData;
if (newData.m_dirty & PathDirty)
pathData.path = newData.path;
if (newData.m_dirty & FillDirty) {
pathData.fillPath = newData.fillPath;
qDeleteAll(pathData.fillNodes);
pathData.fillNodes = newData.fillNodes;
for (auto *node : std::as_const(pathData.fillNodes))
m_rootNode->appendChildNode(node);
}
if (newData.m_dirty & StrokeDirty) {
qDeleteAll(pathData.strokeNodes);
pathData.strokeNodes = newData.strokeNodes;
for (auto *node : std::as_const(pathData.strokeNodes))
m_rootNode->appendChildNode(node);
}
if (newData.m_dirty & UniformsDirty)
updateUniforms(pathData);
// if (pathData.m_dirty && pathData.m_dirty != UniformsDirty && currentRunner.isAsync)
// qDebug("### should enqueue a new sync?");
pathData.currentRunner->deleteLater();
pathData.currentRunner = nullptr;
}
if (pathData.m_dirty == UniformsDirty) {
// Simple case so no runner was created in endSync(); handle it directly here
updateUniforms(pathData);
pathData.m_dirty = 0;
}
}
}
void QQuickShapeCurveRenderer::processPath(PathData *pathData)
{
static const bool doOverlapSolving = !qEnvironmentVariableIntValue("QT_QUICKSHAPES_DISABLE_OVERLAP_SOLVER");
static const bool useTriangulatingStroker = qEnvironmentVariableIntValue("QT_QUICKSHAPES_TRIANGULATING_STROKER");
static const bool simplifyPath = qEnvironmentVariableIntValue("QT_QUICKSHAPES_SIMPLIFY_PATHS");
for (PathData &pathData : m_paths) {
int dirtyFlags = pathData.m_dirty;
int &dirtyFlags = pathData->m_dirty;
if (dirtyFlags & PathDirty) {
if (simplifyPath)
pathData.path = QQuadPath::fromPainterPath(pathData.originalPath.simplified());
if (dirtyFlags & PathDirty) {
if (simplifyPath)
pathData->path = QQuadPath::fromPainterPath(pathData->originalPath.simplified());
else
pathData->path = QQuadPath::fromPainterPath(pathData->originalPath);
pathData->path.setFillRule(pathData->fillRule);
pathData->fillPath = {};
dirtyFlags |= (FillDirty | StrokeDirty);
}
if (dirtyFlags & FillDirty) {
if (pathData->isFillVisible()) {
if (pathData->fillPath.isEmpty()) {
pathData->fillPath = pathData->path.subPathsClosed();
pathData->fillPath.addCurvatureData();
if (doOverlapSolving)
QSGCurveProcessor::solveOverlaps(pathData->fillPath);
}
pathData->fillNodes = addFillNodes(*pathData);
dirtyFlags |= StrokeDirty;
}
}
if (dirtyFlags & StrokeDirty) {
if (pathData->isStrokeVisible()) {
const QPen &pen = pathData->pen;
if (pen.style() == Qt::SolidLine)
pathData->strokePath = pathData->path;
else
pathData.path = QQuadPath::fromPainterPath(pathData.originalPath);
pathData.path.setFillRule(pathData.fillRule);
pathData.fillPath = {};
dirtyFlags |= (FillDirty | StrokeDirty);
pathData->strokePath = pathData->path.dashed(pen.widthF(), pen.dashPattern(), pen.dashOffset());
if (useTriangulatingStroker)
pathData->strokeNodes = addTriangulatingStrokerNodes(*pathData);
else
pathData->strokeNodes = addCurveStrokeNodes(*pathData);
}
if (dirtyFlags & FillDirty) {
deleteAndClear(&pathData.fillNodes);
deleteAndClear(&pathData.fillDebugNodes);
if (pathData.isFillVisible()) {
if (pathData.fillPath.isEmpty()) {
pathData.fillPath = pathData.path.subPathsClosed();
pathData.fillPath.addCurvatureData();
if (doOverlapSolving)
QSGCurveProcessor::solveOverlaps(pathData.fillPath);
}
pathData.fillNodes = addFillNodes(pathData, &pathData.fillDebugNodes);
dirtyFlags |= StrokeDirty;
}
}
if (dirtyFlags & StrokeDirty) {
deleteAndClear(&pathData.strokeNodes);
deleteAndClear(&pathData.strokeDebugNodes);
if (pathData.isStrokeVisible()) {
const QPen &pen = pathData.pen;
if (pen.style() == Qt::SolidLine)
pathData.strokePath = pathData.path;
else
pathData.strokePath = pathData.path.dashed(pen.widthF(), pen.dashPattern(), pen.dashOffset());
if (useTriangulatingStroker)
pathData.strokeNodes = addTriangulatingStrokerNodes(pathData, &pathData.strokeDebugNodes);
else
pathData.strokeNodes = addCurveStrokeNodes(pathData, &pathData.strokeDebugNodes);
}
}
if (dirtyFlags & UniformsDirty) {
if (!(dirtyFlags & FillDirty)) {
for (auto &pathNode : std::as_const(pathData.fillNodes))
pathNode->setColor(pathData.fillColor);
}
if (!(dirtyFlags & StrokeDirty)) {
for (auto &strokeNode : std::as_const(pathData.strokeNodes))
strokeNode->setColor(pathData.pen.color());
}
}
pathData.m_dirty &= ~(PathDirty | FillDirty | StrokeDirty | UniformsDirty);
}
}
QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addFillNodes(const PathData &pathData,
NodeList *debugNodes)
QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addFillNodes(const PathData &pathData)
{
auto *node = new QSGCurveFillNode;
node->setGradientType(pathData.gradientType);
@ -366,7 +472,6 @@ QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addFillNodes(const
node->setFillGradient(pathData.gradient);
node->cookGeometry();
m_rootNode->appendChildNode(node);
ret.append(node);
}
@ -387,14 +492,13 @@ QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addFillNodes(const
wfVertices.data(),
wfg->vertexCount() * wfg->sizeOfVertex());
m_rootNode->appendChildNode(wfNode);
debugNodes->append(wfNode);
ret.append(wfNode);
}
return ret;
}
QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addTriangulatingStrokerNodes(const PathData &pathData, NodeList *debugNodes)
QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addTriangulatingStrokerNodes(const PathData &pathData)
{
NodeList ret;
const QColor &color = pathData.pen.color();
@ -488,7 +592,6 @@ QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addTriangulatingStr
node->setFillGradient(pathData.gradient);
node->cookGeometry();
m_rootNode->appendChildNode(node);
ret.append(node);
}
const bool wireFrame = debugVisualization() & DebugWireframe;
@ -508,8 +611,7 @@ QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addTriangulatingStr
wfVertices.data(),
wfg->vertexCount() * wfg->sizeOfVertex());
m_rootNode->appendChildNode(wfNode);
debugNodes->append(wfNode);
ret.append(wfNode);
}
return ret;
@ -535,14 +637,7 @@ void QQuickShapeCurveRenderer::setDebugVisualization(int options)
debugVisualizationFlags = options;
}
void QQuickShapeCurveRenderer::deleteAndClear(NodeList *nodeList)
{
for (QSGNode *node : std::as_const(*nodeList))
delete node;
nodeList->clear();
}
QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addCurveStrokeNodes(const PathData &pathData, NodeList *debugNodes)
QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addCurveStrokeNodes(const PathData &pathData)
{
NodeList ret;
const QColor &color = pathData.pen.color();
@ -586,7 +681,6 @@ QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addCurveStrokeNodes
node->setColor(color);
node->setStrokeWidth(pathData.pen.widthF());
node->cookGeometry();
m_rootNode->appendChildNode(node);
ret.append(node);
const bool wireFrame = debugVisualization() & DebugWireframe;
@ -607,8 +701,7 @@ QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addCurveStrokeNodes
wfVertices.data(),
wfg->vertexCount() * wfg->sizeOfVertex());
m_rootNode->appendChildNode(wfNode);
debugNodes->append(wfNode);
ret.append(wfNode);
}
return ret;

View File

@ -26,17 +26,20 @@
#include <qsgrendererinterface.h>
#include <qsgtexture.h>
#include <QtCore/qrunnable.h>
#include <QRunnable>
#include <QtGui/private/qtriangulator_p.h>
#include <QtQuick/private/qsgcurvefillnode_p.h>
QT_BEGIN_NAMESPACE
class QQuickShapeCurveRunnable;
class QQuickShapeCurveRenderer : public QQuickAbstractPathRenderer
{
public:
QQuickShapeCurveRenderer(QQuickItem *)
: m_rootNode(nullptr)
QQuickShapeCurveRenderer(QQuickItem *item)
: m_item(item)
{ }
~QQuickShapeCurveRenderer() override;
@ -53,7 +56,7 @@ public:
void setFillGradient(int index, QQuickShapeGradient *gradient) override;
void endSync(bool async) override;
void setAsyncCallback(void (*)(void *), void *) override;
Flags flags() const override { return Flags{}; }
Flags flags() const override { return SupportsAsync; }
void updateNode() override;
@ -90,32 +93,57 @@ private:
QGradient::Type gradientType = QGradient::NoGradient;
QSGGradientCache::GradientDesc gradient;
QColor fillColor;
Qt::FillRule fillRule = Qt::OddEvenFill;
QPen pen;
bool validPenWidth = true;
int m_dirty = 0;
QPainterPath originalPath;
QQuadPath path;
QQuadPath fillPath;
QQuadPath strokePath;
QColor fillColor;
Qt::FillRule fillRule = Qt::OddEvenFill;
QPen pen;
int m_dirty = 0;
bool validPenWidth = true;
bool convexConcaveResolved = false;
NodeList fillNodes;
NodeList fillDebugNodes;
NodeList strokeNodes;
NodeList strokeDebugNodes;
QQuickShapeCurveRunnable *currentRunner = nullptr;
};
void deleteAndClear(NodeList *nodeList);
void createRunner(PathData *pathData);
void maybeUpdateAsyncItem();
NodeList addFillNodes(const PathData &pathData, NodeList *debugNodes);
NodeList addTriangulatingStrokerNodes(const PathData &pathData, NodeList *debugNodes);
NodeList addCurveStrokeNodes(const PathData &pathData, NodeList *debugNodes);
static void processPath(PathData *pathData);
static NodeList addFillNodes(const PathData &pathData);
static NodeList addTriangulatingStrokerNodes(const PathData &pathData);
static NodeList addCurveStrokeNodes(const PathData &pathData);
QSGNode *m_rootNode;
QQuickItem *m_item;
QSGNode *m_rootNode = nullptr;
QVector<PathData> m_paths;
void (*m_asyncCallback)(void *) = nullptr;
void *m_asyncCallbackData = nullptr;
static int debugVisualizationFlags;
friend class QQuickShapeCurveRunnable;
};
class QQuickShapeCurveRunnable : public QObject, public QRunnable
{
Q_OBJECT
public:
void run() override;
bool isAsync = false;
bool isDone = false;
bool orphaned = false;
// input / output
QQuickShapeCurveRenderer::PathData pathData;
Q_SIGNALS:
void done(QQuickShapeCurveRunnable *self);
};
QT_END_NAMESPACE