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