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 | #include "qsgopengllayer_p.h" |
40 | |
41 | #include <private/qqmlglobal_p.h> |
42 | #include <private/qsgrenderer_p.h> |
43 | #include <private/qsgdefaultrendercontext_p.h> |
44 | |
45 | #include <QtGui/QOpenGLFramebufferObject> |
46 | #include <QtGui/QOpenGLFunctions> |
47 | #include <QtGui/private/qopenglextensions_p.h> |
48 | |
49 | #include <QtQuick/private/qsgdepthstencilbuffer_p.h> |
50 | |
51 | #ifdef QSG_DEBUG_FBO_OVERLAY |
52 | DEFINE_BOOL_CONFIG_OPTION(qmlFboOverlay, QML_FBO_OVERLAY) |
53 | #endif |
54 | DEFINE_BOOL_CONFIG_OPTION(qmlFboFlushBeforeDetach, QML_FBO_FLUSH_BEFORE_DETACH) |
55 | |
56 | namespace |
57 | { |
58 | class BindableFbo : public QSGBindable |
59 | { |
60 | public: |
61 | BindableFbo(QOpenGLFramebufferObject *fbo, QSGDepthStencilBuffer *depthStencil); |
62 | virtual ~BindableFbo(); |
63 | void bind() const override; |
64 | private: |
65 | QOpenGLFramebufferObject *m_fbo; |
66 | QSGDepthStencilBuffer *m_depthStencil; |
67 | }; |
68 | |
69 | BindableFbo::BindableFbo(QOpenGLFramebufferObject *fbo, QSGDepthStencilBuffer *depthStencil) |
70 | : m_fbo(fbo) |
71 | , m_depthStencil(depthStencil) |
72 | { |
73 | } |
74 | |
75 | BindableFbo::~BindableFbo() |
76 | { |
77 | if (qmlFboFlushBeforeDetach()) |
78 | QOpenGLContext::currentContext()->functions()->glFlush(); |
79 | if (m_depthStencil) |
80 | m_depthStencil->detach(); |
81 | } |
82 | |
83 | void BindableFbo::bind() const |
84 | { |
85 | m_fbo->bind(); |
86 | if (m_depthStencil) |
87 | m_depthStencil->attach(); |
88 | } |
89 | } |
90 | |
91 | QSGOpenGLLayer::QSGOpenGLLayer(QSGRenderContext *context) |
92 | : QSGLayer(*(new QSGOpenGLLayerPrivate)) |
93 | , m_item(nullptr) |
94 | , m_device_pixel_ratio(1) |
95 | , m_format(GL_RGBA) |
96 | , m_renderer(nullptr) |
97 | , m_fbo(nullptr) |
98 | , m_secondaryFbo(nullptr) |
99 | , m_transparentTexture(0) |
100 | #ifdef QSG_DEBUG_FBO_OVERLAY |
101 | , m_debugOverlay(nullptr) |
102 | #endif |
103 | , m_samples(0) |
104 | , m_mipmap(false) |
105 | , m_live(true) |
106 | , m_recursive(false) |
107 | , m_dirtyTexture(true) |
108 | , m_multisamplingChecked(false) |
109 | , m_multisampling(false) |
110 | , m_grab(false) |
111 | , m_mirrorHorizontal(false) |
112 | , m_mirrorVertical(true) |
113 | { |
114 | m_context = static_cast<QSGDefaultRenderContext *>(context); |
115 | } |
116 | |
117 | QSGOpenGLLayer::~QSGOpenGLLayer() |
118 | { |
119 | invalidated(); |
120 | } |
121 | |
122 | void QSGOpenGLLayer::invalidated() |
123 | { |
124 | delete m_renderer; |
125 | m_renderer = nullptr; |
126 | delete m_fbo; |
127 | delete m_secondaryFbo; |
128 | m_fbo = m_secondaryFbo = nullptr; |
129 | #ifdef QSG_DEBUG_FBO_OVERLAY |
130 | delete m_debugOverlay; |
131 | m_debugOverlay = nullptr; |
132 | #endif |
133 | if (m_transparentTexture) { |
134 | QOpenGLContext::currentContext()->functions()->glDeleteTextures(n: 1, textures: &m_transparentTexture); |
135 | m_transparentTexture = 0; |
136 | } |
137 | } |
138 | |
139 | int QSGOpenGLLayer::textureId() const |
140 | { |
141 | return m_fbo ? m_fbo->texture() : 0; |
142 | } |
143 | |
144 | int QSGOpenGLLayerPrivate::comparisonKey() const |
145 | { |
146 | Q_Q(const QSGOpenGLLayer); |
147 | return q->m_fbo ? q->m_fbo->texture() : 0; |
148 | } |
149 | |
150 | bool QSGOpenGLLayer::hasAlphaChannel() const |
151 | { |
152 | return m_format != GL_RGB; |
153 | } |
154 | |
155 | bool QSGOpenGLLayer::hasMipmaps() const |
156 | { |
157 | return m_mipmap; |
158 | } |
159 | |
160 | |
161 | void QSGOpenGLLayer::bind() |
162 | { |
163 | #ifndef QT_NO_DEBUG |
164 | if (!m_recursive && m_fbo && ((m_multisampling && m_secondaryFbo->isBound()) || m_fbo->isBound())) |
165 | qWarning(msg: "ShaderEffectSource: \'recursive\' must be set to true when rendering recursively." ); |
166 | #endif |
167 | QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); |
168 | if (!m_fbo && m_format == GL_RGBA) { |
169 | if (m_transparentTexture == 0) { |
170 | funcs->glGenTextures(n: 1, textures: &m_transparentTexture); |
171 | funcs->glBindTexture(GL_TEXTURE_2D, texture: m_transparentTexture); |
172 | const uint zero = 0; |
173 | funcs->glTexImage2D(GL_TEXTURE_2D, level: 0, GL_RGBA, width: 1, height: 1, border: 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels: &zero); |
174 | } else { |
175 | funcs->glBindTexture(GL_TEXTURE_2D, texture: m_transparentTexture); |
176 | } |
177 | } else { |
178 | funcs->glBindTexture(GL_TEXTURE_2D, texture: m_fbo ? m_fbo->texture() : 0); |
179 | updateBindOptions(); |
180 | } |
181 | } |
182 | |
183 | bool QSGOpenGLLayer::updateTexture() |
184 | { |
185 | bool doGrab = (m_live || m_grab) && m_dirtyTexture; |
186 | if (doGrab) |
187 | grab(); |
188 | if (m_grab) |
189 | emit scheduledUpdateCompleted(); |
190 | m_grab = false; |
191 | return doGrab; |
192 | } |
193 | |
194 | void QSGOpenGLLayer::setHasMipmaps(bool mipmap) |
195 | { |
196 | if (mipmap == m_mipmap) |
197 | return; |
198 | m_mipmap = mipmap; |
199 | if (m_mipmap && m_fbo && !m_fbo->format().mipmap()) |
200 | markDirtyTexture(); |
201 | } |
202 | |
203 | |
204 | void QSGOpenGLLayer::setItem(QSGNode *item) |
205 | { |
206 | if (item == m_item) |
207 | return; |
208 | m_item = item; |
209 | |
210 | if (m_live && !m_item) { |
211 | delete m_fbo; |
212 | delete m_secondaryFbo; |
213 | m_fbo = m_secondaryFbo = nullptr; |
214 | m_depthStencilBuffer.clear(); |
215 | } |
216 | |
217 | markDirtyTexture(); |
218 | } |
219 | |
220 | void QSGOpenGLLayer::setRect(const QRectF &rect) |
221 | { |
222 | if (rect == m_rect) |
223 | return; |
224 | m_rect = rect; |
225 | markDirtyTexture(); |
226 | } |
227 | |
228 | void QSGOpenGLLayer::setSize(const QSize &size) |
229 | { |
230 | if (size == m_size) |
231 | return; |
232 | m_size = size; |
233 | |
234 | if (m_live && m_size.isNull()) { |
235 | delete m_fbo; |
236 | delete m_secondaryFbo; |
237 | m_fbo = m_secondaryFbo = nullptr; |
238 | m_depthStencilBuffer.clear(); |
239 | } |
240 | |
241 | markDirtyTexture(); |
242 | } |
243 | |
244 | void QSGOpenGLLayer::setFormat(GLenum format) |
245 | { |
246 | if (format == m_format) |
247 | return; |
248 | m_format = format; |
249 | markDirtyTexture(); |
250 | } |
251 | |
252 | void QSGOpenGLLayer::setLive(bool live) |
253 | { |
254 | if (live == m_live) |
255 | return; |
256 | m_live = live; |
257 | |
258 | if (m_live && (!m_item || m_size.isNull())) { |
259 | delete m_fbo; |
260 | delete m_secondaryFbo; |
261 | m_fbo = m_secondaryFbo = nullptr; |
262 | m_depthStencilBuffer.clear(); |
263 | } |
264 | |
265 | markDirtyTexture(); |
266 | } |
267 | |
268 | void QSGOpenGLLayer::scheduleUpdate() |
269 | { |
270 | if (m_grab) |
271 | return; |
272 | m_grab = true; |
273 | if (m_dirtyTexture) |
274 | emit updateRequested(); |
275 | } |
276 | |
277 | void QSGOpenGLLayer::setRecursive(bool recursive) |
278 | { |
279 | m_recursive = recursive; |
280 | } |
281 | |
282 | void QSGOpenGLLayer::setMirrorHorizontal(bool mirror) |
283 | { |
284 | m_mirrorHorizontal = mirror; |
285 | } |
286 | |
287 | void QSGOpenGLLayer::setMirrorVertical(bool mirror) |
288 | { |
289 | m_mirrorVertical = mirror; |
290 | } |
291 | |
292 | void QSGOpenGLLayer::markDirtyTexture() |
293 | { |
294 | m_dirtyTexture = true; |
295 | if (m_live || m_grab) |
296 | emit updateRequested(); |
297 | } |
298 | |
299 | void QSGOpenGLLayer::grab() |
300 | { |
301 | if (!m_item || m_size.isNull()) { |
302 | delete m_fbo; |
303 | delete m_secondaryFbo; |
304 | m_fbo = m_secondaryFbo = nullptr; |
305 | m_depthStencilBuffer.clear(); |
306 | m_dirtyTexture = false; |
307 | return; |
308 | } |
309 | QSGNode *root = m_item; |
310 | while (root->firstChild() && root->type() != QSGNode::RootNodeType) |
311 | root = root->firstChild(); |
312 | if (root->type() != QSGNode::RootNodeType) |
313 | return; |
314 | |
315 | if (!m_renderer) { |
316 | m_renderer = m_context->createRenderer(); |
317 | connect(sender: m_renderer, SIGNAL(sceneGraphChanged()), receiver: this, SLOT(markDirtyTexture())); |
318 | } |
319 | m_renderer->setDevicePixelRatio(m_device_pixel_ratio); |
320 | m_renderer->setRootNode(static_cast<QSGRootNode *>(root)); |
321 | |
322 | QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); |
323 | bool deleteFboLater = false; |
324 | |
325 | int effectiveSamples = m_samples; |
326 | // By default m_samples is 0. Fall back to the context's setting in this case. |
327 | if (effectiveSamples == 0) |
328 | effectiveSamples = m_context->openglContext()->format().samples(); |
329 | |
330 | const bool needsNewFbo = !m_fbo || m_fbo->size() != m_size || m_fbo->format().internalTextureFormat() != m_format; |
331 | const bool mipmapGotEnabled = m_fbo && !m_fbo->format().mipmap() && m_mipmap; |
332 | const bool msaaGotEnabled = effectiveSamples > 1 && (!m_secondaryFbo || m_secondaryFbo->format().samples() != effectiveSamples); |
333 | const bool msaaGotDisabled = effectiveSamples <= 1 && m_secondaryFbo; |
334 | |
335 | if (needsNewFbo || mipmapGotEnabled || msaaGotEnabled || msaaGotDisabled) { |
336 | if (!m_multisamplingChecked) { |
337 | if (effectiveSamples <= 1) { |
338 | m_multisampling = false; |
339 | } else { |
340 | QOpenGLExtensions *e = static_cast<QOpenGLExtensions *>(funcs); |
341 | m_multisampling = e->hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferMultisample) |
342 | && e->hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferBlit); |
343 | } |
344 | m_multisamplingChecked = true; |
345 | } |
346 | if (m_multisampling) { |
347 | // Don't delete the FBO right away in case it is used recursively. |
348 | deleteFboLater = true; |
349 | delete m_secondaryFbo; |
350 | QOpenGLFramebufferObjectFormat format; |
351 | |
352 | format.setInternalTextureFormat(m_format); |
353 | format.setSamples(effectiveSamples); |
354 | m_secondaryFbo = new QOpenGLFramebufferObject(m_size, format); |
355 | m_depthStencilBuffer = m_context->depthStencilBufferForFbo(fbo: m_secondaryFbo); |
356 | } else { |
357 | QOpenGLFramebufferObjectFormat format; |
358 | format.setInternalTextureFormat(m_format); |
359 | format.setMipmap(m_mipmap); |
360 | if (m_recursive) { |
361 | deleteFboLater = true; |
362 | delete m_secondaryFbo; |
363 | m_secondaryFbo = new QOpenGLFramebufferObject(m_size, format); |
364 | funcs->glBindTexture(GL_TEXTURE_2D, texture: m_secondaryFbo->texture()); |
365 | updateBindOptions(force: true); |
366 | m_depthStencilBuffer = m_context->depthStencilBufferForFbo(fbo: m_secondaryFbo); |
367 | } else { |
368 | delete m_fbo; |
369 | delete m_secondaryFbo; |
370 | m_fbo = new QOpenGLFramebufferObject(m_size, format); |
371 | m_secondaryFbo = nullptr; |
372 | funcs->glBindTexture(GL_TEXTURE_2D, texture: m_fbo->texture()); |
373 | updateBindOptions(force: true); |
374 | m_depthStencilBuffer = m_context->depthStencilBufferForFbo(fbo: m_fbo); |
375 | } |
376 | } |
377 | } |
378 | |
379 | if (m_recursive && !m_secondaryFbo) { |
380 | // m_fbo already created, m_recursive was just set. |
381 | Q_ASSERT(m_fbo); |
382 | Q_ASSERT(!m_multisampling); |
383 | |
384 | m_secondaryFbo = new QOpenGLFramebufferObject(m_size, m_fbo->format()); |
385 | funcs->glBindTexture(GL_TEXTURE_2D, texture: m_secondaryFbo->texture()); |
386 | updateBindOptions(force: true); |
387 | } |
388 | |
389 | // Render texture. |
390 | root->markDirty(bits: QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update. |
391 | m_renderer->nodeChanged(node: root, state: QSGNode::DirtyForceUpdate); // Force render list update. |
392 | |
393 | #ifdef QSG_DEBUG_FBO_OVERLAY |
394 | if (qmlFboOverlay()) { |
395 | if (!m_debugOverlay) |
396 | m_debugOverlay = new QSGSimpleRectNode(); |
397 | m_debugOverlay->setRect(QRectF(0, 0, m_size.width(), m_size.height())); |
398 | m_debugOverlay->setColor(QColor(0xff, 0x00, 0x80, 0x40)); |
399 | root->appendChildNode(node: m_debugOverlay); |
400 | } |
401 | #endif |
402 | |
403 | m_dirtyTexture = false; |
404 | |
405 | m_renderer->setDeviceRect(m_size); |
406 | m_renderer->setViewportRect(m_size); |
407 | QRectF mirrored(m_mirrorHorizontal ? m_rect.right() : m_rect.left(), |
408 | m_mirrorVertical ? m_rect.bottom() : m_rect.top(), |
409 | m_mirrorHorizontal ? -m_rect.width() : m_rect.width(), |
410 | m_mirrorVertical ? -m_rect.height() : m_rect.height()); |
411 | m_renderer->setProjectionMatrixToRect(mirrored); |
412 | m_renderer->setClearColor(Qt::transparent); |
413 | |
414 | if (m_multisampling) { |
415 | m_renderer->renderScene(bindable: BindableFbo(m_secondaryFbo, m_depthStencilBuffer.data())); |
416 | |
417 | if (deleteFboLater) { |
418 | delete m_fbo; |
419 | QOpenGLFramebufferObjectFormat format; |
420 | format.setInternalTextureFormat(m_format); |
421 | format.setAttachment(QOpenGLFramebufferObject::NoAttachment); |
422 | format.setMipmap(m_mipmap); |
423 | format.setSamples(0); |
424 | m_fbo = new QOpenGLFramebufferObject(m_size, format); |
425 | funcs->glBindTexture(GL_TEXTURE_2D, texture: m_fbo->texture()); |
426 | updateBindOptions(force: true); |
427 | } |
428 | |
429 | QRect r(QPoint(), m_size); |
430 | QOpenGLFramebufferObject::blitFramebuffer(target: m_fbo, targetRect: r, source: m_secondaryFbo, sourceRect: r); |
431 | } else { |
432 | if (m_recursive) { |
433 | m_renderer->renderScene(bindable: BindableFbo(m_secondaryFbo, m_depthStencilBuffer.data())); |
434 | |
435 | if (deleteFboLater) { |
436 | delete m_fbo; |
437 | QOpenGLFramebufferObjectFormat format; |
438 | format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); |
439 | format.setInternalTextureFormat(m_format); |
440 | format.setMipmap(m_mipmap); |
441 | m_fbo = new QOpenGLFramebufferObject(m_size, format); |
442 | funcs->glBindTexture(GL_TEXTURE_2D, texture: m_fbo->texture()); |
443 | updateBindOptions(force: true); |
444 | } |
445 | qSwap(value1&: m_fbo, value2&: m_secondaryFbo); |
446 | } else { |
447 | m_renderer->renderScene(bindable: BindableFbo(m_fbo, m_depthStencilBuffer.data())); |
448 | } |
449 | } |
450 | |
451 | if (m_mipmap) { |
452 | funcs->glBindTexture(GL_TEXTURE_2D, texture: textureId()); |
453 | funcs->glGenerateMipmap(GL_TEXTURE_2D); |
454 | } |
455 | |
456 | root->markDirty(bits: QSGNode::DirtyForceUpdate); // Force matrix, clip, opacity and render list update. |
457 | |
458 | #ifdef QSG_DEBUG_FBO_OVERLAY |
459 | if (qmlFboOverlay()) |
460 | root->removeChildNode(node: m_debugOverlay); |
461 | #endif |
462 | if (m_recursive) |
463 | markDirtyTexture(); // Continuously update if 'live' and 'recursive'. |
464 | } |
465 | |
466 | QImage QSGOpenGLLayer::toImage() const |
467 | { |
468 | if (m_fbo) |
469 | return m_fbo->toImage(); |
470 | |
471 | return QImage(); |
472 | } |
473 | |
474 | QRectF QSGOpenGLLayer::normalizedTextureSubRect() const |
475 | { |
476 | return QRectF(m_mirrorHorizontal ? 1 : 0, |
477 | m_mirrorVertical ? 0 : 1, |
478 | m_mirrorHorizontal ? -1 : 1, |
479 | m_mirrorVertical ? 1 : -1); |
480 | } |
481 | |
482 | #include "moc_qsgopengllayer_p.cpp" |
483 | |