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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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