1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtOpenGL/QOpenGLFramebufferObject>
5#include <QtGui/QOpenGLContext>
6#include <QtGui/QWindow>
7#include <rhi/qrhi.h>
8#include <qpa/qplatformbackingstore.h>
9
10#include "qopenglcompositor_p.h"
11
12QT_BEGIN_NAMESPACE
13
14/*!
15 \class QOpenGLCompositor
16 \brief A generic OpenGL-based compositor
17 \since 5.4
18 \internal
19 \ingroup qpa
20
21 This class provides a lightweight compositor that maintains the
22 basic stacking order of windows and composites them by drawing
23 textured quads via OpenGL.
24
25 It it meant to be used by platform plugins that run without a
26 windowing system.
27
28 It is up to the platform plugin to manage the lifetime of the
29 compositor (instance(), destroy()), set the correct destination
30 context and window as early as possible (setTarget()),
31 register the composited windows as they are shown, activated,
32 raised and lowered (addWindow(), moveToTop(), etc.), and to
33 schedule repaints (update()).
34
35 \note To get support for QWidget-based windows, just use
36 QOpenGLCompositorBackingStore. It will automatically create
37 textures from the raster-rendered content and trigger the
38 necessary repaints.
39 */
40
41static QOpenGLCompositor *compositor = 0;
42
43QOpenGLCompositor::QOpenGLCompositor()
44 : m_context(0),
45 m_targetWindow(0),
46 m_rotation(0)
47{
48 Q_ASSERT(!compositor);
49 m_updateTimer.setSingleShot(true);
50 m_updateTimer.setInterval(0);
51 connect(asender: &m_updateTimer, SIGNAL(timeout()), SLOT(handleRenderAllRequest()));
52}
53
54QOpenGLCompositor::~QOpenGLCompositor()
55{
56 Q_ASSERT(compositor == this);
57 m_blitter.destroy();
58 compositor = 0;
59}
60
61void QOpenGLCompositor::setTargetWindow(QWindow *targetWindow, const QRect &nativeTargetGeometry)
62{
63 m_targetWindow = targetWindow;
64 m_nativeTargetGeometry = nativeTargetGeometry;
65}
66
67void QOpenGLCompositor::setTargetContext(QOpenGLContext *context)
68{
69 m_context = context;
70}
71
72void QOpenGLCompositor::setRotation(int degrees)
73{
74 m_rotation = degrees;
75 m_rotationMatrix.setToIdentity();
76 m_rotationMatrix.rotate(angle: degrees, x: 0, y: 0, z: 1);
77}
78
79void QOpenGLCompositor::update()
80{
81 if (!m_updateTimer.isActive())
82 m_updateTimer.start();
83}
84
85QImage QOpenGLCompositor::grab()
86{
87 Q_ASSERT(m_context && m_targetWindow);
88 m_context->makeCurrent(surface: m_targetWindow);
89 QScopedPointer<QOpenGLFramebufferObject> fbo(new QOpenGLFramebufferObject(m_nativeTargetGeometry.size()));
90 renderAll(fbo: fbo.data());
91 return fbo->toImage();
92}
93
94void QOpenGLCompositor::handleRenderAllRequest()
95{
96 Q_ASSERT(m_context && m_targetWindow);
97 m_context->makeCurrent(surface: m_targetWindow);
98 renderAll(fbo: 0);
99}
100
101void QOpenGLCompositor::renderAll(QOpenGLFramebufferObject *fbo)
102{
103 if (fbo)
104 fbo->bind();
105
106 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
107 glViewport(x: 0, y: 0, width: m_nativeTargetGeometry.width(), height: m_nativeTargetGeometry.height());
108
109 if (!m_blitter.isCreated())
110 m_blitter.create();
111
112 m_blitter.bind();
113
114 for (int i = 0; i < m_windows.size(); ++i)
115 m_windows.at(i)->beginCompositing();
116
117 for (int i = 0; i < m_windows.size(); ++i)
118 render(window: m_windows.at(i));
119
120 m_blitter.release();
121 if (!fbo)
122 m_context->swapBuffers(surface: m_targetWindow);
123 else
124 fbo->release();
125
126 for (int i = 0; i < m_windows.size(); ++i)
127 m_windows.at(i)->endCompositing();
128}
129
130struct BlendStateBinder
131{
132 BlendStateBinder() : m_blend(false) {
133 glDisable(GL_BLEND);
134 }
135 void set(bool blend) {
136 if (blend != m_blend) {
137 if (blend) {
138 glEnable(GL_BLEND);
139 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
140 } else {
141 glDisable(GL_BLEND);
142 }
143 m_blend = blend;
144 }
145 }
146 ~BlendStateBinder() {
147 if (m_blend)
148 glDisable(GL_BLEND);
149 }
150 bool m_blend;
151};
152
153static inline QRect toBottomLeftRect(const QRect &topLeftRect, int windowHeight)
154{
155 return QRect(topLeftRect.x(), windowHeight - topLeftRect.bottomRight().y() - 1,
156 topLeftRect.width(), topLeftRect.height());
157}
158
159static void clippedBlit(const QPlatformTextureList *textures, int idx, const QRect &sourceWindowRect,
160 const QRect &targetWindowRect,
161 QOpenGLTextureBlitter *blitter, QMatrix4x4 *rotationMatrix)
162{
163 const QRect clipRect = textures->clipRect(index: idx);
164 if (clipRect.isEmpty())
165 return;
166
167 const QRect rectInWindow = textures->geometry(index: idx).translated(p: sourceWindowRect.topLeft());
168 const QRect clippedRectInWindow = rectInWindow & clipRect.translated(p: rectInWindow.topLeft());
169 const QRect srcRect = toBottomLeftRect(topLeftRect: clipRect, windowHeight: rectInWindow.height());
170
171 QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(target: clippedRectInWindow, viewport: targetWindowRect);
172 if (rotationMatrix)
173 target = *rotationMatrix * target;
174
175 const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(subTexture: srcRect, textureSize: rectInWindow.size(),
176 origin: QOpenGLTextureBlitter::OriginBottomLeft);
177
178 const uint textureId = textures->texture(index: idx)->nativeTexture().object;
179 blitter->blit(texture: textureId, targetTransform: target, sourceTransform: source);
180}
181
182void QOpenGLCompositor::render(QOpenGLCompositorWindow *window)
183{
184 const QPlatformTextureList *textures = window->textures();
185 if (!textures)
186 return;
187
188 const QRect targetWindowRect(QPoint(0, 0), m_targetWindow->geometry().size());
189 float currentOpacity = 1.0f;
190 BlendStateBinder blend;
191 const QRect sourceWindowRect = window->sourceWindow()->geometry();
192 for (int i = 0; i < textures->count(); ++i) {
193 const uint textureId = textures->texture(index: i)->nativeTexture().object;
194 const float opacity = window->sourceWindow()->opacity();
195 if (opacity != currentOpacity) {
196 currentOpacity = opacity;
197 m_blitter.setOpacity(currentOpacity);
198 }
199
200 if (textures->count() > 1 && i == textures->count() - 1) {
201 // Backingstore for a widget with QOpenGLWidget subwidgets
202 blend.set(true);
203 QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(target: textures->geometry(index: i), viewport: targetWindowRect);
204 if (m_rotation)
205 target = m_rotationMatrix * target;
206 m_blitter.blit(texture: textureId, targetTransform: target, sourceOrigin: QOpenGLTextureBlitter::OriginTopLeft);
207 } else if (textures->count() == 1) {
208 // A regular QWidget window
209 const bool translucent = window->sourceWindow()->requestedFormat().alphaBufferSize() > 0;
210 blend.set(translucent);
211 QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(target: textures->geometry(index: i), viewport: targetWindowRect);
212 if (m_rotation)
213 target = m_rotationMatrix * target;
214 m_blitter.blit(texture: textureId, targetTransform: target, sourceOrigin: QOpenGLTextureBlitter::OriginTopLeft);
215 } else if (!textures->flags(index: i).testFlag(flag: QPlatformTextureList::StacksOnTop)) {
216 // Texture from an FBO belonging to a QOpenGLWidget or QQuickWidget
217 blend.set(false);
218 clippedBlit(textures, idx: i, sourceWindowRect, targetWindowRect, blitter: &m_blitter, rotationMatrix: m_rotation ? &m_rotationMatrix : nullptr);
219 }
220 }
221
222 for (int i = 0; i < textures->count(); ++i) {
223 if (textures->flags(index: i).testFlag(flag: QPlatformTextureList::StacksOnTop)) {
224 blend.set(true);
225 clippedBlit(textures, idx: i, sourceWindowRect, targetWindowRect, blitter: &m_blitter, rotationMatrix: m_rotation ? &m_rotationMatrix : nullptr);
226 }
227 }
228
229 m_blitter.setOpacity(1.0f);
230}
231
232QOpenGLCompositor *QOpenGLCompositor::instance()
233{
234 if (!compositor)
235 compositor = new QOpenGLCompositor;
236 return compositor;
237}
238
239void QOpenGLCompositor::destroy()
240{
241 delete compositor;
242 compositor = 0;
243}
244
245void QOpenGLCompositor::addWindow(QOpenGLCompositorWindow *window)
246{
247 if (!m_windows.contains(t: window)) {
248 m_windows.append(t: window);
249 ensureCorrectZOrder();
250 if (window == m_windows.constLast())
251 emit topWindowChanged(window);
252 }
253}
254
255void QOpenGLCompositor::removeWindow(QOpenGLCompositorWindow *window)
256{
257 m_windows.removeOne(t: window);
258 if (!m_windows.isEmpty())
259 emit topWindowChanged(window: m_windows.last());
260}
261
262void QOpenGLCompositor::moveToTop(QOpenGLCompositorWindow *window)
263{
264 if (!m_windows.isEmpty() && window == m_windows.constLast()) {
265 // Already on top
266 return;
267 }
268
269 m_windows.removeOne(t: window);
270 m_windows.append(t: window);
271 ensureCorrectZOrder();
272
273 if (window == m_windows.constLast())
274 emit topWindowChanged(window);
275}
276
277void QOpenGLCompositor::changeWindowIndex(QOpenGLCompositorWindow *window, int newIdx)
278{
279 int idx = m_windows.indexOf(t: window);
280 if (idx != -1 && idx != newIdx) {
281 m_windows.move(from: idx, to: newIdx);
282 ensureCorrectZOrder();
283 if (window == m_windows.constLast())
284 emit topWindowChanged(window: m_windows.last());
285 }
286}
287
288void QOpenGLCompositor::ensureCorrectZOrder()
289{
290 const auto originalOrder = m_windows;
291
292 std::sort(first: m_windows.begin(), last: m_windows.end(),
293 comp: [this, &originalOrder](QOpenGLCompositorWindow *cw1, QOpenGLCompositorWindow *cw2) {
294 QWindow *w1 = cw1->sourceWindow();
295 QWindow *w2 = cw2->sourceWindow();
296
297 // Case #1: The main window needs to have less z-order. It can never be in
298 // front of our tool windows, popups etc, because it's fullscreen!
299 if (w1 == m_targetWindow || w2 == m_targetWindow)
300 return w1 == m_targetWindow;
301
302 // Case #2:
303 if (w2->isAncestorOf(child: w1)) {
304 // w1 is transient child of w2. W1 goes in front then.
305 return false;
306 }
307
308 if (w1->isAncestorOf(child: w2)) {
309 // Or the other way around
310 return true;
311 }
312
313 // Case #3: Modality gets higher Z
314 if (w1->modality() != Qt::NonModal && w2->modality() == Qt::NonModal)
315 return false;
316
317 if (w2->modality() != Qt::NonModal && w1->modality() == Qt::NonModal)
318 return true;
319
320 const bool isTool1 = (w1->flags() & Qt::Tool) == Qt::Tool;
321 const bool isTool2 = (w2->flags() & Qt::Tool) == Qt::Tool;
322 const bool isPurePopup1 = !isTool1 && (w1->flags() & Qt::Popup) == Qt::Popup;
323 const bool isPurePopup2 = !isTool2 && (w2->flags() & Qt::Popup) == Qt::Popup;
324
325 // Case #4: By pure-popup we mean menus and tooltips. Qt::Tool implies Qt::Popup
326 // and we don't want to catch QDockWidget and other tool windows just yet
327 if (isPurePopup1 != isPurePopup2)
328 return !isPurePopup1;
329
330 // Case #5: One of the window is a Tool, that goes to front, as done in other QPAs
331 if (isTool1 != isTool2)
332 return !isTool1;
333
334 // Case #6: Just preserve original sorting:
335 return originalOrder.indexOf(t: cw1) < originalOrder.indexOf(t: cw2);
336 });
337}
338
339QT_END_NAMESPACE
340
341#include "moc_qopenglcompositor_p.cpp"
342

source code of qtbase/src/opengl/qopenglcompositor.cpp