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(sender: m_display, signal: &QWaylandDisplay::reconnected, slot: [this]() { |
199 | invalidateContext(); |
200 | }); |
201 | |
202 | switch (format().renderableType()) { |
203 | case QSurfaceFormat::OpenVG: |
204 | m_api = EGL_OPENVG_API; |
205 | break; |
206 | #ifdef EGL_VERSION_1_4 |
207 | case QSurfaceFormat::OpenGL: |
208 | m_api = EGL_OPENGL_API; |
209 | break; |
210 | #endif // EGL_VERSION_1_4 |
211 | default: |
212 | m_api = EGL_OPENGL_ES_API; |
213 | break; |
214 | } |
215 | |
216 | // Create an EGL context for the decorations blitter. By using a dedicated context we don't need to make sure to not |
217 | // change the context state and we also use OpenGL ES 2 API independently to what the app is using to draw. |
218 | QList<EGLint> eglDecorationsContextAttrs = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; |
219 | m_decorationsContext = eglCreateContext(dpy: eglDisplay, config: eglConfig(), share_context: eglContext(), |
220 | attrib_list: eglDecorationsContextAttrs.constData()); |
221 | if (m_decorationsContext == EGL_NO_CONTEXT) |
222 | qWarning(msg: "QWaylandGLContext: Failed to create the decorations EGLContext. Decorations will not be drawn." ); |
223 | |
224 | EGLint a = EGL_MIN_SWAP_INTERVAL; |
225 | EGLint b = EGL_MAX_SWAP_INTERVAL; |
226 | if (!eglGetConfigAttrib(dpy: eglDisplay, config: eglConfig(), attribute: a, value: &a) |
227 | || !eglGetConfigAttrib(dpy: eglDisplay, config: eglConfig(), attribute: b, value: &b) || a > 0) { |
228 | m_supportNonBlockingSwap = false; |
229 | } |
230 | { |
231 | bool ok; |
232 | int supportNonBlockingSwap = qEnvironmentVariableIntValue(varName: "QT_WAYLAND_FORCE_NONBLOCKING_SWAP_SUPPORT" , ok: &ok); |
233 | if (ok) |
234 | m_supportNonBlockingSwap = supportNonBlockingSwap != 0; |
235 | } |
236 | if (!m_supportNonBlockingSwap) { |
237 | qWarning(catFunc: lcQpaWayland) << "Non-blocking swap buffers not supported." |
238 | << "Subsurface rendering can be affected." |
239 | << "It may also cause the event loop to freeze in some situations" ; |
240 | } |
241 | } |
242 | |
243 | EGLSurface QWaylandGLContext::createTemporaryOffscreenSurface() |
244 | { |
245 | m_wlSurface = m_display->createSurface(handle: nullptr); |
246 | m_eglWindow = wl_egl_window_create(surface: m_wlSurface, width: 1, height: 1); |
247 | #if QT_CONFIG(egl_extension_platform_wayland) |
248 | EGLSurface eglSurface = |
249 | eglCreatePlatformWindowSurface(dpy: eglDisplay(), config: eglConfig(), native_window: m_eglWindow, attrib_list: nullptr); |
250 | #else |
251 | EGLSurface eglSurface = eglCreateWindowSurface(eglDisplay(), eglConfig(), m_eglWindow, nullptr); |
252 | #endif |
253 | return eglSurface; |
254 | } |
255 | |
256 | void QWaylandGLContext::destroyTemporaryOffscreenSurface(EGLSurface eglSurface) |
257 | { |
258 | eglDestroySurface(dpy: eglDisplay(), surface: eglSurface); |
259 | wl_egl_window_destroy(egl_window: m_eglWindow); |
260 | m_eglWindow = nullptr; |
261 | wl_surface_destroy(wl_surface: m_wlSurface); |
262 | m_wlSurface = nullptr; |
263 | } |
264 | |
265 | QWaylandGLContext::~QWaylandGLContext() |
266 | { |
267 | QObject::disconnect(m_reconnectionWatcher); |
268 | delete m_blitter; |
269 | m_blitter = nullptr; |
270 | if (m_decorationsContext != EGL_NO_CONTEXT) |
271 | eglDestroyContext(dpy: eglDisplay(), ctx: m_decorationsContext); |
272 | } |
273 | |
274 | void QWaylandGLContext::beginFrame() |
275 | { |
276 | Q_ASSERT(m_currentWindow != nullptr); |
277 | m_currentWindow->beginFrame(); |
278 | } |
279 | |
280 | void QWaylandGLContext::endFrame() |
281 | { |
282 | Q_ASSERT(m_currentWindow != nullptr); |
283 | m_currentWindow->endFrame(); |
284 | } |
285 | |
286 | bool QWaylandGLContext::makeCurrent(QPlatformSurface *surface) |
287 | { |
288 | if (!isValid()) { |
289 | return false; |
290 | } |
291 | |
292 | // in QWaylandGLContext() we called eglBindAPI with the correct value. However, |
293 | // eglBindAPI's documentation says: |
294 | // "eglBindAPI defines the current rendering API for EGL in the thread it is called from" |
295 | // Since makeCurrent() can be called from a different thread than the one we created the |
296 | // context in make sure to call eglBindAPI in the correct thread. |
297 | if (eglQueryAPI() != m_api) { |
298 | eglBindAPI(api: m_api); |
299 | } |
300 | |
301 | m_currentWindow = static_cast<QWaylandEglWindow *>(surface); |
302 | EGLSurface eglSurface = m_currentWindow->eglSurface(); |
303 | |
304 | if (!m_currentWindow->needToUpdateContentFBO() && (eglSurface != EGL_NO_SURFACE)) { |
305 | if (!eglMakeCurrent(dpy: eglDisplay(), draw: eglSurface, read: eglSurface, ctx: eglContext())) { |
306 | qWarning(msg: "QWaylandGLContext::makeCurrent: eglError: %#x, this: %p \n" , eglGetError(), this); |
307 | return false; |
308 | } |
309 | return true; |
310 | } |
311 | |
312 | if (m_currentWindow->isExposed()) |
313 | m_currentWindow->setCanResize(false); |
314 | if (m_decorationsContext != EGL_NO_CONTEXT && !m_currentWindow->decoration()) |
315 | m_currentWindow->createDecoration(); |
316 | |
317 | if (eglSurface == EGL_NO_SURFACE) { |
318 | m_currentWindow->updateSurface(create: true); |
319 | eglSurface = m_currentWindow->eglSurface(); |
320 | } |
321 | |
322 | if (!eglMakeCurrent(dpy: eglDisplay(), draw: eglSurface, read: eglSurface, ctx: eglContext())) { |
323 | qWarning(msg: "QWaylandGLContext::makeCurrent: eglError: %#x, this: %p \n" , eglGetError(), this); |
324 | m_currentWindow->setCanResize(true); |
325 | return false; |
326 | } |
327 | |
328 | //### setCurrentContext will be called in QOpenGLContext::makeCurrent after this function |
329 | // returns, but that's too late, as we need a current context in order to bind the content FBO. |
330 | QOpenGLContextPrivate::setCurrentContext(context()); |
331 | m_currentWindow->bindContentFBO(); |
332 | |
333 | return true; |
334 | } |
335 | |
336 | void QWaylandGLContext::doneCurrent() |
337 | { |
338 | eglMakeCurrent(dpy: eglDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
339 | } |
340 | |
341 | void QWaylandGLContext::swapBuffers(QPlatformSurface *surface) |
342 | { |
343 | QWaylandEglWindow *window = static_cast<QWaylandEglWindow *>(surface); |
344 | |
345 | EGLSurface eglSurface = window->eglSurface(); |
346 | |
347 | if (window->decoration()) { |
348 | if (m_api != EGL_OPENGL_ES_API) |
349 | eglBindAPI(EGL_OPENGL_ES_API); |
350 | |
351 | // save the current EGL content and surface to set it again after the blitter is done |
352 | EGLDisplay currentDisplay = eglGetCurrentDisplay(); |
353 | EGLContext currentContext = eglGetCurrentContext(); |
354 | EGLSurface currentSurfaceDraw = eglGetCurrentSurface(EGL_DRAW); |
355 | EGLSurface currentSurfaceRead = eglGetCurrentSurface(EGL_READ); |
356 | eglMakeCurrent(dpy: eglDisplay(), draw: eglSurface, read: eglSurface, ctx: m_decorationsContext); |
357 | |
358 | if (!m_blitter) |
359 | m_blitter = new DecorationsBlitter(this); |
360 | m_blitter->blit(window); |
361 | |
362 | if (m_api != EGL_OPENGL_ES_API) |
363 | eglBindAPI(api: m_api); |
364 | eglMakeCurrent(dpy: currentDisplay, draw: currentSurfaceDraw, read: currentSurfaceRead, ctx: currentContext); |
365 | } |
366 | |
367 | int swapInterval = m_supportNonBlockingSwap ? 0 : format().swapInterval(); |
368 | eglSwapInterval(dpy: eglDisplay(), interval: swapInterval); |
369 | if (swapInterval == 0 && format().swapInterval() > 0) { |
370 | // Emulating a blocking swap |
371 | glFlush(); // Flush before waiting so we can swap more quickly when the frame event arrives |
372 | window->waitForFrameSync(timeout: 100); |
373 | } |
374 | window->handleUpdate(); |
375 | eglSwapBuffers(dpy: eglDisplay(), surface: eglSurface); |
376 | |
377 | window->setCanResize(true); |
378 | } |
379 | |
380 | GLuint QWaylandGLContext::defaultFramebufferObject(QPlatformSurface *surface) const |
381 | { |
382 | return static_cast<QWaylandEglWindow *>(surface)->contentFBO(); |
383 | } |
384 | |
385 | QFunctionPointer QWaylandGLContext::getProcAddress(const char *procName) |
386 | { |
387 | QFunctionPointer proc = (QFunctionPointer) eglGetProcAddress(procname: procName); |
388 | if (!proc) |
389 | proc = (QFunctionPointer) dlsym(RTLD_DEFAULT, name: procName); |
390 | return proc; |
391 | } |
392 | |
393 | EGLSurface QWaylandGLContext::eglSurfaceForPlatformSurface(QPlatformSurface *surface) |
394 | { |
395 | return static_cast<QWaylandEglWindow *>(surface)->eglSurface(); |
396 | } |
397 | |
398 | } |
399 | |
400 | QT_END_NAMESPACE |
401 | |