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 | |
72 | QT_BEGIN_NAMESPACE |
73 | |
74 | namespace QtWaylandClient { |
75 | |
76 | class DecorationsBlitter : public QOpenGLFunctions |
77 | { |
78 | public: |
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 | |
194 | QWaylandGLContext::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 | |
242 | EGLSurface 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 | |
255 | void 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 | |
264 | void 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 | |
289 | QWaylandGLContext::~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 | |
298 | void QWaylandGLContext::beginFrame() |
299 | { |
300 | Q_ASSERT(m_currentWindow != nullptr); |
301 | if (m_supportNonBlockingSwap) |
302 | m_currentWindow->beginFrame(); |
303 | } |
304 | |
305 | void 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 | |
317 | bool 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 | |
365 | void QWaylandGLContext::doneCurrent() |
366 | { |
367 | eglMakeCurrent(dpy: eglDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
368 | } |
369 | |
370 | void 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 | |
410 | GLuint QWaylandGLContext::defaultFramebufferObject(QPlatformSurface *surface) const |
411 | { |
412 | return static_cast<QWaylandEglWindow *>(surface)->contentFBO(); |
413 | } |
414 | |
415 | QFunctionPointer 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 | |
423 | EGLSurface QWaylandGLContext::eglSurfaceForPlatformSurface(QPlatformSurface *surface) |
424 | { |
425 | return static_cast<QWaylandEglWindow *>(surface)->eglSurface(); |
426 | } |
427 | |
428 | } |
429 | |
430 | QT_END_NAMESPACE |
431 |
Definitions
Learn Advanced QML with KDAB
Find out more