blob: 07a01ae0092ae070b5abb20bd2c8a853f3a0ca47 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtWaylandCompositor module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "waylandeglstreamintegration.h"
#include "waylandeglstreamcontroller.h"
#include <QtWaylandCompositor/QWaylandCompositor>
#include <QtGui/QGuiApplication>
#include <QtGui/QOpenGLContext>
#include <QtGui/QOpenGLTexture>
#include <QtGui/QOffscreenSurface>
#include <QtEglSupport/private/qeglstreamconvenience_p.h>
#include <qpa/qplatformnativeinterface.h>
#include <QtWaylandCompositor/private/qwaylandcompositor_p.h>
#include <QtWaylandCompositor/private/qwlbuffermanager_p.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <unistd.h>
#ifndef GL_TEXTURE_EXTERNAL_OES
#define GL_TEXTURE_EXTERNAL_OES 0x8D65
#endif
#ifndef EGL_WAYLAND_BUFFER_WL
#define EGL_WAYLAND_BUFFER_WL 0x31D5
#endif
#ifndef EGL_WAYLAND_EGLSTREAM_WL
#define EGL_WAYLAND_EGLSTREAM_WL 0x334B
#endif
#ifndef EGL_WAYLAND_PLANE_WL
#define EGL_WAYLAND_PLANE_WL 0x31D6
#endif
#ifndef EGL_WAYLAND_Y_INVERTED_WL
#define EGL_WAYLAND_Y_INVERTED_WL 0x31DB
#endif
#ifndef EGL_TEXTURE_RGB
#define EGL_TEXTURE_RGB 0x305D
#endif
#ifndef EGL_TEXTURE_RGBA
#define EGL_TEXTURE_RGBA 0x305E
#endif
#ifndef EGL_TEXTURE_EXTERNAL_WL
#define EGL_TEXTURE_EXTERNAL_WL 0x31DA
#endif
#ifndef EGL_TEXTURE_Y_U_V_WL
#define EGL_TEXTURE_Y_U_V_WL 0x31D7
#endif
#ifndef EGL_TEXTURE_Y_UV_WL
#define EGL_TEXTURE_Y_UV_WL 0x31D8
#endif
#ifndef EGL_TEXTURE_Y_XUXV_WL
#define EGL_TEXTURE_Y_XUXV_WL 0x31D9
#endif
#ifndef EGL_PLATFORM_X11_KHR
#define EGL_PLATFORM_X11_KHR 0x31D5
#endif
QT_BEGIN_NAMESPACE
/* Needed for compatibility with Mesa older than 10.0. */
typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYWAYLANDBUFFERWL_compat) (EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value);
#ifndef EGL_WL_bind_wayland_display
typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDWAYLANDDISPLAYWL) (EGLDisplay dpy, struct wl_display *display);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLUNBINDWAYLANDDISPLAYWL) (EGLDisplay dpy, struct wl_display *display);
#endif
static const char *
egl_error_string(EGLint code)
{
#define MYERRCODE(x) case x: return #x;
switch (code) {
MYERRCODE(EGL_SUCCESS)
MYERRCODE(EGL_NOT_INITIALIZED)
MYERRCODE(EGL_BAD_ACCESS)
MYERRCODE(EGL_BAD_ALLOC)
MYERRCODE(EGL_BAD_ATTRIBUTE)
MYERRCODE(EGL_BAD_CONTEXT)
MYERRCODE(EGL_BAD_CONFIG)
MYERRCODE(EGL_BAD_CURRENT_SURFACE)
MYERRCODE(EGL_BAD_DISPLAY)
MYERRCODE(EGL_BAD_SURFACE)
MYERRCODE(EGL_BAD_MATCH)
MYERRCODE(EGL_BAD_PARAMETER)
MYERRCODE(EGL_BAD_NATIVE_PIXMAP)
MYERRCODE(EGL_BAD_NATIVE_WINDOW)
MYERRCODE(EGL_CONTEXT_LOST)
default:
return "unknown";
}
#undef MYERRCODE
}
struct BufferState
{
BufferState() = default;
EGLint egl_format = EGL_TEXTURE_EXTERNAL_WL;
QOpenGLTexture *textures[3] = {};
EGLStreamKHR egl_stream = EGL_NO_STREAM_KHR;
bool isYInverted = false;
QSize size;
};
class WaylandEglStreamClientBufferIntegrationPrivate
{
public:
WaylandEglStreamClientBufferIntegrationPrivate() = default;
bool ensureContext();
bool initEglStream(WaylandEglStreamClientBuffer *buffer, struct ::wl_resource *bufferHandle);
void handleEglstreamTexture(WaylandEglStreamClientBuffer *buffer);
void deleteGLTextureWhenPossible(QOpenGLTexture *texture) { orphanedTextures << texture; }
void deleteOrphanedTextures();
EGLDisplay egl_display = EGL_NO_DISPLAY;
bool display_bound = false;
QOffscreenSurface *offscreenSurface = nullptr;
QOpenGLContext *localContext = nullptr;
QVector<QOpenGLTexture *> orphanedTextures;
WaylandEglStreamController *eglStreamController = nullptr;
PFNEGLBINDWAYLANDDISPLAYWL egl_bind_wayland_display = nullptr;
PFNEGLUNBINDWAYLANDDISPLAYWL egl_unbind_wayland_display = nullptr;
PFNEGLQUERYWAYLANDBUFFERWL_compat egl_query_wayland_buffer = nullptr;
QEGLStreamConvenience *funcs = nullptr;
static WaylandEglStreamClientBufferIntegrationPrivate *get(WaylandEglStreamClientBufferIntegration *integration) {
return shuttingDown ? nullptr : integration->d_ptr.data();
}
static bool shuttingDown;
};
bool WaylandEglStreamClientBufferIntegrationPrivate::shuttingDown = false;
void WaylandEglStreamClientBufferIntegrationPrivate::deleteOrphanedTextures()
{
Q_ASSERT(QOpenGLContext::currentContext());
qDeleteAll(orphanedTextures);
orphanedTextures.clear();
}
bool WaylandEglStreamClientBufferIntegrationPrivate::ensureContext()
{
bool localContextNeeded = false;
if (!QOpenGLContext::currentContext()) {
if (!localContext && QOpenGLContext::globalShareContext()) {
localContext = new QOpenGLContext;
localContext->setShareContext(QOpenGLContext::globalShareContext());
localContext->create();
}
if (localContext) {
if (!offscreenSurface) {
offscreenSurface = new QOffscreenSurface;
offscreenSurface->setFormat(localContext->format());
offscreenSurface->create();
}
localContext->makeCurrent(offscreenSurface);
localContextNeeded = true;
}
}
return localContextNeeded;
}
bool WaylandEglStreamClientBufferIntegrationPrivate::initEglStream(WaylandEglStreamClientBuffer *buffer, wl_resource *bufferHandle)
{
BufferState &state = *buffer->d;
state.egl_format = EGL_TEXTURE_EXTERNAL_WL;
state.isYInverted = false;
EGLNativeFileDescriptorKHR streamFd = EGL_NO_FILE_DESCRIPTOR_KHR;
if (egl_query_wayland_buffer(egl_display, bufferHandle, EGL_WAYLAND_BUFFER_WL, &streamFd)) {
state.egl_stream = funcs->create_stream_from_file_descriptor(egl_display, streamFd);
close(streamFd);
} else {
EGLAttrib stream_attribs[] = {
EGL_WAYLAND_EGLSTREAM_WL, (EGLAttrib)bufferHandle,
EGL_NONE
};
state.egl_stream = funcs->create_stream_attrib_nv(egl_display, stream_attribs);
}
if (state.egl_stream == EGL_NO_STREAM_KHR) {
qWarning("%s:%d: eglCreateStreamFromFileDescriptorKHR failed: 0x%x", Q_FUNC_INFO, __LINE__, eglGetError());
return false;
}
bool usingLocalContext = ensureContext();
Q_ASSERT(QOpenGLContext::currentContext());
auto texture = new QOpenGLTexture(static_cast<QOpenGLTexture::Target>(GL_TEXTURE_EXTERNAL_OES));
texture->create();
state.textures[0] = texture; // TODO: support multiple planes
texture->bind();
auto newStream = funcs->stream_consumer_gltexture(egl_display, state.egl_stream);
if (usingLocalContext)
localContext->doneCurrent();
if (!newStream) {
EGLint code = eglGetError();
qWarning() << "Could not initialize EGLStream:" << egl_error_string(code) << Qt::hex << (long)code;
funcs->destroy_stream(egl_display, state.egl_stream);
state.egl_stream = EGL_NO_STREAM_KHR;
return false;
}
return true;
}
void WaylandEglStreamClientBufferIntegrationPrivate::handleEglstreamTexture(WaylandEglStreamClientBuffer *buffer)
{
bool usingLocalContext = ensureContext();
BufferState &state = *buffer->d;
auto texture = state.textures[0];
// EGLStream requires calling acquire on every frame.
texture->bind();
EGLint stream_state;
funcs->query_stream(egl_display, state.egl_stream, EGL_STREAM_STATE_KHR, &stream_state);
if (stream_state == EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR) {
if (funcs->stream_consumer_acquire(egl_display, state.egl_stream) != EGL_TRUE)
qWarning("%s:%d: eglStreamConsumerAcquireKHR failed: 0x%x", Q_FUNC_INFO, __LINE__, eglGetError());
}
if (usingLocalContext)
localContext->doneCurrent();
}
WaylandEglStreamClientBufferIntegration::WaylandEglStreamClientBufferIntegration()
: d_ptr(new WaylandEglStreamClientBufferIntegrationPrivate)
{
}
WaylandEglStreamClientBufferIntegration::~WaylandEglStreamClientBufferIntegration()
{
WaylandEglStreamClientBufferIntegrationPrivate::shuttingDown = true;
}
void WaylandEglStreamClientBufferIntegration::attachEglStreamConsumer(struct ::wl_resource *wl_surface, struct ::wl_resource *wl_buffer)
{
Q_D(WaylandEglStreamClientBufferIntegration);
Q_UNUSED(wl_surface);
// NOTE: must use getBuffer to create the buffer here, so the buffer will end up in the buffer manager's hash
auto *bufferManager = QWaylandCompositorPrivate::get(m_compositor)->bufferManager();
auto *clientBuffer = static_cast<WaylandEglStreamClientBuffer*>(bufferManager->getBuffer(wl_buffer));
d->initEglStream(clientBuffer, wl_buffer);
}
void WaylandEglStreamClientBufferIntegration::initializeHardware(struct wl_display *display)
{
Q_D(WaylandEglStreamClientBufferIntegration);
const bool ignoreBindDisplay = !qgetenv("QT_WAYLAND_IGNORE_BIND_DISPLAY").isEmpty();
QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface();
if (!nativeInterface) {
qWarning("QtCompositor: Failed to initialize EGL display. No native platform interface available.");
return;
}
d->egl_display = nativeInterface->nativeResourceForIntegration("EglDisplay");
if (!d->egl_display) {
qWarning("QtCompositor: Failed to initialize EGL display. Could not get EglDisplay for window.");
return;
}
const char *extensionString = eglQueryString(d->egl_display, EGL_EXTENSIONS);
if ((!extensionString || !strstr(extensionString, "EGL_WL_bind_wayland_display")) && !ignoreBindDisplay) {
qWarning("QtCompositor: Failed to initialize EGL display. There is no EGL_WL_bind_wayland_display extension.");
return;
}
d->egl_bind_wayland_display = reinterpret_cast<PFNEGLBINDWAYLANDDISPLAYWL>(eglGetProcAddress("eglBindWaylandDisplayWL"));
d->egl_unbind_wayland_display = reinterpret_cast<PFNEGLUNBINDWAYLANDDISPLAYWL>(eglGetProcAddress("eglUnbindWaylandDisplayWL"));
if ((!d->egl_bind_wayland_display || !d->egl_unbind_wayland_display) && !ignoreBindDisplay) {
qWarning("QtCompositor: Failed to initialize EGL display. Could not find eglBindWaylandDisplayWL and eglUnbindWaylandDisplayWL.");
return;
}
d->egl_query_wayland_buffer = reinterpret_cast<PFNEGLQUERYWAYLANDBUFFERWL_compat>(eglGetProcAddress("eglQueryWaylandBufferWL"));
if (!d->egl_query_wayland_buffer) {
qWarning("QtCompositor: Failed to initialize EGL display. Could not find eglQueryWaylandBufferWL.");
return;
}
if (d->egl_bind_wayland_display && d->egl_unbind_wayland_display) {
d->display_bound = d->egl_bind_wayland_display(d->egl_display, display);
if (!d->display_bound) {
if (!ignoreBindDisplay) {
qWarning("QtCompositor: Failed to initialize EGL display. Could not bind Wayland display.");
return;
} else {
qWarning("QtCompositor: Could not bind Wayland display. Ignoring.");
}
}
}
d->eglStreamController = new WaylandEglStreamController(display, this);
d->funcs = new QEGLStreamConvenience;
d->funcs->initialize(d->egl_display);
}
QtWayland::ClientBuffer *WaylandEglStreamClientBufferIntegration::createBufferFor(wl_resource *buffer)
{
if (wl_shm_buffer_get(buffer))
return nullptr;
return new WaylandEglStreamClientBuffer(this, buffer);
}
WaylandEglStreamClientBuffer::WaylandEglStreamClientBuffer(WaylandEglStreamClientBufferIntegration *integration, wl_resource *buffer)
: ClientBuffer(buffer)
, m_integration(integration)
{
auto *p = WaylandEglStreamClientBufferIntegrationPrivate::get(m_integration);
d = new BufferState;
if (buffer && !wl_shm_buffer_get(buffer)) {
EGLint width, height;
p->egl_query_wayland_buffer(p->egl_display, buffer, EGL_WIDTH, &width);
p->egl_query_wayland_buffer(p->egl_display, buffer, EGL_HEIGHT, &height);
d->size = QSize(width, height);
}
}
WaylandEglStreamClientBuffer::~WaylandEglStreamClientBuffer()
{
auto *p = WaylandEglStreamClientBufferIntegrationPrivate::get(m_integration);
if (p) {
if (d->egl_stream)
p->funcs->destroy_stream(p->egl_display, d->egl_stream);
for (auto *texture : d->textures)
p->deleteGLTextureWhenPossible(texture);
}
delete d;
}
QWaylandBufferRef::BufferFormatEgl WaylandEglStreamClientBuffer::bufferFormatEgl() const
{
return QWaylandBufferRef::BufferFormatEgl_EXTERNAL_OES;
}
QSize WaylandEglStreamClientBuffer::size() const
{
return d->size;
}
QWaylandSurface::Origin WaylandEglStreamClientBuffer::origin() const
{
return d->isYInverted ? QWaylandSurface::OriginTopLeft : QWaylandSurface::OriginBottomLeft;
}
QOpenGLTexture *WaylandEglStreamClientBuffer::toOpenGlTexture(int plane)
{
auto *p = WaylandEglStreamClientBufferIntegrationPrivate::get(m_integration);
// At this point we should have a valid OpenGL context, so it's safe to destroy textures
p->deleteOrphanedTextures();
if (!m_buffer)
return nullptr;
return d->textures[plane];
}
void WaylandEglStreamClientBuffer::setCommitted(QRegion &damage)
{
ClientBuffer::setCommitted(damage);
auto *p = WaylandEglStreamClientBufferIntegrationPrivate::get(m_integration);
p->handleEglstreamTexture(this);
}
QT_END_NAMESPACE