| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the plugins of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QtGui/QOpenGLContext> |
| #include <QtGui/QOpenGLFramebufferObject> |
| #include <QtGui/QWindow> |
| #include <qpa/qplatformbackingstore.h> |
| |
| #include "qopenglcompositor_p.h" |
| |
| QT_BEGIN_NAMESPACE |
| |
| /*! |
| \class QOpenGLCompositor |
| \brief A generic OpenGL-based compositor |
| \since 5.4 |
| \internal |
| \ingroup qpa |
| |
| This class provides a lightweight compositor that maintains the |
| basic stacking order of windows and composites them by drawing |
| textured quads via OpenGL. |
| |
| It it meant to be used by platform plugins that run without a |
| windowing system. |
| |
| It is up to the platform plugin to manage the lifetime of the |
| compositor (instance(), destroy()), set the correct destination |
| context and window as early as possible (setTarget()), |
| register the composited windows as they are shown, activated, |
| raised and lowered (addWindow(), moveToTop(), etc.), and to |
| schedule repaints (update()). |
| |
| \note To get support for QWidget-based windows, just use |
| QOpenGLCompositorBackingStore. It will automatically create |
| textures from the raster-rendered content and trigger the |
| necessary repaints. |
| */ |
| |
| static QOpenGLCompositor *compositor = 0; |
| |
| QOpenGLCompositor::QOpenGLCompositor() |
| : m_context(0), |
| m_targetWindow(0), |
| m_rotation(0) |
| { |
| Q_ASSERT(!compositor); |
| m_updateTimer.setSingleShot(true); |
| m_updateTimer.setInterval(0); |
| connect(&m_updateTimer, SIGNAL(timeout()), SLOT(handleRenderAllRequest())); |
| } |
| |
| QOpenGLCompositor::~QOpenGLCompositor() |
| { |
| Q_ASSERT(compositor == this); |
| m_blitter.destroy(); |
| compositor = 0; |
| } |
| |
| void QOpenGLCompositor::setTarget(QOpenGLContext *context, QWindow *targetWindow, |
| const QRect &nativeTargetGeometry) |
| { |
| m_context = context; |
| m_targetWindow = targetWindow; |
| m_nativeTargetGeometry = nativeTargetGeometry; |
| } |
| |
| void QOpenGLCompositor::setRotation(int degrees) |
| { |
| m_rotation = degrees; |
| m_rotationMatrix.setToIdentity(); |
| m_rotationMatrix.rotate(degrees, 0, 0, 1); |
| } |
| |
| void QOpenGLCompositor::update() |
| { |
| if (!m_updateTimer.isActive()) |
| m_updateTimer.start(); |
| } |
| |
| QImage QOpenGLCompositor::grab() |
| { |
| Q_ASSERT(m_context && m_targetWindow); |
| m_context->makeCurrent(m_targetWindow); |
| QScopedPointer<QOpenGLFramebufferObject> fbo(new QOpenGLFramebufferObject(m_nativeTargetGeometry.size())); |
| renderAll(fbo.data()); |
| return fbo->toImage(); |
| } |
| |
| void QOpenGLCompositor::handleRenderAllRequest() |
| { |
| Q_ASSERT(m_context && m_targetWindow); |
| m_context->makeCurrent(m_targetWindow); |
| renderAll(0); |
| } |
| |
| void QOpenGLCompositor::renderAll(QOpenGLFramebufferObject *fbo) |
| { |
| if (fbo) |
| fbo->bind(); |
| |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); |
| glViewport(0, 0, m_nativeTargetGeometry.width(), m_nativeTargetGeometry.height()); |
| |
| if (!m_blitter.isCreated()) |
| m_blitter.create(); |
| |
| m_blitter.bind(); |
| |
| for (int i = 0; i < m_windows.size(); ++i) |
| m_windows.at(i)->beginCompositing(); |
| |
| for (int i = 0; i < m_windows.size(); ++i) |
| render(m_windows.at(i)); |
| |
| m_blitter.release(); |
| if (!fbo) |
| m_context->swapBuffers(m_targetWindow); |
| else |
| fbo->release(); |
| |
| for (int i = 0; i < m_windows.size(); ++i) |
| m_windows.at(i)->endCompositing(); |
| } |
| |
| struct BlendStateBinder |
| { |
| BlendStateBinder() : m_blend(false) { |
| glDisable(GL_BLEND); |
| } |
| void set(bool blend) { |
| if (blend != m_blend) { |
| if (blend) { |
| glEnable(GL_BLEND); |
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| } else { |
| glDisable(GL_BLEND); |
| } |
| m_blend = blend; |
| } |
| } |
| ~BlendStateBinder() { |
| if (m_blend) |
| glDisable(GL_BLEND); |
| } |
| bool m_blend; |
| }; |
| |
| static inline QRect toBottomLeftRect(const QRect &topLeftRect, int windowHeight) |
| { |
| return QRect(topLeftRect.x(), windowHeight - topLeftRect.bottomRight().y() - 1, |
| topLeftRect.width(), topLeftRect.height()); |
| } |
| |
| static void clippedBlit(const QPlatformTextureList *textures, int idx, const QRect &sourceWindowRect, |
| const QRect &targetWindowRect, |
| QOpenGLTextureBlitter *blitter, QMatrix4x4 *rotationMatrix) |
| { |
| const QRect clipRect = textures->clipRect(idx); |
| if (clipRect.isEmpty()) |
| return; |
| |
| const QRect rectInWindow = textures->geometry(idx).translated(sourceWindowRect.topLeft()); |
| const QRect clippedRectInWindow = rectInWindow & clipRect.translated(rectInWindow.topLeft()); |
| const QRect srcRect = toBottomLeftRect(clipRect, rectInWindow.height()); |
| |
| QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(clippedRectInWindow, targetWindowRect); |
| if (rotationMatrix) |
| target = *rotationMatrix * target; |
| |
| const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(srcRect, rectInWindow.size(), |
| QOpenGLTextureBlitter::OriginBottomLeft); |
| |
| blitter->blit(textures->textureId(idx), target, source); |
| } |
| |
| void QOpenGLCompositor::render(QOpenGLCompositorWindow *window) |
| { |
| const QPlatformTextureList *textures = window->textures(); |
| if (!textures) |
| return; |
| |
| const QRect targetWindowRect(QPoint(0, 0), m_targetWindow->geometry().size()); |
| float currentOpacity = 1.0f; |
| BlendStateBinder blend; |
| const QRect sourceWindowRect = window->sourceWindow()->geometry(); |
| for (int i = 0; i < textures->count(); ++i) { |
| uint textureId = textures->textureId(i); |
| const float opacity = window->sourceWindow()->opacity(); |
| if (opacity != currentOpacity) { |
| currentOpacity = opacity; |
| m_blitter.setOpacity(currentOpacity); |
| } |
| |
| if (textures->count() > 1 && i == textures->count() - 1) { |
| // Backingstore for a widget with QOpenGLWidget subwidgets |
| blend.set(true); |
| QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect); |
| if (m_rotation) |
| target = m_rotationMatrix * target; |
| m_blitter.blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft); |
| } else if (textures->count() == 1) { |
| // A regular QWidget window |
| const bool translucent = window->sourceWindow()->requestedFormat().alphaBufferSize() > 0; |
| blend.set(translucent); |
| QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect); |
| if (m_rotation) |
| target = m_rotationMatrix * target; |
| m_blitter.blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft); |
| } else if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) { |
| // Texture from an FBO belonging to a QOpenGLWidget or QQuickWidget |
| blend.set(false); |
| clippedBlit(textures, i, sourceWindowRect, targetWindowRect, &m_blitter, m_rotation ? &m_rotationMatrix : nullptr); |
| } |
| } |
| |
| for (int i = 0; i < textures->count(); ++i) { |
| if (textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) { |
| blend.set(true); |
| clippedBlit(textures, i, sourceWindowRect, targetWindowRect, &m_blitter, m_rotation ? &m_rotationMatrix : nullptr); |
| } |
| } |
| |
| m_blitter.setOpacity(1.0f); |
| } |
| |
| QOpenGLCompositor *QOpenGLCompositor::instance() |
| { |
| if (!compositor) |
| compositor = new QOpenGLCompositor; |
| return compositor; |
| } |
| |
| void QOpenGLCompositor::destroy() |
| { |
| delete compositor; |
| compositor = 0; |
| } |
| |
| void QOpenGLCompositor::addWindow(QOpenGLCompositorWindow *window) |
| { |
| if (!m_windows.contains(window)) { |
| m_windows.append(window); |
| emit topWindowChanged(window); |
| } |
| } |
| |
| void QOpenGLCompositor::removeWindow(QOpenGLCompositorWindow *window) |
| { |
| m_windows.removeOne(window); |
| if (!m_windows.isEmpty()) |
| emit topWindowChanged(m_windows.last()); |
| } |
| |
| void QOpenGLCompositor::moveToTop(QOpenGLCompositorWindow *window) |
| { |
| m_windows.removeOne(window); |
| m_windows.append(window); |
| emit topWindowChanged(window); |
| } |
| |
| void QOpenGLCompositor::changeWindowIndex(QOpenGLCompositorWindow *window, int newIdx) |
| { |
| int idx = m_windows.indexOf(window); |
| if (idx != -1 && idx != newIdx) { |
| m_windows.move(idx, newIdx); |
| if (newIdx == m_windows.size() - 1) |
| emit topWindowChanged(m_windows.last()); |
| } |
| } |
| |
| QT_END_NAMESPACE |