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

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