| 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 | |