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