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