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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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