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