diff options
Diffstat (limited to 'src/quickshapes/qquickshapecurverenderer.cpp')
-rw-r--r-- | src/quickshapes/qquickshapecurverenderer.cpp | 748 |
1 files changed, 748 insertions, 0 deletions
diff --git a/src/quickshapes/qquickshapecurverenderer.cpp b/src/quickshapes/qquickshapecurverenderer.cpp new file mode 100644 index 0000000000..856d83fdac --- /dev/null +++ b/src/quickshapes/qquickshapecurverenderer.cpp @@ -0,0 +1,748 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#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> +#include <QtGui/private/qtriangulatingstroker_p.h> +#include <QtGui/private/qrhi_p.h> + +#include <QtQuick/private/qsgcurvefillnode_p.h> +#include <QtQuick/private/qsgcurvestrokenode_p.h> +#include <QtQuick/private/qquadpath_p.h> +#include <QtQuick/private/qsgcurveprocessor_p.h> +#include <QtQuick/qsgmaterial.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcShapeCurveRenderer, "qt.shape.curverenderer"); + +namespace { + +class QQuickShapeWireFrameMaterialShader : public QSGMaterialShader +{ +public: + QQuickShapeWireFrameMaterialShader(int viewCount) + { + setShaderFileName(VertexStage, + QStringLiteral(":/qt-project.org/shapes/shaders_ng/wireframe.vert.qsb"), viewCount); + setShaderFileName(FragmentStage, + QStringLiteral(":/qt-project.org/shapes/shaders_ng/wireframe.frag.qsb"), viewCount); + } + + bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *) override + { + bool changed = false; + QByteArray *buf = state.uniformData(); + Q_ASSERT(buf->size() >= 64); + const int matrixCount = qMin(state.projectionMatrixCount(), newMaterial->viewCount()); + + for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) { + if (state.isMatrixDirty()) { + const QMatrix4x4 m = state.combinedMatrix(viewIndex); + memcpy(buf->data() + 64 * viewIndex, m.constData(), 64); + changed = true; + } + } + + return changed; + } +}; + +class QQuickShapeWireFrameMaterial : public QSGMaterial +{ +public: + QQuickShapeWireFrameMaterial() + { + setFlag(Blending, true); + } + + int compare(const QSGMaterial *other) const override + { + return (type() - other->type()); + } + +protected: + QSGMaterialType *type() const override + { + static QSGMaterialType t; + return &t; + } + QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override + { + return new QQuickShapeWireFrameMaterialShader(viewCount()); + } + +}; + +class QQuickShapeWireFrameNode : public QSGCurveAbstractNode +{ +public: + struct WireFrameVertex + { + float x, y, u, v, w; + }; + + QQuickShapeWireFrameNode() + { + setFlag(OwnsGeometry, true); + setGeometry(new QSGGeometry(attributes(), 0, 0)); + activateMaterial(); + } + + void setColor(QColor col) override + { + Q_UNUSED(col); + } + + void activateMaterial() + { + m_material.reset(new QQuickShapeWireFrameMaterial); + setMaterial(m_material.data()); + } + + static const QSGGeometry::AttributeSet &attributes() + { + static QSGGeometry::Attribute data[] = { + QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute), + QSGGeometry::Attribute::createWithAttributeType(1, 3, QSGGeometry::FloatType, QSGGeometry::TexCoordAttribute), + }; + static QSGGeometry::AttributeSet attrs = { 2, sizeof(WireFrameVertex), data }; + return attrs; + } + + void cookGeometry() override + { + // Intentionally empty + } + +protected: + QScopedPointer<QQuickShapeWireFrameMaterial> m_material; +}; +} + +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) +{ + if (countChanged != nullptr && totalCount != m_paths.size()) + *countChanged = true; + m_paths.resize(totalCount); +} + +void QQuickShapeCurveRenderer::setPath(int index, const QQuickPath *path) +{ + constexpr QQuickShapePath::PathHints noHints; + const auto *shapePath = qobject_cast<const QQuickShapePath *>(path); + setPath(index, path->path(), shapePath ? shapePath->pathHints() : noHints); +} + +void QQuickShapeCurveRenderer::setPath(int index, const QPainterPath &path, QQuickShapePath::PathHints pathHints) +{ + auto &pathData = m_paths[index]; + pathData.originalPath = path; + pathData.pathHints = pathHints; + pathData.m_dirty |= PathDirty; +} + +void QQuickShapeCurveRenderer::setStrokeColor(int index, const QColor &color) +{ + auto &pathData = m_paths[index]; + const bool wasVisible = pathData.isStrokeVisible(); + pathData.pen.setColor(color); + if (pathData.isStrokeVisible() != wasVisible) + pathData.m_dirty |= StrokeDirty; + else + pathData.m_dirty |= UniformsDirty; +} + +void QQuickShapeCurveRenderer::setStrokeWidth(int index, qreal w) +{ + auto &pathData = m_paths[index]; + if (w > 0) { + pathData.validPenWidth = true; + pathData.pen.setWidthF(w); + } else { + pathData.validPenWidth = false; + } + pathData.m_dirty |= StrokeDirty; +} + +void QQuickShapeCurveRenderer::setFillColor(int index, const QColor &color) +{ + auto &pathData = m_paths[index]; + const bool wasVisible = pathData.isFillVisible(); + pathData.fillColor = color; + if (pathData.isFillVisible() != wasVisible) + pathData.m_dirty |= FillDirty; + else + pathData.m_dirty |= UniformsDirty; +} + +void QQuickShapeCurveRenderer::setFillRule(int index, QQuickShapePath::FillRule fillRule) +{ + auto &pathData = m_paths[index]; + pathData.fillRule = Qt::FillRule(fillRule); + pathData.m_dirty |= PathDirty; +} + +void QQuickShapeCurveRenderer::setJoinStyle(int index, + QQuickShapePath::JoinStyle joinStyle, + int miterLimit) +{ + auto &pathData = m_paths[index]; + pathData.pen.setJoinStyle(Qt::PenJoinStyle(joinStyle)); + pathData.pen.setMiterLimit(miterLimit); + pathData.m_dirty |= StrokeDirty; +} + +void QQuickShapeCurveRenderer::setCapStyle(int index, QQuickShapePath::CapStyle capStyle) +{ + auto &pathData = m_paths[index]; + pathData.pen.setCapStyle(Qt::PenCapStyle(capStyle)); + pathData.m_dirty |= StrokeDirty; +} + +void QQuickShapeCurveRenderer::setStrokeStyle(int index, + QQuickShapePath::StrokeStyle strokeStyle, + qreal dashOffset, + const QVector<qreal> &dashPattern) +{ + auto &pathData = m_paths[index]; + pathData.pen.setStyle(Qt::PenStyle(strokeStyle)); + if (strokeStyle == QQuickShapePath::DashLine) { + pathData.pen.setDashPattern(dashPattern); + pathData.pen.setDashOffset(dashOffset); + } + pathData.m_dirty |= StrokeDirty; +} + +void QQuickShapeCurveRenderer::setFillGradient(int index, QQuickShapeGradient *gradient) +{ + PathData &pd(m_paths[index]); + pd.gradientType = QGradient::NoGradient; + if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(gradient)) { + pd.gradientType = QGradient::LinearGradient; + pd.gradient.stops = gradient->gradientStops(); + pd.gradient.spread = QGradient::Spread(gradient->spread()); + pd.gradient.a = QPointF(g->x1(), g->y1()); + pd.gradient.b = QPointF(g->x2(), g->y2()); + } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(gradient)) { + pd.gradientType = QGradient::RadialGradient; + pd.gradient.a = QPointF(g->centerX(), g->centerY()); + pd.gradient.b = QPointF(g->focalX(), g->focalY()); + pd.gradient.v0 = g->centerRadius(); + pd.gradient.v1 = g->focalRadius(); + } else if (QQuickShapeConicalGradient *g = qobject_cast<QQuickShapeConicalGradient *>(gradient)) { + pd.gradientType = QGradient::ConicalGradient; + pd.gradient.a = QPointF(g->centerX(), g->centerY()); + pd.gradient.v0 = g->angle(); + } else + if (gradient != nullptr) { + static bool warned = false; + if (!warned) { + warned = true; + qCWarning(lcShapeCurveRenderer) << "Unsupported gradient fill"; + } + } + + if (pd.gradientType != QGradient::NoGradient) { + pd.gradient.stops = gradient->gradientStops(); + pd.gradient.spread = QGradient::Spread(gradient->spread()); + } + + pd.m_dirty |= FillDirty; +} + +void QQuickShapeCurveRenderer::setAsyncCallback(void (*callback)(void *), void *data) +{ + m_asyncCallback = callback; + m_asyncCallbackData = data; +} + +void QQuickShapeCurveRenderer::endSync(bool 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; + } + if (m_item) + 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()); + }; + + NodeList toBeDeleted; + + for (int i = 0; i < m_paths.size(); i++) { + PathData &pathData = m_paths[i]; + if (pathData.currentRunner) { + if (!pathData.currentRunner->isDone) + continue; + // Find insertion point for new nodes. Default is the first stroke node of this path + QSGNode *nextNode = pathData.strokeNodes.value(0); + // If that is 0, use the first node (stroke or fill) of later paths, if any + for (int j = i + 1; !nextNode && j < m_paths.size(); j++) { + const PathData &pd = m_paths[j]; + nextNode = pd.fillNodes.isEmpty() ? pd.strokeNodes.value(0) : pd.fillNodes.value(0); + } + + const PathData &newData = pathData.currentRunner->pathData; + if (newData.m_dirty & PathDirty) + pathData.path = newData.path; + if (newData.m_dirty & FillDirty) { + pathData.fillPath = newData.fillPath; + for (auto *node : std::as_const(newData.fillNodes)) { + if (nextNode) + m_rootNode->insertChildNodeBefore(node, nextNode); + else + m_rootNode->appendChildNode(node); + } + toBeDeleted += pathData.fillNodes; + pathData.fillNodes = newData.fillNodes; + } + if (newData.m_dirty & StrokeDirty) { + for (auto *node : std::as_const(newData.strokeNodes)) { + if (nextNode) + m_rootNode->insertChildNodeBefore(node, nextNode); + else + m_rootNode->appendChildNode(node); + } + toBeDeleted += pathData.strokeNodes; + pathData.strokeNodes = newData.strokeNodes; + } + + 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; + } + } + qDeleteAll(toBeDeleted); // also removes them from m_rootNode's child list +} + +void QQuickShapeCurveRenderer::processPath(PathData *pathData) +{ + static const bool doOverlapSolving = !qEnvironmentVariableIntValue("QT_QUICKSHAPES_DISABLE_OVERLAP_SOLVER"); + static const bool doIntersetionSolving = !qEnvironmentVariableIntValue("QT_QUICKSHAPES_DISABLE_INTERSECTION_SOLVER"); + static const bool useTriangulatingStroker = qEnvironmentVariableIntValue("QT_QUICKSHAPES_TRIANGULATING_STROKER"); + static const bool simplifyPath = qEnvironmentVariableIntValue("QT_QUICKSHAPES_SIMPLIFY_PATHS"); + + int &dirtyFlags = pathData->m_dirty; + + if (dirtyFlags & PathDirty) { + if (simplifyPath) + pathData->path = QQuadPath::fromPainterPath(pathData->originalPath.simplified(), QQuadPath::PathLinear | QQuadPath::PathNonIntersecting | QQuadPath::PathNonOverlappingControlPointTriangles); + else + pathData->path = QQuadPath::fromPainterPath(pathData->originalPath, QQuadPath::PathHints(int(pathData->pathHints))); + 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(); + if (doIntersetionSolving) + QSGCurveProcessor::solveIntersections(pathData->fillPath); + 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->strokePath = pathData->path.dashed(pen.widthF(), pen.dashPattern(), pen.dashOffset()); + + if (useTriangulatingStroker) + pathData->strokeNodes = addTriangulatingStrokerNodes(*pathData); + else + pathData->strokeNodes = addCurveStrokeNodes(*pathData); + } + } +} + +QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addFillNodes(const PathData &pathData) +{ + auto *node = new QSGCurveFillNode; + node->setGradientType(pathData.gradientType); + const qsizetype approxDataCount = 20 * pathData.fillPath.elementCount(); + node->reserve(approxDataCount); + + NodeList ret; + const QColor &color = pathData.fillColor; + QPainterPath internalHull; + internalHull.setFillRule(pathData.fillPath.fillRule()); + + bool visualizeDebug = debugVisualization() & DebugCurves; + const float dbg = visualizeDebug ? 0.5f : 0.0f; + node->setDebug(dbg); + + QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices; + wfVertices.reserve(approxDataCount); + + QSGCurveProcessor::processFill(pathData.fillPath, + pathData.fillRule, + [&wfVertices, &node](const std::array<QVector2D, 3> &v, + const std::array<QVector2D, 3> &n, + QSGCurveProcessor::uvForPointCallback uvForPoint) + { + node->appendTriangle(v, n, uvForPoint); + + wfVertices.append({v.at(0).x(), v.at(0).y(), 1.0f, 0.0f, 0.0f}); // 0 + wfVertices.append({v.at(1).x(), v.at(1).y(), 0.0f, 1.0f, 0.0f}); // 1 + wfVertices.append({v.at(2).x(), v.at(2).y(), 0.0f, 0.0f, 1.0f}); // 2 + }); + + QVector<quint32> indices = node->uncookedIndexes(); + if (indices.size() > 0) { + node->setColor(color); + node->setFillGradient(pathData.gradient); + + node->cookGeometry(); + ret.append(node); + } + + const bool wireFrame = debugVisualization() & DebugWireframe; + if (wireFrame) { + QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode; + QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(), + wfVertices.size(), + indices.size(), + QSGGeometry::UnsignedIntType); + wfNode->setGeometry(wfg); + + wfg->setDrawingMode(QSGGeometry::DrawTriangles); + memcpy(wfg->indexData(), + indices.data(), + indices.size() * wfg->sizeOfIndex()); + memcpy(wfg->vertexData(), + wfVertices.data(), + wfg->vertexCount() * wfg->sizeOfVertex()); + + ret.append(wfNode); + } + + return ret; +} + +QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addTriangulatingStrokerNodes(const PathData &pathData) +{ + NodeList ret; + const QColor &color = pathData.pen.color(); + + QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices; + + QTriangulatingStroker stroker; + const auto painterPath = pathData.strokePath.toPainterPath(); + const QVectorPath &vp = qtVectorPathForPath(painterPath); + QPen pen = pathData.pen; + stroker.process(vp, pen, {}, {}); + + auto *node = new QSGCurveFillNode; + node->setGradientType(pathData.gradientType); + + auto uvForPoint = [](QVector2D v1, QVector2D v2, QVector2D p) + { + double divisor = v1.x() * v2.y() - v2.x() * v1.y(); + + float u = (p.x() * v2.y() - p.y() * v2.x()) / divisor; + float v = (p.y() * v1.x() - p.x() * v1.y()) / divisor; + + return QVector2D(u, v); + }; + + // Find uv coordinates for the point p, for a quadratic curve from p0 to p2 with control point p1 + // also works for a line from p0 to p2, where p1 is on the inside of the path relative to the line + auto curveUv = [uvForPoint](QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p) + { + QVector2D v1 = 2 * (p1 - p0); + QVector2D v2 = p2 - v1 - p0; + return uvForPoint(v1, v2, p - p0); + }; + + auto findPointOtherSide = [](const QVector2D &startPoint, const QVector2D &endPoint, const QVector2D &referencePoint){ + + QVector2D baseLine = endPoint - startPoint; + QVector2D insideVector = referencePoint - startPoint; + QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()); // TODO: limit size of triangle + + bool swap = QVector2D::dotProduct(insideVector, normal) < 0; + + return swap ? startPoint + normal : startPoint - normal; + }; + + static bool disableExtraTriangles = qEnvironmentVariableIntValue("QT_QUICKSHAPES_WIP_DISABLE_EXTRA_STROKE_TRIANGLES"); + + auto addStrokeTriangle = [&](const QVector2D &p1, const QVector2D &p2, const QVector2D &p3, bool){ + if (p1 == p2 || p2 == p3) { + return; + } + + auto uvForPoint = [&p1, &p2, &p3, curveUv](QVector2D p) { + auto uv = curveUv(p1, p2, p3, p); + return QVector3D(uv.x(), uv.y(), 0.0f); // Line + }; + + node->appendTriangle(p1, p2, p3, uvForPoint); + + + wfVertices.append({p1.x(), p1.y(), 1.0f, 0.0f, 0.0f}); // 0 + wfVertices.append({p2.x(), p2.y(), 0.0f, 0.1f, 0.0f}); // 1 + wfVertices.append({p3.x(), p3.y(), 0.0f, 0.0f, 1.0f}); // 2 + + if (!disableExtraTriangles) { + // Add a triangle on the outer side of the line to get some more AA + // The new point replaces p2 (currentVertex+1) + QVector2D op = findPointOtherSide(p1, p3, p2); + node->appendTriangle(p1, op, p3, uvForPoint); + + wfVertices.append({p1.x(), p1.y(), 1.0f, 0.0f, 0.0f}); + wfVertices.append({op.x(), op.y(), 0.0f, 1.0f, 0.0f}); // replacing p2 + wfVertices.append({p3.x(), p3.y(), 0.0f, 0.0f, 1.0f}); + } + }; + + const int vertCount = stroker.vertexCount() / 2; + const float *verts = stroker.vertices(); + for (int i = 0; i < vertCount - 2; ++i) { + QVector2D p[3]; + for (int j = 0; j < 3; ++j) { + p[j] = QVector2D(verts[(i+j)*2], verts[(i+j)*2 + 1]); + } + bool isOdd = i % 2; + addStrokeTriangle(p[0], p[1], p[2], isOdd); + } + + QVector<quint32> indices = node->uncookedIndexes(); + if (indices.size() > 0) { + node->setColor(color); + node->setFillGradient(pathData.gradient); + + node->cookGeometry(); + ret.append(node); + } + const bool wireFrame = debugVisualization() & DebugWireframe; + if (wireFrame) { + QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode; + QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(), + wfVertices.size(), + indices.size(), + QSGGeometry::UnsignedIntType); + wfNode->setGeometry(wfg); + + wfg->setDrawingMode(QSGGeometry::DrawTriangles); + memcpy(wfg->indexData(), + indices.data(), + indices.size() * wfg->sizeOfIndex()); + memcpy(wfg->vertexData(), + wfVertices.data(), + wfg->vertexCount() * wfg->sizeOfVertex()); + + ret.append(wfNode); + } + + return ret; +} + +void QQuickShapeCurveRenderer::setRootNode(QSGNode *node) +{ + m_rootNode = node; +} + +int QQuickShapeCurveRenderer::debugVisualizationFlags = QQuickShapeCurveRenderer::NoDebug; + +int QQuickShapeCurveRenderer::debugVisualization() +{ + static const int envFlags = qEnvironmentVariableIntValue("QT_QUICKSHAPES_DEBUG"); + return debugVisualizationFlags | envFlags; +} + +void QQuickShapeCurveRenderer::setDebugVisualization(int options) +{ + if (debugVisualizationFlags == options) + return; + debugVisualizationFlags = options; +} + +QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addCurveStrokeNodes(const PathData &pathData) +{ + NodeList ret; + const QColor &color = pathData.pen.color(); + + const bool debug = debugVisualization() & DebugCurves; + auto *node = new QSGCurveStrokeNode; + node->setDebug(0.2f * debug); + QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices; + + const float miterLimit = pathData.pen.miterLimit(); + const float penWidth = pathData.pen.widthF(); + + static const int subdivisions = qEnvironmentVariable("QT_QUICKSHAPES_STROKE_SUBDIVISIONS", QStringLiteral("3")).toInt(); + + QSGCurveProcessor::processStroke(pathData.strokePath, + miterLimit, + penWidth, + pathData.pen.joinStyle(), + pathData.pen.capStyle(), + [&wfVertices, &node](const std::array<QVector2D, 3> &s, + const std::array<QVector2D, 3> &p, + const std::array<QVector2D, 3> &n, + bool isLine) + { + const QVector2D &p0 = s.at(0); + const QVector2D &p1 = s.at(1); + const QVector2D &p2 = s.at(2); + if (isLine) + node->appendTriangle(s, std::array<QVector2D, 2>{p.at(0), p.at(2)}, n); + else + node->appendTriangle(s, p, n); + + wfVertices.append({p0.x(), p0.y(), 1.0f, 0.0f, 0.0f}); + wfVertices.append({p1.x(), p1.y(), 0.0f, 1.0f, 0.0f}); + wfVertices.append({p2.x(), p2.y(), 0.0f, 0.0f, 1.0f}); + }, + subdivisions); + + auto indexCopy = node->uncookedIndexes(); // uncookedIndexes get delete on cooking + + node->setColor(color); + node->setStrokeWidth(pathData.pen.widthF()); + node->cookGeometry(); + ret.append(node); + + const bool wireFrame = debugVisualization() & DebugWireframe; + if (wireFrame) { + QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode; + + QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(), + wfVertices.size(), + indexCopy.size(), + QSGGeometry::UnsignedIntType); + wfNode->setGeometry(wfg); + + wfg->setDrawingMode(QSGGeometry::DrawTriangles); + memcpy(wfg->indexData(), + indexCopy.data(), + indexCopy.size() * wfg->sizeOfIndex()); + memcpy(wfg->vertexData(), + wfVertices.data(), + wfg->vertexCount() * wfg->sizeOfVertex()); + + ret.append(wfNode); + } + + return ret; +} + +QT_END_NAMESPACE |