1// Copyright (C) 2021 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 <QtCore/qtextstream.h>
5#include <qpa/qwindowsysteminterface.h>
6#include <qpa/qplatformintegration.h>
7#include <private/qguiapplication_p.h>
8#include <private/qwindow_p.h>
9#ifndef QT_NO_OPENGL
10# include <QtGui/private/qopenglcontext_p.h>
11# include <QtGui/QOpenGLContext>
12# include <QtOpenGL/private/qopenglcompositorbackingstore_p.h>
13#endif
14#include <QtGui/private/qeglconvenience_p.h>
15
16#include "qeglfswindow_p.h"
17#ifndef QT_NO_OPENGL
18# include "qeglfscursor_p.h"
19#endif
20#include "qeglfshooks_p.h"
21#include "qeglfsdeviceintegration_p.h"
22
23QT_BEGIN_NAMESPACE
24
25QEglFSWindow::QEglFSWindow(QWindow *w)
26 : QPlatformWindow(w),
27#ifndef QT_NO_OPENGL
28 m_backingStore(nullptr),
29 m_rasterCompositingContext(nullptr),
30#endif
31 m_winId(0),
32 m_surface(EGL_NO_SURFACE),
33 m_window(0)
34{
35}
36
37QEglFSWindow::~QEglFSWindow()
38{
39 destroy();
40}
41
42static WId newWId()
43{
44 static WId id = 0;
45
46 if (id == std::numeric_limits<WId>::max())
47 qWarning(msg: "QEGLPlatformWindow: Out of window IDs");
48
49 return ++id;
50}
51
52void QEglFSWindow::create()
53{
54 if (m_flags.testFlag(flag: Created))
55 return;
56
57 m_winId = newWId();
58
59 if (window()->type() == Qt::Desktop) {
60 QRect fullscreenRect(QPoint(), screen()->availableGeometry().size());
61 QWindowSystemInterface::handleGeometryChange(window: window(), newRect: fullscreenRect);
62 return;
63 }
64
65 m_flags = Created;
66
67 // Stop if there is already a window backed by a native window and surface. Additional
68 // raster windows will not have their own native window, surface and context. Instead,
69 // they will be composited onto the root window's surface.
70 QEglFSScreen *screen = this->screen();
71#ifndef QT_NO_OPENGL
72 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
73 if (screen->primarySurface() != EGL_NO_SURFACE) {
74 if (Q_UNLIKELY(isRaster() != (compositor->targetWindow() != nullptr))) {
75# ifndef Q_OS_ANDROID
76 // We can have either a single OpenGL window or multiple raster windows.
77 // Other combinations cannot work.
78 qFatal(msg: "EGLFS: OpenGL windows cannot be mixed with others.");
79# endif
80 return;
81 }
82 m_format = compositor->targetWindow()->format();
83 return;
84 }
85#endif // QT_NO_OPENGL
86
87 m_flags |= HasNativeWindow;
88 setGeometry(QRect()); // will become fullscreen
89
90 resetSurface();
91
92 if (Q_UNLIKELY(m_surface == EGL_NO_SURFACE)) {
93 EGLint error = eglGetError();
94 eglTerminate(dpy: screen->display());
95 qFatal(msg: "EGL Error : Could not create the egl surface: error = 0x%x\n", error);
96 }
97
98 screen->setPrimarySurface(m_surface);
99
100#ifndef QT_NO_OPENGL
101 compositor->setTargetWindow(window: window(), nativeTargetGeometry: screen->rawGeometry());
102 compositor->setRotation(qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_ROTATION"));
103#endif
104}
105
106void QEglFSWindow::setBackingStore(QOpenGLCompositorBackingStore *backingStore)
107{
108#ifndef QT_NO_OPENGL
109 if (!m_rasterCompositingContext) {
110 m_rasterCompositingContext = new QOpenGLContext;
111 m_rasterCompositingContext->setShareContext(QOpenGLContext::globalShareContext());
112 m_rasterCompositingContext->setFormat(m_format);
113 m_rasterCompositingContext->setScreen(window()->screen());
114 if (Q_UNLIKELY(!m_rasterCompositingContext->create()))
115 qFatal(msg: "EGLFS: Failed to create compositing context");
116 // If there is a "root" window into which raster and QOpenGLWidget content is
117 // composited, all other contexts must share with its context.
118 if (!QOpenGLContext::globalShareContext())
119 qt_gl_set_global_share_context(context: m_rasterCompositingContext);
120 }
121 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
122 compositor->setTargetContext(m_rasterCompositingContext);
123#endif
124 m_backingStore = backingStore;
125}
126
127void QEglFSWindow::destroy()
128{
129 if (!m_flags.testFlag(flag: Created))
130 return; // already destroyed
131
132 QEglFSScreen *screen = this->screen();
133#ifndef QT_NO_OPENGL
134 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
135 compositor->removeWindow(window: this);
136 if (compositor->targetWindow() == window()) {
137 QEglFSCursor *cursor = qobject_cast<QEglFSCursor *>(object: screen->cursor());
138 if (cursor)
139 cursor->resetResources();
140
141 if (screen->primarySurface() == m_surface)
142 screen->setPrimarySurface(EGL_NO_SURFACE);
143
144 invalidateSurface();
145
146 if (compositor->windows().isEmpty()) {
147 compositor->destroy();
148 if (QOpenGLContext::globalShareContext() == m_rasterCompositingContext)
149 qt_gl_set_global_share_context(context: nullptr);
150 delete m_rasterCompositingContext;
151 } else {
152 auto topWindow = static_cast<QEglFSWindow *>(compositor->windows().last());
153 // Make fullscreen
154 topWindow->setGeometry(screen->rawGeometry());
155 topWindow->resetSurface();
156 screen->setPrimarySurface(topWindow->surface());
157 compositor->setTargetWindow(window: topWindow->sourceWindow(), nativeTargetGeometry: screen->rawGeometry());
158 }
159 }
160#else
161 if (m_flags.testFlag(HasNativeWindow)) {
162 if (screen->primarySurface() == m_surface)
163 screen->setPrimarySurface(EGL_NO_SURFACE);
164
165 invalidateSurface();
166 }
167#endif
168
169 m_flags = { };
170}
171
172void QEglFSWindow::invalidateSurface()
173{
174 if (m_surface != EGL_NO_SURFACE) {
175 qCDebug(qLcEglDevDebug) << Q_FUNC_INFO << " about to destroy EGLSurface: " << m_surface;
176
177 bool ok = eglDestroySurface(dpy: screen()->display(), surface: m_surface);
178
179 if (!ok) {
180 qCWarning(qLcEglDevDebug, "QEglFSWindow::invalidateSurface() eglDestroySurface failed!"
181 " Follow-up errors or memory leaks are possible."
182 " eglGetError(): %x", eglGetError());
183 }
184
185 if (eglGetCurrentSurface(EGL_READ) == m_surface ||
186 eglGetCurrentSurface(EGL_DRAW) == m_surface) {
187 bool ok = eglMakeCurrent(dpy: eglGetCurrentDisplay(), EGL_NO_DISPLAY, EGL_NO_DISPLAY, EGL_NO_CONTEXT);
188 qCDebug(qLcEglDevDebug) << Q_FUNC_INFO << " due to eglDestroySurface on *currently* bound surface"
189 << "we just called eglMakeCurrent(..,0,0,0)! It returned: " << ok;
190 }
191
192 if (screen()->primarySurface() == m_surface)
193 screen()->setPrimarySurface(EGL_NO_SURFACE);
194
195 m_surface = EGL_NO_SURFACE;
196 m_flags = m_flags & ~Created;
197 }
198 qt_egl_device_integration()->destroyNativeWindow(window: m_window);
199 m_window = 0;
200}
201
202void QEglFSWindow::resetSurface()
203{
204 EGLDisplay display = screen()->display();
205 QSurfaceFormat platformFormat = qt_egl_device_integration()->surfaceFormatFor(inputFormat: window()->requestedFormat());
206
207 m_config = QEglFSDeviceIntegration::chooseConfig(display, format: platformFormat);
208 m_format = q_glFormatFromConfig(display, config: m_config, referenceFormat: platformFormat);
209 const QSize surfaceSize = screen()->rawGeometry().size();
210 m_window = qt_egl_device_integration()->createNativeWindow(platformWindow: this, size: surfaceSize, format: m_format);
211 m_surface = eglCreateWindowSurface(dpy: display, config: m_config, win: m_window, attrib_list: nullptr);
212}
213
214void QEglFSWindow::setVisible(bool visible)
215{
216#ifndef QT_NO_OPENGL
217 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
218 QList<QOpenGLCompositorWindow *> windows = compositor->windows();
219 QWindow *wnd = window();
220
221 if (wnd->type() != Qt::Desktop) {
222 if (visible) {
223 compositor->addWindow(window: this);
224 } else {
225 compositor->removeWindow(window: this);
226 windows = compositor->windows();
227 if (windows.size())
228 windows.last()->sourceWindow()->requestActivate();
229 }
230 }
231#else
232 QWindow *wnd = window();
233#endif
234 QWindowSystemInterface::handleExposeEvent(window: wnd, region: QRect(QPoint(0, 0), wnd->geometry().size()));
235
236 if (visible)
237 QWindowSystemInterface::flushWindowSystemEvents(flags: QEventLoop::ExcludeUserInputEvents);
238}
239
240void QEglFSWindow::setGeometry(const QRect &r)
241{
242 QRect rect = r;
243 if (m_flags.testFlag(flag: HasNativeWindow))
244 rect = screen()->availableGeometry();
245
246 QPlatformWindow::setGeometry(rect);
247
248 QWindowSystemInterface::handleGeometryChange(window: window(), newRect: rect);
249
250 const QRect lastReportedGeometry = qt_window_private(window: window())->geometry;
251 if (rect != lastReportedGeometry)
252 QWindowSystemInterface::handleExposeEvent(window: window(), region: QRect(QPoint(0, 0), rect.size()));
253}
254
255QRect QEglFSWindow::geometry() const
256{
257 // For yet-to-become-fullscreen windows report the geometry covering the entire
258 // screen. This is particularly important for Quick where the root object may get
259 // sized to some geometry queried before calling create().
260 if (!m_flags.testFlag(flag: Created) && screen()->primarySurface() == EGL_NO_SURFACE)
261 return screen()->availableGeometry();
262
263 return QPlatformWindow::geometry();
264}
265
266void QEglFSWindow::requestActivateWindow()
267{
268#ifndef QT_NO_OPENGL
269 if (window()->type() != Qt::Desktop)
270 QOpenGLCompositor::instance()->moveToTop(window: this);
271#endif
272 QWindow *wnd = window();
273 QWindowSystemInterface::handleFocusWindowChanged(window: wnd, r: Qt::ActiveWindowFocusReason);
274 QWindowSystemInterface::handleExposeEvent(window: wnd, region: QRect(QPoint(0, 0), wnd->geometry().size()));
275}
276
277void QEglFSWindow::raise()
278{
279 QWindow *wnd = window();
280 if (wnd->type() != Qt::Desktop) {
281#ifndef QT_NO_OPENGL
282 QOpenGLCompositor::instance()->moveToTop(window: this);
283#endif
284 QWindowSystemInterface::handleExposeEvent(window: wnd, region: QRect(QPoint(0, 0), wnd->geometry().size()));
285 }
286}
287
288void QEglFSWindow::lower()
289{
290#ifndef QT_NO_OPENGL
291 QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
292 QList<QOpenGLCompositorWindow *> windows = compositor->windows();
293 if (window()->type() != Qt::Desktop && windows.size() > 1) {
294 int idx = windows.indexOf(t: this);
295 if (idx > 0) {
296 compositor->changeWindowIndex(window: this, newIdx: idx - 1);
297 QWindowSystemInterface::handleExposeEvent(window: windows.last()->sourceWindow(),
298 region: QRect(QPoint(0, 0), windows.last()->sourceWindow()->geometry().size()));
299 }
300 }
301#endif
302}
303
304EGLSurface QEglFSWindow::surface() const
305{
306 return m_surface != EGL_NO_SURFACE ? m_surface : screen()->primarySurface();
307}
308
309QSurfaceFormat QEglFSWindow::format() const
310{
311 return m_format;
312}
313
314EGLNativeWindowType QEglFSWindow::eglWindow() const
315{
316 return m_window;
317}
318
319QEglFSScreen *QEglFSWindow::screen() const
320{
321 return static_cast<QEglFSScreen *>(QPlatformWindow::screen());
322}
323
324bool QEglFSWindow::isRaster() const
325{
326 const QWindow::SurfaceType type = window()->surfaceType();
327 return type == QSurface::RasterSurface || type == QSurface::RasterGLSurface;
328}
329
330#ifndef QT_NO_OPENGL
331QWindow *QEglFSWindow::sourceWindow() const
332{
333 return window();
334}
335
336const QPlatformTextureList *QEglFSWindow::textures() const
337{
338 if (m_backingStore)
339 return m_backingStore->textures();
340
341 return nullptr;
342}
343
344void QEglFSWindow::endCompositing()
345{
346 if (m_backingStore)
347 m_backingStore->notifyComposited();
348}
349#endif
350
351WId QEglFSWindow::winId() const
352{
353 return m_winId;
354}
355
356void QEglFSWindow::setOpacity(qreal)
357{
358 if (!isRaster() && !backingStore())
359 qWarning(msg: "QEglFSWindow: Cannot set opacity for non-raster windows");
360
361 // Nothing to do here. The opacity is stored in the QWindow.
362}
363
364QT_END_NAMESPACE
365

source code of qtbase/src/plugins/platforms/eglfs/api/qeglfswindow.cpp