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 <QtGui/QOpenGLContext> |
41 | #include <QtGui/QOpenGLFramebufferObject> |
42 | #include <QtGui/QWindow> |
43 | #include <qpa/qplatformbackingstore.h> |
44 | |
45 | #include "qopenglcompositor_p.h" |
46 | |
47 | QT_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 | |
76 | static QOpenGLCompositor *compositor = 0; |
77 | |
78 | QOpenGLCompositor::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(asender: &m_updateTimer, SIGNAL(timeout()), SLOT(handleRenderAllRequest())); |
87 | } |
88 | |
89 | QOpenGLCompositor::~QOpenGLCompositor() |
90 | { |
91 | Q_ASSERT(compositor == this); |
92 | m_blitter.destroy(); |
93 | compositor = 0; |
94 | } |
95 | |
96 | void 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 | |
104 | void QOpenGLCompositor::setRotation(int degrees) |
105 | { |
106 | m_rotation = degrees; |
107 | m_rotationMatrix.setToIdentity(); |
108 | m_rotationMatrix.rotate(angle: degrees, x: 0, y: 0, z: 1); |
109 | } |
110 | |
111 | void QOpenGLCompositor::update() |
112 | { |
113 | if (!m_updateTimer.isActive()) |
114 | m_updateTimer.start(); |
115 | } |
116 | |
117 | QImage QOpenGLCompositor::grab() |
118 | { |
119 | Q_ASSERT(m_context && m_targetWindow); |
120 | m_context->makeCurrent(surface: m_targetWindow); |
121 | QScopedPointer<QOpenGLFramebufferObject> fbo(new QOpenGLFramebufferObject(m_nativeTargetGeometry.size())); |
122 | renderAll(fbo: fbo.data()); |
123 | return fbo->toImage(); |
124 | } |
125 | |
126 | void QOpenGLCompositor::handleRenderAllRequest() |
127 | { |
128 | Q_ASSERT(m_context && m_targetWindow); |
129 | m_context->makeCurrent(surface: m_targetWindow); |
130 | renderAll(fbo: 0); |
131 | } |
132 | |
133 | void 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(x: 0, y: 0, width: m_nativeTargetGeometry.width(), height: 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(window: m_windows.at(i)); |
151 | |
152 | m_blitter.release(); |
153 | if (!fbo) |
154 | m_context->swapBuffers(surface: 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 | |
162 | struct 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 | |
185 | static 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 | |
191 | static 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(index: idx); |
196 | if (clipRect.isEmpty()) |
197 | return; |
198 | |
199 | const QRect rectInWindow = textures->geometry(index: idx).translated(p: sourceWindowRect.topLeft()); |
200 | const QRect clippedRectInWindow = rectInWindow & clipRect.translated(p: rectInWindow.topLeft()); |
201 | const QRect srcRect = toBottomLeftRect(topLeftRect: clipRect, windowHeight: rectInWindow.height()); |
202 | |
203 | QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(target: clippedRectInWindow, viewport: targetWindowRect); |
204 | if (rotationMatrix) |
205 | target = *rotationMatrix * target; |
206 | |
207 | const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(subTexture: srcRect, textureSize: rectInWindow.size(), |
208 | origin: QOpenGLTextureBlitter::OriginBottomLeft); |
209 | |
210 | blitter->blit(texture: textures->textureId(index: idx), targetTransform: target, sourceTransform: source); |
211 | } |
212 | |
213 | void 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(index: 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(target: textures->geometry(index: i), viewport: targetWindowRect); |
235 | if (m_rotation) |
236 | target = m_rotationMatrix * target; |
237 | m_blitter.blit(texture: textureId, targetTransform: target, sourceOrigin: 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(target: textures->geometry(index: i), viewport: targetWindowRect); |
243 | if (m_rotation) |
244 | target = m_rotationMatrix * target; |
245 | m_blitter.blit(texture: textureId, targetTransform: target, sourceOrigin: QOpenGLTextureBlitter::OriginTopLeft); |
246 | } else if (!textures->flags(index: i).testFlag(flag: QPlatformTextureList::StacksOnTop)) { |
247 | // Texture from an FBO belonging to a QOpenGLWidget or QQuickWidget |
248 | blend.set(false); |
249 | clippedBlit(textures, idx: i, sourceWindowRect, targetWindowRect, blitter: &m_blitter, rotationMatrix: m_rotation ? &m_rotationMatrix : nullptr); |
250 | } |
251 | } |
252 | |
253 | for (int i = 0; i < textures->count(); ++i) { |
254 | if (textures->flags(index: i).testFlag(flag: QPlatformTextureList::StacksOnTop)) { |
255 | blend.set(true); |
256 | clippedBlit(textures, idx: i, sourceWindowRect, targetWindowRect, blitter: &m_blitter, rotationMatrix: m_rotation ? &m_rotationMatrix : nullptr); |
257 | } |
258 | } |
259 | |
260 | m_blitter.setOpacity(1.0f); |
261 | } |
262 | |
263 | QOpenGLCompositor *QOpenGLCompositor::instance() |
264 | { |
265 | if (!compositor) |
266 | compositor = new QOpenGLCompositor; |
267 | return compositor; |
268 | } |
269 | |
270 | void QOpenGLCompositor::destroy() |
271 | { |
272 | delete compositor; |
273 | compositor = 0; |
274 | } |
275 | |
276 | void QOpenGLCompositor::addWindow(QOpenGLCompositorWindow *window) |
277 | { |
278 | if (!m_windows.contains(t: window)) { |
279 | m_windows.append(t: window); |
280 | emit topWindowChanged(window); |
281 | } |
282 | } |
283 | |
284 | void QOpenGLCompositor::removeWindow(QOpenGLCompositorWindow *window) |
285 | { |
286 | m_windows.removeOne(t: window); |
287 | if (!m_windows.isEmpty()) |
288 | emit topWindowChanged(window: m_windows.last()); |
289 | } |
290 | |
291 | void QOpenGLCompositor::moveToTop(QOpenGLCompositorWindow *window) |
292 | { |
293 | m_windows.removeOne(t: window); |
294 | m_windows.append(t: window); |
295 | emit topWindowChanged(window); |
296 | } |
297 | |
298 | void QOpenGLCompositor::changeWindowIndex(QOpenGLCompositorWindow *window, int newIdx) |
299 | { |
300 | int idx = m_windows.indexOf(t: window); |
301 | if (idx != -1 && idx != newIdx) { |
302 | m_windows.move(from: idx, to: newIdx); |
303 | if (newIdx == m_windows.size() - 1) |
304 | emit topWindowChanged(window: m_windows.last()); |
305 | } |
306 | } |
307 | |
308 | QT_END_NAMESPACE |
309 | |