1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "waylandeglstreamintegration.h"
5#include "waylandeglstreamcontroller.h"
6
7#include <QtWaylandCompositor/QWaylandCompositor>
8#include <QtOpenGL/QOpenGLTexture>
9#include <QtGui/QGuiApplication>
10#include <QtGui/QOpenGLContext>
11#include <QtGui/QOffscreenSurface>
12#include <QtCore/QMutexLocker>
13
14#include <QtGui/private/qeglstreamconvenience_p.h>
15#include <qpa/qplatformnativeinterface.h>
16
17#include <QtWaylandCompositor/private/qwaylandcompositor_p.h>
18#include <QtWaylandCompositor/private/qwlbuffermanager_p.h>
19
20#include <EGL/egl.h>
21#include <EGL/eglext.h>
22#include <unistd.h>
23
24#ifndef GL_TEXTURE_EXTERNAL_OES
25#define GL_TEXTURE_EXTERNAL_OES 0x8D65
26#endif
27
28#ifndef EGL_WAYLAND_BUFFER_WL
29#define EGL_WAYLAND_BUFFER_WL 0x31D5
30#endif
31
32#ifndef EGL_WAYLAND_EGLSTREAM_WL
33#define EGL_WAYLAND_EGLSTREAM_WL 0x334B
34#endif
35
36#ifndef EGL_WAYLAND_PLANE_WL
37#define EGL_WAYLAND_PLANE_WL 0x31D6
38#endif
39
40#ifndef EGL_WAYLAND_Y_INVERTED_WL
41#define EGL_WAYLAND_Y_INVERTED_WL 0x31DB
42#endif
43
44#ifndef EGL_TEXTURE_RGB
45#define EGL_TEXTURE_RGB 0x305D
46#endif
47
48#ifndef EGL_TEXTURE_RGBA
49#define EGL_TEXTURE_RGBA 0x305E
50#endif
51
52#ifndef EGL_TEXTURE_EXTERNAL_WL
53#define EGL_TEXTURE_EXTERNAL_WL 0x31DA
54#endif
55
56#ifndef EGL_TEXTURE_Y_U_V_WL
57#define EGL_TEXTURE_Y_U_V_WL 0x31D7
58#endif
59
60#ifndef EGL_TEXTURE_Y_UV_WL
61#define EGL_TEXTURE_Y_UV_WL 0x31D8
62#endif
63
64#ifndef EGL_TEXTURE_Y_XUXV_WL
65#define EGL_TEXTURE_Y_XUXV_WL 0x31D9
66#endif
67
68#ifndef EGL_PLATFORM_X11_KHR
69#define EGL_PLATFORM_X11_KHR 0x31D5
70#endif
71
72QT_BEGIN_NAMESPACE
73
74/* Needed for compatibility with Mesa older than 10.0. */
75typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYWAYLANDBUFFERWL_compat) (EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value);
76
77#ifndef EGL_WL_bind_wayland_display
78typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDWAYLANDDISPLAYWL) (EGLDisplay dpy, struct wl_display *display);
79typedef EGLBoolean (EGLAPIENTRYP PFNEGLUNBINDWAYLANDDISPLAYWL) (EGLDisplay dpy, struct wl_display *display);
80#endif
81
82static const char *
83egl_error_string(EGLint code)
84{
85#define MYERRCODE(x) case x: return #x;
86 switch (code) {
87 MYERRCODE(EGL_SUCCESS)
88 MYERRCODE(EGL_NOT_INITIALIZED)
89 MYERRCODE(EGL_BAD_ACCESS)
90 MYERRCODE(EGL_BAD_ALLOC)
91 MYERRCODE(EGL_BAD_ATTRIBUTE)
92 MYERRCODE(EGL_BAD_CONTEXT)
93 MYERRCODE(EGL_BAD_CONFIG)
94 MYERRCODE(EGL_BAD_CURRENT_SURFACE)
95 MYERRCODE(EGL_BAD_DISPLAY)
96 MYERRCODE(EGL_BAD_SURFACE)
97 MYERRCODE(EGL_BAD_MATCH)
98 MYERRCODE(EGL_BAD_PARAMETER)
99 MYERRCODE(EGL_BAD_NATIVE_PIXMAP)
100 MYERRCODE(EGL_BAD_NATIVE_WINDOW)
101 MYERRCODE(EGL_CONTEXT_LOST)
102 default:
103 return "unknown";
104 }
105#undef MYERRCODE
106}
107
108struct BufferState
109{
110 BufferState() = default;
111
112 EGLint egl_format = EGL_TEXTURE_EXTERNAL_WL;
113 QOpenGLTexture *textures[3] = {nullptr, nullptr, nullptr};
114 QOpenGLContext *texturesContext[3] = {nullptr, nullptr, nullptr};
115 QMetaObject::Connection texturesAboutToBeDestroyedConnection[3] = {QMetaObject::Connection(), QMetaObject::Connection(), QMetaObject::Connection()};
116 QMutex texturesLock;
117
118 EGLStreamKHR egl_stream = EGL_NO_STREAM_KHR;
119
120 bool isYInverted = false;
121 QSize size;
122};
123
124class WaylandEglStreamClientBufferIntegrationPrivate
125{
126public:
127 WaylandEglStreamClientBufferIntegrationPrivate() = default;
128
129 bool ensureContext();
130 bool initEglStream(WaylandEglStreamClientBuffer *buffer, struct ::wl_resource *bufferHandle);
131 void setupBufferAndCleanup(BufferState *bs, QOpenGLTexture *texture, int plane);
132 void handleEglstreamTexture(WaylandEglStreamClientBuffer *buffer);
133 void deleteGLTextureWhenPossible(QOpenGLTexture *texture, QOpenGLContext *ctx);
134 void deleteOrphanedTextures();
135 void deleteSpecificOrphanedTexture(QOpenGLTexture *texture);
136
137 EGLDisplay egl_display = EGL_NO_DISPLAY;
138 bool display_bound = false;
139 ::wl_display *wlDisplay = nullptr;
140 QOffscreenSurface *offscreenSurface = nullptr;
141 QOpenGLContext *localContext = nullptr;
142
143 QMutex orphanedTexturesLock;
144 QList<QOpenGLTexture *> orphanedTextures;
145 QList<QMetaObject::Connection> orphanedTexturesAboutToBeDestroyedConnection;
146
147 WaylandEglStreamController *eglStreamController = nullptr;
148
149 PFNEGLBINDWAYLANDDISPLAYWL egl_bind_wayland_display = nullptr;
150 PFNEGLUNBINDWAYLANDDISPLAYWL egl_unbind_wayland_display = nullptr;
151 PFNEGLQUERYWAYLANDBUFFERWL_compat egl_query_wayland_buffer = nullptr;
152
153 QEGLStreamConvenience *funcs = nullptr;
154 static WaylandEglStreamClientBufferIntegrationPrivate *get(WaylandEglStreamClientBufferIntegration *integration) {
155 return shuttingDown ? nullptr : integration->d_ptr.data();
156 }
157
158 static bool shuttingDown;
159};
160
161bool WaylandEglStreamClientBufferIntegrationPrivate::shuttingDown = false;
162
163
164void WaylandEglStreamClientBufferIntegrationPrivate::deleteGLTextureWhenPossible(QOpenGLTexture *texture, QOpenGLContext *ctx) {
165
166 QMutexLocker locker(&orphanedTexturesLock);
167
168 Q_ASSERT(orphanedTextures.size() == orphanedTexturesAboutToBeDestroyedConnection.size());
169
170 orphanedTextures << texture;
171 orphanedTexturesAboutToBeDestroyedConnection << QObject::connect(sender: ctx, signal: &QOpenGLContext::aboutToBeDestroyed,
172 context: ctx, slot: [this, texture]() {
173 this->deleteSpecificOrphanedTexture(texture);
174 }, type: Qt::DirectConnection);
175}
176
177void WaylandEglStreamClientBufferIntegrationPrivate::deleteOrphanedTextures()
178{
179 Q_ASSERT(QOpenGLContext::currentContext());
180
181 QMutexLocker locker(&orphanedTexturesLock);
182
183 for (int i=0; i < orphanedTextures.size(); i++) {
184 qCDebug(qLcWaylandCompositorHardwareIntegration)
185 << Q_FUNC_INFO << " about to delete a texture: "
186 << (void*)orphanedTextures[i];
187 }
188
189 qDeleteAll(c: orphanedTextures);
190
191 for (QMetaObject::Connection con : orphanedTexturesAboutToBeDestroyedConnection)
192 QObject::disconnect(con);
193
194 orphanedTexturesAboutToBeDestroyedConnection.clear();
195 orphanedTextures.clear();
196}
197
198void WaylandEglStreamClientBufferIntegrationPrivate::deleteSpecificOrphanedTexture(QOpenGLTexture *texture)
199{
200 Q_ASSERT(orphanedTextures.size() == orphanedTexturesAboutToBeDestroyedConnection.size());
201
202 QMutexLocker locker(&orphanedTexturesLock);
203
204 // In this case, deleteOrphanedTextures was called while we entered (see lock!) this function!
205 if (orphanedTextures.length()==0) {
206 qCWarning(qLcWaylandCompositorHardwareIntegration)
207 << Q_FUNC_INFO
208 << "Looks like deleteOrphanedTextures() and this function where called simultaneously!"
209 << "This might cause issues!";
210 return;
211 }
212
213 int i = orphanedTextures.indexOf(t: texture);
214 Q_ASSERT(i!=-1); // If it isn't empty (see above if), then it should be guaranteed to still contain this texture
215
216 orphanedTextures.removeAt(i);
217 QMetaObject::Connection con = orphanedTexturesAboutToBeDestroyedConnection.takeAt(i);
218
219 QObject::disconnect(con);
220 delete texture;
221
222 qCDebug(qLcWaylandCompositorHardwareIntegration)
223 << Q_FUNC_INFO
224 << "texture deleted due to QOpenGLContext::aboutToBeDestroyed!"
225 << "Pointer (now dead) was:" << (void*)texture;
226}
227
228bool WaylandEglStreamClientBufferIntegrationPrivate::ensureContext()
229{
230 bool localContextNeeded = false;
231 if (!QOpenGLContext::currentContext()) {
232 if (!localContext && QOpenGLContext::globalShareContext()) {
233 localContext = new QOpenGLContext;
234 localContext->setShareContext(QOpenGLContext::globalShareContext());
235 localContext->create();
236 }
237 if (localContext) {
238 if (!offscreenSurface) {
239 offscreenSurface = new QOffscreenSurface;
240 offscreenSurface->setFormat(localContext->format());
241 offscreenSurface->create();
242 }
243 localContext->makeCurrent(surface: offscreenSurface);
244 localContextNeeded = true;
245 }
246 }
247 return localContextNeeded;
248}
249
250
251void WaylandEglStreamClientBufferIntegrationPrivate::setupBufferAndCleanup(BufferState *bs, QOpenGLTexture *texture, int plane)
252{
253 QMutexLocker locker(&bs->texturesLock);
254
255 bs->textures[plane] = texture;
256 bs->texturesContext[plane] = QOpenGLContext::currentContext();
257
258 Q_ASSERT(bs->texturesContext[plane] != nullptr);
259
260 qCDebug(qLcWaylandCompositorHardwareIntegration)
261 << Q_FUNC_INFO
262 << "(eglstream) creating a cleanup-lambda for QOpenGLContext::aboutToBeDestroyed!"
263 << ", texture: " << bs->textures[plane]
264 << ", ctx: " << (void*)bs->texturesContext[plane];
265
266 bs->texturesAboutToBeDestroyedConnection[plane] =
267 QObject::connect(sender: bs->texturesContext[plane], signal: &QOpenGLContext::aboutToBeDestroyed,
268 context: bs->texturesContext[plane], slot: [bs, plane]() {
269
270 QMutexLocker locker(&bs->texturesLock);
271
272 // See above lock - there is a chance that this has already been removed from textures[plane]!
273 // Furthermore, we can trust that all the rest (e.g. disconnect) has also been properly executed!
274 if (bs->textures[plane] == nullptr)
275 return;
276
277 delete bs->textures[plane];
278
279 qCDebug(qLcWaylandCompositorHardwareIntegration)
280 << Q_FUNC_INFO
281 << "texture deleted due to QOpenGLContext::aboutToBeDestroyed!"
282 << "Pointer (now dead) was:" << (void*)(bs->textures[plane])
283 << " Associated context (about to die too) is: " << (void*)(bs->texturesContext[plane]);
284
285 bs->textures[plane] = nullptr;
286 bs->texturesContext[plane] = nullptr;
287
288 QObject::disconnect(bs->texturesAboutToBeDestroyedConnection[plane]);
289 bs->texturesAboutToBeDestroyedConnection[plane] = QMetaObject::Connection();
290
291 }, type: Qt::DirectConnection);
292}
293
294bool WaylandEglStreamClientBufferIntegrationPrivate::initEglStream(WaylandEglStreamClientBuffer *buffer, wl_resource *bufferHandle)
295{
296 BufferState &state = *buffer->d;
297 state.egl_format = EGL_TEXTURE_EXTERNAL_WL;
298 state.isYInverted = false;
299
300 EGLNativeFileDescriptorKHR streamFd = EGL_NO_FILE_DESCRIPTOR_KHR;
301
302 if (egl_query_wayland_buffer(egl_display, bufferHandle, EGL_WAYLAND_BUFFER_WL, &streamFd)) {
303 state.egl_stream = funcs->create_stream_from_file_descriptor(egl_display, streamFd);
304 close(fd: streamFd);
305 } else {
306 EGLAttrib stream_attribs[] = {
307 EGL_WAYLAND_EGLSTREAM_WL, (EGLAttrib)bufferHandle,
308 EGL_NONE
309 };
310 state.egl_stream = funcs->create_stream_attrib_nv(egl_display, stream_attribs);
311 }
312
313 if (state.egl_stream == EGL_NO_STREAM_KHR) {
314 qWarning(msg: "%s:%d: eglCreateStreamFromFileDescriptorKHR failed: 0x%x", Q_FUNC_INFO, __LINE__, eglGetError());
315 return false;
316 }
317
318 bool usingLocalContext = ensureContext();
319
320 Q_ASSERT(QOpenGLContext::currentContext());
321
322 auto texture = new QOpenGLTexture(static_cast<QOpenGLTexture::Target>(GL_TEXTURE_EXTERNAL_OES));
323 texture->create();
324 setupBufferAndCleanup(bs: buffer->d, texture, plane: 0);
325
326 texture->bind();
327
328 auto newStream = funcs->stream_consumer_gltexture(egl_display, state.egl_stream);
329 if (usingLocalContext)
330 localContext->doneCurrent();
331
332 if (!newStream) {
333 EGLint code = eglGetError();
334 qWarning() << "Could not initialize EGLStream:" << egl_error_string(code) << Qt::hex << (long)code;
335 funcs->destroy_stream(egl_display, state.egl_stream);
336 state.egl_stream = EGL_NO_STREAM_KHR;
337 return false;
338 }
339 return true;
340}
341
342void WaylandEglStreamClientBufferIntegrationPrivate::handleEglstreamTexture(WaylandEglStreamClientBuffer *buffer)
343{
344 bool usingLocalContext = ensureContext();
345
346 BufferState &state = *buffer->d;
347 auto texture = state.textures[0];
348
349 // EGLStream requires calling acquire on every frame.
350 texture->bind();
351 EGLint stream_state;
352 funcs->query_stream(egl_display, state.egl_stream, EGL_STREAM_STATE_KHR, &stream_state);
353
354 if (stream_state == EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR) {
355 if (funcs->stream_consumer_acquire(egl_display, state.egl_stream) != EGL_TRUE)
356 qWarning(msg: "%s:%d: eglStreamConsumerAcquireKHR failed: 0x%x", Q_FUNC_INFO, __LINE__, eglGetError());
357 }
358
359 if (usingLocalContext)
360 localContext->doneCurrent();
361}
362
363
364WaylandEglStreamClientBufferIntegration::WaylandEglStreamClientBufferIntegration()
365 : d_ptr(new WaylandEglStreamClientBufferIntegrationPrivate)
366{
367}
368
369WaylandEglStreamClientBufferIntegration::~WaylandEglStreamClientBufferIntegration()
370{
371 Q_D(WaylandEglStreamClientBufferIntegration);
372 WaylandEglStreamClientBufferIntegrationPrivate::shuttingDown = true;
373 if (d->egl_unbind_wayland_display != nullptr && d->display_bound) {
374 Q_ASSERT(d->wlDisplay != nullptr);
375 if (!d->egl_unbind_wayland_display(d->egl_display, d->wlDisplay))
376 qCWarning(qLcWaylandCompositorHardwareIntegration) << "eglUnbindWaylandDisplayWL failed";
377 }
378}
379
380void WaylandEglStreamClientBufferIntegration::attachEglStreamConsumer(struct ::wl_resource *wl_surface, struct ::wl_resource *wl_buffer)
381{
382 Q_D(WaylandEglStreamClientBufferIntegration);
383 Q_UNUSED(wl_surface);
384
385 auto *clientBuffer = new WaylandEglStreamClientBuffer(this, wl_buffer);
386 auto *bufferManager = QWaylandCompositorPrivate::get(compositor: m_compositor)->bufferManager();
387 bufferManager->registerBuffer(buffer_resource: wl_buffer, clientBuffer);
388
389 d->initEglStream(buffer: clientBuffer, bufferHandle: wl_buffer);
390}
391
392void WaylandEglStreamClientBufferIntegration::initializeHardware(struct wl_display *display)
393{
394 Q_D(WaylandEglStreamClientBufferIntegration);
395
396 const bool ignoreBindDisplay = !qgetenv(varName: "QT_WAYLAND_IGNORE_BIND_DISPLAY").isEmpty();
397
398 QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface();
399 if (!nativeInterface) {
400 qWarning(msg: "QtCompositor: Failed to initialize EGL display. No native platform interface available.");
401 return;
402 }
403
404 d->egl_display = nativeInterface->nativeResourceForIntegration(resource: "EglDisplay");
405 if (!d->egl_display) {
406 qWarning(msg: "QtCompositor: Failed to initialize EGL display. Could not get EglDisplay for window.");
407 return;
408 }
409
410 const char *extensionString = eglQueryString(dpy: d->egl_display, EGL_EXTENSIONS);
411 if ((!extensionString || !strstr(haystack: extensionString, needle: "EGL_WL_bind_wayland_display")) && !ignoreBindDisplay) {
412 qWarning(msg: "QtCompositor: Failed to initialize EGL display. There is no EGL_WL_bind_wayland_display extension.");
413 return;
414 }
415
416 d->egl_bind_wayland_display = reinterpret_cast<PFNEGLBINDWAYLANDDISPLAYWL>(eglGetProcAddress(procname: "eglBindWaylandDisplayWL"));
417 d->egl_unbind_wayland_display = reinterpret_cast<PFNEGLUNBINDWAYLANDDISPLAYWL>(eglGetProcAddress(procname: "eglUnbindWaylandDisplayWL"));
418 if ((!d->egl_bind_wayland_display || !d->egl_unbind_wayland_display) && !ignoreBindDisplay) {
419 qWarning(msg: "QtCompositor: Failed to initialize EGL display. Could not find eglBindWaylandDisplayWL and eglUnbindWaylandDisplayWL.");
420 return;
421 }
422
423 d->egl_query_wayland_buffer = reinterpret_cast<PFNEGLQUERYWAYLANDBUFFERWL_compat>(eglGetProcAddress(procname: "eglQueryWaylandBufferWL"));
424 if (!d->egl_query_wayland_buffer) {
425 qWarning(msg: "QtCompositor: Failed to initialize EGL display. Could not find eglQueryWaylandBufferWL.");
426 return;
427 }
428
429 if (d->egl_bind_wayland_display && d->egl_unbind_wayland_display) {
430 d->display_bound = d->egl_bind_wayland_display(d->egl_display, display);
431 if (!d->display_bound)
432 qCDebug(qLcWaylandCompositorHardwareIntegration) << "Wayland display already bound by other client buffer integration.";
433
434 d->wlDisplay = display;
435 }
436
437 d->eglStreamController = new WaylandEglStreamController(display, this);
438
439 d->funcs = new QEGLStreamConvenience;
440 d->funcs->initialize(dpy: d->egl_display);
441}
442
443QtWayland::ClientBuffer *WaylandEglStreamClientBufferIntegration::createBufferFor(wl_resource *buffer)
444{
445 if (wl_shm_buffer_get(resource: buffer))
446 return nullptr;
447
448 return new WaylandEglStreamClientBuffer(this, buffer);
449}
450
451
452WaylandEglStreamClientBuffer::WaylandEglStreamClientBuffer(WaylandEglStreamClientBufferIntegration *integration, wl_resource *buffer)
453 : ClientBuffer(buffer)
454 , m_integration(integration)
455{
456 auto *p = WaylandEglStreamClientBufferIntegrationPrivate::get(integration: m_integration);
457 d = new BufferState;
458 if (buffer && !wl_shm_buffer_get(resource: buffer)) {
459 EGLint width, height;
460 p->egl_query_wayland_buffer(p->egl_display, buffer, EGL_WIDTH, &width);
461 p->egl_query_wayland_buffer(p->egl_display, buffer, EGL_HEIGHT, &height);
462 d->size = QSize(width, height);
463 }
464}
465
466WaylandEglStreamClientBuffer::~WaylandEglStreamClientBuffer()
467{
468 auto *p = WaylandEglStreamClientBufferIntegrationPrivate::get(integration: m_integration);
469
470 if (p) {
471 if (d->egl_stream)
472 p->funcs->destroy_stream(p->egl_display, d->egl_stream);
473
474 QMutexLocker locker(&d->texturesLock);
475
476 for (int i=0; i<3; i++) {
477 if (d->textures[i] != nullptr) {
478
479 qCDebug(qLcWaylandCompositorHardwareIntegration)
480 << Q_FUNC_INFO << " handing over texture!"
481 << (void*)d->textures[i] << "; " << (void*)d->texturesContext[i]
482 << " ... current context might be the same: " << QOpenGLContext::currentContext();
483
484 p->deleteGLTextureWhenPossible(texture: d->textures[i], ctx: d->texturesContext[i]);
485 d->textures[i] = nullptr; // in case the aboutToBeDestroyed lambda is called while we where here
486 d->texturesContext[i] = nullptr;
487 QObject::disconnect(d->texturesAboutToBeDestroyedConnection[i]);
488 d->texturesAboutToBeDestroyedConnection[i] = QMetaObject::Connection();
489 }
490 }
491 }
492
493 delete d;
494}
495
496
497QWaylandBufferRef::BufferFormatEgl WaylandEglStreamClientBuffer::bufferFormatEgl() const
498{
499 return QWaylandBufferRef::BufferFormatEgl_EXTERNAL_OES;
500}
501
502
503QSize WaylandEglStreamClientBuffer::size() const
504{
505 return d->size;
506}
507
508QWaylandSurface::Origin WaylandEglStreamClientBuffer::origin() const
509{
510 return d->isYInverted ? QWaylandSurface::OriginTopLeft : QWaylandSurface::OriginBottomLeft;
511}
512
513QOpenGLTexture *WaylandEglStreamClientBuffer::toOpenGlTexture(int plane)
514{
515 auto *p = WaylandEglStreamClientBufferIntegrationPrivate::get(integration: m_integration);
516 // At this point we should have a valid OpenGL context, so it's safe to destroy textures
517 p->deleteOrphanedTextures();
518
519 if (!m_buffer)
520 return nullptr;
521
522 return d->textures[plane];
523}
524
525void WaylandEglStreamClientBuffer::setCommitted(QRegion &damage)
526{
527 ClientBuffer::setCommitted(damage);
528 auto *p = WaylandEglStreamClientBufferIntegrationPrivate::get(integration: m_integration);
529 p->handleEglstreamTexture(buffer: this);
530}
531
532QT_END_NAMESPACE
533

source code of qtwayland/src/hardwareintegration/compositor/wayland-eglstream-controller/waylandeglstreamintegration.cpp