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