Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsgdefaultpainternode.cpp
Go to the documentation of this file.
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
5
6#include <QtQuick/private/qquickpainteditem_p.h>
7
8#include <QtQuick/private/qsgdefaultrendercontext_p.h>
9#include <QtQuick/private/qsgcontext_p.h>
10#include <qmath.h>
11#include <qpainter.h>
12
13#if QT_CONFIG(opengl)
14#include <private/qopenglextensions_p.h>
15#include <rhi/qrhi.h>
16#endif
17
19
20#define QT_MINIMUM_DYNAMIC_FBO_SIZE 64U
21
22QSGPainterTexture::QSGPainterTexture()
23 : QSGPlainTexture(*(new QSGPlainTexturePrivate(this)))
24{
25 m_retain_image = true;
26}
27
28void QSGPainterTexture::commitTextureOperations(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
29{
30 if (!m_dirty_rect.isNull()) {
31 setImage(m_image);
32 m_dirty_rect = QRect();
33 }
34 QSGPlainTexture::commitTextureOperations(rhi, resourceUpdates);
35}
36
37QSGDefaultPainterNode::QSGDefaultPainterNode(QQuickPaintedItem *item)
38 : QSGPainterNode()
39 , m_preferredRenderTarget(QQuickPaintedItem::Image)
40 , m_actualRenderTarget(QQuickPaintedItem::Image)
41 , m_item(item)
42 , m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4)
43 , m_texture(nullptr)
44#if QT_CONFIG(opengl)
45 , m_fbo(nullptr)
46 , m_multisampledFbo(nullptr)
47 , m_gl_device(nullptr)
48 , m_wrapperTexture(nullptr)
49#endif
50 , m_fillColor(Qt::transparent)
51 , m_contentsScale(1.0)
52 , m_dirtyContents(false)
53 , m_opaquePainting(false)
54 , m_linear_filtering(false)
55 , m_mipmapping(false)
56 , m_smoothPainting(false)
57#if QT_CONFIG(opengl)
58 , m_extensionsChecked(false)
59 , m_multisamplingSupported(false)
60#endif
61 , m_fastFBOResizing(false)
62 , m_dirtyGeometry(false)
63 , m_dirtyRenderTarget(false)
64 , m_dirtyTexture(false)
65{
66 m_context = static_cast<QSGDefaultRenderContext *>(static_cast<QQuickPaintedItemPrivate *>(QObjectPrivate::get(item))->sceneGraphRenderContext());
67
68 setMaterial(&m_materialO);
69 setOpaqueMaterial(&m_material);
70 setGeometry(&m_geometry);
71
72#ifdef QSG_RUNTIME_DESCRIPTION
73 qsgnode_set_description(this, QString::fromLatin1("QQuickPaintedItem(%1):%2").arg(QString::fromLatin1(item->metaObject()->className())).arg(item->objectName()));
74#endif
75}
76
77QSGDefaultPainterNode::~QSGDefaultPainterNode()
78{
79 delete m_texture;
80
81#if QT_CONFIG(opengl)
82 delete m_wrapperTexture;
83 delete m_fbo;
84 delete m_multisampledFbo;
85 delete m_gl_device;
86#endif
87}
88
89void QSGDefaultPainterNode::paint()
90{
91 QRect dirtyRect = m_dirtyRect.isNull() ? QRect(0, 0, m_size.width(), m_size.height()) : m_dirtyRect;
92
93 QPainter painter;
94#if QT_CONFIG(opengl)
95 if (m_context->rhi()->backend() == QRhi::OpenGLES2 && m_actualRenderTarget != QQuickPaintedItem::Image) {
96 if (!m_gl_device) {
97 m_gl_device = new QOpenGLPaintDevice(m_fboSize);
98 m_gl_device->setPaintFlipped(true);
99 }
100
101 if (m_multisampledFbo)
102 m_multisampledFbo->bind();
103 else
104 m_fbo->bind();
105
106 painter.begin(m_gl_device);
107 } else
108#endif
109 {
110 Q_ASSERT(m_actualRenderTarget == QQuickPaintedItem::Image);
111 if (m_image.isNull())
112 return;
113 // We are not reusing the reference to the image the m_texture, so unset it to avoid detach.
114 m_texture->setImage(QImage());
115 painter.begin(&m_image);
116 }
117
118 if (m_smoothPainting) {
119 painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
120 }
121
122 QRect clipRect;
123 QRect dirtyTextureRect;
124
125 if (m_contentsScale == 1) {
126 qreal scaleX = m_textureSize.width() / (qreal) m_size.width();
127 qreal scaleY = m_textureSize.height() / (qreal) m_size.height();
128 painter.scale(scaleX, scaleY);
129 clipRect = dirtyRect;
130 dirtyTextureRect = QRectF(dirtyRect.x() * scaleX,
131 dirtyRect.y() * scaleY,
132 dirtyRect.width() * scaleX,
133 dirtyRect.height() * scaleY).toAlignedRect();
134 } else {
135 painter.scale(m_contentsScale, m_contentsScale);
136 QRect sclip(qFloor(dirtyRect.x()/m_contentsScale),
137 qFloor(dirtyRect.y()/m_contentsScale),
138 qCeil(dirtyRect.width()/m_contentsScale+dirtyRect.x()/m_contentsScale-qFloor(dirtyRect.x()/m_contentsScale)),
139 qCeil(dirtyRect.height()/m_contentsScale+dirtyRect.y()/m_contentsScale-qFloor(dirtyRect.y()/m_contentsScale)));
140 clipRect = sclip;
141 dirtyTextureRect = dirtyRect;
142 }
143
144 // only clip if we were originally updating only a subrect
145 if (!m_dirtyRect.isNull()) {
146 painter.setClipRect(clipRect);
147 }
148
149 painter.setCompositionMode(QPainter::CompositionMode_Source);
150 if (m_fillColor.isValid())
151 painter.fillRect(clipRect, m_fillColor);
152 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
153
154 m_item->paint(&painter);
155 painter.end();
156
157#if QT_CONFIG(opengl)
158 if (m_context->rhi()->backend() == QRhi::OpenGLES2 && m_actualRenderTarget != QQuickPaintedItem::Image) {
159 if (m_multisampledFbo) {
160 QOpenGLFramebufferObject::blitFramebuffer(m_fbo, dirtyTextureRect, m_multisampledFbo, dirtyTextureRect);
161 m_multisampledFbo->release();
162 } else if (m_fbo) {
163 m_fbo->release();
164 }
165 } else
166#endif
167 {
168 m_texture->setImage(m_image);
169 m_texture->setDirtyRect(dirtyTextureRect);
170 }
171
172 m_dirtyRect = QRect();
173}
174
175void QSGDefaultPainterNode::update()
176{
177 if (m_dirtyRenderTarget)
178 updateRenderTarget();
179 if (m_dirtyGeometry)
180 updateGeometry();
181 if (m_dirtyTexture)
182 updateTexture();
183
184 if (m_dirtyContents)
185 paint();
186
187 m_dirtyGeometry = false;
188 m_dirtyRenderTarget = false;
189 m_dirtyTexture = false;
190 m_dirtyContents = false;
191}
192
193void QSGDefaultPainterNode::updateTexture()
194{
195 m_texture->setHasAlphaChannel(!m_opaquePainting);
196 m_material.setTexture(m_texture);
197 m_materialO.setTexture(m_texture);
198
199 markDirty(DirtyMaterial);
200}
201
202void QSGDefaultPainterNode::updateGeometry()
203{
204 QRectF source;
205#if QT_CONFIG(opengl)
206 if (m_context->rhi()->backend() == QRhi::OpenGLES2 && m_actualRenderTarget != QQuickPaintedItem::Image) {
207 source = QRectF(0, 0, qreal(m_textureSize.width()) / m_fboSize.width(), qreal(m_textureSize.height()) / m_fboSize.height());
208 } else
209#endif
210 {
211 source = QRectF(0, 0, 1, 1);
212 }
213 QRectF dest(0, 0, m_size.width(), m_size.height());
214 if (m_actualRenderTarget == QQuickPaintedItem::InvertedYFramebufferObject)
215 dest = QRectF(QPointF(0, m_size.height()), QPointF(m_size.width(), 0));
216 QSGGeometry::updateTexturedRectGeometry(&m_geometry,
217 dest,
218 source);
219 markDirty(DirtyGeometry);
220}
221
222void QSGDefaultPainterNode::updateRenderTarget()
223{
224 m_dirtyContents = true;
225
226#if QT_CONFIG(opengl)
227 if (m_context->rhi()->backend() == QRhi::OpenGLES2) {
228 if (!m_extensionsChecked) {
229 QOpenGLExtensions *e = static_cast<QOpenGLExtensions *>(QOpenGLContext::currentContext()->functions());
230 m_multisamplingSupported = e->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)
231 && e->hasOpenGLExtension(QOpenGLExtensions::FramebufferBlit);
232 m_extensionsChecked = true;
233 }
234 QQuickPaintedItem::RenderTarget oldTarget = m_actualRenderTarget;
235 if (m_preferredRenderTarget == QQuickPaintedItem::Image) {
236 m_actualRenderTarget = QQuickPaintedItem::Image;
237 } else {
238 // Image is the only option when there is no multisample framebuffer
239 // support and smooth painting is wanted, and when using the RHI. The
240 // latter may change in the future.
241 if (!m_multisamplingSupported && m_smoothPainting)
242 m_actualRenderTarget = QQuickPaintedItem::Image;
243 else
244 m_actualRenderTarget = m_preferredRenderTarget;
245 }
246 if (oldTarget != m_actualRenderTarget) {
247 m_image = QImage();
248 delete m_fbo;
249 delete m_multisampledFbo;
250 delete m_gl_device;
251 m_fbo = m_multisampledFbo = nullptr;
252 m_gl_device = nullptr;
253 }
254
255 if (m_actualRenderTarget == QQuickPaintedItem::FramebufferObject
256 || m_actualRenderTarget == QQuickPaintedItem::InvertedYFramebufferObject)
257 {
258 const QOpenGLContext *ctx = static_cast<const QRhiGles2NativeHandles *>(m_context->rhi()->nativeHandles())->context;
259 if (m_fbo && !m_dirtyGeometry && (!ctx->format().samples() || !m_multisamplingSupported))
260 return;
261
262 if (m_fboSize.isEmpty())
263 updateFBOSize();
264
265 delete m_fbo;
266 delete m_multisampledFbo;
267 m_fbo = m_multisampledFbo = nullptr;
268 if (m_gl_device)
269 m_gl_device->setSize(m_fboSize);
270
271 if (m_smoothPainting && ctx->format().samples() && m_multisamplingSupported) {
272 QOpenGLFramebufferObjectFormat msaaFormat;
273 msaaFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
274 msaaFormat.setSamples(8);
275 m_multisampledFbo = new QOpenGLFramebufferObject(m_fboSize, msaaFormat);
276 QOpenGLFramebufferObjectFormat format;
277 format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
278 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
279 } else {
280 QOpenGLFramebufferObjectFormat format;
281 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
282 m_fbo = new QOpenGLFramebufferObject(m_fboSize, format);
283 }
284 } else {
285 if (!m_image.isNull() && !m_dirtyGeometry)
286 return;
287
288 m_image = QImage(m_textureSize, QImage::Format_RGBA8888_Premultiplied);
289 m_image.fill(Qt::transparent);
290 }
291
292 QSGPainterTexture *texture = new QSGPainterTexture;
293 if (m_actualRenderTarget == QQuickPaintedItem::Image) {
294 texture->setOwnsTexture(true);
295 texture->setTextureSize(m_textureSize);
296 } else {
297 if (!m_wrapperTexture)
298 m_wrapperTexture = m_context->rhi()->newTexture(QRhiTexture::RGBA8, m_fboSize);
299 m_wrapperTexture->createFrom({ m_fbo->texture(), 0 });
300 texture->setTexture(m_wrapperTexture);
301 texture->setOwnsTexture(false);
302 texture->setTextureSize(m_fboSize);
303 }
304
305 if (m_texture)
306 delete m_texture;
307
308 m_texture = texture;
309 } else
310#endif
311 {
312 m_actualRenderTarget = QQuickPaintedItem::Image;
313 if (!m_image.isNull() && !m_dirtyGeometry)
314 return;
315
316 m_image = QImage(m_textureSize, QImage::Format_RGBA8888_Premultiplied);
317 m_image.fill(Qt::transparent);
318
319 if (!m_texture) {
320 m_texture = new QSGPainterTexture;
321 m_texture->setOwnsTexture(true);
322 }
323 m_texture->setTextureSize(m_textureSize);
324 }
325}
326
327#if QT_CONFIG(opengl)
328void QSGDefaultPainterNode::updateFBOSize()
329{
330 int fboWidth;
331 int fboHeight;
332 if (m_fastFBOResizing) {
333 fboWidth = qMax(QT_MINIMUM_DYNAMIC_FBO_SIZE, qNextPowerOfTwo(m_textureSize.width() - 1));
334 fboHeight = qMax(QT_MINIMUM_DYNAMIC_FBO_SIZE, qNextPowerOfTwo(m_textureSize.height() - 1));
335 } else {
336 QSize minimumFBOSize = m_context->sceneGraphContext()->minimumFBOSize();
337 fboWidth = qMax(minimumFBOSize.width(), m_textureSize.width());
338 fboHeight = qMax(minimumFBOSize.height(), m_textureSize.height());
339 }
340 m_fboSize = QSize(fboWidth, fboHeight);
341}
342#endif
343
344void QSGDefaultPainterNode::setPreferredRenderTarget(QQuickPaintedItem::RenderTarget target)
345{
346 if (m_preferredRenderTarget == target)
347 return;
348
349 m_preferredRenderTarget = target;
350
351 m_dirtyRenderTarget = true;
352 m_dirtyGeometry = true;
353 m_dirtyTexture = true;
354}
355
356void QSGDefaultPainterNode::setSize(const QSize &size)
357{
358 if (size == m_size)
359 return;
360
361 m_size = size;
362 m_dirtyGeometry = true;
363}
364
365void QSGDefaultPainterNode::setTextureSize(const QSize &size)
366{
367 if (size == m_textureSize)
368 return;
369
370 m_textureSize = size;
371
372#if QT_CONFIG(opengl)
373 if (m_context->rhi()->backend() == QRhi::OpenGLES2) {
374 updateFBOSize();
375 if (m_fbo)
376 m_dirtyRenderTarget = m_fbo->size() != m_fboSize || m_dirtyRenderTarget;
377 else
378 m_dirtyRenderTarget = true;
379 } else
380#endif
381 {
382 m_dirtyRenderTarget = true;
383 }
384
385 m_dirtyGeometry = true;
386 m_dirtyTexture = true;
387}
388
389void QSGDefaultPainterNode::setDirty(const QRect &dirtyRect)
390{
391 m_dirtyContents = true;
392 m_dirtyRect = dirtyRect;
393
394 if (m_mipmapping)
395 m_dirtyTexture = true;
396
397 markDirty(DirtyMaterial);
398}
399
400void QSGDefaultPainterNode::setOpaquePainting(bool opaque)
401{
402 if (opaque == m_opaquePainting)
403 return;
404
405 m_opaquePainting = opaque;
406 m_dirtyTexture = true;
407}
408
409void QSGDefaultPainterNode::setLinearFiltering(bool linearFiltering)
410{
411 if (linearFiltering == m_linear_filtering)
412 return;
413
414 m_linear_filtering = linearFiltering;
415
416 m_material.setFiltering(linearFiltering ? QSGTexture::Linear : QSGTexture::Nearest);
417 m_materialO.setFiltering(linearFiltering ? QSGTexture::Linear : QSGTexture::Nearest);
418 markDirty(DirtyMaterial);
419}
420
421void QSGDefaultPainterNode::setMipmapping(bool mipmapping)
422{
423 if (mipmapping == m_mipmapping)
424 return;
425
426 m_mipmapping = mipmapping;
427 m_material.setMipmapFiltering(mipmapping ? QSGTexture::Linear : QSGTexture::None);
428 m_materialO.setMipmapFiltering(mipmapping ? QSGTexture::Linear : QSGTexture::None);
429 m_dirtyTexture = true;
430}
431
432void QSGDefaultPainterNode::setSmoothPainting(bool s)
433{
434 if (s == m_smoothPainting)
435 return;
436
437 m_smoothPainting = s;
438 m_dirtyRenderTarget = true;
439}
440
441void QSGDefaultPainterNode::setFillColor(const QColor &c)
442{
443 if (c == m_fillColor)
444 return;
445
446 m_fillColor = c;
447 markDirty(DirtyMaterial);
448}
449
450void QSGDefaultPainterNode::setContentsScale(qreal s)
451{
452 if (s == m_contentsScale)
453 return;
454
455 m_contentsScale = s;
456 markDirty(DirtyMaterial);
457}
458
459void QSGDefaultPainterNode::setFastFBOResizing(bool fastResizing)
460{
461 if (m_fastFBOResizing == fastResizing)
462 return;
463
464 m_fastFBOResizing = fastResizing;
465
466#if QT_CONFIG(opengl)
467 if (m_context->rhi()->backend() == QRhi::OpenGLES2) {
468 updateFBOSize();
469 if ((m_preferredRenderTarget == QQuickPaintedItem::FramebufferObject
470 || m_preferredRenderTarget == QQuickPaintedItem::InvertedYFramebufferObject)
471 && (!m_fbo || (m_fbo && m_fbo->size() != m_fboSize))) {
472 m_dirtyRenderTarget = true;
473 m_dirtyGeometry = true;
474 m_dirtyTexture = true;
475 }
476 }
477#endif
478}
479
480QImage QSGDefaultPainterNode::toImage() const
481{
482#if QT_CONFIG(opengl)
483 if (m_context->rhi()->backend() == QRhi::OpenGLES2 && m_actualRenderTarget != QQuickPaintedItem::Image) {
484 Q_ASSERT(m_fbo);
485 return m_fbo->toImage();
486 } else
487#endif
488 {
489 Q_ASSERT(m_actualRenderTarget == QQuickPaintedItem::Image);
490 return m_image;
491 }
492}
493
494QT_END_NAMESPACE