1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickshapecurverenderer_p.h"
5#include "qquickshapecurverenderer_p_p.h"
6
7#if QT_CONFIG(thread)
8#include <QtCore/qthreadpool.h>
9#endif
10
11#include <QtGui/qvector2d.h>
12#include <QtGui/qvector4d.h>
13#include <QtGui/private/qtriangulator_p.h>
14#include <QtGui/private/qtriangulatingstroker_p.h>
15#include <QtGui/private/qrhi_p.h>
16
17#include <QtQuick/private/qsgcurvefillnode_p.h>
18#include <QtQuick/private/qsgcurvestrokenode_p.h>
19#include <QtQuick/private/qquadpath_p.h>
20#include <QtQuick/private/qsgcurveprocessor_p.h>
21#include <QtQuick/qsgmaterial.h>
22
23QT_BEGIN_NAMESPACE
24
25Q_LOGGING_CATEGORY(lcShapeCurveRenderer, "qt.shape.curverenderer");
26
27namespace {
28
29class QQuickShapeWireFrameMaterialShader : public QSGMaterialShader
30{
31public:
32 QQuickShapeWireFrameMaterialShader(int viewCount)
33 {
34 setShaderFileName(stage: VertexStage,
35 QStringLiteral(":/qt-project.org/shapes/shaders_ng/wireframe.vert.qsb"), viewCount);
36 setShaderFileName(stage: FragmentStage,
37 QStringLiteral(":/qt-project.org/shapes/shaders_ng/wireframe.frag.qsb"), viewCount);
38 }
39
40 bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *) override
41 {
42 bool changed = false;
43 QByteArray *buf = state.uniformData();
44 Q_ASSERT(buf->size() >= 64);
45 const int matrixCount = qMin(a: state.projectionMatrixCount(), b: newMaterial->viewCount());
46
47 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
48 if (state.isMatrixDirty()) {
49 const QMatrix4x4 m = state.combinedMatrix(index: viewIndex);
50 memcpy(dest: buf->data() + 64 * viewIndex, src: m.constData(), n: 64);
51 changed = true;
52 }
53 }
54
55 return changed;
56 }
57};
58
59class QQuickShapeWireFrameMaterial : public QSGMaterial
60{
61public:
62 QQuickShapeWireFrameMaterial()
63 {
64 setFlag(flags: Blending, on: true);
65 }
66
67 int compare(const QSGMaterial *other) const override
68 {
69 return (type() - other->type());
70 }
71
72protected:
73 QSGMaterialType *type() const override
74 {
75 static QSGMaterialType t;
76 return &t;
77 }
78 QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override
79 {
80 return new QQuickShapeWireFrameMaterialShader(viewCount());
81 }
82
83};
84
85class QQuickShapeWireFrameNode : public QSGCurveAbstractNode
86{
87public:
88 struct WireFrameVertex
89 {
90 float x, y, u, v, w;
91 };
92
93 QQuickShapeWireFrameNode()
94 {
95 isDebugNode = true;
96 setFlag(OwnsGeometry, true);
97 setGeometry(new QSGGeometry(attributes(), 0, 0));
98 activateMaterial();
99 }
100
101 void setColor(QColor col) override
102 {
103 Q_UNUSED(col);
104 }
105
106 void activateMaterial()
107 {
108 m_material.reset(other: new QQuickShapeWireFrameMaterial);
109 setMaterial(m_material.data());
110 }
111
112 static const QSGGeometry::AttributeSet &attributes()
113 {
114 static QSGGeometry::Attribute data[] = {
115 QSGGeometry::Attribute::createWithAttributeType(pos: 0, tupleSize: 2, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::PositionAttribute),
116 QSGGeometry::Attribute::createWithAttributeType(pos: 1, tupleSize: 3, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::TexCoordAttribute),
117 };
118 static QSGGeometry::AttributeSet attrs = { .count: 2, .stride: sizeof(WireFrameVertex), .attributes: data };
119 return attrs;
120 }
121
122 void cookGeometry() override
123 {
124 // Intentionally empty
125 }
126
127protected:
128 QScopedPointer<QQuickShapeWireFrameMaterial> m_material;
129};
130}
131
132QQuickShapeCurveRenderer::~QQuickShapeCurveRenderer()
133{
134 for (const PathData &pd : std::as_const(t&: m_paths)) {
135 if (pd.currentRunner) {
136 pd.currentRunner->orphaned = true;
137 if (!pd.currentRunner->isAsync || pd.currentRunner->isDone)
138 delete pd.currentRunner;
139 }
140 }
141}
142
143void QQuickShapeCurveRenderer::beginSync(int totalCount, bool *countChanged)
144{
145 if (countChanged != nullptr && totalCount != m_paths.size())
146 *countChanged = true;
147 m_paths.resize(size: totalCount);
148}
149
150void QQuickShapeCurveRenderer::setPath(int index, const QQuickPath *path)
151{
152 constexpr QQuickShapePath::PathHints noHints;
153 const auto *shapePath = qobject_cast<const QQuickShapePath *>(object: path);
154 setPath(index, path: path->path(), pathHints: shapePath ? shapePath->pathHints() : noHints);
155}
156
157void QQuickShapeCurveRenderer::setPath(int index, const QPainterPath &path, QQuickShapePath::PathHints pathHints)
158{
159 auto &pathData = m_paths[index];
160 pathData.originalPath = path;
161 pathData.pathHints = pathHints;
162 pathData.m_dirty |= PathDirty;
163}
164
165void QQuickShapeCurveRenderer::setStrokeColor(int index, const QColor &color)
166{
167 auto &pathData = m_paths[index];
168 const bool wasVisible = pathData.isStrokeVisible();
169 pathData.pen.setColor(color);
170 if (pathData.isStrokeVisible() != wasVisible)
171 pathData.m_dirty |= StrokeDirty;
172 else
173 pathData.m_dirty |= UniformsDirty;
174}
175
176void QQuickShapeCurveRenderer::setStrokeWidth(int index, qreal w)
177{
178 auto &pathData = m_paths[index];
179 if (w > 0) {
180 pathData.validPenWidth = true;
181 pathData.pen.setWidthF(w);
182 } else {
183 pathData.validPenWidth = false;
184 }
185 pathData.m_dirty |= StrokeDirty;
186}
187
188void QQuickShapeCurveRenderer::setFillColor(int index, const QColor &color)
189{
190 auto &pathData = m_paths[index];
191 const bool wasVisible = pathData.isFillVisible();
192 pathData.fillColor = color;
193 if (pathData.isFillVisible() != wasVisible)
194 pathData.m_dirty |= FillDirty;
195 else
196 pathData.m_dirty |= UniformsDirty;
197}
198
199void QQuickShapeCurveRenderer::setFillRule(int index, QQuickShapePath::FillRule fillRule)
200{
201 auto &pathData = m_paths[index];
202 pathData.fillRule = Qt::FillRule(fillRule);
203 pathData.m_dirty |= PathDirty;
204}
205
206void QQuickShapeCurveRenderer::setJoinStyle(int index,
207 QQuickShapePath::JoinStyle joinStyle,
208 int miterLimit)
209{
210 auto &pathData = m_paths[index];
211 pathData.pen.setJoinStyle(Qt::PenJoinStyle(joinStyle));
212 pathData.pen.setMiterLimit(miterLimit);
213 pathData.m_dirty |= StrokeDirty;
214}
215
216void QQuickShapeCurveRenderer::setCapStyle(int index, QQuickShapePath::CapStyle capStyle)
217{
218 auto &pathData = m_paths[index];
219 pathData.pen.setCapStyle(Qt::PenCapStyle(capStyle));
220 pathData.m_dirty |= StrokeDirty;
221}
222
223void QQuickShapeCurveRenderer::setStrokeStyle(int index,
224 QQuickShapePath::StrokeStyle strokeStyle,
225 qreal dashOffset,
226 const QVector<qreal> &dashPattern)
227{
228 auto &pathData = m_paths[index];
229 pathData.pen.setStyle(Qt::PenStyle(strokeStyle));
230 if (strokeStyle == QQuickShapePath::DashLine) {
231 pathData.pen.setDashPattern(dashPattern);
232 pathData.pen.setDashOffset(dashOffset);
233 }
234 pathData.m_dirty |= StrokeDirty;
235}
236
237void QQuickShapeCurveRenderer::setFillGradient(int index, QQuickShapeGradient *gradient)
238{
239 PathData &pd(m_paths[index]);
240 const bool wasVisible = pd.isFillVisible();
241 pd.gradientType = QGradient::NoGradient;
242 if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(object: gradient)) {
243 pd.gradientType = QGradient::LinearGradient;
244 pd.gradient.stops = gradient->gradientStops();
245 pd.gradient.spread = QGradient::Spread(gradient->spread());
246 pd.gradient.a = QPointF(g->x1(), g->y1());
247 pd.gradient.b = QPointF(g->x2(), g->y2());
248 } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(object: gradient)) {
249 pd.gradientType = QGradient::RadialGradient;
250 pd.gradient.a = QPointF(g->centerX(), g->centerY());
251 pd.gradient.b = QPointF(g->focalX(), g->focalY());
252 pd.gradient.v0 = g->centerRadius();
253 pd.gradient.v1 = g->focalRadius();
254 } else if (QQuickShapeConicalGradient *g = qobject_cast<QQuickShapeConicalGradient *>(object: gradient)) {
255 pd.gradientType = QGradient::ConicalGradient;
256 pd.gradient.a = QPointF(g->centerX(), g->centerY());
257 pd.gradient.v0 = g->angle();
258 } else if (gradient != nullptr) {
259 static bool warned = false;
260 if (!warned) {
261 warned = true;
262 qCWarning(lcShapeCurveRenderer) << "Unsupported gradient fill";
263 }
264 }
265
266 if (pd.gradientType != QGradient::NoGradient) {
267 pd.gradient.stops = gradient->gradientStops();
268 pd.gradient.spread = QGradient::Spread(gradient->spread());
269 }
270
271 pd.m_dirty |= (pd.isFillVisible() != wasVisible) ? FillDirty : UniformsDirty;
272}
273
274void QQuickShapeCurveRenderer::setFillTransform(int index, const QSGTransform &transform)
275{
276 auto &pathData = m_paths[index];
277 pathData.fillTransform = transform;
278 pathData.m_dirty |= UniformsDirty;
279}
280
281void QQuickShapeCurveRenderer::setFillTextureProvider(int index, QQuickItem *textureProviderItem)
282{
283 auto &pathData = m_paths[index];
284 const bool wasVisible = pathData.isFillVisible();
285 if (pathData.fillTextureProviderItem != nullptr)
286 QQuickItemPrivate::get(item: pathData.fillTextureProviderItem)->derefWindow();
287 pathData.fillTextureProviderItem = textureProviderItem;
288 if (pathData.fillTextureProviderItem != nullptr)
289 QQuickItemPrivate::get(item: pathData.fillTextureProviderItem)->refWindow(m_item->window());
290 pathData.m_dirty |= (pathData.isFillVisible() != wasVisible) ? FillDirty : UniformsDirty;
291}
292
293void QQuickShapeCurveRenderer::handleSceneChange(QQuickWindow *window)
294{
295 for (auto &pathData : m_paths) {
296 if (pathData.fillTextureProviderItem != nullptr) {
297 if (window == nullptr)
298 QQuickItemPrivate::get(item: pathData.fillTextureProviderItem)->derefWindow();
299 else
300 QQuickItemPrivate::get(item: pathData.fillTextureProviderItem)->refWindow(window);
301 }
302 }
303}
304
305void QQuickShapeCurveRenderer::setAsyncCallback(void (*callback)(void *), void *data)
306{
307 m_asyncCallback = callback;
308 m_asyncCallbackData = data;
309}
310
311void QQuickShapeCurveRenderer::endSync(bool async)
312{
313 bool asyncThreadsRunning = false;
314
315 for (PathData &pathData : m_paths) {
316 if (!pathData.m_dirty)
317 continue;
318
319 if (pathData.m_dirty == UniformsDirty) {
320 // Requires no curve node computation, gets handled directly in updateNode()
321 continue;
322 }
323
324 if (pathData.currentRunner) {
325 // Already performing async computing. New dirty flags will be handled in the next sync
326 // after the current computation is done and the item is updated
327 asyncThreadsRunning = true;
328 continue;
329 }
330
331 pathData.currentRunner = new QQuickShapeCurveRunnable;
332 setUpRunner(&pathData);
333
334#if QT_CONFIG(thread)
335 if (async) {
336 pathData.currentRunner->isAsync = true;
337 QThreadPool::globalInstance()->start(runnable: pathData.currentRunner);
338 asyncThreadsRunning = true;
339 } else
340#endif
341 {
342 pathData.currentRunner->run();
343 }
344 }
345
346 if (async && !asyncThreadsRunning && m_asyncCallback)
347 m_asyncCallback(m_asyncCallbackData);
348}
349
350void QQuickShapeCurveRenderer::setUpRunner(PathData *pathData)
351{
352 Q_ASSERT(pathData->currentRunner);
353 QQuickShapeCurveRunnable *runner = pathData->currentRunner;
354 runner->isDone = false;
355 runner->pathData = *pathData;
356 runner->pathData.fillNodes.clear();
357 runner->pathData.strokeNodes.clear();
358 runner->pathData.currentRunner = nullptr;
359 pathData->m_dirty = 0;
360 if (!runner->isInitialized) {
361 runner->isInitialized = true;
362 runner->setAutoDelete(false);
363 QObject::connect(sender: runner, signal: &QQuickShapeCurveRunnable::done, qApp,
364 slot: [this](QQuickShapeCurveRunnable *r) {
365 r->isDone = true;
366 if (r->orphaned) {
367 delete r; // Renderer was destroyed
368 } else if (r->isAsync) {
369 maybeUpdateAsyncItem();
370 }
371 });
372 }
373}
374
375void QQuickShapeCurveRenderer::maybeUpdateAsyncItem()
376{
377 for (const PathData &pd : std::as_const(t&: m_paths)) {
378 if (pd.currentRunner && !pd.currentRunner->isDone)
379 return;
380 }
381 if (m_item)
382 m_item->update();
383 if (m_asyncCallback)
384 m_asyncCallback(m_asyncCallbackData);
385}
386
387QQuickShapeCurveRunnable::~QQuickShapeCurveRunnable()
388{
389 qDeleteAll(c: pathData.fillNodes);
390 qDeleteAll(c: pathData.strokeNodes);
391}
392
393void QQuickShapeCurveRunnable::run()
394{
395 QQuickShapeCurveRenderer::processPath(pathData: &pathData);
396 emit done(self: this);
397}
398
399void QQuickShapeCurveRenderer::updateNode()
400{
401 if (!m_rootNode)
402 return;
403
404 auto updateUniforms = [](const PathData &pathData) {
405 for (auto &pathNode : std::as_const(t: pathData.fillNodes)) {
406 if (pathNode->isDebugNode)
407 continue;
408 QSGCurveFillNode *fillNode = static_cast<QSGCurveFillNode *>(pathNode);
409 fillNode->setColor(pathData.fillColor);
410 fillNode->setGradientType(pathData.gradientType);
411 fillNode->setFillGradient(pathData.gradient);
412 fillNode->setFillTransform(pathData.fillTransform);
413 fillNode->setFillTextureProvider(pathData.fillTextureProviderItem != nullptr
414 ? pathData.fillTextureProviderItem->textureProvider()
415 : nullptr);
416 }
417 for (auto &strokeNode : std::as_const(t: pathData.strokeNodes))
418 strokeNode->setColor(pathData.pen.color());
419 };
420
421 NodeList toBeDeleted;
422
423 for (int i = 0; i < m_paths.size(); i++) {
424 PathData &pathData = m_paths[i];
425 if (pathData.currentRunner) {
426 if (!pathData.currentRunner->isDone)
427 continue;
428 // Find insertion point for new nodes. Default is the first stroke node of this path
429 QSGNode *nextNode = pathData.strokeNodes.value(i: 0);
430 // If that is 0, use the first node (stroke or fill) of later paths, if any
431 for (int j = i + 1; !nextNode && j < m_paths.size(); j++) {
432 const PathData &pd = m_paths[j];
433 nextNode = pd.fillNodes.isEmpty() ? pd.strokeNodes.value(i: 0) : pd.fillNodes.value(i: 0);
434 }
435
436 PathData &newData = pathData.currentRunner->pathData;
437 if (newData.m_dirty & PathDirty)
438 pathData.path = newData.path;
439 if (newData.m_dirty & FillDirty) {
440 pathData.fillPath = newData.fillPath;
441 for (auto *node : std::as_const(t&: newData.fillNodes)) {
442 if (nextNode)
443 m_rootNode->insertChildNodeBefore(node, before: nextNode);
444 else
445 m_rootNode->appendChildNode(node);
446 }
447 toBeDeleted += pathData.fillNodes;
448 pathData.fillNodes = newData.fillNodes;
449 }
450 if (newData.m_dirty & StrokeDirty) {
451 for (auto *node : std::as_const(t&: newData.strokeNodes)) {
452 if (nextNode)
453 m_rootNode->insertChildNodeBefore(node, before: nextNode);
454 else
455 m_rootNode->appendChildNode(node);
456 }
457 toBeDeleted += pathData.strokeNodes;
458 pathData.strokeNodes = newData.strokeNodes;
459 }
460 if (newData.m_dirty & UniformsDirty)
461 updateUniforms(newData);
462
463 // Ownership of new nodes have been transferred to root node
464 newData.fillNodes.clear();
465 newData.strokeNodes.clear();
466
467#if QT_CONFIG(thread)
468 if (pathData.currentRunner->isAsync && (pathData.m_dirty & ~UniformsDirty)) {
469 // New changes have arrived while runner was computing; restart it to handle them
470 setUpRunner(&pathData);
471 QThreadPool::globalInstance()->start(runnable: pathData.currentRunner);
472 } else
473#endif
474 {
475 pathData.currentRunner->deleteLater();
476 pathData.currentRunner = nullptr;
477 }
478 }
479
480 if (pathData.m_dirty == UniformsDirty && !pathData.currentRunner) {
481 // Simple case so no runner was created in endSync(); handle it directly here
482 updateUniforms(pathData);
483 pathData.m_dirty = 0;
484 }
485 }
486 qDeleteAll(c: toBeDeleted); // also removes them from m_rootNode's child list
487}
488
489void QQuickShapeCurveRenderer::processPath(PathData *pathData)
490{
491 static const bool doOverlapSolving = !qEnvironmentVariableIntValue(varName: "QT_QUICKSHAPES_DISABLE_OVERLAP_SOLVER");
492 static const bool doIntersetionSolving = !qEnvironmentVariableIntValue(varName: "QT_QUICKSHAPES_DISABLE_INTERSECTION_SOLVER");
493 static const bool useTriangulatingStroker = qEnvironmentVariableIntValue(varName: "QT_QUICKSHAPES_TRIANGULATING_STROKER");
494 static const bool simplifyPath = qEnvironmentVariableIntValue(varName: "QT_QUICKSHAPES_SIMPLIFY_PATHS");
495
496 int &dirtyFlags = pathData->m_dirty;
497
498 if (dirtyFlags & PathDirty) {
499 if (simplifyPath)
500 pathData->path = QQuadPath::fromPainterPath(path: pathData->originalPath.simplified(), hints: QQuadPath::PathLinear | QQuadPath::PathNonIntersecting | QQuadPath::PathNonOverlappingControlPointTriangles);
501 else
502 pathData->path = QQuadPath::fromPainterPath(path: pathData->originalPath, hints: QQuadPath::PathHints(int(pathData->pathHints)));
503 pathData->path.setFillRule(pathData->fillRule);
504 pathData->fillPath = {};
505 dirtyFlags |= (FillDirty | StrokeDirty);
506 }
507
508 if (dirtyFlags & FillDirty) {
509 if (pathData->isFillVisible()) {
510 if (pathData->fillPath.isEmpty()) {
511 pathData->fillPath = pathData->path.subPathsClosed();
512 if (doIntersetionSolving)
513 QSGCurveProcessor::solveIntersections(path&: pathData->fillPath);
514 pathData->fillPath.addCurvatureData();
515 if (doOverlapSolving)
516 QSGCurveProcessor::solveOverlaps(path&: pathData->fillPath);
517 }
518 pathData->fillNodes = addFillNodes(path: pathData->fillPath);
519 dirtyFlags |= (StrokeDirty | UniformsDirty);
520 }
521 }
522
523 if (dirtyFlags & StrokeDirty) {
524 if (pathData->isStrokeVisible()) {
525 const QPen &pen = pathData->pen;
526 const bool solid = (pen.style() == Qt::SolidLine);
527 const QQuadPath &strokePath = solid ? pathData->path
528 : pathData->path.dashed(lineWidth: pen.widthF(),
529 dashPattern: pen.dashPattern(),
530 dashOffset: pen.dashOffset());
531 if (useTriangulatingStroker)
532 pathData->strokeNodes = addTriangulatingStrokerNodes(path: strokePath, pen);
533 else
534 pathData->strokeNodes = addCurveStrokeNodes(path: strokePath, pen);
535 }
536 }
537}
538
539QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addFillNodes(const QQuadPath &path)
540{
541 NodeList ret;
542 std::unique_ptr<QSGCurveFillNode> node(new QSGCurveFillNode);
543 std::unique_ptr<QQuickShapeWireFrameNode> wfNode;
544
545 const qsizetype approxDataCount = 20 * path.elementCount();
546 node->reserve(size: approxDataCount);
547
548 const int debugFlags = debugVisualization();
549 const bool wireFrame = debugFlags & DebugWireframe;
550
551 if (Q_LIKELY(!wireFrame)) {
552 QSGCurveProcessor::processFill(path,
553 fillRule: path.fillRule(),
554 addTriangle: [&node](const std::array<QVector2D, 3> &v,
555 const std::array<QVector2D, 3> &n,
556 QSGCurveProcessor::uvForPointCallback uvForPoint)
557 {
558 node->appendTriangle(v, n, uvForPoint);
559 });
560 } else {
561 QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices;
562 wfVertices.reserve(asize: approxDataCount);
563 QSGCurveProcessor::processFill(path,
564 fillRule: path.fillRule(),
565 addTriangle: [&wfVertices, &node](const std::array<QVector2D, 3> &v,
566 const std::array<QVector2D, 3> &n,
567 QSGCurveProcessor::uvForPointCallback uvForPoint)
568 {
569 node->appendTriangle(v, n, uvForPoint);
570
571 wfVertices.append(t: {.x: v.at(n: 0).x(), .y: v.at(n: 0).y(), .u: 1.0f, .v: 0.0f, .w: 0.0f}); // 0
572 wfVertices.append(t: {.x: v.at(n: 1).x(), .y: v.at(n: 1).y(), .u: 0.0f, .v: 1.0f, .w: 0.0f}); // 1
573 wfVertices.append(t: {.x: v.at(n: 2).x(), .y: v.at(n: 2).y(), .u: 0.0f, .v: 0.0f, .w: 1.0f}); // 2
574 });
575
576 wfNode.reset(p: new QQuickShapeWireFrameNode);
577 const QVector<quint32> indices = node->uncookedIndexes();
578 QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(),
579 wfVertices.size(),
580 indices.size(),
581 QSGGeometry::UnsignedIntType);
582 wfNode->setGeometry(wfg);
583
584 wfg->setDrawingMode(QSGGeometry::DrawTriangles);
585 memcpy(dest: wfg->indexData(),
586 src: indices.data(),
587 n: indices.size() * wfg->sizeOfIndex());
588 memcpy(dest: wfg->vertexData(),
589 src: wfVertices.data(),
590 n: wfg->vertexCount() * wfg->sizeOfVertex());
591 }
592
593 if (Q_UNLIKELY(debugFlags & DebugCurves))
594 node->setDebug(0.5f);
595
596 if (node->uncookedIndexes().size() > 0) {
597 node->cookGeometry();
598 ret.append(t: node.release());
599 if (wireFrame)
600 ret.append(t: wfNode.release());
601 }
602
603 return ret;
604}
605
606QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addTriangulatingStrokerNodes(const QQuadPath &path, const QPen &pen)
607{
608 NodeList ret;
609 const QColor &color = pen.color();
610
611 QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices;
612
613 QTriangulatingStroker stroker;
614 const auto painterPath = path.toPainterPath();
615 const QVectorPath &vp = qtVectorPathForPath(path: painterPath);
616 stroker.process(path: vp, pen, clip: {}, hints: {});
617
618 auto *node = new QSGCurveFillNode;
619
620 auto uvForPoint = [](QVector2D v1, QVector2D v2, QVector2D p)
621 {
622 double divisor = v1.x() * v2.y() - v2.x() * v1.y();
623
624 float u = (p.x() * v2.y() - p.y() * v2.x()) / divisor;
625 float v = (p.y() * v1.x() - p.x() * v1.y()) / divisor;
626
627 return QVector2D(u, v);
628 };
629
630 // Find uv coordinates for the point p, for a quadratic curve from p0 to p2 with control point p1
631 // also works for a line from p0 to p2, where p1 is on the inside of the path relative to the line
632 auto curveUv = [uvForPoint](QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
633 {
634 QVector2D v1 = 2 * (p1 - p0);
635 QVector2D v2 = p2 - v1 - p0;
636 return uvForPoint(v1, v2, p - p0);
637 };
638
639 auto findPointOtherSide = [](const QVector2D &startPoint, const QVector2D &endPoint, const QVector2D &referencePoint){
640
641 QVector2D baseLine = endPoint - startPoint;
642 QVector2D insideVector = referencePoint - startPoint;
643 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()); // TODO: limit size of triangle
644
645 bool swap = QVector2D::dotProduct(v1: insideVector, v2: normal) < 0;
646
647 return swap ? startPoint + normal : startPoint - normal;
648 };
649
650 static bool disableExtraTriangles = qEnvironmentVariableIntValue(varName: "QT_QUICKSHAPES_WIP_DISABLE_EXTRA_STROKE_TRIANGLES");
651
652 auto addStrokeTriangle = [&](const QVector2D &p1, const QVector2D &p2, const QVector2D &p3){
653 if (p1 == p2 || p2 == p3) {
654 return;
655 }
656
657 auto uvForPoint = [&p1, &p2, &p3, curveUv](QVector2D p) {
658 auto uv = curveUv(p1, p2, p3, p);
659 return QVector3D(uv.x(), uv.y(), 0.0f); // Line
660 };
661
662 node->appendTriangle(v1: p1, v2: p2, v3: p3, uvForPoint);
663
664
665 wfVertices.append(t: {.x: p1.x(), .y: p1.y(), .u: 1.0f, .v: 0.0f, .w: 0.0f}); // 0
666 wfVertices.append(t: {.x: p2.x(), .y: p2.y(), .u: 0.0f, .v: 0.1f, .w: 0.0f}); // 1
667 wfVertices.append(t: {.x: p3.x(), .y: p3.y(), .u: 0.0f, .v: 0.0f, .w: 1.0f}); // 2
668
669 if (!disableExtraTriangles) {
670 // Add a triangle on the outer side of the line to get some more AA
671 // The new point replaces p2 (currentVertex+1)
672 QVector2D op = findPointOtherSide(p1, p3, p2);
673 node->appendTriangle(v1: p1, v2: op, v3: p3, uvForPoint);
674
675 wfVertices.append(t: {.x: p1.x(), .y: p1.y(), .u: 1.0f, .v: 0.0f, .w: 0.0f});
676 wfVertices.append(t: {.x: op.x(), .y: op.y(), .u: 0.0f, .v: 1.0f, .w: 0.0f}); // replacing p2
677 wfVertices.append(t: {.x: p3.x(), .y: p3.y(), .u: 0.0f, .v: 0.0f, .w: 1.0f});
678 }
679 };
680
681 const int vertCount = stroker.vertexCount() / 2;
682 const float *verts = stroker.vertices();
683 for (int i = 0; i < vertCount - 2; ++i) {
684 QVector2D p[3];
685 for (int j = 0; j < 3; ++j) {
686 p[j] = QVector2D(verts[(i+j)*2], verts[(i+j)*2 + 1]);
687 }
688 addStrokeTriangle(p[0], p[1], p[2]);
689 }
690
691 QVector<quint32> indices = node->uncookedIndexes();
692 if (indices.size() > 0) {
693 node->setColor(color);
694
695 node->cookGeometry();
696 ret.append(t: node);
697 }
698 const bool wireFrame = debugVisualization() & DebugWireframe;
699 if (wireFrame) {
700 QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode;
701 QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(),
702 wfVertices.size(),
703 indices.size(),
704 QSGGeometry::UnsignedIntType);
705 wfNode->setGeometry(wfg);
706
707 wfg->setDrawingMode(QSGGeometry::DrawTriangles);
708 memcpy(dest: wfg->indexData(),
709 src: indices.data(),
710 n: indices.size() * wfg->sizeOfIndex());
711 memcpy(dest: wfg->vertexData(),
712 src: wfVertices.data(),
713 n: wfg->vertexCount() * wfg->sizeOfVertex());
714
715 ret.append(t: wfNode);
716 }
717
718 return ret;
719}
720
721void QQuickShapeCurveRenderer::setRootNode(QSGNode *node)
722{
723 clearNodeReferences();
724 m_rootNode = node;
725}
726
727void QQuickShapeCurveRenderer::clearNodeReferences()
728{
729 for (PathData &pd : m_paths) {
730 pd.fillNodes.clear();
731 pd.strokeNodes.clear();
732 }
733}
734
735int QQuickShapeCurveRenderer::debugVisualizationFlags = QQuickShapeCurveRenderer::NoDebug;
736
737int QQuickShapeCurveRenderer::debugVisualization()
738{
739 static const int envFlags = qEnvironmentVariableIntValue(varName: "QT_QUICKSHAPES_DEBUG");
740 return debugVisualizationFlags | envFlags;
741}
742
743void QQuickShapeCurveRenderer::setDebugVisualization(int options)
744{
745 if (debugVisualizationFlags == options)
746 return;
747 debugVisualizationFlags = options;
748}
749
750QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addCurveStrokeNodes(const QQuadPath &path, const QPen &pen)
751{
752 NodeList ret;
753
754 const bool debug = debugVisualization() & DebugCurves;
755 auto *node = new QSGCurveStrokeNode;
756 node->setDebug(0.2f * debug);
757 QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices;
758
759 const float penWidth = pen.widthF();
760
761 static const int subdivisions = qEnvironmentVariable(varName: "QT_QUICKSHAPES_STROKE_SUBDIVISIONS", QStringLiteral("3")).toInt();
762
763 QSGCurveProcessor::processStroke(strokePath: path,
764 miterLimit: pen.miterLimit(),
765 penWidth,
766 joinStyle: pen.joinStyle(),
767 capStyle: pen.capStyle(),
768 addTriangle: [&wfVertices, &node](const std::array<QVector2D, 3> &s,
769 const std::array<QVector2D, 3> &p,
770 const std::array<QVector2D, 3> &n,
771 bool isLine)
772 {
773 const QVector2D &p0 = s.at(n: 0);
774 const QVector2D &p1 = s.at(n: 1);
775 const QVector2D &p2 = s.at(n: 2);
776 if (isLine)
777 node->appendTriangle(v: s, p: std::array<QVector2D, 2>{p.at(n: 0), p.at(n: 2)}, n);
778 else
779 node->appendTriangle(v: s, p, n);
780
781 wfVertices.append(t: {.x: p0.x(), .y: p0.y(), .u: 1.0f, .v: 0.0f, .w: 0.0f});
782 wfVertices.append(t: {.x: p1.x(), .y: p1.y(), .u: 0.0f, .v: 1.0f, .w: 0.0f});
783 wfVertices.append(t: {.x: p2.x(), .y: p2.y(), .u: 0.0f, .v: 0.0f, .w: 1.0f});
784 },
785 subdivisions);
786
787 auto indexCopy = node->uncookedIndexes(); // uncookedIndexes get delete on cooking
788
789 node->setColor(pen.color());
790 node->setStrokeWidth(penWidth);
791 node->cookGeometry();
792 ret.append(t: node);
793
794 const bool wireFrame = debugVisualization() & DebugWireframe;
795 if (wireFrame) {
796 QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode;
797
798 QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(),
799 wfVertices.size(),
800 indexCopy.size(),
801 QSGGeometry::UnsignedIntType);
802 wfNode->setGeometry(wfg);
803
804 wfg->setDrawingMode(QSGGeometry::DrawTriangles);
805 memcpy(dest: wfg->indexData(),
806 src: indexCopy.data(),
807 n: indexCopy.size() * wfg->sizeOfIndex());
808 memcpy(dest: wfg->vertexData(),
809 src: wfVertices.data(),
810 n: wfg->vertexCount() * wfg->sizeOfVertex());
811
812 ret.append(t: wfNode);
813 }
814
815 return ret;
816}
817
818QT_END_NAMESPACE
819

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtdeclarative/src/quickshapes/qquickshapecurverenderer.cpp