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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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