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 plugins 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 <QtOpenGL/QOpenGLFramebufferObject>
41#include <QtGui/QOpenGLContext>
42#include <QtGui/QWindow>
43#include <qpa/qplatformbackingstore.h>
44
45#include "qopenglcompositor_p.h"
46
47QT_BEGIN_NAMESPACE
48
49/*!
50 \class QOpenGLCompositor
51 \brief A generic OpenGL-based compositor
52 \since 5.4
53 \internal
54 \ingroup qpa
55
56 This class provides a lightweight compositor that maintains the
57 basic stacking order of windows and composites them by drawing
58 textured quads via OpenGL.
59
60 It it meant to be used by platform plugins that run without a
61 windowing system.
62
63 It is up to the platform plugin to manage the lifetime of the
64 compositor (instance(), destroy()), set the correct destination
65 context and window as early as possible (setTarget()),
66 register the composited windows as they are shown, activated,
67 raised and lowered (addWindow(), moveToTop(), etc.), and to
68 schedule repaints (update()).
69
70 \note To get support for QWidget-based windows, just use
71 QOpenGLCompositorBackingStore. It will automatically create
72 textures from the raster-rendered content and trigger the
73 necessary repaints.
74 */
75
76static QOpenGLCompositor *compositor = 0;
77
78QOpenGLCompositor::QOpenGLCompositor()
79 : m_context(0),
80 m_targetWindow(0),
81 m_rotation(0)
82{
83 Q_ASSERT(!compositor);
84 m_updateTimer.setSingleShot(true);
85 m_updateTimer.setInterval(0);
86 connect(&m_updateTimer, SIGNAL(timeout()), SLOT(handleRenderAllRequest()));
87}
88
89QOpenGLCompositor::~QOpenGLCompositor()
90{
91 Q_ASSERT(compositor == this);
92 m_blitter.destroy();
93 compositor = 0;
94}
95
96void QOpenGLCompositor::setTarget(QOpenGLContext *context, QWindow *targetWindow,
97 const QRect &nativeTargetGeometry)
98{
99 m_context = context;
100 m_targetWindow = targetWindow;
101 m_nativeTargetGeometry = nativeTargetGeometry;
102}
103
104void QOpenGLCompositor::setRotation(int degrees)
105{
106 m_rotation = degrees;
107 m_rotationMatrix.setToIdentity();
108 m_rotationMatrix.rotate(degrees, 0, 0, 1);
109}
110
111void QOpenGLCompositor::update()
112{
113 if (!m_updateTimer.isActive())
114 m_updateTimer.start();
115}
116
117QImage QOpenGLCompositor::grab()
118{
119 Q_ASSERT(m_context && m_targetWindow);
120 m_context->makeCurrent(m_targetWindow);
121 QScopedPointer<QOpenGLFramebufferObject> fbo(new QOpenGLFramebufferObject(m_nativeTargetGeometry.size()));
122 renderAll(fbo.data());
123 return fbo->toImage();
124}
125
126void QOpenGLCompositor::handleRenderAllRequest()
127{
128 Q_ASSERT(m_context && m_targetWindow);
129 m_context->makeCurrent(m_targetWindow);
130 renderAll(0);
131}
132
133void QOpenGLCompositor::renderAll(QOpenGLFramebufferObject *fbo)
134{
135 if (fbo)
136 fbo->bind();
137
138 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
139 glViewport(0, 0, m_nativeTargetGeometry.width(), m_nativeTargetGeometry.height());
140
141 if (!m_blitter.isCreated())
142 m_blitter.create();
143
144 m_blitter.bind();
145
146 for (int i = 0; i < m_windows.size(); ++i)
147 m_windows.at(i)->beginCompositing();
148
149 for (int i = 0; i < m_windows.size(); ++i)
150 render(m_windows.at(i));
151
152 m_blitter.release();
153 if (!fbo)
154 m_context->swapBuffers(m_targetWindow);
155 else
156 fbo->release();
157
158 for (int i = 0; i < m_windows.size(); ++i)
159 m_windows.at(i)->endCompositing();
160}
161
162struct BlendStateBinder
163{
164 BlendStateBinder() : m_blend(false) {
165 glDisable(GL_BLEND);
166 }
167 void set(bool blend) {
168 if (blend != m_blend) {
169 if (blend) {
170 glEnable(GL_BLEND);
171 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
172 } else {
173 glDisable(GL_BLEND);
174 }
175 m_blend = blend;
176 }
177 }
178 ~BlendStateBinder() {
179 if (m_blend)
180 glDisable(GL_BLEND);
181 }
182 bool m_blend;
183};
184
185static inline QRect toBottomLeftRect(const QRect &topLeftRect, int windowHeight)
186{
187 return QRect(topLeftRect.x(), windowHeight - topLeftRect.bottomRight().y() - 1,
188 topLeftRect.width(), topLeftRect.height());
189}
190
191static void clippedBlit(const QPlatformTextureList *textures, int idx, const QRect &sourceWindowRect,
192 const QRect &targetWindowRect,
193 QOpenGLTextureBlitter *blitter, QMatrix4x4 *rotationMatrix)
194{
195 const QRect clipRect = textures->clipRect(idx);
196 if (clipRect.isEmpty())
197 return;
198
199 const QRect rectInWindow = textures->geometry(idx).translated(sourceWindowRect.topLeft());
200 const QRect clippedRectInWindow = rectInWindow & clipRect.translated(rectInWindow.topLeft());
201 const QRect srcRect = toBottomLeftRect(clipRect, rectInWindow.height());
202
203 QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(clippedRectInWindow, targetWindowRect);
204 if (rotationMatrix)
205 target = *rotationMatrix * target;
206
207 const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(srcRect, rectInWindow.size(),
208 QOpenGLTextureBlitter::OriginBottomLeft);
209
210 blitter->blit(textures->textureId(idx), target, source);
211}
212
213void QOpenGLCompositor::render(QOpenGLCompositorWindow *window)
214{
215 const QPlatformTextureList *textures = window->textures();
216 if (!textures)
217 return;
218
219 const QRect targetWindowRect(QPoint(0, 0), m_targetWindow->geometry().size());
220 float currentOpacity = 1.0f;
221 BlendStateBinder blend;
222 const QRect sourceWindowRect = window->sourceWindow()->geometry();
223 for (int i = 0; i < textures->count(); ++i) {
224 uint textureId = textures->textureId(i);
225 const float opacity = window->sourceWindow()->opacity();
226 if (opacity != currentOpacity) {
227 currentOpacity = opacity;
228 m_blitter.setOpacity(currentOpacity);
229 }
230
231 if (textures->count() > 1 && i == textures->count() - 1) {
232 // Backingstore for a widget with QOpenGLWidget subwidgets
233 blend.set(true);
234 QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect);
235 if (m_rotation)
236 target = m_rotationMatrix * target;
237 m_blitter.blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft);
238 } else if (textures->count() == 1) {
239 // A regular QWidget window
240 const bool translucent = window->sourceWindow()->requestedFormat().alphaBufferSize() > 0;
241 blend.set(translucent);
242 QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect);
243 if (m_rotation)
244 target = m_rotationMatrix * target;
245 m_blitter.blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft);
246 } else if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) {
247 // Texture from an FBO belonging to a QOpenGLWidget or QQuickWidget
248 blend.set(false);
249 clippedBlit(textures, i, sourceWindowRect, targetWindowRect, &m_blitter, m_rotation ? &m_rotationMatrix : nullptr);
250 }
251 }
252
253 for (int i = 0; i < textures->count(); ++i) {
254 if (textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) {
255 blend.set(true);
256 clippedBlit(textures, i, sourceWindowRect, targetWindowRect, &m_blitter, m_rotation ? &m_rotationMatrix : nullptr);
257 }
258 }
259
260 m_blitter.setOpacity(1.0f);
261}
262
263QOpenGLCompositor *QOpenGLCompositor::instance()
264{
265 if (!compositor)
266 compositor = new QOpenGLCompositor;
267 return compositor;
268}
269
270void QOpenGLCompositor::destroy()
271{
272 delete compositor;
273 compositor = 0;
274}
275
276void QOpenGLCompositor::addWindow(QOpenGLCompositorWindow *window)
277{
278 if (!m_windows.contains(window)) {
279 m_windows.append(window);
280 emit topWindowChanged(window);
281 }
282}
283
284void QOpenGLCompositor::removeWindow(QOpenGLCompositorWindow *window)
285{
286 m_windows.removeOne(window);
287 if (!m_windows.isEmpty())
288 emit topWindowChanged(m_windows.last());
289}
290
291void QOpenGLCompositor::moveToTop(QOpenGLCompositorWindow *window)
292{
293 m_windows.removeOne(window);
294 m_windows.append(window);
295 emit topWindowChanged(window);
296}
297
298void QOpenGLCompositor::changeWindowIndex(QOpenGLCompositorWindow *window, int newIdx)
299{
300 int idx = m_windows.indexOf(window);
301 if (idx != -1 && idx != newIdx) {
302 m_windows.move(idx, newIdx);
303 if (newIdx == m_windows.size() - 1)
304 emit topWindowChanged(m_windows.last());
305 }
306}
307
308QT_END_NAMESPACE
309