1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtWaylandCompositor module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 or (at your option) any later version
20 ** approved by the KDE Free Qt Foundation. The licenses are as published by
21 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29
30 #include "waylandeglstreamintegration.h"
31 #include "waylandeglstreamcontroller.h"
32
33 #include <QtWaylandCompositor/QWaylandCompositor>
34 #include <QtGui/QGuiApplication>
35 #include <QtGui/QOpenGLContext>
36 #include <QtGui/QOpenGLTexture>
37 #include <QtGui/QOffscreenSurface>
38
39 #include <QtEglSupport/private/qeglstreamconvenience_p.h>
40 #include <qpa/qplatformnativeinterface.h>
41
42 #include <QtWaylandCompositor/private/qwaylandcompositor_p.h>
43 #include <QtWaylandCompositor/private/qwlbuffermanager_p.h>
44
45 #include <EGL/egl.h>
46 #include <EGL/eglext.h>
47 #include <unistd.h>
48
49 #ifndef GL_TEXTURE_EXTERNAL_OES
50 #define GL_TEXTURE_EXTERNAL_OES 0x8D65
51 #endif
52
53 #ifndef EGL_WAYLAND_BUFFER_WL
54 #define EGL_WAYLAND_BUFFER_WL 0x31D5
55 #endif
56
57 #ifndef EGL_WAYLAND_EGLSTREAM_WL
58 #define EGL_WAYLAND_EGLSTREAM_WL 0x334B
59 #endif
60
61 #ifndef EGL_WAYLAND_PLANE_WL
62 #define EGL_WAYLAND_PLANE_WL 0x31D6
63 #endif
64
65 #ifndef EGL_WAYLAND_Y_INVERTED_WL
66 #define EGL_WAYLAND_Y_INVERTED_WL 0x31DB
67 #endif
68
69 #ifndef EGL_TEXTURE_RGB
70 #define EGL_TEXTURE_RGB 0x305D
71 #endif
72
73 #ifndef EGL_TEXTURE_RGBA
74 #define EGL_TEXTURE_RGBA 0x305E
75 #endif
76
77 #ifndef EGL_TEXTURE_EXTERNAL_WL
78 #define EGL_TEXTURE_EXTERNAL_WL 0x31DA
79 #endif
80
81 #ifndef EGL_TEXTURE_Y_U_V_WL
82 #define EGL_TEXTURE_Y_U_V_WL 0x31D7
83 #endif
84
85 #ifndef EGL_TEXTURE_Y_UV_WL
86 #define EGL_TEXTURE_Y_UV_WL 0x31D8
87 #endif
88
89 #ifndef EGL_TEXTURE_Y_XUXV_WL
90 #define EGL_TEXTURE_Y_XUXV_WL 0x31D9
91 #endif
92
93 #ifndef EGL_PLATFORM_X11_KHR
94 #define EGL_PLATFORM_X11_KHR 0x31D5
95 #endif
96
97 QT_BEGIN_NAMESPACE
98
99 /* Needed for compatibility with Mesa older than 10.0. */
100 typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYWAYLANDBUFFERWL_compat) (EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value);
101
102 #ifndef EGL_WL_bind_wayland_display
103 typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDWAYLANDDISPLAYWL) (EGLDisplay dpy, struct wl_display *display);
104 typedef EGLBoolean (EGLAPIENTRYP PFNEGLUNBINDWAYLANDDISPLAYWL) (EGLDisplay dpy, struct wl_display *display);
105 #endif
106
107 static const char *
egl_error_string(EGLint code)108 egl_error_string(EGLint code)
109 {
110 #define MYERRCODE(x) case x: return #x;
111 switch (code) {
112 MYERRCODE(EGL_SUCCESS)
113 MYERRCODE(EGL_NOT_INITIALIZED)
114 MYERRCODE(EGL_BAD_ACCESS)
115 MYERRCODE(EGL_BAD_ALLOC)
116 MYERRCODE(EGL_BAD_ATTRIBUTE)
117 MYERRCODE(EGL_BAD_CONTEXT)
118 MYERRCODE(EGL_BAD_CONFIG)
119 MYERRCODE(EGL_BAD_CURRENT_SURFACE)
120 MYERRCODE(EGL_BAD_DISPLAY)
121 MYERRCODE(EGL_BAD_SURFACE)
122 MYERRCODE(EGL_BAD_MATCH)
123 MYERRCODE(EGL_BAD_PARAMETER)
124 MYERRCODE(EGL_BAD_NATIVE_PIXMAP)
125 MYERRCODE(EGL_BAD_NATIVE_WINDOW)
126 MYERRCODE(EGL_CONTEXT_LOST)
127 default:
128 return "unknown";
129 }
130 #undef MYERRCODE
131 }
132
133 struct BufferState
134 {
135 BufferState() = default;
136
137 EGLint egl_format = EGL_TEXTURE_EXTERNAL_WL;
138 QOpenGLTexture *textures[3] = {};
139 EGLStreamKHR egl_stream = EGL_NO_STREAM_KHR;
140
141 bool isYInverted = false;
142 QSize size;
143 };
144
145 class WaylandEglStreamClientBufferIntegrationPrivate
146 {
147 public:
148 WaylandEglStreamClientBufferIntegrationPrivate() = default;
149
150 bool ensureContext();
151 bool initEglStream(WaylandEglStreamClientBuffer *buffer, struct ::wl_resource *bufferHandle);
152 void handleEglstreamTexture(WaylandEglStreamClientBuffer *buffer);
deleteGLTextureWhenPossible(QOpenGLTexture * texture)153 void deleteGLTextureWhenPossible(QOpenGLTexture *texture) { orphanedTextures << texture; }
154 void deleteOrphanedTextures();
155
156 EGLDisplay egl_display = EGL_NO_DISPLAY;
157 bool display_bound = false;
158 QOffscreenSurface *offscreenSurface = nullptr;
159 QOpenGLContext *localContext = nullptr;
160 QVector<QOpenGLTexture *> orphanedTextures;
161
162 WaylandEglStreamController *eglStreamController = nullptr;
163
164 PFNEGLBINDWAYLANDDISPLAYWL egl_bind_wayland_display = nullptr;
165 PFNEGLUNBINDWAYLANDDISPLAYWL egl_unbind_wayland_display = nullptr;
166 PFNEGLQUERYWAYLANDBUFFERWL_compat egl_query_wayland_buffer = nullptr;
167
168 QEGLStreamConvenience *funcs = nullptr;
get(WaylandEglStreamClientBufferIntegration * integration)169 static WaylandEglStreamClientBufferIntegrationPrivate *get(WaylandEglStreamClientBufferIntegration *integration) {
170 return shuttingDown ? nullptr : integration->d_ptr.data();
171 }
172
173 static bool shuttingDown;
174 };
175
176 bool WaylandEglStreamClientBufferIntegrationPrivate::shuttingDown = false;
177
deleteOrphanedTextures()178 void WaylandEglStreamClientBufferIntegrationPrivate::deleteOrphanedTextures()
179 {
180 Q_ASSERT(QOpenGLContext::currentContext());
181 qDeleteAll(orphanedTextures);
182 orphanedTextures.clear();
183 }
184
ensureContext()185 bool WaylandEglStreamClientBufferIntegrationPrivate::ensureContext()
186 {
187 bool localContextNeeded = false;
188 if (!QOpenGLContext::currentContext()) {
189 if (!localContext && QOpenGLContext::globalShareContext()) {
190 localContext = new QOpenGLContext;
191 localContext->setShareContext(QOpenGLContext::globalShareContext());
192 localContext->create();
193 }
194 if (localContext) {
195 if (!offscreenSurface) {
196 offscreenSurface = new QOffscreenSurface;
197 offscreenSurface->setFormat(localContext->format());
198 offscreenSurface->create();
199 }
200 localContext->makeCurrent(offscreenSurface);
201 localContextNeeded = true;
202 }
203 }
204 return localContextNeeded;
205 }
206
207
initEglStream(WaylandEglStreamClientBuffer * buffer,wl_resource * bufferHandle)208 bool WaylandEglStreamClientBufferIntegrationPrivate::initEglStream(WaylandEglStreamClientBuffer *buffer, wl_resource *bufferHandle)
209 {
210 BufferState &state = *buffer->d;
211 state.egl_format = EGL_TEXTURE_EXTERNAL_WL;
212 state.isYInverted = false;
213
214 EGLNativeFileDescriptorKHR streamFd = EGL_NO_FILE_DESCRIPTOR_KHR;
215
216 if (egl_query_wayland_buffer(egl_display, bufferHandle, EGL_WAYLAND_BUFFER_WL, &streamFd)) {
217 state.egl_stream = funcs->create_stream_from_file_descriptor(egl_display, streamFd);
218 close(streamFd);
219 } else {
220 EGLAttrib stream_attribs[] = {
221 EGL_WAYLAND_EGLSTREAM_WL, (EGLAttrib)bufferHandle,
222 EGL_NONE
223 };
224 state.egl_stream = funcs->create_stream_attrib_nv(egl_display, stream_attribs);
225 }
226
227 if (state.egl_stream == EGL_NO_STREAM_KHR) {
228 qWarning("%s:%d: eglCreateStreamFromFileDescriptorKHR failed: 0x%x", Q_FUNC_INFO, __LINE__, eglGetError());
229 return false;
230 }
231
232 bool usingLocalContext = ensureContext();
233
234 Q_ASSERT(QOpenGLContext::currentContext());
235
236 auto texture = new QOpenGLTexture(static_cast<QOpenGLTexture::Target>(GL_TEXTURE_EXTERNAL_OES));
237 texture->create();
238 state.textures[0] = texture; // TODO: support multiple planes
239
240 texture->bind();
241
242 auto newStream = funcs->stream_consumer_gltexture(egl_display, state.egl_stream);
243 if (usingLocalContext)
244 localContext->doneCurrent();
245
246 if (!newStream) {
247 EGLint code = eglGetError();
248 qWarning() << "Could not initialize EGLStream:" << egl_error_string(code) << Qt::hex << (long)code;
249 funcs->destroy_stream(egl_display, state.egl_stream);
250 state.egl_stream = EGL_NO_STREAM_KHR;
251 return false;
252 }
253 return true;
254 }
255
handleEglstreamTexture(WaylandEglStreamClientBuffer * buffer)256 void WaylandEglStreamClientBufferIntegrationPrivate::handleEglstreamTexture(WaylandEglStreamClientBuffer *buffer)
257 {
258 bool usingLocalContext = ensureContext();
259
260 BufferState &state = *buffer->d;
261 auto texture = state.textures[0];
262
263 // EGLStream requires calling acquire on every frame.
264 texture->bind();
265 EGLint stream_state;
266 funcs->query_stream(egl_display, state.egl_stream, EGL_STREAM_STATE_KHR, &stream_state);
267
268 if (stream_state == EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR) {
269 if (funcs->stream_consumer_acquire(egl_display, state.egl_stream) != EGL_TRUE)
270 qWarning("%s:%d: eglStreamConsumerAcquireKHR failed: 0x%x", Q_FUNC_INFO, __LINE__, eglGetError());
271 }
272
273 if (usingLocalContext)
274 localContext->doneCurrent();
275 }
276
277
WaylandEglStreamClientBufferIntegration()278 WaylandEglStreamClientBufferIntegration::WaylandEglStreamClientBufferIntegration()
279 : d_ptr(new WaylandEglStreamClientBufferIntegrationPrivate)
280 {
281 }
282
~WaylandEglStreamClientBufferIntegration()283 WaylandEglStreamClientBufferIntegration::~WaylandEglStreamClientBufferIntegration()
284 {
285 WaylandEglStreamClientBufferIntegrationPrivate::shuttingDown = true;
286 }
287
attachEglStreamConsumer(struct::wl_resource * wl_surface,struct::wl_resource * wl_buffer)288 void WaylandEglStreamClientBufferIntegration::attachEglStreamConsumer(struct ::wl_resource *wl_surface, struct ::wl_resource *wl_buffer)
289 {
290 Q_D(WaylandEglStreamClientBufferIntegration);
291 Q_UNUSED(wl_surface);
292
293 // NOTE: must use getBuffer to create the buffer here, so the buffer will end up in the buffer manager's hash
294
295 auto *bufferManager = QWaylandCompositorPrivate::get(m_compositor)->bufferManager();
296 auto *clientBuffer = static_cast<WaylandEglStreamClientBuffer*>(bufferManager->getBuffer(wl_buffer));
297
298 d->initEglStream(clientBuffer, wl_buffer);
299 }
300
initializeHardware(struct wl_display * display)301 void WaylandEglStreamClientBufferIntegration::initializeHardware(struct wl_display *display)
302 {
303 Q_D(WaylandEglStreamClientBufferIntegration);
304
305 const bool ignoreBindDisplay = !qgetenv("QT_WAYLAND_IGNORE_BIND_DISPLAY").isEmpty();
306
307 QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface();
308 if (!nativeInterface) {
309 qWarning("QtCompositor: Failed to initialize EGL display. No native platform interface available.");
310 return;
311 }
312
313 d->egl_display = nativeInterface->nativeResourceForIntegration("EglDisplay");
314 if (!d->egl_display) {
315 qWarning("QtCompositor: Failed to initialize EGL display. Could not get EglDisplay for window.");
316 return;
317 }
318
319 const char *extensionString = eglQueryString(d->egl_display, EGL_EXTENSIONS);
320 if ((!extensionString || !strstr(extensionString, "EGL_WL_bind_wayland_display")) && !ignoreBindDisplay) {
321 qWarning("QtCompositor: Failed to initialize EGL display. There is no EGL_WL_bind_wayland_display extension.");
322 return;
323 }
324
325 d->egl_bind_wayland_display = reinterpret_cast<PFNEGLBINDWAYLANDDISPLAYWL>(eglGetProcAddress("eglBindWaylandDisplayWL"));
326 d->egl_unbind_wayland_display = reinterpret_cast<PFNEGLUNBINDWAYLANDDISPLAYWL>(eglGetProcAddress("eglUnbindWaylandDisplayWL"));
327 if ((!d->egl_bind_wayland_display || !d->egl_unbind_wayland_display) && !ignoreBindDisplay) {
328 qWarning("QtCompositor: Failed to initialize EGL display. Could not find eglBindWaylandDisplayWL and eglUnbindWaylandDisplayWL.");
329 return;
330 }
331
332 d->egl_query_wayland_buffer = reinterpret_cast<PFNEGLQUERYWAYLANDBUFFERWL_compat>(eglGetProcAddress("eglQueryWaylandBufferWL"));
333 if (!d->egl_query_wayland_buffer) {
334 qWarning("QtCompositor: Failed to initialize EGL display. Could not find eglQueryWaylandBufferWL.");
335 return;
336 }
337
338 if (d->egl_bind_wayland_display && d->egl_unbind_wayland_display) {
339 d->display_bound = d->egl_bind_wayland_display(d->egl_display, display);
340 if (!d->display_bound) {
341 if (!ignoreBindDisplay) {
342 qWarning("QtCompositor: Failed to initialize EGL display. Could not bind Wayland display.");
343 return;
344 } else {
345 qWarning("QtCompositor: Could not bind Wayland display. Ignoring.");
346 }
347 }
348 }
349
350 d->eglStreamController = new WaylandEglStreamController(display, this);
351
352 d->funcs = new QEGLStreamConvenience;
353 d->funcs->initialize(d->egl_display);
354 }
355
createBufferFor(wl_resource * buffer)356 QtWayland::ClientBuffer *WaylandEglStreamClientBufferIntegration::createBufferFor(wl_resource *buffer)
357 {
358 if (wl_shm_buffer_get(buffer))
359 return nullptr;
360
361 return new WaylandEglStreamClientBuffer(this, buffer);
362 }
363
364
WaylandEglStreamClientBuffer(WaylandEglStreamClientBufferIntegration * integration,wl_resource * buffer)365 WaylandEglStreamClientBuffer::WaylandEglStreamClientBuffer(WaylandEglStreamClientBufferIntegration *integration, wl_resource *buffer)
366 : ClientBuffer(buffer)
367 , m_integration(integration)
368 {
369 auto *p = WaylandEglStreamClientBufferIntegrationPrivate::get(m_integration);
370 d = new BufferState;
371 if (buffer && !wl_shm_buffer_get(buffer)) {
372 EGLint width, height;
373 p->egl_query_wayland_buffer(p->egl_display, buffer, EGL_WIDTH, &width);
374 p->egl_query_wayland_buffer(p->egl_display, buffer, EGL_HEIGHT, &height);
375 d->size = QSize(width, height);
376 }
377 }
378
~WaylandEglStreamClientBuffer()379 WaylandEglStreamClientBuffer::~WaylandEglStreamClientBuffer()
380 {
381 auto *p = WaylandEglStreamClientBufferIntegrationPrivate::get(m_integration);
382
383 if (p) {
384 if (d->egl_stream)
385 p->funcs->destroy_stream(p->egl_display, d->egl_stream);
386
387 for (auto *texture : d->textures)
388 p->deleteGLTextureWhenPossible(texture);
389 }
390 delete d;
391 }
392
393
bufferFormatEgl() const394 QWaylandBufferRef::BufferFormatEgl WaylandEglStreamClientBuffer::bufferFormatEgl() const
395 {
396 return QWaylandBufferRef::BufferFormatEgl_EXTERNAL_OES;
397 }
398
399
size() const400 QSize WaylandEglStreamClientBuffer::size() const
401 {
402 return d->size;
403 }
404
origin() const405 QWaylandSurface::Origin WaylandEglStreamClientBuffer::origin() const
406 {
407 return d->isYInverted ? QWaylandSurface::OriginTopLeft : QWaylandSurface::OriginBottomLeft;
408 }
409
toOpenGlTexture(int plane)410 QOpenGLTexture *WaylandEglStreamClientBuffer::toOpenGlTexture(int plane)
411 {
412 auto *p = WaylandEglStreamClientBufferIntegrationPrivate::get(m_integration);
413 // At this point we should have a valid OpenGL context, so it's safe to destroy textures
414 p->deleteOrphanedTextures();
415
416 if (!m_buffer)
417 return nullptr;
418
419 return d->textures[plane];
420 }
421
setCommitted(QRegion & damage)422 void WaylandEglStreamClientBuffer::setCommitted(QRegion &damage)
423 {
424 ClientBuffer::setCommitted(damage);
425 auto *p = WaylandEglStreamClientBufferIntegrationPrivate::get(m_integration);
426 p->handleEglstreamTexture(this);
427 }
428
429 QT_END_NAMESPACE
430