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 | |
53 | QT_BEGIN_NAMESPACE |
54 | |
55 | #define QT_MINIMUM_DYNAMIC_FBO_SIZE 64U |
56 | |
57 | QSGPainterTexture::QSGPainterTexture() |
58 | : QSGPlainTexture(*(new QSGPainterTexturePrivate)) |
59 | { |
60 | m_retain_image = true; |
61 | } |
62 | |
63 | void 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 | |
76 | void 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 | |
86 | QSGDefaultPainterNode::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 | |
121 | QSGDefaultPainterNode::~QSGDefaultPainterNode() |
122 | { |
123 | delete m_texture; |
124 | delete m_fbo; |
125 | delete m_multisampledFbo; |
126 | delete m_gl_device; |
127 | } |
128 | |
129 | void 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 | |
206 | void 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 | |
224 | void 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 | |
233 | void 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 | |
249 | void 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 | |
339 | void 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 | |
355 | void 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 | |
367 | void QSGDefaultPainterNode::setSize(const QSize &size) |
368 | { |
369 | if (size == m_size) |
370 | return; |
371 | |
372 | m_size = size; |
373 | m_dirtyGeometry = true; |
374 | } |
375 | |
376 | void 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 | |
392 | void 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 | |
403 | void QSGDefaultPainterNode::setOpaquePainting(bool opaque) |
404 | { |
405 | if (opaque == m_opaquePainting) |
406 | return; |
407 | |
408 | m_opaquePainting = opaque; |
409 | m_dirtyTexture = true; |
410 | } |
411 | |
412 | void 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 | |
424 | void 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 | |
435 | void QSGDefaultPainterNode::setSmoothPainting(bool s) |
436 | { |
437 | if (s == m_smoothPainting) |
438 | return; |
439 | |
440 | m_smoothPainting = s; |
441 | m_dirtyRenderTarget = true; |
442 | } |
443 | |
444 | void QSGDefaultPainterNode::setFillColor(const QColor &c) |
445 | { |
446 | if (c == m_fillColor) |
447 | return; |
448 | |
449 | m_fillColor = c; |
450 | markDirty(bits: DirtyMaterial); |
451 | } |
452 | |
453 | void QSGDefaultPainterNode::setContentsScale(qreal s) |
454 | { |
455 | if (s == m_contentsScale) |
456 | return; |
457 | |
458 | m_contentsScale = s; |
459 | markDirty(bits: DirtyMaterial); |
460 | } |
461 | |
462 | void 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 | |
479 | QImage QSGDefaultPainterNode::toImage() const |
480 | { |
481 | if (m_actualRenderTarget == QQuickPaintedItem::Image) |
482 | return m_image; |
483 | else |
484 | return m_fbo->toImage(); |
485 | } |
486 | |
487 | QT_END_NAMESPACE |
488 | |