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