1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickshapegenericrenderer_p.h"
5#include <QtGui/private/qtriangulator_p.h>
6#include <QtGui/private/qtriangulatingstroker_p.h>
7#include <rhi/qrhi.h>
8#include <QSGVertexColorMaterial>
9
10#if QT_CONFIG(thread)
11#include <QThreadPool>
12#endif
13
14QT_BEGIN_NAMESPACE
15
16struct ColoredVertex // must match QSGGeometry::ColoredPoint2D
17{
18 float x, y;
19 QQuickShapeGenericRenderer::Color4ub color;
20 void set(float nx, float ny, QQuickShapeGenericRenderer::Color4ub ncolor)
21 {
22 x = nx; y = ny; color = ncolor;
23 }
24};
25
26static inline QQuickShapeGenericRenderer::Color4ub colorToColor4ub(const QColor &c)
27{
28 float r, g, b, a;
29 c.getRgbF(r: &r, g: &g, b: &b, a: &a);
30 QQuickShapeGenericRenderer::Color4ub color = {
31 .r: uchar(qRound(f: r * a * 255)),
32 .g: uchar(qRound(f: g * a * 255)),
33 .b: uchar(qRound(f: b * a * 255)),
34 .a: uchar(qRound(f: a * 255))
35 };
36 return color;
37}
38
39QQuickShapeGenericStrokeFillNode::QQuickShapeGenericStrokeFillNode(QQuickWindow *window)
40 : m_material(nullptr)
41{
42 setFlag(QSGNode::OwnsGeometry, true);
43 setGeometry(new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0, 0));
44 activateMaterial(window, m: MatSolidColor);
45#ifdef QSG_RUNTIME_DESCRIPTION
46 qsgnode_set_description(node: this, description: QLatin1String("stroke-fill"));
47#endif
48}
49
50void QQuickShapeGenericStrokeFillNode::activateMaterial(QQuickWindow *window, Material m)
51{
52 switch (m) {
53 case MatSolidColor:
54 // Use vertexcolor material. Items with different colors remain batchable
55 // this way, at the expense of having to provide per-vertex color values.
56 m_material.reset(other: QQuickShapeGenericMaterialFactory::createVertexColor(window));
57 break;
58 case MatLinearGradient:
59 m_material.reset(other: QQuickShapeGenericMaterialFactory::createLinearGradient(window, node: this));
60 break;
61 case MatRadialGradient:
62 m_material.reset(other: QQuickShapeGenericMaterialFactory::createRadialGradient(window, node: this));
63 break;
64 case MatConicalGradient:
65 m_material.reset(other: QQuickShapeGenericMaterialFactory::createConicalGradient(window, node: this));
66 break;
67 default:
68 qWarning(msg: "Unknown material %d", m);
69 return;
70 }
71
72 if (material() != m_material.data())
73 setMaterial(m_material.data());
74}
75
76QQuickShapeGenericRenderer::~QQuickShapeGenericRenderer()
77{
78 for (ShapePathData &d : m_sp) {
79 if (d.pendingFill)
80 d.pendingFill->orphaned = true;
81 if (d.pendingStroke)
82 d.pendingStroke->orphaned = true;
83 }
84}
85
86// sync, and so triangulation too, happens on the gui thread
87// - except when async is set, in which case triangulation is moved to worker threads
88
89void QQuickShapeGenericRenderer::beginSync(int totalCount, bool *countChanged)
90{
91 if (m_sp.size() != totalCount) {
92 m_sp.resize(size: totalCount);
93 m_accDirty |= DirtyList;
94 *countChanged = true;
95 } else {
96 *countChanged = false;
97 }
98 for (ShapePathData &d : m_sp)
99 d.syncDirty = 0;
100}
101
102void QQuickShapeGenericRenderer::setPath(int index, const QQuickPath *path)
103{
104 ShapePathData &d(m_sp[index]);
105 d.path = path ? path->path() : QPainterPath();
106 d.syncDirty |= DirtyFillGeom | DirtyStrokeGeom;
107}
108
109void QQuickShapeGenericRenderer::setStrokeColor(int index, const QColor &color)
110{
111 ShapePathData &d(m_sp[index]);
112 const bool wasTransparent = d.strokeColor.a == 0;
113 d.strokeColor = colorToColor4ub(c: color);
114 const bool isTransparent = d.strokeColor.a == 0;
115 d.syncDirty |= DirtyColor;
116 if (wasTransparent && !isTransparent)
117 d.syncDirty |= DirtyStrokeGeom;
118}
119
120void QQuickShapeGenericRenderer::setStrokeWidth(int index, qreal w)
121{
122 ShapePathData &d(m_sp[index]);
123 d.strokeWidth = w;
124 if (w >= 0.0f)
125 d.pen.setWidthF(w);
126 d.syncDirty |= DirtyStrokeGeom;
127}
128
129void QQuickShapeGenericRenderer::setFillColor(int index, const QColor &color)
130{
131 ShapePathData &d(m_sp[index]);
132 const bool wasTransparent = d.fillColor.a == 0;
133 d.fillColor = colorToColor4ub(c: color);
134 const bool isTransparent = d.fillColor.a == 0;
135 d.syncDirty |= DirtyColor;
136 if (wasTransparent && !isTransparent)
137 d.syncDirty |= DirtyFillGeom;
138}
139
140void QQuickShapeGenericRenderer::setFillRule(int index, QQuickShapePath::FillRule fillRule)
141{
142 ShapePathData &d(m_sp[index]);
143 d.fillRule = Qt::FillRule(fillRule);
144 d.syncDirty |= DirtyFillGeom;
145}
146
147void QQuickShapeGenericRenderer::setJoinStyle(int index, QQuickShapePath::JoinStyle joinStyle, int miterLimit)
148{
149 ShapePathData &d(m_sp[index]);
150 d.pen.setJoinStyle(Qt::PenJoinStyle(joinStyle));
151 d.pen.setMiterLimit(miterLimit);
152 d.syncDirty |= DirtyStrokeGeom;
153}
154
155void QQuickShapeGenericRenderer::setCapStyle(int index, QQuickShapePath::CapStyle capStyle)
156{
157 ShapePathData &d(m_sp[index]);
158 d.pen.setCapStyle(Qt::PenCapStyle(capStyle));
159 d.syncDirty |= DirtyStrokeGeom;
160}
161
162void QQuickShapeGenericRenderer::setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle,
163 qreal dashOffset, const QVector<qreal> &dashPattern)
164{
165 ShapePathData &d(m_sp[index]);
166 d.pen.setStyle(Qt::PenStyle(strokeStyle));
167 if (strokeStyle == QQuickShapePath::DashLine) {
168 d.pen.setDashPattern(dashPattern);
169 d.pen.setDashOffset(dashOffset);
170 }
171 d.syncDirty |= DirtyStrokeGeom;
172}
173
174void QQuickShapeGenericRenderer::setFillGradient(int index, QQuickShapeGradient *gradient)
175{
176 ShapePathData &d(m_sp[index]);
177 if (gradient) {
178 d.fillGradient.stops = gradient->gradientStops(); // sorted
179 d.fillGradient.spread = gradient->spread();
180 if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(object: gradient)) {
181 d.fillGradientActive = LinearGradient;
182 d.fillGradient.a = QPointF(g->x1(), g->y1());
183 d.fillGradient.b = QPointF(g->x2(), g->y2());
184 } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(object: gradient)) {
185 d.fillGradientActive = RadialGradient;
186 d.fillGradient.a = QPointF(g->centerX(), g->centerY());
187 d.fillGradient.b = QPointF(g->focalX(), g->focalY());
188 d.fillGradient.v0 = g->centerRadius();
189 d.fillGradient.v1 = g->focalRadius();
190 } else if (QQuickShapeConicalGradient *g = qobject_cast<QQuickShapeConicalGradient *>(object: gradient)) {
191 d.fillGradientActive = ConicalGradient;
192 d.fillGradient.a = QPointF(g->centerX(), g->centerY());
193 d.fillGradient.v0 = g->angle();
194 } else {
195 Q_UNREACHABLE();
196 }
197 } else {
198 d.fillGradientActive = NoGradient;
199 }
200 d.syncDirty |= DirtyFillGradient;
201}
202
203void QQuickShapeGenericRenderer::setTriangulationScale(qreal scale)
204{
205 // No dirty, this is called at the start of every sync. Just store the value.
206 m_triangulationScale = scale;
207}
208
209void QQuickShapeFillRunnable::run()
210{
211 QQuickShapeGenericRenderer::triangulateFill(path, fillColor, fillVertices: &fillVertices, fillIndices: &fillIndices, indexType: &indexType,
212 supportsElementIndexUint, triangulationScale);
213 emit done(self: this);
214}
215
216void QQuickShapeStrokeRunnable::run()
217{
218 QQuickShapeGenericRenderer::triangulateStroke(path, pen, strokeColor, strokeVertices: &strokeVertices, clipSize, triangulationScale);
219 emit done(self: this);
220}
221
222void QQuickShapeGenericRenderer::setAsyncCallback(void (*callback)(void *), void *data)
223{
224 m_asyncCallback = callback;
225 m_asyncCallbackData = data;
226}
227
228#if QT_CONFIG(thread)
229static QThreadPool *pathWorkThreadPool = nullptr;
230
231static void deletePathWorkThreadPool()
232{
233 delete pathWorkThreadPool;
234 pathWorkThreadPool = nullptr;
235}
236#endif
237
238void QQuickShapeGenericRenderer::endSync(bool async)
239{
240#if !QT_CONFIG(thread)
241 // Force synchronous mode for the no-thread configuration due
242 // to lack of QThreadPool.
243 async = false;
244#endif
245
246 bool didKickOffAsync = false;
247
248 for (int i = 0; i < m_sp.size(); ++i) {
249 ShapePathData &d(m_sp[i]);
250 if (!d.syncDirty)
251 continue;
252
253 m_accDirty |= d.syncDirty;
254
255 // Use a shadow dirty flag in order to avoid losing state in case there are
256 // multiple syncs with different dirty flags before we get to updateNode()
257 // on the render thread (with the gui thread blocked). For our purposes
258 // here syncDirty is still required since geometry regeneration must only
259 // happen when there was an actual change in this particular sync round.
260 d.effectiveDirty |= d.syncDirty;
261
262 if (d.path.isEmpty()) {
263 d.fillVertices.clear();
264 d.fillIndices.clear();
265 d.strokeVertices.clear();
266 continue;
267 }
268
269#if QT_CONFIG(thread)
270 if (async && !pathWorkThreadPool) {
271 qAddPostRoutine(deletePathWorkThreadPool);
272 pathWorkThreadPool = new QThreadPool;
273 const int idealCount = QThread::idealThreadCount();
274 pathWorkThreadPool->setMaxThreadCount(idealCount > 0 ? idealCount * 2 : 4);
275 }
276#endif
277 auto testFeatureIndexUint = [](QQuickItem *item) -> bool {
278 if (auto *w = item->window()) {
279 if (auto *rhi = QQuickWindowPrivate::get(c: w)->rhi)
280 return rhi->isFeatureSupported(feature: QRhi::ElementIndexUint);
281 }
282 return true;
283 };
284 static bool supportsElementIndexUint = testFeatureIndexUint(m_item);
285 if ((d.syncDirty & DirtyFillGeom) && d.fillColor.a) {
286 d.path.setFillRule(d.fillRule);
287 if (m_api == QSGRendererInterface::Unknown)
288 m_api = m_item->window()->rendererInterface()->graphicsApi();
289 if (async) {
290 QQuickShapeFillRunnable *r = new QQuickShapeFillRunnable;
291 r->setAutoDelete(false);
292 if (d.pendingFill)
293 d.pendingFill->orphaned = true;
294 d.pendingFill = r;
295 r->path = d.path;
296 r->fillColor = d.fillColor;
297 r->supportsElementIndexUint = supportsElementIndexUint;
298 r->triangulationScale = m_triangulationScale;
299 // Unlikely in practice but in theory m_sp could be
300 // resized. Therefore, capture 'i' instead of 'd'.
301 QObject::connect(sender: r, signal: &QQuickShapeFillRunnable::done, qApp, slot: [this, i](QQuickShapeFillRunnable *r) {
302 // Bail out when orphaned (meaning either another run was
303 // started after this one, or the renderer got destroyed).
304 if (!r->orphaned && i < m_sp.size()) {
305 ShapePathData &d(m_sp[i]);
306 d.fillVertices = r->fillVertices;
307 d.fillIndices = r->fillIndices;
308 d.indexType = r->indexType;
309 d.pendingFill = nullptr;
310 d.effectiveDirty |= DirtyFillGeom;
311 maybeUpdateAsyncItem();
312 }
313 r->deleteLater();
314 });
315 didKickOffAsync = true;
316#if QT_CONFIG(thread)
317 // qtVectorPathForPath() initializes a unique_ptr without locking.
318 // Do that before starting the threads as otherwise we get a race condition.
319 qtVectorPathForPath(path: r->path);
320 pathWorkThreadPool->start(runnable: r);
321#endif
322 } else {
323 triangulateFill(path: d.path, fillColor: d.fillColor, fillVertices: &d.fillVertices, fillIndices: &d.fillIndices, indexType: &d.indexType,
324 supportsElementIndexUint,
325 triangulationScale: m_triangulationScale);
326 }
327 }
328
329 if ((d.syncDirty & DirtyStrokeGeom) && d.strokeWidth >= 0.0f && d.strokeColor.a) {
330 if (async) {
331 QQuickShapeStrokeRunnable *r = new QQuickShapeStrokeRunnable;
332 r->setAutoDelete(false);
333 if (d.pendingStroke)
334 d.pendingStroke->orphaned = true;
335 d.pendingStroke = r;
336 r->path = d.path;
337 r->pen = d.pen;
338 r->strokeColor = d.strokeColor;
339 r->clipSize = QSize(m_item->width(), m_item->height());
340 r->triangulationScale = m_triangulationScale;
341 QObject::connect(sender: r, signal: &QQuickShapeStrokeRunnable::done, qApp, slot: [this, i](QQuickShapeStrokeRunnable *r) {
342 if (!r->orphaned && i < m_sp.size()) {
343 ShapePathData &d(m_sp[i]);
344 d.strokeVertices = r->strokeVertices;
345 d.pendingStroke = nullptr;
346 d.effectiveDirty |= DirtyStrokeGeom;
347 maybeUpdateAsyncItem();
348 }
349 r->deleteLater();
350 });
351 didKickOffAsync = true;
352#if QT_CONFIG(thread)
353 // qtVectorPathForPath() initializes a unique_ptr without locking.
354 // Do that before starting the threads as otherwise we get a race condition.
355 qtVectorPathForPath(path: r->path);
356 pathWorkThreadPool->start(runnable: r);
357#endif
358 } else {
359 triangulateStroke(path: d.path, pen: d.pen, strokeColor: d.strokeColor, strokeVertices: &d.strokeVertices,
360 clipSize: QSize(m_item->width(), m_item->height()), triangulationScale: m_triangulationScale);
361 }
362 }
363 }
364
365 if (!didKickOffAsync && async && m_asyncCallback)
366 m_asyncCallback(m_asyncCallbackData);
367}
368
369void QQuickShapeGenericRenderer::maybeUpdateAsyncItem()
370{
371 for (const ShapePathData &d : std::as_const(t&: m_sp)) {
372 if (d.pendingFill || d.pendingStroke)
373 return;
374 }
375 m_accDirty |= DirtyFillGeom | DirtyStrokeGeom;
376 m_item->update();
377 if (m_asyncCallback)
378 m_asyncCallback(m_asyncCallbackData);
379}
380
381// the stroke/fill triangulation functions may be invoked either on the gui
382// thread or some worker thread and must thus be self-contained.
383void QQuickShapeGenericRenderer::triangulateFill(const QPainterPath &path,
384 const Color4ub &fillColor,
385 VertexContainerType *fillVertices,
386 IndexContainerType *fillIndices,
387 QSGGeometry::Type *indexType,
388 bool supportsElementIndexUint,
389 qreal triangulationScale)
390{
391 const QVectorPath &vp = qtVectorPathForPath(path);
392
393 QTriangleSet ts = qTriangulate(path: vp, matrix: QTransform::fromScale(dx: triangulationScale, dy: triangulationScale), lod: 1, allowUintIndices: supportsElementIndexUint);
394 const int vertexCount = ts.vertices.size() / 2; // just a qreal vector with x,y hence the / 2
395 fillVertices->resize(size: vertexCount);
396 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(fillVertices->data());
397 const qreal *vsrc = ts.vertices.constData();
398 for (int i = 0; i < vertexCount; ++i)
399 vdst[i].set(nx: vsrc[i * 2] / triangulationScale, ny: vsrc[i * 2 + 1] / triangulationScale, ncolor: fillColor);
400
401 size_t indexByteSize;
402 if (ts.indices.type() == QVertexIndexVector::UnsignedShort) {
403 *indexType = QSGGeometry::UnsignedShortType;
404 // fillIndices is still QVector<quint32>. Just resize to N/2 and pack
405 // the N quint16s into it.
406 fillIndices->resize(size: ts.indices.size() / 2);
407 indexByteSize = ts.indices.size() * sizeof(quint16);
408 } else {
409 *indexType = QSGGeometry::UnsignedIntType;
410 fillIndices->resize(size: ts.indices.size());
411 indexByteSize = ts.indices.size() * sizeof(quint32);
412 }
413 memcpy(dest: fillIndices->data(), src: ts.indices.data(), n: indexByteSize);
414}
415
416void QQuickShapeGenericRenderer::triangulateStroke(const QPainterPath &path,
417 const QPen &pen,
418 const Color4ub &strokeColor,
419 VertexContainerType *strokeVertices,
420 const QSize &clipSize,
421 qreal triangulationScale)
422{
423 const QVectorPath &vp = qtVectorPathForPath(path);
424 const QRectF clip(QPointF(0, 0), clipSize);
425 const qreal inverseScale = 1.0 / triangulationScale;
426
427 QTriangulatingStroker stroker;
428 stroker.setInvScale(inverseScale);
429
430 if (pen.style() == Qt::SolidLine) {
431 stroker.process(path: vp, pen, clip, hints: {});
432 } else {
433 QDashedStrokeProcessor dashStroker;
434 dashStroker.setInvScale(inverseScale);
435 dashStroker.process(path: vp, pen, clip, hints: {});
436 QVectorPath dashStroke(dashStroker.points(), dashStroker.elementCount(),
437 dashStroker.elementTypes(), 0);
438 stroker.process(path: dashStroke, pen, clip, hints: {});
439 }
440
441 if (!stroker.vertexCount()) {
442 strokeVertices->clear();
443 return;
444 }
445
446 const int vertexCount = stroker.vertexCount() / 2; // just a float vector with x,y hence the / 2
447 strokeVertices->resize(size: vertexCount);
448 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(strokeVertices->data());
449 const float *vsrc = stroker.vertices();
450 for (int i = 0; i < vertexCount; ++i)
451 vdst[i].set(nx: vsrc[i * 2], ny: vsrc[i * 2 + 1], ncolor: strokeColor);
452}
453
454void QQuickShapeGenericRenderer::setRootNode(QQuickShapeGenericNode *node)
455{
456 if (m_rootNode != node) {
457 m_rootNode = node;
458 m_accDirty |= DirtyList;
459 }
460}
461
462// on the render thread with gui blocked
463void QQuickShapeGenericRenderer::updateNode()
464{
465 if (!m_rootNode || !m_accDirty)
466 return;
467
468// [ m_rootNode ]
469// / / /
470// #0 [ fill ] [ stroke ] [ next ]
471// / / |
472// #1 [ fill ] [ stroke ] [ next ]
473// / / |
474// #2 [ fill ] [ stroke ] [ next ]
475// ...
476// ...
477
478 QQuickShapeGenericNode **nodePtr = &m_rootNode;
479 QQuickShapeGenericNode *prevNode = nullptr;
480
481 for (ShapePathData &d : m_sp) {
482 if (!*nodePtr) {
483 Q_ASSERT(prevNode);
484 *nodePtr = new QQuickShapeGenericNode;
485 prevNode->m_next = *nodePtr;
486 prevNode->appendChildNode(node: *nodePtr);
487 }
488
489 QQuickShapeGenericNode *node = *nodePtr;
490
491 if (m_accDirty & DirtyList)
492 d.effectiveDirty |= DirtyFillGeom | DirtyStrokeGeom | DirtyColor | DirtyFillGradient;
493
494 if (!d.effectiveDirty) {
495 prevNode = node;
496 nodePtr = &node->m_next;
497 continue;
498 }
499
500 if (d.fillColor.a == 0) {
501 delete node->m_fillNode;
502 node->m_fillNode = nullptr;
503 } else if (!node->m_fillNode) {
504 node->m_fillNode = new QQuickShapeGenericStrokeFillNode(m_item->window());
505 if (node->m_strokeNode)
506 node->removeChildNode(node: node->m_strokeNode);
507 node->appendChildNode(node: node->m_fillNode);
508 if (node->m_strokeNode)
509 node->appendChildNode(node: node->m_strokeNode);
510 d.effectiveDirty |= DirtyFillGeom;
511 }
512
513 if (d.strokeWidth < 0.0f || d.strokeColor.a == 0) {
514 delete node->m_strokeNode;
515 node->m_strokeNode = nullptr;
516 } else if (!node->m_strokeNode) {
517 node->m_strokeNode = new QQuickShapeGenericStrokeFillNode(m_item->window());
518 node->appendChildNode(node: node->m_strokeNode);
519 d.effectiveDirty |= DirtyStrokeGeom;
520 }
521
522 updateFillNode(d: &d, node);
523 updateStrokeNode(d: &d, node);
524
525 d.effectiveDirty = 0;
526
527 prevNode = node;
528 nodePtr = &node->m_next;
529 }
530
531 if (*nodePtr && prevNode) {
532 prevNode->removeChildNode(node: *nodePtr);
533 delete *nodePtr;
534 *nodePtr = nullptr;
535 }
536
537 m_accDirty = 0;
538}
539
540void QQuickShapeGenericRenderer::updateShadowDataInNode(ShapePathData *d, QQuickShapeGenericStrokeFillNode *n)
541{
542 if (d->fillGradientActive) {
543 if (d->effectiveDirty & DirtyFillGradient)
544 n->m_fillGradient = d->fillGradient;
545 }
546}
547
548void QQuickShapeGenericRenderer::updateFillNode(ShapePathData *d, QQuickShapeGenericNode *node)
549{
550 if (!node->m_fillNode)
551 return;
552 if (!(d->effectiveDirty & (DirtyFillGeom | DirtyColor | DirtyFillGradient)))
553 return;
554
555 // Make a copy of the data that will be accessed by the material on
556 // the render thread. This must be done even when we bail out below.
557 QQuickShapeGenericStrokeFillNode *n = node->m_fillNode;
558 updateShadowDataInNode(d, n);
559
560 QSGGeometry *g = n->geometry();
561 if (d->fillVertices.isEmpty()) {
562 if (g->vertexCount() || g->indexCount()) {
563 g->allocate(vertexCount: 0, indexCount: 0);
564 n->markDirty(bits: QSGNode::DirtyGeometry);
565 }
566 return;
567 }
568
569 if (d->fillGradientActive) {
570 QQuickShapeGenericStrokeFillNode::Material gradMat;
571 switch (d->fillGradientActive) {
572 case LinearGradient:
573 gradMat = QQuickShapeGenericStrokeFillNode::MatLinearGradient;
574 break;
575 case RadialGradient:
576 gradMat = QQuickShapeGenericStrokeFillNode::MatRadialGradient;
577 break;
578 case ConicalGradient:
579 gradMat = QQuickShapeGenericStrokeFillNode::MatConicalGradient;
580 break;
581 default:
582 Q_UNREACHABLE_RETURN();
583 }
584 n->activateMaterial(window: m_item->window(), m: gradMat);
585 if (d->effectiveDirty & DirtyFillGradient) {
586 // Gradients are implemented via a texture-based material.
587 n->markDirty(bits: QSGNode::DirtyMaterial);
588 // stop here if only the gradient changed; no need to touch the geometry
589 if (!(d->effectiveDirty & DirtyFillGeom))
590 return;
591 }
592 } else {
593 n->activateMaterial(window: m_item->window(), m: QQuickShapeGenericStrokeFillNode::MatSolidColor);
594 // fast path for updating only color values when no change in vertex positions
595 if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyFillGeom)) {
596 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(g->vertexData());
597 for (int i = 0; i < g->vertexCount(); ++i)
598 vdst[i].set(nx: vdst[i].x, ny: vdst[i].y, ncolor: d->fillColor);
599 n->markDirty(bits: QSGNode::DirtyGeometry);
600 return;
601 }
602 }
603
604 const int indexCount = d->indexType == QSGGeometry::UnsignedShortType
605 ? d->fillIndices.size() * 2 : d->fillIndices.size();
606 if (g->indexType() != d->indexType) {
607 g = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(),
608 d->fillVertices.size(), indexCount, d->indexType);
609 n->setGeometry(g);
610 } else {
611 g->allocate(vertexCount: d->fillVertices.size(), indexCount);
612 }
613 g->setDrawingMode(QSGGeometry::DrawTriangles);
614 memcpy(dest: g->vertexData(), src: d->fillVertices.constData(), n: g->vertexCount() * g->sizeOfVertex());
615 memcpy(dest: g->indexData(), src: d->fillIndices.constData(), n: g->indexCount() * g->sizeOfIndex());
616
617 n->markDirty(bits: QSGNode::DirtyGeometry);
618}
619
620void QQuickShapeGenericRenderer::updateStrokeNode(ShapePathData *d, QQuickShapeGenericNode *node)
621{
622 if (!node->m_strokeNode)
623 return;
624 if (!(d->effectiveDirty & (DirtyStrokeGeom | DirtyColor)))
625 return;
626
627 QQuickShapeGenericStrokeFillNode *n = node->m_strokeNode;
628 QSGGeometry *g = n->geometry();
629 if (d->strokeVertices.isEmpty()) {
630 if (g->vertexCount() || g->indexCount()) {
631 g->allocate(vertexCount: 0, indexCount: 0);
632 n->markDirty(bits: QSGNode::DirtyGeometry);
633 }
634 return;
635 }
636
637 n->markDirty(bits: QSGNode::DirtyGeometry);
638
639 // Async loading runs update once, bails out above, then updates again once
640 // ready. Set the material dirty then. This is in-line with fill where the
641 // first activateMaterial() achieves the same.
642 if (!g->vertexCount())
643 n->markDirty(bits: QSGNode::DirtyMaterial);
644
645 if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyStrokeGeom)) {
646 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(g->vertexData());
647 for (int i = 0; i < g->vertexCount(); ++i)
648 vdst[i].set(nx: vdst[i].x, ny: vdst[i].y, ncolor: d->strokeColor);
649 return;
650 }
651
652 g->allocate(vertexCount: d->strokeVertices.size(), indexCount: 0);
653 g->setDrawingMode(QSGGeometry::DrawTriangleStrip);
654 memcpy(dest: g->vertexData(), src: d->strokeVertices.constData(), n: g->vertexCount() * g->sizeOfVertex());
655}
656
657QSGMaterial *QQuickShapeGenericMaterialFactory::createVertexColor(QQuickWindow *window)
658{
659 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
660
661 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
662 return new QSGVertexColorMaterial;
663
664 qWarning(msg: "Vertex-color material: Unsupported graphics API %d", api);
665 return nullptr;
666}
667
668QSGMaterial *QQuickShapeGenericMaterialFactory::createLinearGradient(QQuickWindow *window,
669 QQuickShapeGenericStrokeFillNode *node)
670{
671 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
672
673 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
674 return new QQuickShapeLinearGradientMaterial(node);
675
676 qWarning(msg: "Linear gradient material: Unsupported graphics API %d", api);
677 return nullptr;
678}
679
680QSGMaterial *QQuickShapeGenericMaterialFactory::createRadialGradient(QQuickWindow *window,
681 QQuickShapeGenericStrokeFillNode *node)
682{
683 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
684
685 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
686 return new QQuickShapeRadialGradientMaterial(node);
687
688 qWarning(msg: "Radial gradient material: Unsupported graphics API %d", api);
689 return nullptr;
690}
691
692QSGMaterial *QQuickShapeGenericMaterialFactory::createConicalGradient(QQuickWindow *window,
693 QQuickShapeGenericStrokeFillNode *node)
694{
695 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
696
697 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
698 return new QQuickShapeConicalGradientMaterial(node);
699
700 qWarning(msg: "Conical gradient material: Unsupported graphics API %d", api);
701 return nullptr;
702}
703
704QQuickShapeLinearGradientRhiShader::QQuickShapeLinearGradientRhiShader()
705{
706 setShaderFileName(stage: VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.vert.qsb"));
707 setShaderFileName(stage: FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.frag.qsb"));
708}
709
710bool QQuickShapeLinearGradientRhiShader::updateUniformData(RenderState &state,
711 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
712{
713 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
714 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
715 bool changed = false;
716 QByteArray *buf = state.uniformData();
717 Q_ASSERT(buf->size() >= 84);
718
719 if (state.isMatrixDirty()) {
720 const QMatrix4x4 m = state.combinedMatrix();
721 memcpy(dest: buf->data(), src: m.constData(), n: 64);
722 changed = true;
723 }
724
725 QQuickShapeGenericStrokeFillNode *node = m->node();
726
727 if (!oldMaterial || m_gradA.x() != node->m_fillGradient.a.x() || m_gradA.y() != node->m_fillGradient.a.y()) {
728 m_gradA = QVector2D(node->m_fillGradient.a.x(), node->m_fillGradient.a.y());
729 Q_ASSERT(sizeof(m_gradA) == 8);
730 memcpy(dest: buf->data() + 64, src: &m_gradA, n: 8);
731 changed = true;
732 }
733
734 if (!oldMaterial || m_gradB.x() != node->m_fillGradient.b.x() || m_gradB.y() != node->m_fillGradient.b.y()) {
735 m_gradB = QVector2D(node->m_fillGradient.b.x(), node->m_fillGradient.b.y());
736 memcpy(dest: buf->data() + 72, src: &m_gradB, n: 8);
737 changed = true;
738 }
739
740 if (state.isOpacityDirty()) {
741 const float opacity = state.opacity();
742 memcpy(dest: buf->data() + 80, src: &opacity, n: 4);
743 changed = true;
744 }
745
746 return changed;
747}
748
749void QQuickShapeLinearGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
750 QSGMaterial *newMaterial, QSGMaterial *)
751{
752 if (binding != 1)
753 return;
754
755 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
756 QQuickShapeGenericStrokeFillNode *node = m->node();
757 const QQuickShapeGradientCacheKey cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread);
758 QSGTexture *t = QQuickShapeGradientCache::cacheForRhi(rhi: state.rhi())->get(grad: cacheKey);
759 t->commitTextureOperations(rhi: state.rhi(), resourceUpdates: state.resourceUpdateBatch());
760 *texture = t;
761}
762
763QSGMaterialType *QQuickShapeLinearGradientMaterial::type() const
764{
765 static QSGMaterialType type;
766 return &type;
767}
768
769int QQuickShapeLinearGradientMaterial::compare(const QSGMaterial *other) const
770{
771 Q_ASSERT(other && type() == other->type());
772 const QQuickShapeLinearGradientMaterial *m = static_cast<const QQuickShapeLinearGradientMaterial *>(other);
773
774 QQuickShapeGenericStrokeFillNode *a = node();
775 QQuickShapeGenericStrokeFillNode *b = m->node();
776 Q_ASSERT(a && b);
777 if (a == b)
778 return 0;
779
780 const QQuickAbstractPathRenderer::GradientDesc *ga = &a->m_fillGradient;
781 const QQuickAbstractPathRenderer::GradientDesc *gb = &b->m_fillGradient;
782
783 if (int d = ga->spread - gb->spread)
784 return d;
785
786 if (int d = ga->a.x() - gb->a.x())
787 return d;
788 if (int d = ga->a.y() - gb->a.y())
789 return d;
790 if (int d = ga->b.x() - gb->b.x())
791 return d;
792 if (int d = ga->b.y() - gb->b.y())
793 return d;
794
795 if (int d = ga->stops.size() - gb->stops.size())
796 return d;
797
798 for (int i = 0; i < ga->stops.size(); ++i) {
799 if (int d = ga->stops[i].first - gb->stops[i].first)
800 return d;
801 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
802 return d;
803 }
804
805 return 0;
806}
807
808QSGMaterialShader *QQuickShapeLinearGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
809{
810 Q_UNUSED(renderMode);
811 return new QQuickShapeLinearGradientRhiShader;
812}
813
814QQuickShapeRadialGradientRhiShader::QQuickShapeRadialGradientRhiShader()
815{
816 setShaderFileName(stage: VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.vert.qsb"));
817 setShaderFileName(stage: FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.frag.qsb"));
818}
819
820bool QQuickShapeRadialGradientRhiShader::updateUniformData(RenderState &state,
821 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
822{
823 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
824 QQuickShapeRadialGradientMaterial *m = static_cast<QQuickShapeRadialGradientMaterial *>(newMaterial);
825 bool changed = false;
826 QByteArray *buf = state.uniformData();
827 Q_ASSERT(buf->size() >= 92);
828
829 if (state.isMatrixDirty()) {
830 const QMatrix4x4 m = state.combinedMatrix();
831 memcpy(dest: buf->data(), src: m.constData(), n: 64);
832 changed = true;
833 }
834
835 QQuickShapeGenericStrokeFillNode *node = m->node();
836
837 const QPointF centerPoint = node->m_fillGradient.a;
838 const QPointF focalPoint = node->m_fillGradient.b;
839 const QPointF focalToCenter = centerPoint - focalPoint;
840 const float centerRadius = node->m_fillGradient.v0;
841 const float focalRadius = node->m_fillGradient.v1;
842
843 if (!oldMaterial || m_focalPoint.x() != focalPoint.x() || m_focalPoint.y() != focalPoint.y()) {
844 m_focalPoint = QVector2D(focalPoint.x(), focalPoint.y());
845 Q_ASSERT(sizeof(m_focalPoint) == 8);
846 memcpy(dest: buf->data() + 64, src: &m_focalPoint, n: 8);
847 changed = true;
848 }
849
850 if (!oldMaterial || m_focalToCenter.x() != focalToCenter.x() || m_focalToCenter.y() != focalToCenter.y()) {
851 m_focalToCenter = QVector2D(focalToCenter.x(), focalToCenter.y());
852 Q_ASSERT(sizeof(m_focalToCenter) == 8);
853 memcpy(dest: buf->data() + 72, src: &m_focalToCenter, n: 8);
854 changed = true;
855 }
856
857 if (!oldMaterial || m_centerRadius != centerRadius) {
858 m_centerRadius = centerRadius;
859 memcpy(dest: buf->data() + 80, src: &m_centerRadius, n: 4);
860 changed = true;
861 }
862
863 if (!oldMaterial || m_focalRadius != focalRadius) {
864 m_focalRadius = focalRadius;
865 memcpy(dest: buf->data() + 84, src: &m_focalRadius, n: 4);
866 changed = true;
867 }
868
869 if (state.isOpacityDirty()) {
870 const float opacity = state.opacity();
871 memcpy(dest: buf->data() + 88, src: &opacity, n: 4);
872 changed = true;
873 }
874
875 return changed;
876}
877
878void QQuickShapeRadialGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
879 QSGMaterial *newMaterial, QSGMaterial *)
880{
881 if (binding != 1)
882 return;
883
884 QQuickShapeRadialGradientMaterial *m = static_cast<QQuickShapeRadialGradientMaterial *>(newMaterial);
885 QQuickShapeGenericStrokeFillNode *node = m->node();
886 const QQuickShapeGradientCacheKey cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread);
887 QSGTexture *t = QQuickShapeGradientCache::cacheForRhi(rhi: state.rhi())->get(grad: cacheKey);
888 t->commitTextureOperations(rhi: state.rhi(), resourceUpdates: state.resourceUpdateBatch());
889 *texture = t;
890}
891
892QSGMaterialType *QQuickShapeRadialGradientMaterial::type() const
893{
894 static QSGMaterialType type;
895 return &type;
896}
897
898int QQuickShapeRadialGradientMaterial::compare(const QSGMaterial *other) const
899{
900 Q_ASSERT(other && type() == other->type());
901 const QQuickShapeRadialGradientMaterial *m = static_cast<const QQuickShapeRadialGradientMaterial *>(other);
902
903 QQuickShapeGenericStrokeFillNode *a = node();
904 QQuickShapeGenericStrokeFillNode *b = m->node();
905 Q_ASSERT(a && b);
906 if (a == b)
907 return 0;
908
909 const QQuickAbstractPathRenderer::GradientDesc *ga = &a->m_fillGradient;
910 const QQuickAbstractPathRenderer::GradientDesc *gb = &b->m_fillGradient;
911
912 if (int d = ga->spread - gb->spread)
913 return d;
914
915 if (int d = ga->a.x() - gb->a.x())
916 return d;
917 if (int d = ga->a.y() - gb->a.y())
918 return d;
919 if (int d = ga->b.x() - gb->b.x())
920 return d;
921 if (int d = ga->b.y() - gb->b.y())
922 return d;
923
924 if (int d = ga->v0 - gb->v0)
925 return d;
926 if (int d = ga->v1 - gb->v1)
927 return d;
928
929 if (int d = ga->stops.size() - gb->stops.size())
930 return d;
931
932 for (int i = 0; i < ga->stops.size(); ++i) {
933 if (int d = ga->stops[i].first - gb->stops[i].first)
934 return d;
935 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
936 return d;
937 }
938
939 return 0;
940}
941
942QSGMaterialShader *QQuickShapeRadialGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
943{
944 Q_UNUSED(renderMode);
945 return new QQuickShapeRadialGradientRhiShader;
946}
947
948QQuickShapeConicalGradientRhiShader::QQuickShapeConicalGradientRhiShader()
949{
950 setShaderFileName(stage: VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.vert.qsb"));
951 setShaderFileName(stage: FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.frag.qsb"));
952}
953
954bool QQuickShapeConicalGradientRhiShader::updateUniformData(RenderState &state,
955 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
956{
957 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
958 QQuickShapeConicalGradientMaterial *m = static_cast<QQuickShapeConicalGradientMaterial *>(newMaterial);
959 bool changed = false;
960 QByteArray *buf = state.uniformData();
961 Q_ASSERT(buf->size() >= 80);
962
963 if (state.isMatrixDirty()) {
964 const QMatrix4x4 m = state.combinedMatrix();
965 memcpy(dest: buf->data(), src: m.constData(), n: 64);
966 changed = true;
967 }
968
969 QQuickShapeGenericStrokeFillNode *node = m->node();
970
971 const QPointF centerPoint = node->m_fillGradient.a;
972 const float angle = -qDegreesToRadians(degrees: node->m_fillGradient.v0);
973
974 if (!oldMaterial || m_centerPoint.x() != centerPoint.x() || m_centerPoint.y() != centerPoint.y()) {
975 m_centerPoint = QVector2D(centerPoint.x(), centerPoint.y());
976 Q_ASSERT(sizeof(m_centerPoint) == 8);
977 memcpy(dest: buf->data() + 64, src: &m_centerPoint, n: 8);
978 changed = true;
979 }
980
981 if (!oldMaterial || m_angle != angle) {
982 m_angle = angle;
983 memcpy(dest: buf->data() + 72, src: &m_angle, n: 4);
984 changed = true;
985 }
986
987 if (state.isOpacityDirty()) {
988 const float opacity = state.opacity();
989 memcpy(dest: buf->data() + 76, src: &opacity, n: 4);
990 changed = true;
991 }
992
993 return changed;
994}
995
996void QQuickShapeConicalGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
997 QSGMaterial *newMaterial, QSGMaterial *)
998{
999 if (binding != 1)
1000 return;
1001
1002 QQuickShapeConicalGradientMaterial *m = static_cast<QQuickShapeConicalGradientMaterial *>(newMaterial);
1003 QQuickShapeGenericStrokeFillNode *node = m->node();
1004 const QQuickShapeGradientCacheKey cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread);
1005 QSGTexture *t = QQuickShapeGradientCache::cacheForRhi(rhi: state.rhi())->get(grad: cacheKey);
1006 t->commitTextureOperations(rhi: state.rhi(), resourceUpdates: state.resourceUpdateBatch());
1007 *texture = t;
1008}
1009
1010QSGMaterialType *QQuickShapeConicalGradientMaterial::type() const
1011{
1012 static QSGMaterialType type;
1013 return &type;
1014}
1015
1016int QQuickShapeConicalGradientMaterial::compare(const QSGMaterial *other) const
1017{
1018 Q_ASSERT(other && type() == other->type());
1019 const QQuickShapeConicalGradientMaterial *m = static_cast<const QQuickShapeConicalGradientMaterial *>(other);
1020
1021 QQuickShapeGenericStrokeFillNode *a = node();
1022 QQuickShapeGenericStrokeFillNode *b = m->node();
1023 Q_ASSERT(a && b);
1024 if (a == b)
1025 return 0;
1026
1027 const QQuickAbstractPathRenderer::GradientDesc *ga = &a->m_fillGradient;
1028 const QQuickAbstractPathRenderer::GradientDesc *gb = &b->m_fillGradient;
1029
1030 if (int d = ga->a.x() - gb->a.x())
1031 return d;
1032 if (int d = ga->a.y() - gb->a.y())
1033 return d;
1034
1035 if (int d = ga->v0 - gb->v0)
1036 return d;
1037
1038 if (int d = ga->stops.size() - gb->stops.size())
1039 return d;
1040
1041 for (int i = 0; i < ga->stops.size(); ++i) {
1042 if (int d = ga->stops[i].first - gb->stops[i].first)
1043 return d;
1044 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
1045 return d;
1046 }
1047
1048 return 0;
1049}
1050
1051QSGMaterialShader *QQuickShapeConicalGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
1052{
1053 Q_UNUSED(renderMode);
1054 return new QQuickShapeConicalGradientRhiShader;
1055}
1056
1057QT_END_NAMESPACE
1058
1059#include "moc_qquickshapegenericrenderer_p.cpp"
1060

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