1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "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#include <QSGTextureProvider>
10#include <private/qsgplaintexture_p.h>
11
12#include <QtQuick/private/qsggradientcache_p.h>
13
14#if QT_CONFIG(thread)
15#include <QThreadPool>
16#endif
17
18QT_BEGIN_NAMESPACE
19
20struct ColoredVertex // must match QSGGeometry::ColoredPoint2D
21{
22 float x, y;
23 QQuickShapeGenericRenderer::Color4ub color;
24 void set(float nx, float ny, QQuickShapeGenericRenderer::Color4ub ncolor)
25 {
26 x = nx; y = ny; color = ncolor;
27 }
28};
29
30static inline QQuickShapeGenericRenderer::Color4ub colorToColor4ub(const QColor &c)
31{
32 float r, g, b, a;
33 c.getRgbF(r: &r, g: &g, b: &b, a: &a);
34 QQuickShapeGenericRenderer::Color4ub color = {
35 .r: uchar(qRound(f: r * a * 255)),
36 .g: uchar(qRound(f: g * a * 255)),
37 .b: uchar(qRound(f: b * a * 255)),
38 .a: uchar(qRound(f: a * 255))
39 };
40 return color;
41}
42
43QQuickShapeGenericStrokeFillNode::QQuickShapeGenericStrokeFillNode(QQuickWindow *window)
44 : m_material(nullptr)
45{
46 setFlag(QSGNode::OwnsGeometry, true);
47 setFlag(QSGNode::UsePreprocess, true);
48 setGeometry(new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0, 0));
49 activateMaterial(window, m: MatSolidColor);
50#ifdef QSG_RUNTIME_DESCRIPTION
51 qsgnode_set_description(node: this, description: QLatin1String("stroke-fill"));
52#endif
53}
54
55void QQuickShapeGenericStrokeFillNode::activateMaterial(QQuickWindow *window, Material m)
56{
57 switch (m) {
58 case MatSolidColor:
59 // Use vertexcolor material. Items with different colors remain batchable
60 // this way, at the expense of having to provide per-vertex color values.
61 m_material.reset(other: QQuickShapeGenericMaterialFactory::createVertexColor(window));
62 break;
63 case MatLinearGradient:
64 m_material.reset(other: QQuickShapeGenericMaterialFactory::createLinearGradient(window, node: this));
65 break;
66 case MatRadialGradient:
67 m_material.reset(other: QQuickShapeGenericMaterialFactory::createRadialGradient(window, node: this));
68 break;
69 case MatConicalGradient:
70 m_material.reset(other: QQuickShapeGenericMaterialFactory::createConicalGradient(window, node: this));
71 break;
72 case MatTextureFill:
73 m_material.reset(other: QQuickShapeGenericMaterialFactory::createTextureFill(window, node: this));
74 break;
75 default:
76 qWarning(msg: "Unknown material %d", m);
77 return;
78 }
79
80 if (material() != m_material.data())
81 setMaterial(m_material.data());
82}
83
84void QQuickShapeGenericStrokeFillNode::preprocess()
85{
86 if (m_fillTextureProvider != nullptr) {
87 if (QSGDynamicTexture *texture = qobject_cast<QSGDynamicTexture *>(object: m_fillTextureProvider->texture()))
88 texture->updateTexture();
89 }
90}
91
92void QQuickShapeGenericStrokeFillNode::handleTextureChanged()
93{
94 markDirty(bits: QSGNode::DirtyMaterial);
95}
96
97void QQuickShapeGenericStrokeFillNode::handleTextureProviderDestroyed()
98{
99 m_fillTextureProvider = nullptr;
100 markDirty(bits: QSGNode::DirtyMaterial);
101}
102
103QQuickShapeGenericRenderer::~QQuickShapeGenericRenderer()
104{
105 for (ShapePathData &d : m_sp) {
106 if (d.pendingFill)
107 d.pendingFill->orphaned = true;
108 if (d.pendingStroke)
109 d.pendingStroke->orphaned = true;
110 }
111}
112
113// sync, and so triangulation too, happens on the gui thread
114// - except when async is set, in which case triangulation is moved to worker threads
115
116void QQuickShapeGenericRenderer::beginSync(int totalCount, bool *countChanged)
117{
118 for (int i = totalCount; i < m_sp.size(); i++) // Handle removal of paths
119 setFillTextureProvider(index: i, textureProviderItem: nullptr); // deref window
120
121 if (m_sp.size() != totalCount) {
122 m_sp.resize(size: totalCount);
123 m_accDirty |= DirtyList;
124 *countChanged = true;
125 } else {
126 *countChanged = false;
127 }
128 for (ShapePathData &d : m_sp)
129 d.syncDirty = 0;
130}
131
132void QQuickShapeGenericRenderer::setPath(int index, const QPainterPath &path, QQuickShapePath::PathHints)
133{
134 ShapePathData &d(m_sp[index]);
135 d.path = path;
136 d.syncDirty |= DirtyFillGeom | DirtyStrokeGeom;
137}
138
139void QQuickShapeGenericRenderer::setStrokeColor(int index, const QColor &color)
140{
141 ShapePathData &d(m_sp[index]);
142 const bool wasTransparent = d.strokeColor.a == 0;
143 d.strokeColor = colorToColor4ub(c: color);
144 const bool isTransparent = d.strokeColor.a == 0;
145 d.syncDirty |= DirtyColor;
146 if (wasTransparent && !isTransparent)
147 d.syncDirty |= DirtyStrokeGeom;
148}
149
150void QQuickShapeGenericRenderer::setStrokeWidth(int index, qreal w)
151{
152 ShapePathData &d(m_sp[index]);
153 d.strokeWidth = w;
154 if (w > 0.0f)
155 d.pen.setWidthF(w);
156 d.syncDirty |= DirtyStrokeGeom;
157}
158
159void QQuickShapeGenericRenderer::setFillColor(int index, const QColor &color)
160{
161 ShapePathData &d(m_sp[index]);
162 const bool wasTransparent = d.fillColor.a == 0;
163 d.fillColor = colorToColor4ub(c: color);
164 const bool isTransparent = d.fillColor.a == 0;
165 d.syncDirty |= DirtyColor;
166 if (wasTransparent && !isTransparent)
167 d.syncDirty |= DirtyFillGeom;
168}
169
170void QQuickShapeGenericRenderer::setFillRule(int index, QQuickShapePath::FillRule fillRule)
171{
172 ShapePathData &d(m_sp[index]);
173 d.fillRule = Qt::FillRule(fillRule);
174 d.syncDirty |= DirtyFillGeom;
175}
176
177void QQuickShapeGenericRenderer::setJoinStyle(int index, QQuickShapePath::JoinStyle joinStyle, int miterLimit)
178{
179 ShapePathData &d(m_sp[index]);
180 d.pen.setJoinStyle(Qt::PenJoinStyle(joinStyle));
181 d.pen.setMiterLimit(miterLimit);
182 d.syncDirty |= DirtyStrokeGeom;
183}
184
185void QQuickShapeGenericRenderer::setCapStyle(int index, QQuickShapePath::CapStyle capStyle)
186{
187 ShapePathData &d(m_sp[index]);
188 d.pen.setCapStyle(Qt::PenCapStyle(capStyle));
189 d.syncDirty |= DirtyStrokeGeom;
190}
191
192void QQuickShapeGenericRenderer::setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle,
193 qreal dashOffset, const QVector<qreal> &dashPattern)
194{
195 ShapePathData &d(m_sp[index]);
196 d.pen.setStyle(Qt::PenStyle(strokeStyle));
197 if (strokeStyle == QQuickShapePath::DashLine) {
198 d.pen.setDashPattern(dashPattern);
199 d.pen.setDashOffset(dashOffset);
200 }
201 d.syncDirty |= DirtyStrokeGeom;
202}
203
204void QQuickShapeGenericRenderer::setFillGradient(int index, QQuickShapeGradient *gradient)
205{
206 ShapePathData &d(m_sp[index]);
207 if (gradient) {
208 d.fillGradient.stops = gradient->gradientStops(); // sorted
209 d.fillGradient.spread = QGradient::Spread(gradient->spread());
210 if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(object: gradient)) {
211 d.fillGradientActive = LinearGradient;
212 d.fillGradient.a = QPointF(g->x1(), g->y1());
213 d.fillGradient.b = QPointF(g->x2(), g->y2());
214 } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(object: gradient)) {
215 d.fillGradientActive = RadialGradient;
216 d.fillGradient.a = QPointF(g->centerX(), g->centerY());
217 d.fillGradient.b = QPointF(g->focalX(), g->focalY());
218 d.fillGradient.v0 = g->centerRadius();
219 d.fillGradient.v1 = g->focalRadius();
220 } else if (QQuickShapeConicalGradient *g = qobject_cast<QQuickShapeConicalGradient *>(object: gradient)) {
221 d.fillGradientActive = ConicalGradient;
222 d.fillGradient.a = QPointF(g->centerX(), g->centerY());
223 d.fillGradient.v0 = g->angle();
224 } else {
225 Q_UNREACHABLE();
226 }
227 } else {
228 d.fillGradientActive = NoGradient;
229 }
230 d.syncDirty |= DirtyFillGradient;
231}
232
233void QQuickShapeGenericRenderer::setFillTextureProvider(int index, QQuickItem *textureProviderItem)
234{
235 ShapePathData &d(m_sp[index]);
236 if ((d.fillTextureProviderItem == nullptr) != (textureProviderItem == nullptr))
237 d.syncDirty |= DirtyFillGeom;
238 if (d.fillTextureProviderItem != nullptr)
239 QQuickItemPrivate::get(item: d.fillTextureProviderItem)->derefWindow();
240 d.fillTextureProviderItem = textureProviderItem;
241 if (d.fillTextureProviderItem != nullptr)
242 QQuickItemPrivate::get(item: d.fillTextureProviderItem)->refWindow(m_item->window());
243 d.syncDirty |= DirtyFillTexture;
244}
245
246void QQuickShapeGenericRenderer::handleSceneChange(QQuickWindow *window)
247{
248 for (auto &pathData : m_sp) {
249 if (pathData.fillTextureProviderItem != nullptr) {
250 if (window == nullptr)
251 QQuickItemPrivate::get(item: pathData.fillTextureProviderItem)->derefWindow();
252 else
253 QQuickItemPrivate::get(item: pathData.fillTextureProviderItem)->refWindow(window);
254 }
255 }
256}
257
258
259void QQuickShapeGenericRenderer::setFillTransform(int index, const QSGTransform &transform)
260{
261 ShapePathData &d(m_sp[index]);
262 d.fillTransform = transform;
263 d.syncDirty |= DirtyFillTransform;
264}
265
266void QQuickShapeGenericRenderer::setTriangulationScale(qreal scale)
267{
268 // No dirty, this is called at the start of every sync. Just store the value.
269 m_triangulationScale = scale;
270}
271
272void QQuickShapeFillRunnable::run()
273{
274 QQuickShapeGenericRenderer::triangulateFill(path, fillColor, fillVertices: &fillVertices, fillIndices: &fillIndices, indexType: &indexType,
275 supportsElementIndexUint, triangulationScale);
276 emit done(self: this);
277}
278
279void QQuickShapeStrokeRunnable::run()
280{
281 QQuickShapeGenericRenderer::triangulateStroke(path, pen, strokeColor, strokeVertices: &strokeVertices, clipSize, triangulationScale);
282 emit done(self: this);
283}
284
285void QQuickShapeGenericRenderer::setAsyncCallback(void (*callback)(void *), void *data)
286{
287 m_asyncCallback = callback;
288 m_asyncCallbackData = data;
289}
290
291#if QT_CONFIG(thread)
292static QThreadPool *pathWorkThreadPool = nullptr;
293
294static void deletePathWorkThreadPool()
295{
296 delete pathWorkThreadPool;
297 pathWorkThreadPool = nullptr;
298}
299#endif
300
301void QQuickShapeGenericRenderer::endSync(bool async)
302{
303#if !QT_CONFIG(thread)
304 // Force synchronous mode for the no-thread configuration due
305 // to lack of QThreadPool.
306 async = false;
307#endif
308
309 bool didKickOffAsync = false;
310
311 for (int i = 0; i < m_sp.size(); ++i) {
312 ShapePathData &d(m_sp[i]);
313 if (!d.syncDirty)
314 continue;
315
316 m_accDirty |= d.syncDirty;
317
318 // Use a shadow dirty flag in order to avoid losing state in case there are
319 // multiple syncs with different dirty flags before we get to updateNode()
320 // on the render thread (with the gui thread blocked). For our purposes
321 // here syncDirty is still required since geometry regeneration must only
322 // happen when there was an actual change in this particular sync round.
323 d.effectiveDirty |= d.syncDirty;
324
325 if (d.path.isEmpty()) {
326 d.fillVertices.clear();
327 d.fillIndices.clear();
328 d.strokeVertices.clear();
329 continue;
330 }
331
332#if QT_CONFIG(thread)
333 if (async && !pathWorkThreadPool) {
334 qAddPostRoutine(deletePathWorkThreadPool);
335 pathWorkThreadPool = new QThreadPool;
336 const int idealCount = QThread::idealThreadCount();
337 pathWorkThreadPool->setMaxThreadCount(idealCount > 0 ? idealCount * 2 : 4);
338 }
339#endif
340 auto testFeatureIndexUint = [](QQuickItem *item) -> bool {
341 if (auto *w = item->window()) {
342 if (auto *rhi = QQuickWindowPrivate::get(c: w)->rhi)
343 return rhi->isFeatureSupported(feature: QRhi::ElementIndexUint);
344 }
345 return true;
346 };
347 static bool supportsElementIndexUint = testFeatureIndexUint(m_item);
348 if ((d.syncDirty & DirtyFillGeom) && d.fillColor.a) {
349 d.path.setFillRule(d.fillRule);
350 if (m_api == QSGRendererInterface::Unknown)
351 m_api = m_item->window()->rendererInterface()->graphicsApi();
352 if (async) {
353 QQuickShapeFillRunnable *r = new QQuickShapeFillRunnable;
354 r->setAutoDelete(false);
355 if (d.pendingFill)
356 d.pendingFill->orphaned = true;
357 d.pendingFill = r;
358 r->path = d.path;
359 r->fillColor = d.fillColor;
360 r->supportsElementIndexUint = supportsElementIndexUint;
361 r->triangulationScale = m_triangulationScale;
362 // Unlikely in practice but in theory m_sp could be
363 // resized. Therefore, capture 'i' instead of 'd'.
364 QObject::connect(sender: r, signal: &QQuickShapeFillRunnable::done, qApp, slot: [this, i](QQuickShapeFillRunnable *r) {
365 // Bail out when orphaned (meaning either another run was
366 // started after this one, or the renderer got destroyed).
367 if (!r->orphaned && i < m_sp.size()) {
368 ShapePathData &d(m_sp[i]);
369 d.fillVertices = r->fillVertices;
370 d.fillIndices = r->fillIndices;
371 d.indexType = r->indexType;
372 d.pendingFill = nullptr;
373 d.effectiveDirty |= DirtyFillGeom;
374 maybeUpdateAsyncItem();
375 }
376 r->deleteLater();
377 });
378 didKickOffAsync = true;
379#if QT_CONFIG(thread)
380 // qtVectorPathForPath() initializes a unique_ptr without locking.
381 // Do that before starting the threads as otherwise we get a race condition.
382 qtVectorPathForPath(path: r->path);
383 pathWorkThreadPool->start(runnable: r);
384#endif
385 } else {
386 triangulateFill(path: d.path, fillColor: d.fillColor, fillVertices: &d.fillVertices, fillIndices: &d.fillIndices, indexType: &d.indexType,
387 supportsElementIndexUint,
388 triangulationScale: m_triangulationScale);
389 }
390 }
391
392 if ((d.syncDirty & DirtyStrokeGeom) && d.strokeWidth > 0.0f && d.strokeColor.a) {
393 if (async) {
394 QQuickShapeStrokeRunnable *r = new QQuickShapeStrokeRunnable;
395 r->setAutoDelete(false);
396 if (d.pendingStroke)
397 d.pendingStroke->orphaned = true;
398 d.pendingStroke = r;
399 r->path = d.path;
400 r->pen = d.pen;
401 r->strokeColor = d.strokeColor;
402 r->clipSize = QSize(m_item->width(), m_item->height());
403 r->triangulationScale = m_triangulationScale;
404 QObject::connect(sender: r, signal: &QQuickShapeStrokeRunnable::done, qApp, slot: [this, i](QQuickShapeStrokeRunnable *r) {
405 if (!r->orphaned && i < m_sp.size()) {
406 ShapePathData &d(m_sp[i]);
407 d.strokeVertices = r->strokeVertices;
408 d.pendingStroke = nullptr;
409 d.effectiveDirty |= DirtyStrokeGeom;
410 maybeUpdateAsyncItem();
411 }
412 r->deleteLater();
413 });
414 didKickOffAsync = true;
415#if QT_CONFIG(thread)
416 // qtVectorPathForPath() initializes a unique_ptr without locking.
417 // Do that before starting the threads as otherwise we get a race condition.
418 qtVectorPathForPath(path: r->path);
419 pathWorkThreadPool->start(runnable: r);
420#endif
421 } else {
422 triangulateStroke(path: d.path, pen: d.pen, strokeColor: d.strokeColor, strokeVertices: &d.strokeVertices,
423 clipSize: QSize(m_item->width(), m_item->height()), triangulationScale: m_triangulationScale);
424 }
425 }
426 }
427
428 if (!didKickOffAsync && async && m_asyncCallback)
429 m_asyncCallback(m_asyncCallbackData);
430}
431
432void QQuickShapeGenericRenderer::maybeUpdateAsyncItem()
433{
434 for (const ShapePathData &d : std::as_const(t&: m_sp)) {
435 if (d.pendingFill || d.pendingStroke)
436 return;
437 }
438 m_accDirty |= DirtyFillGeom | DirtyStrokeGeom;
439 m_item->update();
440 if (m_asyncCallback)
441 m_asyncCallback(m_asyncCallbackData);
442}
443
444// the stroke/fill triangulation functions may be invoked either on the gui
445// thread or some worker thread and must thus be self-contained.
446void QQuickShapeGenericRenderer::triangulateFill(const QPainterPath &path,
447 const Color4ub &fillColor,
448 VertexContainerType *fillVertices,
449 IndexContainerType *fillIndices,
450 QSGGeometry::Type *indexType,
451 bool supportsElementIndexUint,
452 qreal triangulationScale)
453{
454 const QVectorPath &vp = qtVectorPathForPath(path);
455
456 QTriangleSet ts = qTriangulate(path: vp, matrix: QTransform::fromScale(dx: triangulationScale, dy: triangulationScale), lod: 1, allowUintIndices: supportsElementIndexUint);
457 const int vertexCount = ts.vertices.size() / 2; // just a qreal vector with x,y hence the / 2
458 fillVertices->resize(size: vertexCount);
459 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(fillVertices->data());
460 const qreal *vsrc = ts.vertices.constData();
461 for (int i = 0; i < vertexCount; ++i)
462 vdst[i].set(nx: vsrc[i * 2] / triangulationScale, ny: vsrc[i * 2 + 1] / triangulationScale, ncolor: fillColor);
463
464 size_t indexByteSize;
465 if (ts.indices.type() == QVertexIndexVector::UnsignedShort) {
466 *indexType = QSGGeometry::UnsignedShortType;
467 // fillIndices is still QVector<quint32>. Just resize to N/2 and pack
468 // the N quint16s into it.
469 fillIndices->resize(size: ts.indices.size() / 2);
470 indexByteSize = ts.indices.size() * sizeof(quint16);
471 } else {
472 *indexType = QSGGeometry::UnsignedIntType;
473 fillIndices->resize(size: ts.indices.size());
474 indexByteSize = ts.indices.size() * sizeof(quint32);
475 }
476 memcpy(dest: fillIndices->data(), src: ts.indices.data(), n: indexByteSize);
477}
478
479void QQuickShapeGenericRenderer::triangulateStroke(const QPainterPath &path,
480 const QPen &pen,
481 const Color4ub &strokeColor,
482 VertexContainerType *strokeVertices,
483 const QSize &clipSize,
484 qreal triangulationScale)
485{
486 const QVectorPath &vp = qtVectorPathForPath(path);
487 const QRectF clip(QPointF(0, 0), clipSize);
488 const qreal inverseScale = 1.0 / triangulationScale;
489
490 QTriangulatingStroker stroker;
491 stroker.setInvScale(inverseScale);
492
493 if (pen.style() == Qt::SolidLine) {
494 stroker.process(path: vp, pen, clip, hints: {});
495 } else {
496 QDashedStrokeProcessor dashStroker;
497 dashStroker.setInvScale(inverseScale);
498 dashStroker.process(path: vp, pen, clip, hints: {});
499 QVectorPath dashStroke(dashStroker.points(), dashStroker.elementCount(),
500 dashStroker.elementTypes(), 0);
501 stroker.process(path: dashStroke, pen, clip, hints: {});
502 }
503
504 if (!stroker.vertexCount()) {
505 strokeVertices->clear();
506 return;
507 }
508
509 const int vertexCount = stroker.vertexCount() / 2; // just a float vector with x,y hence the / 2
510 strokeVertices->resize(size: vertexCount);
511 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(strokeVertices->data());
512 const float *vsrc = stroker.vertices();
513 for (int i = 0; i < vertexCount; ++i)
514 vdst[i].set(nx: vsrc[i * 2], ny: vsrc[i * 2 + 1], ncolor: strokeColor);
515}
516
517void QQuickShapeGenericRenderer::setRootNode(QQuickShapeGenericNode *node)
518{
519 if (m_rootNode != node) {
520 m_rootNode = node;
521 m_accDirty |= DirtyList;
522 }
523}
524
525// on the render thread with gui blocked
526void QQuickShapeGenericRenderer::updateNode()
527{
528 if (!m_rootNode || !m_accDirty)
529 return;
530
531// [ m_rootNode ]
532// / / /
533// #0 [ fill ] [ stroke ] [ next ]
534// / / |
535// #1 [ fill ] [ stroke ] [ next ]
536// / / |
537// #2 [ fill ] [ stroke ] [ next ]
538// ...
539// ...
540
541 QQuickShapeGenericNode **nodePtr = &m_rootNode;
542 QQuickShapeGenericNode *prevNode = nullptr;
543
544 for (ShapePathData &d : m_sp) {
545 if (!*nodePtr) {
546 Q_ASSERT(prevNode);
547 *nodePtr = new QQuickShapeGenericNode;
548 prevNode->m_next = *nodePtr;
549 prevNode->appendChildNode(node: *nodePtr);
550 }
551
552 QQuickShapeGenericNode *node = *nodePtr;
553
554 if (m_accDirty & DirtyList)
555 d.effectiveDirty |= DirtyFillGeom | DirtyStrokeGeom | DirtyColor | DirtyFillGradient | DirtyFillTransform | DirtyFillTexture;
556
557 if (!d.effectiveDirty) {
558 prevNode = node;
559 nodePtr = &node->m_next;
560 continue;
561 }
562
563 if (d.fillColor.a == 0) {
564 delete node->m_fillNode;
565 node->m_fillNode = nullptr;
566 } else if (!node->m_fillNode) {
567 node->m_fillNode = new QQuickShapeGenericStrokeFillNode(m_item->window());
568 if (node->m_strokeNode)
569 node->removeChildNode(node: node->m_strokeNode);
570 node->appendChildNode(node: node->m_fillNode);
571 if (node->m_strokeNode)
572 node->appendChildNode(node: node->m_strokeNode);
573 d.effectiveDirty |= DirtyFillGeom;
574 }
575
576 if (d.strokeWidth <= 0.0f || d.strokeColor.a == 0) {
577 delete node->m_strokeNode;
578 node->m_strokeNode = nullptr;
579 } else if (!node->m_strokeNode) {
580 node->m_strokeNode = new QQuickShapeGenericStrokeFillNode(m_item->window());
581 node->appendChildNode(node: node->m_strokeNode);
582 d.effectiveDirty |= DirtyStrokeGeom;
583 }
584
585 updateFillNode(d: &d, node);
586 updateStrokeNode(d: &d, node);
587
588 d.effectiveDirty = 0;
589
590 prevNode = node;
591 nodePtr = &node->m_next;
592 }
593
594 if (*nodePtr && prevNode) {
595 prevNode->removeChildNode(node: *nodePtr);
596 delete *nodePtr;
597 *nodePtr = nullptr;
598 }
599
600 if (m_sp.isEmpty()) {
601 delete m_rootNode->m_fillNode;
602 m_rootNode->m_fillNode = nullptr;
603 delete m_rootNode->m_strokeNode;
604 m_rootNode->m_strokeNode = nullptr;
605 delete m_rootNode->m_next;
606 m_rootNode->m_next = nullptr;
607 }
608
609 m_accDirty = 0;
610}
611
612void QQuickShapeGenericRenderer::updateShadowDataInNode(ShapePathData *d, QQuickShapeGenericStrokeFillNode *n)
613{
614 if (d->fillGradientActive) {
615 if (d->effectiveDirty & DirtyFillGradient)
616 n->m_fillGradient = d->fillGradient;
617 }
618 if (d->effectiveDirty & DirtyFillTexture) {
619 bool needsUpdate = d->fillTextureProviderItem == nullptr && n->m_fillTextureProvider != nullptr;
620 if (!needsUpdate
621 && d->fillTextureProviderItem != nullptr
622 && n->m_fillTextureProvider != d->fillTextureProviderItem->textureProvider()) {
623 needsUpdate = true;
624 }
625
626 if (needsUpdate) {
627 if (n->m_fillTextureProvider != nullptr) {
628 QObject::disconnect(sender: n->m_fillTextureProvider, signal: &QSGTextureProvider::textureChanged,
629 receiver: n, slot: &QQuickShapeGenericStrokeFillNode::handleTextureChanged);
630 QObject::disconnect(sender: n->m_fillTextureProvider, signal: &QSGTextureProvider::destroyed,
631 receiver: n, slot: &QQuickShapeGenericStrokeFillNode::handleTextureProviderDestroyed);
632 }
633
634 n->m_fillTextureProvider = d->fillTextureProviderItem == nullptr
635 ? nullptr
636 : d->fillTextureProviderItem->textureProvider();
637
638 if (n->m_fillTextureProvider != nullptr) {
639 QObject::connect(sender: n->m_fillTextureProvider, signal: &QSGTextureProvider::textureChanged,
640 context: n, slot: &QQuickShapeGenericStrokeFillNode::handleTextureChanged);
641 QObject::connect(sender: n->m_fillTextureProvider, signal: &QSGTextureProvider::destroyed,
642 context: n, slot: &QQuickShapeGenericStrokeFillNode::handleTextureProviderDestroyed);
643 }
644 }
645 }
646 if (d->effectiveDirty & DirtyFillTransform)
647 n->m_fillTransform = d->fillTransform;
648}
649
650void QQuickShapeGenericRenderer::updateFillNode(ShapePathData *d, QQuickShapeGenericNode *node)
651{
652 if (!node->m_fillNode)
653 return;
654 if (!(d->effectiveDirty & (DirtyFillGeom | DirtyColor | DirtyFillGradient | DirtyFillTransform | DirtyFillTexture)))
655 return;
656
657 // Make a copy of the data that will be accessed by the material on
658 // the render thread. This must be done even when we bail out below.
659 QQuickShapeGenericStrokeFillNode *n = node->m_fillNode;
660 updateShadowDataInNode(d, n);
661
662 QSGGeometry *g = n->geometry();
663 if (d->fillVertices.isEmpty()) {
664 if (g->vertexCount() || g->indexCount()) {
665 g->allocate(vertexCount: 0, indexCount: 0);
666 n->markDirty(bits: QSGNode::DirtyGeometry);
667 }
668 return;
669 }
670
671 if (d->fillGradientActive) {
672 QQuickShapeGenericStrokeFillNode::Material gradMat;
673 switch (d->fillGradientActive) {
674 case LinearGradient:
675 gradMat = QQuickShapeGenericStrokeFillNode::MatLinearGradient;
676 break;
677 case RadialGradient:
678 gradMat = QQuickShapeGenericStrokeFillNode::MatRadialGradient;
679 break;
680 case ConicalGradient:
681 gradMat = QQuickShapeGenericStrokeFillNode::MatConicalGradient;
682 break;
683 default:
684 Q_UNREACHABLE_RETURN();
685 }
686 n->activateMaterial(window: m_item->window(), m: gradMat);
687 if (d->effectiveDirty & (DirtyFillGradient | DirtyFillTransform)) {
688 // Gradients are implemented via a texture-based material.
689 n->markDirty(bits: QSGNode::DirtyMaterial);
690 // stop here if only the gradient or filltransform changed; no need to touch the geometry
691 if (!(d->effectiveDirty & DirtyFillGeom))
692 return;
693 }
694 } else if (d->fillTextureProviderItem != nullptr) {
695 n->activateMaterial(window: m_item->window(), m: QQuickShapeGenericStrokeFillNode::MatTextureFill);
696 if (d->effectiveDirty & DirtyFillTexture)
697 n->markDirty(bits: QSGNode::DirtyMaterial);
698 } else {
699 n->activateMaterial(window: m_item->window(), m: QQuickShapeGenericStrokeFillNode::MatSolidColor);
700 // fast path for updating only color values when no change in vertex positions
701 if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyFillGeom) && d->fillTextureProviderItem == nullptr) {
702 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(g->vertexData());
703 for (int i = 0; i < g->vertexCount(); ++i)
704 vdst[i].set(nx: vdst[i].x, ny: vdst[i].y, ncolor: d->fillColor);
705 n->markDirty(bits: QSGNode::DirtyGeometry);
706 return;
707 }
708 }
709
710 const int indexCount = d->indexType == QSGGeometry::UnsignedShortType
711 ? d->fillIndices.size() * 2 : d->fillIndices.size();
712 if (g->indexType() != d->indexType) {
713 g = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(),
714 d->fillVertices.size(), indexCount, d->indexType);
715 n->setGeometry(g);
716 } else {
717 g->allocate(vertexCount: d->fillVertices.size(), indexCount);
718 }
719 g->setDrawingMode(QSGGeometry::DrawTriangles);
720
721 memcpy(dest: g->vertexData(), src: d->fillVertices.constData(), n: g->vertexCount() * g->sizeOfVertex());
722 memcpy(dest: g->indexData(), src: d->fillIndices.constData(), n: g->indexCount() * g->sizeOfIndex());
723
724 n->markDirty(bits: QSGNode::DirtyGeometry);
725}
726
727void QQuickShapeGenericRenderer::updateStrokeNode(ShapePathData *d, QQuickShapeGenericNode *node)
728{
729 if (!node->m_strokeNode)
730 return;
731 if (!(d->effectiveDirty & (DirtyStrokeGeom | DirtyColor)))
732 return;
733
734 QQuickShapeGenericStrokeFillNode *n = node->m_strokeNode;
735 QSGGeometry *g = n->geometry();
736 if (d->strokeVertices.isEmpty()) {
737 if (g->vertexCount() || g->indexCount()) {
738 g->allocate(vertexCount: 0, indexCount: 0);
739 n->markDirty(bits: QSGNode::DirtyGeometry);
740 }
741 return;
742 }
743
744 n->markDirty(bits: QSGNode::DirtyGeometry);
745
746 // Async loading runs update once, bails out above, then updates again once
747 // ready. Set the material dirty then. This is in-line with fill where the
748 // first activateMaterial() achieves the same.
749 if (!g->vertexCount())
750 n->markDirty(bits: QSGNode::DirtyMaterial);
751
752 if ((d->effectiveDirty & DirtyColor) && !(d->effectiveDirty & DirtyStrokeGeom)) {
753 ColoredVertex *vdst = reinterpret_cast<ColoredVertex *>(g->vertexData());
754 for (int i = 0; i < g->vertexCount(); ++i)
755 vdst[i].set(nx: vdst[i].x, ny: vdst[i].y, ncolor: d->strokeColor);
756 return;
757 }
758
759 g->allocate(vertexCount: d->strokeVertices.size(), indexCount: 0);
760 g->setDrawingMode(QSGGeometry::DrawTriangleStrip);
761 memcpy(dest: g->vertexData(), src: d->strokeVertices.constData(), n: g->vertexCount() * g->sizeOfVertex());
762}
763
764QSGMaterial *QQuickShapeGenericMaterialFactory::createVertexColor(QQuickWindow *window)
765{
766 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
767
768 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
769 return new QSGVertexColorMaterial;
770
771 qWarning(msg: "Vertex-color material: Unsupported graphics API %d", api);
772 return nullptr;
773}
774
775QSGMaterial *QQuickShapeGenericMaterialFactory::createLinearGradient(QQuickWindow *window,
776 QQuickShapeGenericStrokeFillNode *node)
777{
778 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
779
780 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
781 return new QQuickShapeLinearGradientMaterial(node);
782
783 qWarning(msg: "Linear gradient material: Unsupported graphics API %d", api);
784 return nullptr;
785}
786
787QSGMaterial *QQuickShapeGenericMaterialFactory::createRadialGradient(QQuickWindow *window,
788 QQuickShapeGenericStrokeFillNode *node)
789{
790 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
791
792 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
793 return new QQuickShapeRadialGradientMaterial(node);
794
795 qWarning(msg: "Radial gradient material: Unsupported graphics API %d", api);
796 return nullptr;
797}
798
799QSGMaterial *QQuickShapeGenericMaterialFactory::createConicalGradient(QQuickWindow *window,
800 QQuickShapeGenericStrokeFillNode *node)
801{
802 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
803
804 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
805 return new QQuickShapeConicalGradientMaterial(node);
806
807 qWarning(msg: "Conical gradient material: Unsupported graphics API %d", api);
808 return nullptr;
809}
810
811QSGMaterial *QQuickShapeGenericMaterialFactory::createTextureFill(QQuickWindow *window,
812 QQuickShapeGenericStrokeFillNode *node)
813{
814 QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi();
815
816 if (api == QSGRendererInterface::OpenGL || QSGRendererInterface::isApiRhiBased(api))
817 return new QQuickShapeTextureFillMaterial(node);
818
819 qWarning(msg: "Texture fill material: Unsupported graphics API %d", api);
820 return nullptr;
821}
822
823QQuickShapeLinearGradientRhiShader::QQuickShapeLinearGradientRhiShader(int viewCount)
824{
825 setShaderFileName(stage: VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.vert.qsb"), viewCount);
826 setShaderFileName(stage: FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/lineargradient.frag.qsb"), viewCount);
827}
828
829bool QQuickShapeLinearGradientRhiShader::updateUniformData(RenderState &state,
830 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
831{
832 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
833 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
834 bool changed = false;
835 QByteArray *buf = state.uniformData();
836 Q_ASSERT(buf->size() >= 84 + 64);
837 const int shaderMatrixCount = newMaterial->viewCount();
838 const int matrixCount = qMin(a: state.projectionMatrixCount(), b: shaderMatrixCount);
839
840 if (state.isMatrixDirty()) {
841 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
842 const QMatrix4x4 m = state.combinedMatrix();
843 memcpy(dest: buf->data() + 64 * viewIndex, src: m.constData(), n: 64);
844 changed = true;
845 }
846 }
847
848 QQuickShapeGenericStrokeFillNode *node = m->node();
849
850 if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
851 memcpy(dest: buf->data() + 64 * shaderMatrixCount, src: node->m_fillTransform.invertedData(), n: 64);
852 m_fillTransform = node->m_fillTransform;
853 changed = true;
854 }
855
856 if (!oldMaterial || m_gradA.x() != node->m_fillGradient.a.x() || m_gradA.y() != node->m_fillGradient.a.y()) {
857 m_gradA = QVector2D(node->m_fillGradient.a.x(), node->m_fillGradient.a.y());
858 Q_ASSERT(sizeof(m_gradA) == 8);
859 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64, src: &m_gradA, n: 8);
860 changed = true;
861 }
862
863 if (!oldMaterial || m_gradB.x() != node->m_fillGradient.b.x() || m_gradB.y() != node->m_fillGradient.b.y()) {
864 m_gradB = QVector2D(node->m_fillGradient.b.x(), node->m_fillGradient.b.y());
865 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64 + 8, src: &m_gradB, n: 8);
866 changed = true;
867 }
868
869 if (state.isOpacityDirty()) {
870 const float opacity = state.opacity();
871 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8, src: &opacity, n: 4);
872 changed = true;
873 }
874
875 return changed;
876}
877
878void QQuickShapeLinearGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
879 QSGMaterial *newMaterial, QSGMaterial *)
880{
881 if (binding != 1)
882 return;
883
884 QQuickShapeLinearGradientMaterial *m = static_cast<QQuickShapeLinearGradientMaterial *>(newMaterial);
885 QQuickShapeGenericStrokeFillNode *node = m->node();
886 const QSGGradientCacheKey cacheKey(node->m_fillGradient.stops, QGradient::Spread(node->m_fillGradient.spread));
887 QSGTexture *t = QSGGradientCache::cacheForRhi(rhi: state.rhi())->get(grad: cacheKey);
888 t->commitTextureOperations(rhi: state.rhi(), resourceUpdates: state.resourceUpdateBatch());
889 *texture = t;
890}
891
892QSGMaterialType *QQuickShapeLinearGradientMaterial::type() const
893{
894 static QSGMaterialType type;
895 return &type;
896}
897
898int QQuickShapeLinearGradientMaterial::compare(const QSGMaterial *other) const
899{
900 Q_ASSERT(other && type() == other->type());
901 const QQuickShapeLinearGradientMaterial *m = static_cast<const QQuickShapeLinearGradientMaterial *>(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 QSGGradientCache::GradientDesc *ga = &a->m_fillGradient;
910 const QSGGradientCache::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->stops.size() - gb->stops.size())
925 return d;
926
927 for (int i = 0; i < ga->stops.size(); ++i) {
928 if (int d = ga->stops[i].first - gb->stops[i].first)
929 return d;
930 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
931 return d;
932 }
933
934 if (int d = a->m_fillTransform.compareTo(other: b->m_fillTransform))
935 return d;
936
937 return 0;
938}
939
940QSGMaterialShader *QQuickShapeLinearGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
941{
942 Q_UNUSED(renderMode);
943 return new QQuickShapeLinearGradientRhiShader(viewCount());
944}
945
946QQuickShapeRadialGradientRhiShader::QQuickShapeRadialGradientRhiShader(int viewCount)
947{
948 setShaderFileName(stage: VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.vert.qsb"), viewCount);
949 setShaderFileName(stage: FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/radialgradient.frag.qsb"), viewCount);
950}
951
952bool QQuickShapeRadialGradientRhiShader::updateUniformData(RenderState &state,
953 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
954{
955 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
956 QQuickShapeRadialGradientMaterial *m = static_cast<QQuickShapeRadialGradientMaterial *>(newMaterial);
957 bool changed = false;
958 QByteArray *buf = state.uniformData();
959 Q_ASSERT(buf->size() >= 92 + 64);
960 const int shaderMatrixCount = newMaterial->viewCount();
961 const int matrixCount = qMin(a: state.projectionMatrixCount(), b: shaderMatrixCount);
962
963 if (state.isMatrixDirty()) {
964 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
965 const QMatrix4x4 m = state.combinedMatrix();
966 memcpy(dest: buf->data() + 64 * viewIndex, src: m.constData(), n: 64);
967 changed = true;
968 }
969 }
970
971 QQuickShapeGenericStrokeFillNode *node = m->node();
972
973 if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
974 memcpy(dest: buf->data() + 64 * shaderMatrixCount, src: node->m_fillTransform.invertedData(), n: 64);
975 m_fillTransform = node->m_fillTransform;
976 changed = true;
977 }
978
979 const QPointF centerPoint = node->m_fillGradient.a;
980 const QPointF focalPoint = node->m_fillGradient.b;
981 const QPointF focalToCenter = centerPoint - focalPoint;
982 const float centerRadius = node->m_fillGradient.v0;
983 const float focalRadius = node->m_fillGradient.v1;
984
985 if (!oldMaterial || m_focalPoint.x() != focalPoint.x() || m_focalPoint.y() != focalPoint.y()) {
986 m_focalPoint = QVector2D(focalPoint.x(), focalPoint.y());
987 Q_ASSERT(sizeof(m_focalPoint) == 8);
988 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64, src: &m_focalPoint, n: 8);
989 changed = true;
990 }
991
992 if (!oldMaterial || m_focalToCenter.x() != focalToCenter.x() || m_focalToCenter.y() != focalToCenter.y()) {
993 m_focalToCenter = QVector2D(focalToCenter.x(), focalToCenter.y());
994 Q_ASSERT(sizeof(m_focalToCenter) == 8);
995 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64 + 8, src: &m_focalToCenter, n: 8);
996 changed = true;
997 }
998
999 if (!oldMaterial || m_centerRadius != centerRadius) {
1000 m_centerRadius = centerRadius;
1001 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8, src: &m_centerRadius, n: 4);
1002 changed = true;
1003 }
1004
1005 if (!oldMaterial || m_focalRadius != focalRadius) {
1006 m_focalRadius = focalRadius;
1007 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8 + 4, src: &m_focalRadius, n: 4);
1008 changed = true;
1009 }
1010
1011 if (state.isOpacityDirty()) {
1012 const float opacity = state.opacity();
1013 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64 + 8 + 8 + 4 + 4, src: &opacity, n: 4);
1014 changed = true;
1015 }
1016
1017 return changed;
1018}
1019
1020void QQuickShapeRadialGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
1021 QSGMaterial *newMaterial, QSGMaterial *)
1022{
1023 if (binding != 1)
1024 return;
1025
1026 QQuickShapeRadialGradientMaterial *m = static_cast<QQuickShapeRadialGradientMaterial *>(newMaterial);
1027 QQuickShapeGenericStrokeFillNode *node = m->node();
1028 const QSGGradientCacheKey cacheKey(node->m_fillGradient.stops, QGradient::Spread(node->m_fillGradient.spread));
1029 QSGTexture *t = QSGGradientCache::cacheForRhi(rhi: state.rhi())->get(grad: cacheKey);
1030 t->commitTextureOperations(rhi: state.rhi(), resourceUpdates: state.resourceUpdateBatch());
1031 *texture = t;
1032}
1033
1034QSGMaterialType *QQuickShapeRadialGradientMaterial::type() const
1035{
1036 static QSGMaterialType type;
1037 return &type;
1038}
1039
1040int QQuickShapeRadialGradientMaterial::compare(const QSGMaterial *other) const
1041{
1042 Q_ASSERT(other && type() == other->type());
1043 const QQuickShapeRadialGradientMaterial *m = static_cast<const QQuickShapeRadialGradientMaterial *>(other);
1044
1045 QQuickShapeGenericStrokeFillNode *a = node();
1046 QQuickShapeGenericStrokeFillNode *b = m->node();
1047 Q_ASSERT(a && b);
1048 if (a == b)
1049 return 0;
1050
1051 const QSGGradientCache::GradientDesc *ga = &a->m_fillGradient;
1052 const QSGGradientCache::GradientDesc *gb = &b->m_fillGradient;
1053
1054 if (int d = ga->spread - gb->spread)
1055 return d;
1056
1057 if (int d = ga->a.x() - gb->a.x())
1058 return d;
1059 if (int d = ga->a.y() - gb->a.y())
1060 return d;
1061 if (int d = ga->b.x() - gb->b.x())
1062 return d;
1063 if (int d = ga->b.y() - gb->b.y())
1064 return d;
1065
1066 if (int d = ga->v0 - gb->v0)
1067 return d;
1068 if (int d = ga->v1 - gb->v1)
1069 return d;
1070
1071 if (int d = ga->stops.size() - gb->stops.size())
1072 return d;
1073
1074 for (int i = 0; i < ga->stops.size(); ++i) {
1075 if (int d = ga->stops[i].first - gb->stops[i].first)
1076 return d;
1077 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
1078 return d;
1079 }
1080
1081 if (int d = a->m_fillTransform.compareTo(other: b->m_fillTransform))
1082 return d;
1083
1084 return 0;
1085}
1086
1087QSGMaterialShader *QQuickShapeRadialGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
1088{
1089 Q_UNUSED(renderMode);
1090 return new QQuickShapeRadialGradientRhiShader(viewCount());
1091}
1092
1093QQuickShapeConicalGradientRhiShader::QQuickShapeConicalGradientRhiShader(int viewCount)
1094{
1095 setShaderFileName(stage: VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.vert.qsb"), viewCount);
1096 setShaderFileName(stage: FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/conicalgradient.frag.qsb"), viewCount);
1097}
1098
1099bool QQuickShapeConicalGradientRhiShader::updateUniformData(RenderState &state,
1100 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
1101{
1102 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
1103 QQuickShapeConicalGradientMaterial *m = static_cast<QQuickShapeConicalGradientMaterial *>(newMaterial);
1104 bool changed = false;
1105 QByteArray *buf = state.uniformData();
1106 Q_ASSERT(buf->size() >= 80 + 64);
1107 const int shaderMatrixCount = newMaterial->viewCount();
1108 const int matrixCount = qMin(a: state.projectionMatrixCount(), b: shaderMatrixCount);
1109
1110 if (state.isMatrixDirty()) {
1111 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
1112 const QMatrix4x4 m = state.combinedMatrix();
1113 memcpy(dest: buf->data() + 64 * viewIndex, src: m.constData(), n: 64);
1114 changed = true;
1115 }
1116 }
1117
1118 QQuickShapeGenericStrokeFillNode *node = m->node();
1119
1120 if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
1121 memcpy(dest: buf->data() + 64 * shaderMatrixCount, src: node->m_fillTransform.invertedData(), n: 64);
1122 m_fillTransform = node->m_fillTransform;
1123 changed = true;
1124 }
1125
1126 const QPointF centerPoint = node->m_fillGradient.a;
1127 const float angle = -qDegreesToRadians(degrees: node->m_fillGradient.v0);
1128
1129 if (!oldMaterial || m_centerPoint.x() != centerPoint.x() || m_centerPoint.y() != centerPoint.y()) {
1130 m_centerPoint = QVector2D(centerPoint.x(), centerPoint.y());
1131 Q_ASSERT(sizeof(m_centerPoint) == 8);
1132 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64, src: &m_centerPoint, n: 8);
1133 changed = true;
1134 }
1135
1136 if (!oldMaterial || m_angle != angle) {
1137 m_angle = angle;
1138 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64 + 8, src: &m_angle, n: 4);
1139 changed = true;
1140 }
1141
1142 if (state.isOpacityDirty()) {
1143 const float opacity = state.opacity();
1144 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64 + 8 + 4, src: &opacity, n: 4);
1145 changed = true;
1146 }
1147
1148 return changed;
1149}
1150
1151void QQuickShapeConicalGradientRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
1152 QSGMaterial *newMaterial, QSGMaterial *)
1153{
1154 if (binding != 1)
1155 return;
1156
1157 QQuickShapeConicalGradientMaterial *m = static_cast<QQuickShapeConicalGradientMaterial *>(newMaterial);
1158 QQuickShapeGenericStrokeFillNode *node = m->node();
1159 const QSGGradientCacheKey cacheKey(node->m_fillGradient.stops, QGradient::Spread(node->m_fillGradient.spread));
1160 QSGTexture *t = QSGGradientCache::cacheForRhi(rhi: state.rhi())->get(grad: cacheKey);
1161 t->commitTextureOperations(rhi: state.rhi(), resourceUpdates: state.resourceUpdateBatch());
1162 *texture = t;
1163}
1164
1165QSGMaterialType *QQuickShapeConicalGradientMaterial::type() const
1166{
1167 static QSGMaterialType type;
1168 return &type;
1169}
1170
1171int QQuickShapeConicalGradientMaterial::compare(const QSGMaterial *other) const
1172{
1173 Q_ASSERT(other && type() == other->type());
1174 const QQuickShapeConicalGradientMaterial *m = static_cast<const QQuickShapeConicalGradientMaterial *>(other);
1175
1176 QQuickShapeGenericStrokeFillNode *a = node();
1177 QQuickShapeGenericStrokeFillNode *b = m->node();
1178 Q_ASSERT(a && b);
1179 if (a == b)
1180 return 0;
1181
1182 const QSGGradientCache::GradientDesc *ga = &a->m_fillGradient;
1183 const QSGGradientCache::GradientDesc *gb = &b->m_fillGradient;
1184
1185 if (int d = ga->a.x() - gb->a.x())
1186 return d;
1187 if (int d = ga->a.y() - gb->a.y())
1188 return d;
1189
1190 if (int d = ga->v0 - gb->v0)
1191 return d;
1192
1193 if (int d = ga->stops.size() - gb->stops.size())
1194 return d;
1195
1196 for (int i = 0; i < ga->stops.size(); ++i) {
1197 if (int d = ga->stops[i].first - gb->stops[i].first)
1198 return d;
1199 if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba())
1200 return d;
1201 }
1202
1203 if (int d = a->m_fillTransform.compareTo(other: b->m_fillTransform))
1204 return d;
1205
1206 return 0;
1207}
1208
1209QSGMaterialShader *QQuickShapeConicalGradientMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
1210{
1211 Q_UNUSED(renderMode);
1212 return new QQuickShapeConicalGradientRhiShader(viewCount());
1213}
1214
1215QQuickShapeTextureFillRhiShader::QQuickShapeTextureFillRhiShader(int viewCount)
1216{
1217 setShaderFileName(stage: VertexStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/texturefill.vert.qsb"), viewCount);
1218 setShaderFileName(stage: FragmentStage, QStringLiteral(":/qt-project.org/shapes/shaders_ng/texturefill.frag.qsb"), viewCount);
1219}
1220
1221bool QQuickShapeTextureFillRhiShader::updateUniformData(RenderState &state,
1222 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
1223{
1224 Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type());
1225 QQuickShapeTextureFillMaterial *m = static_cast<QQuickShapeTextureFillMaterial *>(newMaterial);
1226 bool changed = false;
1227 QByteArray *buf = state.uniformData();
1228 const int shaderMatrixCount = newMaterial->viewCount();
1229 const int matrixCount = qMin(a: state.projectionMatrixCount(), b: shaderMatrixCount);
1230 Q_ASSERT(buf->size() >= 64 * shaderMatrixCount + 64 + 8 + 4);
1231
1232 if (state.isMatrixDirty()) {
1233 for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
1234 const QMatrix4x4 m = state.combinedMatrix();
1235 memcpy(dest: buf->data() + 64 * viewIndex, src: m.constData(), n: 64);
1236 changed = true;
1237 }
1238 }
1239
1240 QQuickShapeGenericStrokeFillNode *node = m->node();
1241
1242 if (!oldMaterial || m_fillTransform != node->m_fillTransform) {
1243 memcpy(dest: buf->data() + 64 * shaderMatrixCount, src: node->m_fillTransform.invertedData(), n: 64);
1244 m_fillTransform = node->m_fillTransform;
1245 changed = true;
1246 }
1247
1248 const QSizeF boundsSize = node->m_fillTextureProvider != nullptr && node->m_fillTextureProvider->texture() != nullptr
1249 ? node->m_fillTextureProvider->texture()->textureSize()
1250 : QSizeF(0, 0);
1251
1252
1253 const QVector2D boundsVector(boundsSize.width() / state.devicePixelRatio(),
1254 boundsSize.height() / state.devicePixelRatio());
1255 if (!oldMaterial || m_boundsSize != boundsVector) {
1256 m_boundsSize = boundsVector;
1257 Q_ASSERT(sizeof(m_boundsSize) == 8);
1258 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64, src: &m_boundsSize, n: 8);
1259 changed = true;
1260 }
1261
1262 if (state.isOpacityDirty()) {
1263 const float opacity = state.opacity();
1264 memcpy(dest: buf->data() + 64 * shaderMatrixCount + 64 + 8, src: &opacity, n: 4);
1265 changed = true;
1266 }
1267
1268 return changed;
1269}
1270
1271void QQuickShapeTextureFillRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
1272 QSGMaterial *newMaterial, QSGMaterial *)
1273{
1274 if (binding != 1)
1275 return;
1276
1277 QQuickShapeTextureFillMaterial *m = static_cast<QQuickShapeTextureFillMaterial *>(newMaterial);
1278 QQuickShapeGenericStrokeFillNode *node = m->node();
1279 if (node->m_fillTextureProvider != nullptr) {
1280 QSGTexture *providedTexture = node->m_fillTextureProvider->texture();
1281 if (providedTexture != nullptr) {
1282 if (providedTexture->isAtlasTexture()) {
1283 // Create a non-atlas copy to make texture coordinate wrapping work. This
1284 // texture copy is owned by the QSGTexture so memory is managed with the original
1285 // texture provider.
1286 QSGTexture *newTexture = providedTexture->removedFromAtlas(resourceUpdates: state.resourceUpdateBatch());
1287 if (newTexture != nullptr)
1288 providedTexture = newTexture;
1289 }
1290
1291 providedTexture->commitTextureOperations(rhi: state.rhi(), resourceUpdates: state.resourceUpdateBatch());
1292 *texture = providedTexture;
1293 return;
1294 }
1295 }
1296
1297 if (m->dummyTexture() == nullptr) {
1298 QSGPlainTexture *dummyTexture = new QSGPlainTexture;
1299 dummyTexture->setFiltering(QSGTexture::Nearest);
1300 dummyTexture->setHorizontalWrapMode(QSGTexture::Repeat);
1301 dummyTexture->setVerticalWrapMode(QSGTexture::Repeat);
1302 QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);
1303 img.fill(pixel: 0);
1304 dummyTexture->setImage(img);
1305 dummyTexture->commitTextureOperations(rhi: state.rhi(), resourceUpdates: state.resourceUpdateBatch());
1306
1307 m->setDummyTexture(dummyTexture);
1308 }
1309
1310 *texture = m->dummyTexture();
1311}
1312
1313QQuickShapeTextureFillMaterial::~QQuickShapeTextureFillMaterial()
1314{
1315 delete m_dummyTexture;
1316}
1317
1318QSGMaterialType *QQuickShapeTextureFillMaterial::type() const
1319{
1320 static QSGMaterialType type;
1321 return &type;
1322}
1323
1324int QQuickShapeTextureFillMaterial::compare(const QSGMaterial *other) const
1325{
1326 Q_ASSERT(other && type() == other->type());
1327 const QQuickShapeTextureFillMaterial *m = static_cast<const QQuickShapeTextureFillMaterial *>(other);
1328
1329 QQuickShapeGenericStrokeFillNode *a = node();
1330 QQuickShapeGenericStrokeFillNode *b = m->node();
1331 Q_ASSERT(a && b);
1332 if (a == b)
1333 return 0;
1334
1335 if (int d = a->m_fillTransform.compareTo(other: b->m_fillTransform))
1336 return d;
1337
1338 const qintptr diff = qintptr(a->m_fillTextureProvider) - qintptr(b->m_fillTextureProvider);
1339 return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
1340}
1341
1342QSGMaterialShader *QQuickShapeTextureFillMaterial::createShader(QSGRendererInterface::RenderMode renderMode) const
1343{
1344 Q_UNUSED(renderMode);
1345 return new QQuickShapeTextureFillRhiShader(viewCount());
1346}
1347
1348QT_END_NAMESPACE
1349
1350#include "moc_qquickshapegenericrenderer_p.cpp"
1351

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