1// Copyright (C) 2016 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 "qquickpainteditem.h"
5#include <private/qquickpainteditem_p.h>
6
7#include <QtQuick/private/qsgdefaultpainternode_p.h>
8#include <QtQuick/private/qsgcontext_p.h>
9#include <private/qsgadaptationlayer_p.h>
10#include <qsgtextureprovider.h>
11#include <rhi/qrhi.h>
12
13#include <qmath.h>
14
15QT_BEGIN_NAMESPACE
16
17class QQuickPaintedItemTextureProvider : public QSGTextureProvider
18{
19public:
20 QSGPainterNode *node;
21 QSGTexture *texture() const override { return node ? node->texture() : nullptr; }
22 void fireTextureChanged() { emit textureChanged(); }
23};
24
25/*!
26 \class QQuickPaintedItem
27 \brief The QQuickPaintedItem class provides a way to use the QPainter API in the
28 QML Scene Graph.
29
30 \inmodule QtQuick
31
32 The QQuickPaintedItem makes it possible to use the QPainter API with the QML
33 Scene Graph. It sets up a textured rectangle in the Scene Graph and uses a
34 QPainter to paint onto the texture. The render target in Qt 6 is always a
35 QImage. When the render target is a QImage, QPainter first renders into the
36 image then the content is uploaded to the texture. Call update() to trigger
37 a repaint.
38
39 To enable QPainter to do anti-aliased rendering, use setAntialiasing().
40
41 To write your own painted item, you first create a subclass of
42 QQuickPaintedItem, and then start by implementing its only pure virtual
43 public function: paint(), which implements the actual painting. The
44 painting will be inside the rectangle spanning from 0,0 to
45 width(),height().
46
47 \note It important to understand the performance implications such items
48 can incur. See QQuickPaintedItem::RenderTarget and
49 QQuickPaintedItem::renderTarget.
50
51 \sa {Scene Graph - Painted Item}, {Writing QML Extensions with C++}
52*/
53
54/*!
55 \enum QQuickPaintedItem::RenderTarget
56
57 This enum describes QQuickPaintedItem's render targets. The render target is the
58 surface QPainter paints onto before the item is rendered on screen.
59
60 \value Image The default; QPainter paints into a QImage using the raster paint engine.
61 The image's content needs to be uploaded to graphics memory afterward, this operation
62 can potentially be slow if the item is large. This render target allows high quality
63 anti-aliasing and fast item resizing.
64
65 \value FramebufferObject As of Qt 6.0, this value is ignored.
66
67 \value InvertedYFramebufferObject As of Qt 6.0, this value is ignored.
68
69 \sa setRenderTarget()
70*/
71
72/*!
73 \enum QQuickPaintedItem::PerformanceHint
74
75 This enum describes flags that you can enable to improve rendering
76 performance in QQuickPaintedItem. By default, none of these flags are set.
77
78 \value FastFBOResizing As of Qt 6.0, this value is ignored.
79*/
80
81/*!
82 \internal
83*/
84QQuickPaintedItemPrivate::QQuickPaintedItemPrivate()
85 : QQuickItemPrivate()
86 , contentsScale(1.0)
87 , fillColor(Qt::transparent)
88 , renderTarget(QQuickPaintedItem::Image)
89 , opaquePainting(false)
90 , antialiasing(false)
91 , mipmap(false)
92 , textureProvider(nullptr)
93 , node(nullptr)
94{
95}
96
97/*!
98 Constructs a QQuickPaintedItem with the given \a parent item.
99 */
100QQuickPaintedItem::QQuickPaintedItem(QQuickItem *parent)
101 : QQuickItem(*(new QQuickPaintedItemPrivate), parent)
102{
103 setFlag(flag: ItemHasContents);
104}
105
106/*!
107 \internal
108*/
109QQuickPaintedItem::QQuickPaintedItem(QQuickPaintedItemPrivate &dd, QQuickItem *parent)
110 : QQuickItem(dd, parent)
111{
112 setFlag(flag: ItemHasContents);
113}
114
115/*!
116 Destroys the QQuickPaintedItem.
117*/
118QQuickPaintedItem::~QQuickPaintedItem()
119{
120 Q_D(QQuickPaintedItem);
121 if (d->textureProvider)
122 QQuickWindowQObjectCleanupJob::schedule(window: window(), object: d->textureProvider);
123}
124
125/*!
126 Schedules a redraw of the area covered by \a rect in this item. You can call this function
127 whenever your item needs to be redrawn, such as if it changes appearance or size.
128
129 This function does not cause an immediate paint; instead it schedules a paint request that
130 is processed by the QML Scene Graph when the next frame is rendered. The item will only be
131 redrawn if it is visible.
132
133 \sa paint()
134*/
135void QQuickPaintedItem::update(const QRect &rect)
136{
137 Q_D(QQuickPaintedItem);
138
139 if (rect.isNull() && !d->dirtyRect.isNull())
140 d->dirtyRect = contentsBoundingRect().toAlignedRect();
141 else
142 d->dirtyRect |= (contentsBoundingRect() & rect).toAlignedRect();
143 QQuickItem::update();
144}
145
146/*!
147 Returns true if this item is opaque; otherwise, false is returned.
148
149 By default, painted items are not opaque.
150
151 \sa setOpaquePainting()
152*/
153bool QQuickPaintedItem::opaquePainting() const
154{
155 Q_D(const QQuickPaintedItem);
156 return d->opaquePainting;
157}
158
159/*!
160 If \a opaque is true, the item is opaque; otherwise, it is considered as translucent.
161
162 Opaque items are not blended with the rest of the scene, you should set this to true
163 if the content of the item is opaque to speed up rendering.
164
165 By default, painted items are not opaque.
166
167 \sa opaquePainting()
168*/
169void QQuickPaintedItem::setOpaquePainting(bool opaque)
170{
171 Q_D(QQuickPaintedItem);
172
173 if (d->opaquePainting == opaque)
174 return;
175
176 d->opaquePainting = opaque;
177 QQuickItem::update();
178}
179
180/*!
181 Returns true if antialiased painting is enabled; otherwise, false is returned.
182
183 By default, antialiasing is not enabled.
184
185 \sa setAntialiasing()
186*/
187bool QQuickPaintedItem::antialiasing() const
188{
189 Q_D(const QQuickPaintedItem);
190 return d->antialiasing;
191}
192
193/*!
194 If \a enable is true, antialiased painting is enabled.
195
196 By default, antialiasing is not enabled.
197
198 \sa antialiasing()
199*/
200void QQuickPaintedItem::setAntialiasing(bool enable)
201{
202 Q_D(QQuickPaintedItem);
203
204 if (d->antialiasing == enable)
205 return;
206
207 d->antialiasing = enable;
208 update();
209}
210
211/*!
212 Returns true if mipmaps are enabled; otherwise, false is returned.
213
214 By default, mipmapping is not enabled.
215
216 \sa setMipmap()
217*/
218bool QQuickPaintedItem::mipmap() const
219{
220 Q_D(const QQuickPaintedItem);
221 return d->mipmap;
222}
223
224/*!
225 If \a enable is true, mipmapping is enabled on the associated texture.
226
227 Mipmapping increases rendering speed and reduces aliasing artifacts when the item is
228 scaled down.
229
230 By default, mipmapping is not enabled.
231
232 \sa mipmap()
233*/
234void QQuickPaintedItem::setMipmap(bool enable)
235{
236 Q_D(QQuickPaintedItem);
237
238 if (d->mipmap == enable)
239 return;
240
241 d->mipmap = enable;
242 update();
243}
244
245/*!
246 Returns the performance hints.
247
248 By default, no performance hint is enabled.
249
250 \sa setPerformanceHint(), setPerformanceHints()
251*/
252QQuickPaintedItem::PerformanceHints QQuickPaintedItem::performanceHints() const
253{
254 Q_D(const QQuickPaintedItem);
255 return d->performanceHints;
256}
257
258/*!
259 Sets the given performance \a hint on the item if \a enabled is true;
260 otherwise clears the performance hint.
261
262 By default, no performance hint is enabled/
263
264 \sa setPerformanceHints(), performanceHints()
265*/
266void QQuickPaintedItem::setPerformanceHint(QQuickPaintedItem::PerformanceHint hint, bool enabled)
267{
268 Q_D(QQuickPaintedItem);
269 PerformanceHints oldHints = d->performanceHints;
270 if (enabled)
271 d->performanceHints |= hint;
272 else
273 d->performanceHints &= ~hint;
274 if (oldHints != d->performanceHints)
275 update();
276}
277
278/*!
279 Sets the performance hints to \a hints
280
281 By default, no performance hint is enabled/
282
283 \sa setPerformanceHint(), performanceHints()
284*/
285void QQuickPaintedItem::setPerformanceHints(QQuickPaintedItem::PerformanceHints hints)
286{
287 Q_D(QQuickPaintedItem);
288 if (d->performanceHints == hints)
289 return;
290 d->performanceHints = hints;
291 update();
292}
293
294QSize QQuickPaintedItem::textureSize() const
295{
296 Q_D(const QQuickPaintedItem);
297 return d->textureSize;
298}
299
300/*!
301 \property QQuickPaintedItem::textureSize
302
303 \brief Defines the size of the texture.
304
305 Changing the texture's size does not affect the coordinate system used in
306 paint(). A scale factor is instead applied so painting should still happen
307 inside 0,0 to width(),height().
308
309 By default, the texture size will have the same size as this item.
310
311 \note If the item is on a window with a device pixel ratio different from
312 1, this scale factor will be implicitly applied to the texture size.
313
314 */
315void QQuickPaintedItem::setTextureSize(const QSize &size)
316{
317 Q_D(QQuickPaintedItem);
318 if (d->textureSize == size)
319 return;
320 d->textureSize = size;
321 emit textureSizeChanged();
322}
323
324/*!
325 \deprecated
326
327 This function is provided for compatibility, use size in combination
328 with textureSize to decide the size of what you are drawing.
329
330 \sa width(), height(), textureSize()
331*/
332QRectF QQuickPaintedItem::contentsBoundingRect() const
333{
334 Q_D(const QQuickPaintedItem);
335
336 qreal w = d->width;
337 QSizeF sz = d->contentsSize * d->contentsScale;
338 if (w < sz.width())
339 w = sz.width();
340 qreal h = d->height;
341 if (h < sz.height())
342 h = sz.height();
343
344 return QRectF(0, 0, w, h);
345}
346
347/*!
348 \property QQuickPaintedItem::contentsSize
349 \brief Obsolete method for setting the contents size.
350 \deprecated
351
352 This function is provided for compatibility, use size in combination
353 with textureSize to decide the size of what you are drawing.
354
355 \sa width(), height(), textureSize()
356*/
357QSize QQuickPaintedItem::contentsSize() const
358{
359 Q_D(const QQuickPaintedItem);
360 return d->contentsSize;
361}
362
363void QQuickPaintedItem::setContentsSize(const QSize &size)
364{
365 Q_D(QQuickPaintedItem);
366
367 if (d->contentsSize == size)
368 return;
369
370 d->contentsSize = size;
371 update();
372
373 emit contentsSizeChanged();
374}
375
376/*!
377 \deprecated
378 This convenience function is equivalent to calling setContentsSize(QSize()).
379*/
380void QQuickPaintedItem::resetContentsSize()
381{
382 setContentsSize(QSize());
383}
384
385/*!
386 \property QQuickPaintedItem::contentsScale
387 \brief Obsolete method for scaling the contents.
388 \deprecated
389
390 This function is provided for compatibility, use size() in combination
391 with textureSize() to decide the size of what you are drawing.
392
393 \sa width(), height(), textureSize()
394*/
395qreal QQuickPaintedItem::contentsScale() const
396{
397 Q_D(const QQuickPaintedItem);
398 return d->contentsScale;
399}
400
401void QQuickPaintedItem::setContentsScale(qreal scale)
402{
403 Q_D(QQuickPaintedItem);
404
405 if (d->contentsScale == scale)
406 return;
407
408 d->contentsScale = scale;
409 update();
410
411 emit contentsScaleChanged();
412}
413
414/*!
415 \property QQuickPaintedItem::fillColor
416 \brief The item's background fill color.
417
418 By default, the fill color is set to Qt::transparent.
419
420 Set the fill color to an invalid color (e.g. QColor()) to disable background
421 filling. This may improve performance, and is safe to do if the paint() function
422 draws to all pixels on each frame.
423*/
424QColor QQuickPaintedItem::fillColor() const
425{
426 Q_D(const QQuickPaintedItem);
427 return d->fillColor;
428}
429
430void QQuickPaintedItem::setFillColor(const QColor &c)
431{
432 Q_D(QQuickPaintedItem);
433
434 if (d->fillColor == c)
435 return;
436
437 d->fillColor = c;
438 update();
439
440 emit fillColorChanged();
441}
442
443/*!
444 \property QQuickPaintedItem::renderTarget
445 \brief The item's render target.
446
447 This property defines which render target the QPainter renders into, it can be either
448 QQuickPaintedItem::Image, QQuickPaintedItem::FramebufferObject or QQuickPaintedItem::InvertedYFramebufferObject.
449
450 Each has certain benefits, typically performance versus quality. Using a framebuffer
451 object avoids a costly upload of the image contents to the texture in graphics memory,
452 while using an image enables high quality anti-aliasing.
453
454 \warning Resizing a framebuffer object is a costly operation, avoid using
455 the QQuickPaintedItem::FramebufferObject render target if the item gets resized often.
456
457 By default, the render target is QQuickPaintedItem::Image.
458*/
459QQuickPaintedItem::RenderTarget QQuickPaintedItem::renderTarget() const
460{
461 Q_D(const QQuickPaintedItem);
462 return d->renderTarget;
463}
464
465void QQuickPaintedItem::setRenderTarget(RenderTarget target)
466{
467 Q_D(QQuickPaintedItem);
468
469 if (d->renderTarget == target)
470 return;
471
472 d->renderTarget = target;
473 update();
474
475 emit renderTargetChanged();
476}
477
478/*!
479 \fn virtual void QQuickPaintedItem::paint(QPainter *painter) = 0
480
481 This function, which is usually called by the QML Scene Graph, paints the
482 contents of an item in local coordinates.
483
484 The underlying texture will have a size defined by textureSize when set,
485 or the item's size, multiplied by the window's device pixel ratio.
486
487 The function is called after the item has been filled with the fillColor.
488
489 Reimplement this function in a QQuickPaintedItem subclass to provide the
490 item's painting implementation, using \a painter.
491
492 \note The QML Scene Graph uses two separate threads, the main thread does
493 things such as processing events or updating animations while a second
494 thread does the actual issuing of graphics resource updates and the
495 recording of draw calls. As a consequence, paint() is not called from the
496 main GUI thread but from the GL enabled renderer thread. At the moment
497 paint() is called, the GUI thread is blocked and this is therefore
498 thread-safe.
499
500 \warning Extreme caution must be used when creating QObjects, emitting signals, starting
501 timers and similar inside this function as these will have affinity to the rendering thread.
502
503 \sa width(), height(), textureSize
504*/
505
506/*!
507 \reimp
508*/
509QSGNode *QQuickPaintedItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
510{
511 Q_UNUSED(data);
512 Q_D(QQuickPaintedItem);
513
514 if (width() <= 0 || height() <= 0) {
515 delete oldNode;
516 if (d->textureProvider) {
517 d->textureProvider->node = nullptr;
518 d->textureProvider->fireTextureChanged();
519 }
520 return nullptr;
521 }
522
523 QSGPainterNode *node = static_cast<QSGPainterNode *>(oldNode);
524 if (!node) {
525 node = d->sceneGraphContext()->createPainterNode(item: this);
526 d->node = node;
527 }
528
529 bool hasTextureSize = d->textureSize.width() > 0 && d->textureSize.height() > 0;
530
531 // Use the compat mode if any of the compat things are set and
532 // textureSize is 0x0.
533 if (!hasTextureSize
534 && (d->contentsScale != 1
535 || (d->contentsSize.width() > 0 && d->contentsSize.height() > 0))) {
536 QRectF br = contentsBoundingRect();
537 node->setContentsScale(d->contentsScale);
538 QSize size = QSize(qRound(d: br.width()), qRound(d: br.height()));
539 node->setSize(size);
540 node->setTextureSize(size);
541 } else {
542 // The default, use textureSize or item's size * device pixel ratio
543 node->setContentsScale(1);
544 QSize itemSize(qRound(d: width()), qRound(d: height()));
545 node->setSize(itemSize);
546 QSize textureSize = (hasTextureSize ? d->textureSize : itemSize)
547 * window()->effectiveDevicePixelRatio();
548 node->setTextureSize(textureSize);
549 }
550
551 node->setPreferredRenderTarget(d->renderTarget);
552 node->setFastFBOResizing(d->performanceHints & FastFBOResizing);
553 node->setSmoothPainting(d->antialiasing);
554 node->setLinearFiltering(d->smooth);
555 node->setMipmapping(d->mipmap);
556 node->setOpaquePainting(d->opaquePainting);
557 node->setFillColor(d->fillColor);
558 node->setDirty(d->dirtyRect);
559 node->update();
560
561 d->dirtyRect = QRect();
562
563 if (d->textureProvider) {
564 d->textureProvider->node = node;
565 d->textureProvider->fireTextureChanged();
566 }
567
568 return node;
569}
570
571/*!
572 \reimp
573*/
574void QQuickPaintedItem::releaseResources()
575{
576 Q_D(QQuickPaintedItem);
577 if (d->textureProvider) {
578 QQuickWindowQObjectCleanupJob::schedule(window: window(), object: d->textureProvider);
579 d->textureProvider = nullptr;
580 }
581 d->node = nullptr; // Managed by the scene graph, just clear the pointer.
582}
583
584void QQuickPaintedItem::invalidateSceneGraph()
585{
586 Q_D(QQuickPaintedItem);
587 delete d->textureProvider;
588 d->textureProvider = nullptr;
589 d->node = nullptr; // Managed by the scene graph, just clear the pointer
590}
591
592/*!
593 \reimp
594*/
595bool QQuickPaintedItem::isTextureProvider() const
596{
597 return true;
598}
599
600/*!
601 \reimp
602*/
603QSGTextureProvider *QQuickPaintedItem::textureProvider() const
604{
605 // When Item::layer::enabled == true, QQuickItem will be a texture
606 // provider. In this case we should prefer to return the layer rather
607 // than the image itself. The layer will include any children and any
608 // the image's wrap and fill mode.
609 if (QQuickItem::isTextureProvider())
610 return QQuickItem::textureProvider();
611
612 Q_D(const QQuickPaintedItem);
613 QQuickWindow *w = window();
614 if (!w || !w->isSceneGraphInitialized() || QThread::currentThread() != d->sceneGraphContext()->thread()) {
615 qWarning(msg: "QQuickPaintedItem::textureProvider: can only be queried on the rendering thread of an exposed window");
616 return nullptr;
617 }
618 if (!d->textureProvider)
619 d->textureProvider = new QQuickPaintedItemTextureProvider();
620 d->textureProvider->node = d->node;
621 return d->textureProvider;
622}
623
624
625/*!
626 \reimp
627*/
628void QQuickPaintedItem::itemChange(ItemChange change, const ItemChangeData &value)
629{
630 if (change == ItemDevicePixelRatioHasChanged)
631 update();
632 QQuickItem::itemChange(change, value);
633}
634
635QT_END_NAMESPACE
636
637#include "moc_qquickpainteditem.cpp"
638

source code of qtdeclarative/src/quick/items/qquickpainteditem.cpp