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 "qwaylandglcontext_p.h"
5
6#include <QtWaylandClient/private/qwaylanddisplay_p.h>
7#include <QtWaylandClient/private/qwaylandwindow_p.h>
8#include <QtWaylandClient/private/qwaylandsubsurface_p.h>
9#include <QtWaylandClient/private/qwaylandabstractdecoration_p.h>
10#include <QtWaylandClient/private/qwaylandintegration_p.h>
11#include "qwaylandeglwindow_p.h"
12
13#include <QDebug>
14#include <QtGui/private/qeglconvenience_p.h>
15#include <QtGui/private/qopenglcontext_p.h>
16#include <QtOpenGL/private/qopengltexturecache_p.h>
17#include <QtGui/private/qguiapplication_p.h>
18
19#include <qpa/qplatformopenglcontext.h>
20#include <QtGui/QSurfaceFormat>
21#include <QtOpenGL/QOpenGLShaderProgram>
22#include <QtGui/QOpenGLFunctions>
23#include <QOpenGLBuffer>
24
25#include <QtCore/qmutex.h>
26
27#include <dlfcn.h>
28
29// Constants from EGL_KHR_create_context
30#ifndef EGL_CONTEXT_MINOR_VERSION_KHR
31#define EGL_CONTEXT_MINOR_VERSION_KHR 0x30FB
32#endif
33#ifndef EGL_CONTEXT_FLAGS_KHR
34#define EGL_CONTEXT_FLAGS_KHR 0x30FC
35#endif
36#ifndef EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR
37#define EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR 0x30FD
38#endif
39#ifndef EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR
40#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0x00000001
41#endif
42#ifndef EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR
43#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR 0x00000002
44#endif
45#ifndef EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR
46#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR 0x00000001
47#endif
48#ifndef EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR
49#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR 0x00000002
50#endif
51
52// Constants for OpenGL which are not available in the ES headers.
53#ifndef GL_CONTEXT_FLAGS
54#define GL_CONTEXT_FLAGS 0x821E
55#endif
56#ifndef GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT
57#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x0001
58#endif
59#ifndef GL_CONTEXT_FLAG_DEBUG_BIT
60#define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002
61#endif
62#ifndef GL_CONTEXT_PROFILE_MASK
63#define GL_CONTEXT_PROFILE_MASK 0x9126
64#endif
65#ifndef GL_CONTEXT_CORE_PROFILE_BIT
66#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001
67#endif
68#ifndef GL_CONTEXT_COMPATIBILITY_PROFILE_BIT
69#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002
70#endif
71
72QT_BEGIN_NAMESPACE
73
74namespace QtWaylandClient {
75
76class DecorationsBlitter : public QOpenGLFunctions
77{
78public:
79 DecorationsBlitter(QWaylandGLContext *context)
80 : m_context(context)
81 {
82 initializeOpenGLFunctions();
83 m_blitProgram = new QOpenGLShaderProgram();
84 m_blitProgram->addShaderFromSourceCode(type: QOpenGLShader::Vertex, source: "attribute vec4 position;\n\
85 attribute vec4 texCoords;\n\
86 varying vec2 outTexCoords;\n\
87 void main()\n\
88 {\n\
89 gl_Position = position;\n\
90 outTexCoords = texCoords.xy;\n\
91 }");
92 m_blitProgram->addShaderFromSourceCode(type: QOpenGLShader::Fragment, source: "varying highp vec2 outTexCoords;\n\
93 uniform sampler2D texture;\n\
94 void main()\n\
95 {\n\
96 gl_FragColor = texture2D(texture, outTexCoords);\n\
97 }");
98
99 m_blitProgram->bindAttributeLocation(name: "position", location: 0);
100 m_blitProgram->bindAttributeLocation(name: "texCoords", location: 1);
101
102 if (!m_blitProgram->link()) {
103 qDebug() << "Shader Program link failed.";
104 qDebug() << m_blitProgram->log();
105 }
106
107 m_blitProgram->bind();
108 m_blitProgram->enableAttributeArray(location: 0);
109 m_blitProgram->enableAttributeArray(location: 1);
110
111 glDisable(GL_DEPTH_TEST);
112 glDisable(GL_BLEND);
113 glDisable(GL_CULL_FACE);
114 glDisable(GL_SCISSOR_TEST);
115 glDepthMask(GL_FALSE);
116 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
117
118 m_buffer.create();
119 m_buffer.bind();
120
121 static const GLfloat squareVertices[] = {
122 -1.f, -1.f,
123 1.0f, -1.f,
124 -1.f, 1.0f,
125 1.0f, 1.0f
126 };
127 static const GLfloat inverseSquareVertices[] = {
128 -1.f, 1.f,
129 1.f, 1.f,
130 -1.f, -1.f,
131 1.f, -1.f
132 };
133 static const GLfloat textureVertices[] = {
134 0.0f, 0.0f,
135 1.0f, 0.0f,
136 0.0f, 1.0f,
137 1.0f, 1.0f,
138 };
139
140 m_squareVerticesOffset = 0;
141 m_inverseSquareVerticesOffset = sizeof(squareVertices);
142 m_textureVerticesOffset = sizeof(squareVertices) + sizeof(textureVertices);
143
144 m_buffer.allocate(count: sizeof(squareVertices) + sizeof(inverseSquareVertices) + sizeof(textureVertices));
145 m_buffer.write(offset: m_squareVerticesOffset, data: squareVertices, count: sizeof(squareVertices));
146 m_buffer.write(offset: m_inverseSquareVerticesOffset, data: inverseSquareVertices, count: sizeof(inverseSquareVertices));
147 m_buffer.write(offset: m_textureVerticesOffset, data: textureVertices, count: sizeof(textureVertices));
148
149 m_blitProgram->setAttributeBuffer(location: 1, GL_FLOAT, offset: m_textureVerticesOffset, tupleSize: 2);
150
151 m_textureWrap = m_context->context()->functions()->hasOpenGLFeature(feature: QOpenGLFunctions::NPOTTextureRepeat) ? GL_REPEAT : GL_CLAMP_TO_EDGE;
152 }
153 ~DecorationsBlitter()
154 {
155 delete m_blitProgram;
156 }
157 void blit(QWaylandEglWindow *window)
158 {
159 QOpenGLTextureCache *cache = QOpenGLTextureCache::cacheForContext(context: m_context->context());
160
161 QSize surfaceSize = window->surfaceSize();
162 qreal scale = window->scale() ;
163 glViewport(x: 0, y: 0, width: surfaceSize.width() * scale, height: surfaceSize.height() * scale);
164
165 //Draw Decoration
166 if (auto *decoration = window->decoration()) {
167 m_blitProgram->setAttributeBuffer(location: 0, GL_FLOAT, offset: m_inverseSquareVerticesOffset, tupleSize: 2);
168 QImage decorationImage = decoration->contentImage();
169 cache->bindTexture(context: m_context->context(), image: decorationImage);
170 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
171 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
172 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, param: m_textureWrap);
173 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, param: m_textureWrap);
174 glDrawArrays(GL_TRIANGLE_STRIP, first: 0, count: 4);
175 }
176
177 //Draw Content
178 m_blitProgram->setAttributeBuffer(location: 0, GL_FLOAT, offset: m_squareVerticesOffset, tupleSize: 2);
179 glBindTexture(GL_TEXTURE_2D, texture: window->contentTexture());
180 QRect r = window->contentsRect();
181 glViewport(x: r.x() * scale, y: r.y() * scale, width: r.width() * scale, height: r.height() * scale);
182 glDrawArrays(GL_TRIANGLE_STRIP, first: 0, count: 4);
183 }
184
185 QOpenGLShaderProgram *m_blitProgram = nullptr;
186 QWaylandGLContext *m_context = nullptr;
187 QOpenGLBuffer m_buffer;
188 int m_squareVerticesOffset;
189 int m_inverseSquareVerticesOffset;
190 int m_textureVerticesOffset;
191 int m_textureWrap;
192};
193
194QWaylandGLContext::QWaylandGLContext(EGLDisplay eglDisplay, QWaylandDisplay *display,
195 const QSurfaceFormat &fmt, QPlatformOpenGLContext *share)
196 : QEGLPlatformContext(fmt, share, eglDisplay), m_display(display)
197{
198 m_reconnectionWatcher = QObject::connect(m_display, &QWaylandDisplay::connected,
199 m_display, [this] { invalidateContext(); });
200
201 switch (format().renderableType()) {
202 case QSurfaceFormat::OpenVG:
203 m_api = EGL_OPENVG_API;
204 break;
205#ifdef EGL_VERSION_1_4
206 case QSurfaceFormat::OpenGL:
207 m_api = EGL_OPENGL_API;
208 break;
209#endif // EGL_VERSION_1_4
210 default:
211 m_api = EGL_OPENGL_ES_API;
212 break;
213 }
214
215 // Create an EGL context for the decorations blitter. By using a dedicated context we don't need to make sure to not
216 // change the context state and we also use OpenGL ES 2 API independently to what the app is using to draw.
217 QList<EGLint> eglDecorationsContextAttrs = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
218 m_decorationsContext = eglCreateContext(dpy: eglDisplay, config: eglConfig(), share_context: eglContext(),
219 attrib_list: eglDecorationsContextAttrs.constData());
220 if (m_decorationsContext == EGL_NO_CONTEXT)
221 qWarning(msg: "QWaylandGLContext: Failed to create the decorations EGLContext. Decorations will not be drawn.");
222
223 EGLint a = EGL_MIN_SWAP_INTERVAL;
224 EGLint b = EGL_MAX_SWAP_INTERVAL;
225 if (!eglGetConfigAttrib(dpy: eglDisplay, config: eglConfig(), attribute: a, value: &a)
226 || !eglGetConfigAttrib(dpy: eglDisplay, config: eglConfig(), attribute: b, value: &b) || a > 0) {
227 m_supportNonBlockingSwap = false;
228 }
229 {
230 bool ok;
231 int supportNonBlockingSwap = qEnvironmentVariableIntValue(varName: "QT_WAYLAND_FORCE_NONBLOCKING_SWAP_SUPPORT", ok: &ok);
232 if (ok)
233 m_supportNonBlockingSwap = supportNonBlockingSwap != 0;
234 }
235 if (!m_supportNonBlockingSwap) {
236 qCWarning(lcQpaWayland) << "Non-blocking swap buffers not supported."
237 << "Subsurface rendering can be affected."
238 << "It may also cause the event loop to freeze in some situations";
239 }
240}
241
242EGLSurface QWaylandGLContext::createTemporaryOffscreenSurface()
243{
244 m_wlSurface = m_display->createSurface(handle: nullptr);
245 m_eglWindow = wl_egl_window_create(surface: m_wlSurface, width: 1, height: 1);
246#if QT_CONFIG(egl_extension_platform_wayland)
247 EGLSurface eglSurface =
248 eglCreatePlatformWindowSurface(dpy: eglDisplay(), config: eglConfig(), native_window: m_eglWindow, attrib_list: nullptr);
249#else
250 EGLSurface eglSurface = eglCreateWindowSurface(eglDisplay(), eglConfig(), m_eglWindow, nullptr);
251#endif
252 return eglSurface;
253}
254
255void QWaylandGLContext::destroyTemporaryOffscreenSurface(EGLSurface eglSurface)
256{
257 eglDestroySurface(dpy: eglDisplay(), surface: eglSurface);
258 wl_egl_window_destroy(egl_window: m_eglWindow);
259 m_eglWindow = nullptr;
260 wl_surface_destroy(wl_surface: m_wlSurface);
261 m_wlSurface = nullptr;
262}
263
264void QWaylandGLContext::runGLChecks()
265{
266 bool ok;
267 const int doneCurrentWorkAround = qEnvironmentVariableIntValue(varName: "QT_WAYLAND_ENABLE_DONECURRENT_WORKAROUND", ok: &ok);
268 if (ok) {
269 m_doneCurrentWorkAround = doneCurrentWorkAround != 0;
270 if (m_doneCurrentWorkAround)
271 qCDebug(lcQpaWayland) << "Enabling doneCurrent() workaround on request.";
272 else
273 qCDebug(lcQpaWayland) << "Disabling doneCurrent() workaround on request.";
274
275 } else {
276 // Note that even though there is an EGL context current here,
277 // QOpenGLContext and QOpenGLFunctions are not yet usable at this stage.
278 const char *renderer = reinterpret_cast<const char *>(glGetString(GL_RENDERER));
279 if (renderer && strstr(haystack: renderer, needle: "Mali")) {
280 qCDebug(lcQpaWayland) << "Enabling doneCurrent() workaround for Mali GPU."
281 << "Set QT_WAYLAND_ENABLE_DONECURRENT_WORKAROUND=0 to disable.";
282 m_doneCurrentWorkAround = true;
283 }
284 }
285
286 QEGLPlatformContext::runGLChecks();
287}
288
289QWaylandGLContext::~QWaylandGLContext()
290{
291 QObject::disconnect(m_reconnectionWatcher);
292 delete m_blitter;
293 m_blitter = nullptr;
294 if (m_decorationsContext != EGL_NO_CONTEXT)
295 eglDestroyContext(dpy: eglDisplay(), ctx: m_decorationsContext);
296}
297
298void QWaylandGLContext::beginFrame()
299{
300 Q_ASSERT(m_currentWindow != nullptr);
301 if (m_supportNonBlockingSwap)
302 m_currentWindow->beginFrame();
303}
304
305void QWaylandGLContext::endFrame()
306{
307 Q_ASSERT(m_currentWindow != nullptr);
308 if (m_doneCurrentWorkAround) {
309 doneCurrent();
310 QOpenGLContextPrivate::setCurrentContext(nullptr);
311 }
312
313 if (m_supportNonBlockingSwap)
314 m_currentWindow->endFrame();
315}
316
317bool QWaylandGLContext::makeCurrent(QPlatformSurface *surface)
318{
319 if (!isValid()) {
320 return false;
321 }
322
323 // in QWaylandGLContext() we called eglBindAPI with the correct value. However,
324 // eglBindAPI's documentation says:
325 // "eglBindAPI defines the current rendering API for EGL in the thread it is called from"
326 // Since makeCurrent() can be called from a different thread than the one we created the
327 // context in make sure to call eglBindAPI in the correct thread.
328 if (eglQueryAPI() != m_api) {
329 eglBindAPI(api: m_api);
330 }
331
332 m_currentWindow = static_cast<QWaylandEglWindow *>(surface);
333 EGLSurface eglSurface = m_currentWindow->eglSurface();
334
335 if (!m_currentWindow->needToUpdateContentFBO() && (eglSurface != EGL_NO_SURFACE)) {
336 if (!eglMakeCurrent(dpy: eglDisplay(), draw: eglSurface, read: eglSurface, ctx: eglContext())) {
337 qWarning(msg: "QWaylandGLContext::makeCurrent: eglError: %#x, this: %p \n", eglGetError(), this);
338 return false;
339 }
340 return true;
341 }
342
343 if (m_currentWindow->isExposed())
344 m_currentWindow->setCanResize(false);
345
346 if (eglSurface == EGL_NO_SURFACE) {
347 m_currentWindow->updateSurface(create: true);
348 eglSurface = m_currentWindow->eglSurface();
349 }
350
351 if (!eglMakeCurrent(dpy: eglDisplay(), draw: eglSurface, read: eglSurface, ctx: eglContext())) {
352 qWarning(msg: "QWaylandGLContext::makeCurrent: eglError: %#x, this: %p \n", eglGetError(), this);
353 m_currentWindow->setCanResize(true);
354 return false;
355 }
356
357 //### setCurrentContext will be called in QOpenGLContext::makeCurrent after this function
358 // returns, but that's too late, as we need a current context in order to bind the content FBO.
359 QOpenGLContextPrivate::setCurrentContext(context());
360 m_currentWindow->bindContentFBO();
361
362 return true;
363}
364
365void QWaylandGLContext::doneCurrent()
366{
367 eglMakeCurrent(dpy: eglDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
368}
369
370void QWaylandGLContext::swapBuffers(QPlatformSurface *surface)
371{
372 QWaylandEglWindow *window = static_cast<QWaylandEglWindow *>(surface);
373
374 EGLSurface eglSurface = window->eglSurface();
375
376 if (window->decoration()) {
377 if (m_api != EGL_OPENGL_ES_API)
378 eglBindAPI(EGL_OPENGL_ES_API);
379
380 // save the current EGL content and surface to set it again after the blitter is done
381 EGLDisplay currentDisplay = eglGetCurrentDisplay();
382 EGLContext currentContext = eglGetCurrentContext();
383 EGLSurface currentSurfaceDraw = eglGetCurrentSurface(EGL_DRAW);
384 EGLSurface currentSurfaceRead = eglGetCurrentSurface(EGL_READ);
385 eglMakeCurrent(dpy: eglDisplay(), draw: eglSurface, read: eglSurface, ctx: m_decorationsContext);
386
387 if (!m_blitter)
388 m_blitter = new DecorationsBlitter(this);
389 m_blitter->blit(window);
390
391 if (m_api != EGL_OPENGL_ES_API)
392 eglBindAPI(api: m_api);
393 eglMakeCurrent(dpy: currentDisplay, draw: currentSurfaceDraw, read: currentSurfaceRead, ctx: currentContext);
394 }
395
396 int swapInterval = m_supportNonBlockingSwap ? 0 : format().swapInterval();
397 eglSwapInterval(dpy: eglDisplay(), interval: swapInterval);
398 if (swapInterval == 0 && format().swapInterval() > 0) {
399 // Emulating a blocking swap
400 glFlush(); // Flush before waiting so we can swap more quickly when the frame event arrives
401 window->waitForFrameSync(timeout: 100);
402 }
403 window->handleUpdate();
404 if (!eglSwapBuffers(dpy: eglDisplay(), surface: eglSurface))
405 qCWarning(lcQpaWayland, "eglSwapBuffers failed with %#x, surface: %p", eglGetError(), eglSurface);
406
407 window->setCanResize(true);
408}
409
410GLuint QWaylandGLContext::defaultFramebufferObject(QPlatformSurface *surface) const
411{
412 return static_cast<QWaylandEglWindow *>(surface)->contentFBO();
413}
414
415QFunctionPointer QWaylandGLContext::getProcAddress(const char *procName)
416{
417 QFunctionPointer proc = (QFunctionPointer) eglGetProcAddress(procname: procName);
418 if (!proc)
419 proc = (QFunctionPointer) dlsym(RTLD_DEFAULT, name: procName);
420 return proc;
421}
422
423EGLSurface QWaylandGLContext::eglSurfaceForPlatformSurface(QPlatformSurface *surface)
424{
425 return static_cast<QWaylandEglWindow *>(surface)->eglSurface();
426}
427
428}
429
430QT_END_NAMESPACE
431

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp