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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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