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