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 QOpenGLFramebufferObject fbo(m_nativeTargetGeometry.size());
89 grabToFrameBufferObject(fbo: &fbo);
90 return fbo.toImage();
91}
92
93bool QOpenGLCompositor::grabToFrameBufferObject(QOpenGLFramebufferObject *fbo, GrabOrientation orientation)
94{
95 Q_ASSERT(fbo);
96 if (fbo->size() != m_nativeTargetGeometry.size()
97 || fbo->format().textureTarget() != GL_TEXTURE_2D)
98 return false;
99
100 m_context->makeCurrent(surface: m_targetWindow);
101 renderAll(fbo,
102 origin: orientation == Flipped ? QOpenGLTextureBlitter::OriginTopLeft
103 : QOpenGLTextureBlitter::OriginBottomLeft);
104 return true;
105}
106
107void QOpenGLCompositor::handleRenderAllRequest()
108{
109 Q_ASSERT(m_context && m_targetWindow);
110 m_context->makeCurrent(surface: m_targetWindow);
111 renderAll(fbo: 0);
112}
113
114void QOpenGLCompositor::renderAll(QOpenGLFramebufferObject *fbo, QOpenGLTextureBlitter::Origin origin)
115{
116 if (fbo)
117 fbo->bind();
118
119 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
120 glViewport(x: 0, y: 0, width: m_nativeTargetGeometry.width(), height: m_nativeTargetGeometry.height());
121
122 if (!m_blitter.isCreated())
123 m_blitter.create();
124
125 m_blitter.bind();
126
127 for (int i = 0; i < m_windows.size(); ++i)
128 m_windows.at(i)->beginCompositing();
129
130 for (int i = 0; i < m_windows.size(); ++i)
131 render(window: m_windows.at(i), origin);
132
133 m_blitter.release();
134 if (!fbo)
135 m_context->swapBuffers(surface: m_targetWindow);
136 else
137 fbo->release();
138
139 for (int i = 0; i < m_windows.size(); ++i)
140 m_windows.at(i)->endCompositing();
141}
142
143struct BlendStateBinder
144{
145 BlendStateBinder() : m_blend(false) {
146 glDisable(GL_BLEND);
147 }
148 void set(bool blend) {
149 if (blend != m_blend) {
150 if (blend) {
151 glEnable(GL_BLEND);
152 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
153 } else {
154 glDisable(GL_BLEND);
155 }
156 m_blend = blend;
157 }
158 }
159 ~BlendStateBinder() {
160 if (m_blend)
161 glDisable(GL_BLEND);
162 }
163 bool m_blend;
164};
165
166static inline QRect toBottomLeftRect(const QRect &topLeftRect, int windowHeight)
167{
168 return QRect(topLeftRect.x(), windowHeight - topLeftRect.bottomRight().y() - 1,
169 topLeftRect.width(), topLeftRect.height());
170}
171
172static void clippedBlit(const QPlatformTextureList *textures, int idx,
173 const QRect &sourceWindowRect, const QRect &targetWindowRect,
174 QOpenGLTextureBlitter *blitter, QMatrix4x4 *rotationMatrix,
175 QOpenGLTextureBlitter::Origin sourceOrigin)
176{
177 const QRect clipRect = textures->clipRect(index: idx);
178 if (clipRect.isEmpty())
179 return;
180
181 const QRect rectInWindow = textures->geometry(index: idx).translated(p: sourceWindowRect.topLeft());
182 const QRect clippedRectInWindow = rectInWindow & clipRect.translated(p: rectInWindow.topLeft());
183 const QRect srcRect = toBottomLeftRect(topLeftRect: clipRect, windowHeight: rectInWindow.height());
184
185 QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(target: clippedRectInWindow, viewport: targetWindowRect);
186 if (rotationMatrix)
187 target = *rotationMatrix * target;
188
189 const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(subTexture: srcRect, textureSize: rectInWindow.size(),
190 origin: sourceOrigin);
191
192 const uint textureId = textures->texture(index: idx)->nativeTexture().object;
193 blitter->blit(texture: textureId, targetTransform: target, sourceTransform: source);
194}
195
196void QOpenGLCompositor::render(QOpenGLCompositorWindow *window, QOpenGLTextureBlitter::Origin origin)
197{
198 const QPlatformTextureList *textures = window->textures();
199 if (!textures)
200 return;
201
202 const QRect targetWindowRect(QPoint(0, 0), m_targetWindow->geometry().size());
203 float currentOpacity = 1.0f;
204 BlendStateBinder blend;
205 const QRect sourceWindowRect = window->sourceWindow()->geometry();
206 auto clippedBlitSourceOrigin = origin == QOpenGLTextureBlitter::OriginTopLeft
207 ? QOpenGLTextureBlitter::OriginBottomLeft
208 : QOpenGLTextureBlitter::OriginTopLeft;
209 for (int i = 0; i < textures->count(); ++i) {
210 const uint textureId = textures->texture(index: i)->nativeTexture().object;
211 const float opacity = window->sourceWindow()->opacity();
212 if (opacity != currentOpacity) {
213 currentOpacity = opacity;
214 m_blitter.setOpacity(currentOpacity);
215 }
216
217 if (textures->count() > 1 && i == textures->count() - 1) {
218 // Backingstore for a widget with QOpenGLWidget subwidgets
219 blend.set(true);
220 QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(target: textures->geometry(index: i), viewport: targetWindowRect);
221 if (m_rotation)
222 target = m_rotationMatrix * target;
223 m_blitter.blit(texture: textureId, targetTransform: target, sourceOrigin: origin);
224 } else if (textures->count() == 1) {
225 // A regular QWidget window
226 const bool translucent = window->sourceWindow()->requestedFormat().alphaBufferSize() > 0;
227 blend.set(translucent);
228 QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(target: textures->geometry(index: i), viewport: targetWindowRect);
229 if (m_rotation)
230 target = m_rotationMatrix * target;
231 m_blitter.blit(texture: textureId, targetTransform: target, sourceOrigin: origin);
232 } else if (!textures->flags(index: i).testFlag(flag: QPlatformTextureList::StacksOnTop)) {
233 // Texture from an FBO belonging to a QOpenGLWidget or QQuickWidget
234 blend.set(false);
235 clippedBlit(textures, idx: i, sourceWindowRect, targetWindowRect, blitter: &m_blitter,
236 rotationMatrix: m_rotation ? &m_rotationMatrix : nullptr, sourceOrigin: clippedBlitSourceOrigin);
237 }
238 }
239
240 for (int i = 0; i < textures->count(); ++i) {
241 if (textures->flags(index: i).testFlag(flag: QPlatformTextureList::StacksOnTop)) {
242 blend.set(true);
243 clippedBlit(textures, idx: i, sourceWindowRect, targetWindowRect, blitter: &m_blitter,
244 rotationMatrix: m_rotation ? &m_rotationMatrix : nullptr, sourceOrigin: clippedBlitSourceOrigin);
245 }
246 }
247
248 m_blitter.setOpacity(1.0f);
249}
250
251QOpenGLCompositor *QOpenGLCompositor::instance()
252{
253 if (!compositor)
254 compositor = new QOpenGLCompositor;
255 return compositor;
256}
257
258void QOpenGLCompositor::destroy()
259{
260 delete compositor;
261 compositor = 0;
262}
263
264void QOpenGLCompositor::addWindow(QOpenGLCompositorWindow *window)
265{
266 if (!m_windows.contains(t: window)) {
267 m_windows.append(t: window);
268 ensureCorrectZOrder();
269 if (window == m_windows.constLast())
270 emit topWindowChanged(window);
271 }
272}
273
274void QOpenGLCompositor::removeWindow(QOpenGLCompositorWindow *window)
275{
276 m_windows.removeOne(t: window);
277 if (!m_windows.isEmpty())
278 emit topWindowChanged(window: m_windows.last());
279}
280
281void QOpenGLCompositor::moveToTop(QOpenGLCompositorWindow *window)
282{
283 if (!m_windows.isEmpty() && window == m_windows.constLast()) {
284 // Already on top
285 return;
286 }
287
288 m_windows.removeOne(t: window);
289 m_windows.append(t: window);
290 ensureCorrectZOrder();
291
292 if (window == m_windows.constLast())
293 emit topWindowChanged(window);
294}
295
296void QOpenGLCompositor::changeWindowIndex(QOpenGLCompositorWindow *window, int newIdx)
297{
298 int idx = m_windows.indexOf(t: window);
299 if (idx != -1 && idx != newIdx) {
300 m_windows.move(from: idx, to: newIdx);
301 ensureCorrectZOrder();
302 if (window == m_windows.constLast())
303 emit topWindowChanged(window: m_windows.last());
304 }
305}
306
307void QOpenGLCompositor::ensureCorrectZOrder()
308{
309 const auto originalOrder = m_windows;
310
311 std::sort(first: m_windows.begin(), last: m_windows.end(),
312 comp: [this, &originalOrder](QOpenGLCompositorWindow *cw1, QOpenGLCompositorWindow *cw2) {
313 QWindow *w1 = cw1->sourceWindow();
314 QWindow *w2 = cw2->sourceWindow();
315
316 // Case #1: The main window needs to have less z-order. It can never be in
317 // front of our tool windows, popups etc, because it's fullscreen!
318 if (w1 == m_targetWindow || w2 == m_targetWindow)
319 return w1 == m_targetWindow;
320
321 // Case #2:
322 if (w2->isAncestorOf(child: w1)) {
323 // w1 is transient child of w2. W1 goes in front then.
324 return false;
325 }
326
327 if (w1->isAncestorOf(child: w2)) {
328 // Or the other way around
329 return true;
330 }
331
332 // Case #3: Modality gets higher Z
333 if (w1->modality() != Qt::NonModal && w2->modality() == Qt::NonModal)
334 return false;
335
336 if (w2->modality() != Qt::NonModal && w1->modality() == Qt::NonModal)
337 return true;
338
339 const bool isTool1 = (w1->flags() & Qt::Tool) == Qt::Tool;
340 const bool isTool2 = (w2->flags() & Qt::Tool) == Qt::Tool;
341 const bool isPurePopup1 = !isTool1 && (w1->flags() & Qt::Popup) == Qt::Popup;
342 const bool isPurePopup2 = !isTool2 && (w2->flags() & Qt::Popup) == Qt::Popup;
343
344 // Case #4: By pure-popup we mean menus and tooltips. Qt::Tool implies Qt::Popup
345 // and we don't want to catch QDockWidget and other tool windows just yet
346 if (isPurePopup1 != isPurePopup2)
347 return !isPurePopup1;
348
349 // Case #5: One of the window is a Tool, that goes to front, as done in other QPAs
350 if (isTool1 != isTool2)
351 return !isTool1;
352
353 // Case #6: Just preserve original sorting:
354 return originalOrder.indexOf(t: cw1) < originalOrder.indexOf(t: cw2);
355 });
356}
357
358QT_END_NAMESPACE
359
360#include "moc_qopenglcompositor_p.cpp"
361

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