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

source code of qtdeclarative/src/quick/scenegraph/util/qsgdefaultpainternode.cpp