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 for (int i = totalCount; i < m_paths.size(); i++) { // Handle removal of paths
148 setFillTextureProvider(index: i, textureProviderItem: nullptr); // deref window
149 m_removedPaths.append(t: m_paths.at(i));
150 }
151 m_paths.resize(size: totalCount);
152}
153
154void QQuickShapeCurveRenderer::setPath(int index, const QPainterPath &path, QQuickShapePath::PathHints pathHints)
155{
156 auto &pathData = m_paths[index];
157 pathData.originalPath = path;
158 pathData.pathHints = pathHints;
159 pathData.m_dirty |= PathDirty;
160}
161
162void QQuickShapeCurveRenderer::setStrokeColor(int index, const QColor &color)
163{
164 auto &pathData = m_paths[index];
165 const bool wasVisible = pathData.isStrokeVisible();
166 pathData.pen.setColor(color);
167 if (pathData.isStrokeVisible() != wasVisible)
168 pathData.m_dirty |= StrokeDirty;
169 else
170 pathData.m_dirty |= UniformsDirty;
171}
172
173void QQuickShapeCurveRenderer::setStrokeWidth(int index, qreal w)
174{
175 auto &pathData = m_paths[index];
176 if (w > 0) {
177 pathData.validPenWidth = true;
178 pathData.pen.setWidthF(w);
179 } else {
180 pathData.validPenWidth = false;
181 }
182 pathData.m_dirty |= StrokeDirty;
183}
184
185void QQuickShapeCurveRenderer::setFillColor(int index, const QColor &color)
186{
187 auto &pathData = m_paths[index];
188 const bool wasVisible = pathData.isFillVisible();
189 pathData.fillColor = color;
190 if (pathData.isFillVisible() != wasVisible)
191 pathData.m_dirty |= FillDirty;
192 else
193 pathData.m_dirty |= UniformsDirty;
194}
195
196void QQuickShapeCurveRenderer::setFillRule(int index, QQuickShapePath::FillRule fillRule)
197{
198 auto &pathData = m_paths[index];
199 pathData.fillRule = Qt::FillRule(fillRule);
200 pathData.m_dirty |= PathDirty;
201}
202
203void QQuickShapeCurveRenderer::setJoinStyle(int index,
204 QQuickShapePath::JoinStyle joinStyle,
205 int miterLimit)
206{
207 auto &pathData = m_paths[index];
208 pathData.pen.setJoinStyle(Qt::PenJoinStyle(joinStyle));
209 pathData.pen.setMiterLimit(miterLimit);
210 pathData.m_dirty |= StrokeDirty;
211}
212
213void QQuickShapeCurveRenderer::setCapStyle(int index, QQuickShapePath::CapStyle capStyle)
214{
215 auto &pathData = m_paths[index];
216 pathData.pen.setCapStyle(Qt::PenCapStyle(capStyle));
217 pathData.m_dirty |= StrokeDirty;
218}
219
220void QQuickShapeCurveRenderer::setStrokeStyle(int index,
221 QQuickShapePath::StrokeStyle strokeStyle,
222 qreal dashOffset,
223 const QVector<qreal> &dashPattern)
224{
225 auto &pathData = m_paths[index];
226 pathData.pen.setStyle(Qt::PenStyle(strokeStyle));
227 if (strokeStyle == QQuickShapePath::DashLine) {
228 pathData.pen.setDashPattern(dashPattern);
229 pathData.pen.setDashOffset(dashOffset);
230 }
231 pathData.m_dirty |= StrokeDirty;
232}
233
234void QQuickShapeCurveRenderer::setFillGradient(int index, QQuickShapeGradient *gradient)
235{
236 PathData &pd(m_paths[index]);
237 const bool wasVisible = pd.isFillVisible();
238 pd.gradientType = QGradient::NoGradient;
239 if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(object: gradient)) {
240 pd.gradientType = QGradient::LinearGradient;
241 pd.gradient.stops = gradient->gradientStops();
242 pd.gradient.spread = QGradient::Spread(gradient->spread());
243 pd.gradient.a = QPointF(g->x1(), g->y1());
244 pd.gradient.b = QPointF(g->x2(), g->y2());
245 } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(object: gradient)) {
246 pd.gradientType = QGradient::RadialGradient;
247 pd.gradient.a = QPointF(g->centerX(), g->centerY());
248 pd.gradient.b = QPointF(g->focalX(), g->focalY());
249 pd.gradient.v0 = g->centerRadius();
250 pd.gradient.v1 = g->focalRadius();
251 } else if (QQuickShapeConicalGradient *g = qobject_cast<QQuickShapeConicalGradient *>(object: gradient)) {
252 pd.gradientType = QGradient::ConicalGradient;
253 pd.gradient.a = QPointF(g->centerX(), g->centerY());
254 pd.gradient.v0 = g->angle();
255 } else if (gradient != nullptr) {
256 static bool warned = false;
257 if (!warned) {
258 warned = true;
259 qCWarning(lcShapeCurveRenderer) << "Unsupported gradient fill";
260 }
261 }
262
263 if (pd.gradientType != QGradient::NoGradient) {
264 pd.gradient.stops = gradient->gradientStops();
265 pd.gradient.spread = QGradient::Spread(gradient->spread());
266 }
267
268 pd.m_dirty |= (pd.isFillVisible() != wasVisible) ? FillDirty : UniformsDirty;
269}
270
271void QQuickShapeCurveRenderer::setFillTransform(int index, const QSGTransform &transform)
272{
273 auto &pathData = m_paths[index];
274 pathData.fillTransform = transform;
275 pathData.m_dirty |= UniformsDirty;
276}
277
278void QQuickShapeCurveRenderer::setFillTextureProvider(int index, QQuickItem *textureProviderItem)
279{
280 auto &pathData = m_paths[index];
281 const bool wasVisible = pathData.isFillVisible();
282 if (pathData.fillTextureProviderItem != nullptr)
283 QQuickItemPrivate::get(item: pathData.fillTextureProviderItem)->derefWindow();
284 pathData.fillTextureProviderItem = textureProviderItem;
285 if (pathData.fillTextureProviderItem != nullptr)
286 QQuickItemPrivate::get(item: pathData.fillTextureProviderItem)->refWindow(m_item->window());
287 pathData.m_dirty |= (pathData.isFillVisible() != wasVisible) ? FillDirty : UniformsDirty;
288}
289
290void QQuickShapeCurveRenderer::handleSceneChange(QQuickWindow *window)
291{
292 for (auto &pathData : m_paths) {
293 if (pathData.fillTextureProviderItem != nullptr) {
294 if (window == nullptr)
295 QQuickItemPrivate::get(item: pathData.fillTextureProviderItem)->derefWindow();
296 else
297 QQuickItemPrivate::get(item: pathData.fillTextureProviderItem)->refWindow(window);
298 }
299 }
300}
301
302void QQuickShapeCurveRenderer::setAsyncCallback(void (*callback)(void *), void *data)
303{
304 m_asyncCallback = callback;
305 m_asyncCallbackData = data;
306}
307
308void QQuickShapeCurveRenderer::endSync(bool async)
309{
310 bool asyncThreadsRunning = false;
311
312 for (PathData &pathData : m_paths) {
313 if (!pathData.m_dirty)
314 continue;
315
316 if (pathData.m_dirty == UniformsDirty) {
317 // Requires no curve node computation, gets handled directly in updateNode()
318 continue;
319 }
320
321 if (pathData.currentRunner) {
322 // We are in a new sync round before updateNode() has been called to commit the results
323 // of the previous sync and processing round
324 if (pathData.currentRunner->isAsync) {
325 // Already performing async processing. A new run of the runner will be started in
326 // updateNode() to take care of the new dirty flags
327 asyncThreadsRunning = true;
328 continue;
329 } else {
330 // Throw away outdated results and start a new processing
331 delete pathData.currentRunner;
332 pathData.currentRunner = nullptr;
333 }
334 }
335
336 pathData.currentRunner = new QQuickShapeCurveRunnable;
337 setUpRunner(&pathData);
338
339#if QT_CONFIG(thread)
340 if (async) {
341 pathData.currentRunner->isAsync = true;
342 QThreadPool::globalInstance()->start(runnable: pathData.currentRunner);
343 asyncThreadsRunning = true;
344 } else
345#endif
346 {
347 pathData.currentRunner->run();
348 }
349 }
350
351 if (async && !asyncThreadsRunning && m_asyncCallback)
352 m_asyncCallback(m_asyncCallbackData);
353}
354
355void QQuickShapeCurveRenderer::setUpRunner(PathData *pathData)
356{
357 Q_ASSERT(pathData->currentRunner);
358 QQuickShapeCurveRunnable *runner = pathData->currentRunner;
359 runner->isDone = false;
360 runner->pathData = *pathData;
361 runner->pathData.fillNodes.clear();
362 runner->pathData.strokeNodes.clear();
363 runner->pathData.currentRunner = nullptr;
364 pathData->m_dirty = 0;
365 if (!runner->isInitialized) {
366 runner->isInitialized = true;
367 runner->setAutoDelete(false);
368 QObject::connect(sender: runner, signal: &QQuickShapeCurveRunnable::done, qApp,
369 slot: [this](QQuickShapeCurveRunnable *r) {
370 r->isDone = true;
371 if (r->orphaned) {
372 delete r; // Renderer was destroyed
373 } else if (r->isAsync) {
374 maybeUpdateAsyncItem();
375 }
376 });
377 }
378}
379
380void QQuickShapeCurveRenderer::maybeUpdateAsyncItem()
381{
382 for (const PathData &pd : std::as_const(t&: m_paths)) {
383 if (pd.currentRunner && !pd.currentRunner->isDone)
384 return;
385 }
386 if (m_item)
387 m_item->update();
388 if (m_asyncCallback)
389 m_asyncCallback(m_asyncCallbackData);
390}
391
392QQuickShapeCurveRunnable::~QQuickShapeCurveRunnable()
393{
394 qDeleteAll(c: pathData.fillNodes);
395 qDeleteAll(c: pathData.strokeNodes);
396}
397
398void QQuickShapeCurveRunnable::run()
399{
400 QQuickShapeCurveRenderer::processPath(pathData: &pathData);
401 emit done(self: this);
402}
403
404void QQuickShapeCurveRenderer::updateNode()
405{
406 if (!m_rootNode)
407 return;
408
409 auto updateUniforms = [](const PathData &pathData) {
410 for (auto &pathNode : std::as_const(t: pathData.fillNodes)) {
411 if (pathNode->isDebugNode)
412 continue;
413 QSGCurveFillNode *fillNode = static_cast<QSGCurveFillNode *>(pathNode);
414 fillNode->setColor(pathData.fillColor);
415 fillNode->setGradientType(pathData.gradientType);
416 fillNode->setFillGradient(pathData.gradient);
417 fillNode->setFillTransform(pathData.fillTransform);
418 fillNode->setFillTextureProvider(pathData.fillTextureProviderItem != nullptr
419 ? pathData.fillTextureProviderItem->textureProvider()
420 : nullptr);
421 }
422 for (auto &strokeNode : std::as_const(t: pathData.strokeNodes))
423 strokeNode->setColor(pathData.pen.color());
424 };
425
426 NodeList toBeDeleted;
427
428 for (const PathData &pathData : std::as_const(t&: m_removedPaths)) {
429 toBeDeleted += pathData.fillNodes;
430 toBeDeleted += pathData.strokeNodes;
431 }
432 m_removedPaths.clear();
433
434 for (int i = 0; i < m_paths.size(); i++) {
435 PathData &pathData = m_paths[i];
436 if (pathData.currentRunner) {
437 if (!pathData.currentRunner->isDone)
438 continue;
439 // Find insertion point for new nodes. Default is the first stroke node of this path
440 QSGNode *nextNode = pathData.strokeNodes.value(i: 0);
441 // If that is 0, use the first node (stroke or fill) of later paths, if any
442 for (int j = i + 1; !nextNode && j < m_paths.size(); j++) {
443 const PathData &pd = m_paths[j];
444 nextNode = pd.fillNodes.isEmpty() ? pd.strokeNodes.value(i: 0) : pd.fillNodes.value(i: 0);
445 }
446
447 PathData &newData = pathData.currentRunner->pathData;
448 if (newData.m_dirty & PathDirty)
449 pathData.path = newData.path;
450 if (newData.m_dirty & FillDirty) {
451 pathData.fillPath = newData.fillPath;
452 for (auto *node : std::as_const(t&: newData.fillNodes)) {
453 if (nextNode)
454 m_rootNode->insertChildNodeBefore(node, before: nextNode);
455 else
456 m_rootNode->appendChildNode(node);
457 }
458 toBeDeleted += pathData.fillNodes;
459 pathData.fillNodes = newData.fillNodes;
460 }
461 if (newData.m_dirty & StrokeDirty) {
462 for (auto *node : std::as_const(t&: newData.strokeNodes)) {
463 if (nextNode)
464 m_rootNode->insertChildNodeBefore(node, before: nextNode);
465 else
466 m_rootNode->appendChildNode(node);
467 }
468 toBeDeleted += pathData.strokeNodes;
469 pathData.strokeNodes = newData.strokeNodes;
470 }
471 if (newData.m_dirty & UniformsDirty)
472 updateUniforms(newData);
473
474 // Ownership of new nodes have been transferred to root node
475 newData.fillNodes.clear();
476 newData.strokeNodes.clear();
477
478#if QT_CONFIG(thread)
479 if (pathData.currentRunner->isAsync && (pathData.m_dirty & ~UniformsDirty)) {
480 // New changes have arrived while runner was computing; restart it to handle them
481 setUpRunner(&pathData);
482 QThreadPool::globalInstance()->start(runnable: pathData.currentRunner);
483 } else
484#endif
485 {
486 pathData.currentRunner->deleteLater();
487 pathData.currentRunner = nullptr;
488 }
489 }
490
491 if (pathData.m_dirty == UniformsDirty && !pathData.currentRunner) {
492 // Simple case so no runner was created in endSync(); handle it directly here
493 updateUniforms(pathData);
494 pathData.m_dirty = 0;
495 }
496 }
497 qDeleteAll(c: toBeDeleted); // also removes them from m_rootNode's child list
498}
499
500void QQuickShapeCurveRenderer::processPath(PathData *pathData)
501{
502 static const bool doOverlapSolving = !qEnvironmentVariableIntValue(varName: "QT_QUICKSHAPES_DISABLE_OVERLAP_SOLVER");
503 static const bool doIntersetionSolving = !qEnvironmentVariableIntValue(varName: "QT_QUICKSHAPES_DISABLE_INTERSECTION_SOLVER");
504 static const bool useTriangulatingStroker = qEnvironmentVariableIntValue(varName: "QT_QUICKSHAPES_TRIANGULATING_STROKER");
505 static const bool simplifyPath = qEnvironmentVariableIntValue(varName: "QT_QUICKSHAPES_SIMPLIFY_PATHS");
506
507 int &dirtyFlags = pathData->m_dirty;
508
509 if (dirtyFlags & PathDirty) {
510 if (simplifyPath)
511 pathData->path = QQuadPath::fromPainterPath(path: pathData->originalPath.simplified(), hints: QQuadPath::PathLinear | QQuadPath::PathNonIntersecting | QQuadPath::PathNonOverlappingControlPointTriangles);
512 else
513 pathData->path = QQuadPath::fromPainterPath(path: pathData->originalPath, hints: QQuadPath::PathHints(int(pathData->pathHints)));
514 pathData->path.setFillRule(pathData->fillRule);
515 pathData->fillPath = {};
516 dirtyFlags |= (FillDirty | StrokeDirty);
517 }
518
519 if (dirtyFlags & FillDirty) {
520 if (pathData->isFillVisible()) {
521 if (pathData->fillPath.isEmpty()) {
522 pathData->fillPath = pathData->path.subPathsClosed();
523 if (doIntersetionSolving)
524 QSGCurveProcessor::solveIntersections(path&: pathData->fillPath);
525 pathData->fillPath.addCurvatureData();
526 if (doOverlapSolving)
527 QSGCurveProcessor::solveOverlaps(path&: pathData->fillPath);
528 }
529 pathData->fillNodes = addFillNodes(path: pathData->fillPath);
530 dirtyFlags |= (StrokeDirty | UniformsDirty);
531 }
532 }
533
534 if (dirtyFlags & StrokeDirty) {
535 if (pathData->isStrokeVisible()) {
536 const QPen &pen = pathData->pen;
537 const bool solid = (pen.style() == Qt::SolidLine);
538 const QQuadPath &strokePath = solid ? pathData->path
539 : pathData->path.dashed(lineWidth: pen.widthF(),
540 dashPattern: pen.dashPattern(),
541 dashOffset: pen.dashOffset());
542 if (useTriangulatingStroker)
543 pathData->strokeNodes = addTriangulatingStrokerNodes(path: strokePath, pen);
544 else
545 pathData->strokeNodes = addCurveStrokeNodes(path: strokePath, pen);
546 }
547 }
548}
549
550QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addFillNodes(const QQuadPath &path)
551{
552 NodeList ret;
553 std::unique_ptr<QSGCurveFillNode> node(new QSGCurveFillNode);
554 std::unique_ptr<QQuickShapeWireFrameNode> wfNode;
555
556 const qsizetype approxDataCount = 20 * path.elementCount();
557 node->reserve(size: approxDataCount);
558
559 const int debugFlags = debugVisualization();
560 const bool wireFrame = debugFlags & DebugWireframe;
561
562 if (Q_LIKELY(!wireFrame)) {
563 QSGCurveProcessor::processFill(path,
564 fillRule: path.fillRule(),
565 addTriangle: [&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 } else {
572 QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices;
573 wfVertices.reserve(asize: approxDataCount);
574 QSGCurveProcessor::processFill(path,
575 fillRule: path.fillRule(),
576 addTriangle: [&wfVertices, &node](const std::array<QVector2D, 3> &v,
577 const std::array<QVector2D, 3> &n,
578 QSGCurveProcessor::uvForPointCallback uvForPoint)
579 {
580 node->appendTriangle(v, n, uvForPoint);
581
582 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
583 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
584 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
585 });
586
587 wfNode.reset(p: new QQuickShapeWireFrameNode);
588 const QVector<quint32> indices = node->uncookedIndexes();
589 QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(),
590 wfVertices.size(),
591 indices.size(),
592 QSGGeometry::UnsignedIntType);
593 wfNode->setGeometry(wfg);
594
595 wfg->setDrawingMode(QSGGeometry::DrawTriangles);
596 memcpy(dest: wfg->indexData(),
597 src: indices.data(),
598 n: indices.size() * wfg->sizeOfIndex());
599 memcpy(dest: wfg->vertexData(),
600 src: wfVertices.data(),
601 n: wfg->vertexCount() * wfg->sizeOfVertex());
602 }
603
604 if (Q_UNLIKELY(debugFlags & DebugCurves))
605 node->setDebug(0.5f);
606
607 if (node->uncookedIndexes().size() > 0) {
608 node->cookGeometry();
609 ret.append(t: node.release());
610 if (wireFrame)
611 ret.append(t: wfNode.release());
612 }
613
614 return ret;
615}
616
617QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addTriangulatingStrokerNodes(const QQuadPath &path, const QPen &pen)
618{
619 NodeList ret;
620 const QColor &color = pen.color();
621
622 QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices;
623
624 QTriangulatingStroker stroker;
625 const auto painterPath = path.toPainterPath();
626 const QVectorPath &vp = qtVectorPathForPath(path: painterPath);
627 stroker.process(path: vp, pen, clip: {}, hints: {});
628
629 auto *node = new QSGCurveFillNode;
630
631 auto uvForPoint = [](QVector2D v1, QVector2D v2, QVector2D p)
632 {
633 double divisor = v1.x() * v2.y() - v2.x() * v1.y();
634
635 float u = (p.x() * v2.y() - p.y() * v2.x()) / divisor;
636 float v = (p.y() * v1.x() - p.x() * v1.y()) / divisor;
637
638 return QVector2D(u, v);
639 };
640
641 // Find uv coordinates for the point p, for a quadratic curve from p0 to p2 with control point p1
642 // also works for a line from p0 to p2, where p1 is on the inside of the path relative to the line
643 auto curveUv = [uvForPoint](QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
644 {
645 QVector2D v1 = 2 * (p1 - p0);
646 QVector2D v2 = p2 - v1 - p0;
647 return uvForPoint(v1, v2, p - p0);
648 };
649
650 auto findPointOtherSide = [](const QVector2D &startPoint, const QVector2D &endPoint, const QVector2D &referencePoint){
651
652 QVector2D baseLine = endPoint - startPoint;
653 QVector2D insideVector = referencePoint - startPoint;
654 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()); // TODO: limit size of triangle
655
656 bool swap = QVector2D::dotProduct(v1: insideVector, v2: normal) < 0;
657
658 return swap ? startPoint + normal : startPoint - normal;
659 };
660
661 static bool disableExtraTriangles = qEnvironmentVariableIntValue(varName: "QT_QUICKSHAPES_WIP_DISABLE_EXTRA_STROKE_TRIANGLES");
662
663 auto addStrokeTriangle = [&](const QVector2D &p1, const QVector2D &p2, const QVector2D &p3){
664 if (p1 == p2 || p2 == p3) {
665 return;
666 }
667
668 auto uvForPoint = [&p1, &p2, &p3, curveUv](QVector2D p) {
669 auto uv = curveUv(p1, p2, p3, p);
670 return QVector3D(uv.x(), uv.y(), 0.0f); // Line
671 };
672
673 node->appendTriangle(v1: p1, v2: p2, v3: p3, uvForPoint);
674
675
676 wfVertices.append(t: {.x: p1.x(), .y: p1.y(), .u: 1.0f, .v: 0.0f, .w: 0.0f}); // 0
677 wfVertices.append(t: {.x: p2.x(), .y: p2.y(), .u: 0.0f, .v: 0.1f, .w: 0.0f}); // 1
678 wfVertices.append(t: {.x: p3.x(), .y: p3.y(), .u: 0.0f, .v: 0.0f, .w: 1.0f}); // 2
679
680 if (!disableExtraTriangles) {
681 // Add a triangle on the outer side of the line to get some more AA
682 // The new point replaces p2 (currentVertex+1)
683 QVector2D op = findPointOtherSide(p1, p3, p2);
684 node->appendTriangle(v1: p1, v2: op, v3: p3, uvForPoint);
685
686 wfVertices.append(t: {.x: p1.x(), .y: p1.y(), .u: 1.0f, .v: 0.0f, .w: 0.0f});
687 wfVertices.append(t: {.x: op.x(), .y: op.y(), .u: 0.0f, .v: 1.0f, .w: 0.0f}); // replacing p2
688 wfVertices.append(t: {.x: p3.x(), .y: p3.y(), .u: 0.0f, .v: 0.0f, .w: 1.0f});
689 }
690 };
691
692 const int vertCount = stroker.vertexCount() / 2;
693 const float *verts = stroker.vertices();
694 for (int i = 0; i < vertCount - 2; ++i) {
695 QVector2D p[3];
696 for (int j = 0; j < 3; ++j) {
697 p[j] = QVector2D(verts[(i+j)*2], verts[(i+j)*2 + 1]);
698 }
699 addStrokeTriangle(p[0], p[1], p[2]);
700 }
701
702 QVector<quint32> indices = node->uncookedIndexes();
703 if (indices.size() > 0) {
704 node->setColor(color);
705
706 node->cookGeometry();
707 ret.append(t: node);
708 }
709 const bool wireFrame = debugVisualization() & DebugWireframe;
710 if (wireFrame) {
711 QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode;
712 QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(),
713 wfVertices.size(),
714 indices.size(),
715 QSGGeometry::UnsignedIntType);
716 wfNode->setGeometry(wfg);
717
718 wfg->setDrawingMode(QSGGeometry::DrawTriangles);
719 memcpy(dest: wfg->indexData(),
720 src: indices.data(),
721 n: indices.size() * wfg->sizeOfIndex());
722 memcpy(dest: wfg->vertexData(),
723 src: wfVertices.data(),
724 n: wfg->vertexCount() * wfg->sizeOfVertex());
725
726 ret.append(t: wfNode);
727 }
728
729 return ret;
730}
731
732void QQuickShapeCurveRenderer::setRootNode(QSGNode *node)
733{
734 clearNodeReferences();
735 m_rootNode = node;
736}
737
738void QQuickShapeCurveRenderer::clearNodeReferences()
739{
740 for (PathData &pd : m_paths) {
741 pd.fillNodes.clear();
742 pd.strokeNodes.clear();
743 }
744}
745
746int QQuickShapeCurveRenderer::debugVisualizationFlags = QQuickShapeCurveRenderer::NoDebug;
747
748int QQuickShapeCurveRenderer::debugVisualization()
749{
750 static const int envFlags = qEnvironmentVariableIntValue(varName: "QT_QUICKSHAPES_DEBUG");
751 return debugVisualizationFlags | envFlags;
752}
753
754void QQuickShapeCurveRenderer::setDebugVisualization(int options)
755{
756 if (debugVisualizationFlags == options)
757 return;
758 debugVisualizationFlags = options;
759}
760
761QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addCurveStrokeNodes(const QQuadPath &path, const QPen &pen)
762{
763 NodeList ret;
764
765 const bool debug = debugVisualization() & DebugCurves;
766 auto *node = new QSGCurveStrokeNode;
767 node->setDebug(0.2f * debug);
768 QVector<QQuickShapeWireFrameNode::WireFrameVertex> wfVertices;
769
770 const float penWidth = pen.widthF();
771
772 static const int subdivisions = qEnvironmentVariable(varName: "QT_QUICKSHAPES_STROKE_SUBDIVISIONS", QStringLiteral("3")).toInt();
773
774 const bool wireFrame = debugVisualization() & DebugWireframe;
775 QSGCurveProcessor::processStroke(strokePath: path,
776 miterLimit: pen.miterLimit(),
777 penWidth,
778 joinStyle: pen.joinStyle(),
779 capStyle: pen.capStyle(),
780 addTriangle: [&wfVertices, &node, &wireFrame](const std::array<QVector2D, 3> &s,
781 const std::array<QVector2D, 3> &p,
782 const std::array<QVector2D, 3> &n,
783 QSGCurveStrokeNode::TriangleFlags flags)
784 {
785 const QVector2D &p0 = s.at(n: 0);
786 const QVector2D &p1 = s.at(n: 1);
787 const QVector2D &p2 = s.at(n: 2);
788 if (flags.testFlag(flag: QSGCurveStrokeNode::TriangleFlag::Line))
789 node->appendTriangle(v: s, p: std::array<QVector2D, 2>{p.at(n: 0), p.at(n: 2)}, n, flags);
790 else
791 node->appendTriangle(v: s, p, n);
792
793 if (Q_UNLIKELY(wireFrame)) {
794 wfVertices.append(t: {.x: p0.x(), .y: p0.y(), .u: 1.0f, .v: 0.0f, .w: 0.0f});
795 wfVertices.append(t: {.x: p1.x(), .y: p1.y(), .u: 0.0f, .v: 1.0f, .w: 0.0f});
796 wfVertices.append(t: {.x: p2.x(), .y: p2.y(), .u: 0.0f, .v: 0.0f, .w: 1.0f});
797 }
798 },
799 subdivisions);
800
801 auto indexCopy = node->uncookedIndexes(); // uncookedIndexes get delete on cooking
802
803 node->setColor(pen.color());
804 node->setStrokeWidth(penWidth);
805 node->cookGeometry();
806 ret.append(t: node);
807
808 if (Q_UNLIKELY(wireFrame)) {
809 QQuickShapeWireFrameNode *wfNode = new QQuickShapeWireFrameNode;
810
811 QSGGeometry *wfg = new QSGGeometry(QQuickShapeWireFrameNode::attributes(),
812 wfVertices.size(),
813 indexCopy.size(),
814 QSGGeometry::UnsignedIntType);
815 wfNode->setGeometry(wfg);
816
817 wfg->setDrawingMode(QSGGeometry::DrawTriangles);
818 memcpy(dest: wfg->indexData(),
819 src: indexCopy.data(),
820 n: indexCopy.size() * wfg->sizeOfIndex());
821 memcpy(dest: wfg->vertexData(),
822 src: wfVertices.data(),
823 n: wfg->vertexCount() * wfg->sizeOfVertex());
824
825 ret.append(t: wfNode);
826 }
827
828 return ret;
829}
830
831QT_END_NAMESPACE
832

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