fix: comprehensive boot warnings and exceptions — fixable silenced, unfixable diagnosed

Build system (5 gaps hardened):
- COOKBOOK_OFFLINE defaults to true (fork-mode)
- normalize_patch handles diff -ruN format
- New 'repo validate-patches' command (25/25 relibc patches)
- 14 patched Qt/Wayland/display recipes added to protected list
- relibc archive regenerated with current patch chain

Boot fixes (fixable):
- Full ISO EFI partition: 16 MiB → 1 MiB (matches mini, BIOS hardcoded 2 MiB offset)
- D-Bus system bus: absolute /usr/bin/dbus-daemon path (was skipped)
- redbear-sessiond: absolute /usr/bin/redbear-sessiond path (was skipped)
- daemon framework: silenced spurious INIT_NOTIFY warnings for oneshot_async services (P0-daemon-silence-init-notify.patch)
- udev-shim: demoted INIT_NOTIFY warning to INFO (expected for oneshot_async)
- relibc: comprehensive named semaphores (sem_open/close/unlink) replacing upstream todo!() stubs
- greeterd: Wayland socket timeout 15s → 30s (compositor DRM wait)
- greeter-ui: built and linked (header guard unification, sem_compat stubs removed)
- mc: un-ignored in both configs, fixed glib/libiconv/pcre2 transitive deps
- greeter config: removed stale keymapd dependency from display/greeter services
- prefix toolchain: relibc headers synced, _RELIBC_STDLIB_H guard unified

Unfixable (diagnosed, upstream):
- i2c-hidd: abort on no-I2C-hardware (QEMU) — process::exit → relibc abort
- kded6/greeter-ui: page fault 0x8 — Qt library null deref
- Thread panics fd != -1 — Rust std library on Redox
- DHCP timeout / eth0 MAC — QEMU user-mode networking
- hwrngd/thermald — no hardware RNG/thermal in VM
- live preload allocation — BIOS memory fragmentation, continues on demand
This commit is contained in:
2026-05-05 20:20:37 +01:00
parent a5f97b6632
commit f31522130f
81834 changed files with 11051982 additions and 108 deletions
@@ -0,0 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(TARGET Qt::WaylandCompositor)
add_subdirectory(compositor)
endif()
@@ -0,0 +1,8 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
# Generated from compositor.pro.
if(QT_FEATURE_wayland_egl)
add_subdirectory(wayland-egl)
endif()
@@ -0,0 +1,38 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "brcmbuffer.h"
#include <EGL/eglext.h>
#include <EGL/eglext_brcm.h>
QT_BEGIN_NAMESPACE
BrcmBuffer::BrcmBuffer(struct ::wl_client *client, uint32_t id, const QSize &size, EGLint *data, size_t count)
: QtWaylandServer::wl_buffer(client, id, 1)
, m_handle(count)
, m_size(size)
{
for (size_t i = 0; i < count; ++i)
m_handle[i] = data[i];
}
BrcmBuffer::~BrcmBuffer()
{
static PFNEGLDESTROYGLOBALIMAGEBRCMPROC eglDestroyGlobalImage =
(PFNEGLDESTROYGLOBALIMAGEBRCMPROC) eglGetProcAddress("eglDestroyGlobalImageBRCM");
eglDestroyGlobalImage(handle());
}
void BrcmBuffer::buffer_destroy_resource(Resource *)
{
delete this;
}
void BrcmBuffer::buffer_destroy(Resource *resource)
{
wl_resource_destroy(resource->handle);
}
QT_END_NAMESPACE
@@ -0,0 +1,44 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef BRCMBUFFER_H
#define BRCMBUFFER_H
#include <QtWaylandCompositor/private/qwayland-server-wayland.h>
#include <QtWaylandCompositor/private/qwaylandutils_p.h>
#include <QtCore/QList>
#include <QtCore/QSize>
#include <EGL/egl.h>
QT_BEGIN_NAMESPACE
class BrcmBuffer : public QtWaylandServer::wl_buffer
{
public:
BrcmBuffer(struct ::wl_client *client, uint32_t id, const QSize &size, EGLint *data, size_t count);
~BrcmBuffer();
bool isYInverted() const { return m_invertedY; }
void setInvertedY(bool inverted) { m_invertedY = inverted; }
EGLint *handle() { return m_handle.data(); }
QSize size() { return m_size; }
static BrcmBuffer *fromResource(struct ::wl_resource *resource) { return QtWayland::fromResource<BrcmBuffer *>(resource); }
protected:
void buffer_destroy_resource(Resource *resource) override;
void buffer_destroy(Resource *resource) override;
private:
QList<EGLint> m_handle;
bool m_invertedY = false;
QSize m_size;
};
QT_END_NAMESPACE
#endif // BRCMBUFFER_H
@@ -0,0 +1,171 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "brcmeglintegration.h"
#include "brcmbuffer.h"
#include <QtWaylandCompositor/qwaylandsurface.h>
#include <qpa/qplatformnativeinterface.h>
#include <QtGui/QGuiApplication>
#include <QtGui/QOpenGLContext>
#include <QOpenGLTexture>
#include <qpa/qplatformscreen.h>
#include <QtGui/QWindow>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglext_brcm.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
QT_BEGIN_NAMESPACE
class BrcmEglIntegrationPrivate
{
public:
BrcmEglIntegrationPrivate() = default;
static BrcmEglIntegrationPrivate *get(BrcmEglIntegration *integration);
EGLDisplay egl_display = EGL_NO_DISPLAY;
bool valid = false;
PFNEGLQUERYGLOBALIMAGEBRCMPROC eglQueryGlobalImageBRCM;
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;
PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;
};
BrcmEglIntegration::BrcmEglIntegration()
: d_ptr(new BrcmEglIntegrationPrivate)
{
}
void BrcmEglIntegration::initializeHardware(struct ::wl_display *display)
{
Q_D(BrcmEglIntegration);
QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface();
if (nativeInterface) {
d->egl_display = nativeInterface->nativeResourceForIntegration("EglDisplay");
if (!d->egl_display)
qWarning("Failed to acquire EGL display from platform integration");
d->eglQueryGlobalImageBRCM = (PFNEGLQUERYGLOBALIMAGEBRCMPROC) eglGetProcAddress("eglQueryGlobalImageBRCM");
if (!d->eglQueryGlobalImageBRCM) {
qWarning("Failed to resolve eglQueryGlobalImageBRCM");
return;
}
d->glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
if (!d->glEGLImageTargetTexture2DOES) {
qWarning("Failed to resolve glEGLImageTargetTexture2DOES");
return;
}
d->eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
if (!d->eglCreateImageKHR) {
qWarning("Failed to resolve eglCreateImageKHR");
return;
}
d->eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
if (!d->eglDestroyImageKHR) {
qWarning("Failed to resolve eglDestroyImageKHR");
return;
}
d->valid = true;
init(display, 1);
}
}
QtWayland::ClientBuffer *BrcmEglIntegration::createBufferFor(wl_resource *buffer)
{
if (wl_shm_buffer_get(buffer))
return nullptr;
return new BrcmEglClientBuffer(this, buffer);
}
BrcmEglIntegrationPrivate *BrcmEglIntegrationPrivate::get(BrcmEglIntegration *integration)
{
return integration->d_ptr.data();
}
QOpenGLTexture *BrcmEglClientBuffer::toOpenGlTexture(int plane)
{
Q_UNUSED(plane);
auto d = BrcmEglIntegrationPrivate::get(m_integration);
if (!d->valid) {
qWarning("bindTextureToBuffer failed!");
return nullptr;
}
BrcmBuffer *brcmBuffer = BrcmBuffer::fromResource(m_buffer);
if (!d->eglQueryGlobalImageBRCM(brcmBuffer->handle(), brcmBuffer->handle() + 2)) {
qWarning("eglQueryGlobalImageBRCM failed!");
return nullptr;
}
EGLImageKHR image = d->eglCreateImageKHR(d->egl_display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR, (EGLClientBuffer)brcmBuffer->handle(), NULL);
if (image == EGL_NO_IMAGE_KHR)
qWarning("eglCreateImageKHR() failed: %x\n", eglGetError());
if (!m_texture) {
m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
m_texture->create();
}
m_texture->bind();
d->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
d->eglDestroyImageKHR(d->egl_display, image);
return m_texture;
}
void BrcmEglIntegration::brcm_bind_resource(Resource *)
{
}
void BrcmEglIntegration::brcm_create_buffer(Resource *resource, uint32_t id, int32_t width, int32_t height, wl_array *data)
{
new BrcmBuffer(resource->client(), id, QSize(width, height), static_cast<EGLint *>(data->data), data->size / sizeof(EGLint));
}
BrcmEglClientBuffer::BrcmEglClientBuffer(BrcmEglIntegration *integration, wl_resource *buffer)
: ClientBuffer(buffer)
, m_integration(integration)
{
}
QWaylandBufferRef::BufferFormatEgl BrcmEglClientBuffer::bufferFormatEgl() const
{
return QWaylandBufferRef::BufferFormatEgl_RGBA;
}
QSize BrcmEglClientBuffer::size() const
{
BrcmBuffer *brcmBuffer = BrcmBuffer::fromResource(m_buffer);
return brcmBuffer->size();
}
QWaylandSurface::Origin BrcmEglClientBuffer::origin() const
{
BrcmBuffer *brcmBuffer = BrcmBuffer::fromResource(m_buffer);
return brcmBuffer->isYInverted() ? QWaylandSurface::OriginTopLeft : QWaylandSurface::OriginBottomLeft;
}
QT_END_NAMESPACE
@@ -0,0 +1,54 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef BRCMEGLINTEGRATION_H
#define BRCMEGLINTEGRATION_H
#include <QtWaylandCompositor/private/qwlclientbufferintegration_p.h>
#include "qwayland-server-brcm.h"
#include <QtCore/QScopedPointer>
#include <private/qwlclientbuffer_p.h>
QT_BEGIN_NAMESPACE
class BrcmEglIntegrationPrivate;
class BrcmEglIntegration : public QtWayland::ClientBufferIntegration, public QtWaylandServer::qt_brcm
{
Q_DECLARE_PRIVATE(BrcmEglIntegration)
public:
BrcmEglIntegration();
void initializeHardware(struct ::wl_display *display) override;
QtWayland::ClientBuffer *createBufferFor(wl_resource *buffer) override;
protected:
void brcm_bind_resource(Resource *resource) override;
void brcm_create_buffer(Resource *resource, uint32_t id, int32_t width, int32_t height, wl_array *data) override;
private:
Q_DISABLE_COPY(BrcmEglIntegration)
QScopedPointer<BrcmEglIntegrationPrivate> d_ptr;
};
class BrcmEglClientBuffer : public QtWayland::ClientBuffer
{
public:
BrcmEglClientBuffer(BrcmEglIntegration *integration, wl_resource *buffer);
QWaylandBufferRef::BufferFormatEgl bufferFormatEgl() const override;
QSize size() const override;
QWaylandSurface::Origin origin() const override;
QOpenGLTexture *toOpenGlTexture(int plane) override;
private:
BrcmEglIntegration *m_integration = nullptr;
QOpenGLTexture *m_texture = nullptr;
};
QT_END_NAMESPACE
#endif // BRCMEGLINTEGRATION_H
@@ -0,0 +1,183 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "dmabufserverbufferintegration.h"
#include <QtOpenGL/QOpenGLTexture>
#include <QtGui/QOpenGLContext>
#include <drm_fourcc.h>
#include <unistd.h>
QT_BEGIN_NAMESPACE
DmaBufServerBuffer::DmaBufServerBuffer(DmaBufServerBufferIntegration *integration, const QImage &qimage, QtWayland::ServerBuffer::Format format)
: QtWayland::ServerBuffer(qimage.size(),format)
, m_integration(integration)
{
m_format = format;
EGLContext eglContext = eglGetCurrentContext();
m_texture = new QOpenGLTexture(qimage);
m_image = m_integration->eglCreateImageKHR(eglContext, EGL_GL_TEXTURE_2D_KHR, (EGLClientBuffer)(unsigned long)m_texture->textureId(), nullptr);
qCDebug(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBuffer created egl image" << m_image;
int err = eglGetError();
if (err != EGL_SUCCESS || m_image == EGL_NO_IMAGE_KHR)
qCWarning(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBuffer error creating EGL image" << Qt::hex << err;
// TODO: formats with more than one plane
int num_planes = 1;
if (!m_integration->eglExportDMABUFImageQueryMESA(m_image, &m_fourcc_format, &num_planes, nullptr)) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBuffer: Failed to query egl image";
qCDebug(qLcWaylandCompositorHardwareIntegration) << "error" << Qt::hex << eglGetError();
} else {
qCDebug(qLcWaylandCompositorHardwareIntegration) << "num_planes" << num_planes << "fourcc_format" << m_fourcc_format;
if (num_planes != 1) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBuffer: multi-plane formats not supported";
delete m_texture;
m_texture = nullptr;
m_integration->eglDestroyImageKHR(m_image);
m_image = EGL_NO_IMAGE_KHR;
return;
}
}
if (!m_integration->eglExportDMABUFImageMESA(m_image, &m_fd, &m_stride, &m_offset)) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBuffer: Failed to export egl image. Error code" << Qt::hex << eglGetError();
} else {
qCDebug(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBuffer exported egl image: fd" << m_fd << "stride" << m_stride << "offset" << m_offset;
m_texture->release();
}
}
DmaBufServerBuffer::~DmaBufServerBuffer()
{
delete m_texture;
int err;
m_integration->eglDestroyImageKHR(m_image);
if ((err = eglGetError()) != EGL_SUCCESS)
qCWarning(qLcWaylandCompositorHardwareIntegration) << "~DmaBufServerBuffer: eglDestroyImageKHR error" << Qt::hex << err;
err = ::close(m_fd);
if (err)
perror("~DmaBufServerBuffer:: error closing fd");
}
struct ::wl_resource *DmaBufServerBuffer::resourceForClient(struct ::wl_client *client)
{
auto *bufferResource = resourceMap().value(client);
if (!bufferResource) {
auto integrationResource = m_integration->resourceMap().value(client);
if (!integrationResource) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBuffer::resourceForClient: Trying to get resource for ServerBuffer. But client is not bound to the qt_dmabuf_server_buffer interface";
return nullptr;
}
struct ::wl_resource *dmabuf_integration_resource = integrationResource->handle;
Resource *resource = add(client, 1);
m_integration->send_server_buffer_created(dmabuf_integration_resource, resource->handle, m_fd, m_size.width(), m_size.height(), m_stride, m_offset, m_fourcc_format);
return resource->handle;
}
return bufferResource->handle;
}
QOpenGLTexture *DmaBufServerBuffer::toOpenGlTexture()
{
if (!m_texture) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBuffer::toOpenGlTexture: no texture defined";
}
return m_texture;
}
bool DmaBufServerBuffer::bufferInUse()
{
return resourceMap().size() > 0;
}
DmaBufServerBufferIntegration::DmaBufServerBufferIntegration()
{
}
DmaBufServerBufferIntegration::~DmaBufServerBufferIntegration()
{
}
bool DmaBufServerBufferIntegration::initializeHardware(QWaylandCompositor *compositor)
{
Q_ASSERT(QGuiApplication::platformNativeInterface());
m_egl_display = static_cast<EGLDisplay>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay"));
if (!m_egl_display) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Cannot initialize dmabuf server buffer integration. Missing egl display from platform plugin";
return false;
}
const char *extensionString = eglQueryString(m_egl_display, EGL_EXTENSIONS);
if (!extensionString || !strstr(extensionString, "EGL_KHR_image")) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Failed to initialize dmabuf server buffer integration. There is no EGL_KHR_image extension.";
return false;
}
m_egl_create_image = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
m_egl_destroy_image = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
if (!m_egl_create_image || !m_egl_destroy_image) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Failed to initialize dmabuf server buffer integration. Could not resolve eglCreateImageKHR or eglDestroyImageKHR";
return false;
}
m_gl_egl_image_target_texture_2d = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
if (!m_gl_egl_image_target_texture_2d) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Failed to initialize dmabuf server buffer integration. Could not find glEGLImageTargetTexture2DOES.";
return false;
}
m_egl_export_dmabuf_image_query = reinterpret_cast<PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC>(eglGetProcAddress("eglExportDMABUFImageQueryMESA"));
if (!m_egl_export_dmabuf_image_query) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Failed to initialize dmabuf server buffer integration. Could not find eglExportDMABUFImageQueryMESA.";
return false;
}
m_egl_export_dmabuf_image = reinterpret_cast<PFNEGLEXPORTDMABUFIMAGEMESAPROC>(eglGetProcAddress("eglExportDMABUFImageMESA"));
if (!m_egl_export_dmabuf_image) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Failed to initialize dmabuf server buffer integration. Could not find eglExportDMABUFImageMESA.";
return false;
}
QtWaylandServer::qt_dmabuf_server_buffer::init(compositor->display(), 1);
return true;
}
bool DmaBufServerBufferIntegration::supportsFormat(QtWayland::ServerBuffer::Format format) const
{
// TODO: 8-bit format support
switch (format) {
case QtWayland::ServerBuffer::RGBA32:
return true;
case QtWayland::ServerBuffer::A8:
return false;
default:
return false;
}
}
QtWayland::ServerBuffer *DmaBufServerBufferIntegration::createServerBufferFromImage(const QImage &qimage, QtWayland::ServerBuffer::Format format)
{
return new DmaBufServerBuffer(this, qimage, format);
}
void DmaBufServerBuffer::server_buffer_release(Resource *resource)
{
qCDebug(qLcWaylandCompositorHardwareIntegration) << "server_buffer RELEASE resource" << resource->handle << wl_resource_get_id(resource->handle) << "for client" << resource->client();
wl_resource_destroy(resource->handle);
}
QT_END_NAMESPACE
@@ -0,0 +1,160 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef DMABUFSERVERBUFFERINTEGRATION_H
#define DMABUFSERVERBUFFERINTEGRATION_H
#include <QtCore/QVariant>
#include <QtWaylandCompositor/private/qwlserverbufferintegration_p.h>
#include "qwayland-server-qt-dmabuf-server-buffer.h"
#include <QtGui/QWindow>
#include <QtGui/qpa/qplatformnativeinterface.h>
#include <QtGui/QGuiApplication>
#include <QtWaylandCompositor/qwaylandcompositor.h>
#include <QtWaylandCompositor/private/qwayland-server-server-buffer-extension.h>
#include <QtCore/QDebug>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#ifndef EGL_KHR_image
typedef void *EGLImageKHR;
typedef EGLImageKHR (EGLAPIENTRYP PFNEGLCREATEIMAGEKHRPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay dpy, EGLImageKHR image);
#endif
#ifndef GL_OES_EGL_image
typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES image);
#endif
#ifndef EGL_MESA_image_dma_buf_export
typedef EGLBoolean (EGLAPIENTRYP PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC) (EGLDisplay dpy, EGLImageKHR image, int *fourcc, int *num_planes, EGLuint64KHR *modifiers);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLEXPORTDMABUFIMAGEMESAPROC) (EGLDisplay dpy, EGLImageKHR image, int *fds, EGLint *strides, EGLint *offsets);
#endif
QT_BEGIN_NAMESPACE
class DmaBufServerBufferIntegration;
class QImage;
class DmaBufServerBuffer : public QtWayland::ServerBuffer, public QtWaylandServer::qt_server_buffer
{
public:
DmaBufServerBuffer(DmaBufServerBufferIntegration *integration, const QImage &qimage, QtWayland::ServerBuffer::Format format);
~DmaBufServerBuffer() override;
struct ::wl_resource *resourceForClient(struct ::wl_client *) override;
QOpenGLTexture *toOpenGlTexture() override;
bool bufferInUse() override;
protected:
void server_buffer_release(Resource *resource) override;
private:
DmaBufServerBufferIntegration *m_integration = nullptr;
EGLImageKHR m_image;
int32_t m_offset;
int32_t m_stride;
QOpenGLTexture *m_texture = nullptr;
int m_fourcc_format;
int m_fd;
};
class DmaBufServerBufferIntegration :
public QtWayland::ServerBufferIntegration,
public QtWaylandServer::qt_dmabuf_server_buffer
{
public:
DmaBufServerBufferIntegration();
~DmaBufServerBufferIntegration() override;
bool initializeHardware(QWaylandCompositor *) override;
bool supportsFormat(QtWayland::ServerBuffer::Format format) const override;
QtWayland::ServerBuffer *createServerBufferFromImage(const QImage &qimage, QtWayland::ServerBuffer::Format format) override;
EGLDisplay display() const { return m_egl_display; }
inline EGLImageKHR eglCreateImageKHR(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);
inline EGLBoolean eglDestroyImageKHR(EGLImageKHR image);
inline EGLBoolean eglExportDMABUFImageQueryMESA(EGLImageKHR image,
int *fourcc,
int *num_planes,
EGLuint64KHR *modifiers);
inline EGLBoolean eglExportDMABUFImageMESA(EGLImageKHR image,
int *fds,
EGLint *strides,
EGLint *offsets);
inline void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image);
private:
EGLDisplay m_egl_display;
PFNEGLEXPORTDMABUFIMAGEMESAPROC m_egl_export_dmabuf_image = nullptr;
PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC m_egl_export_dmabuf_image_query = nullptr;
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_gl_egl_image_target_texture_2d = nullptr;
PFNEGLCREATEIMAGEKHRPROC m_egl_create_image = nullptr;
PFNEGLDESTROYIMAGEKHRPROC m_egl_destroy_image = nullptr;
};
EGLImageKHR DmaBufServerBufferIntegration::eglCreateImageKHR(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list)
{
if (!m_egl_create_image) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBufferIntegration: Trying to use unresolved function eglCreateImageKHR";
return EGL_NO_IMAGE_KHR;
}
return m_egl_create_image(m_egl_display, ctx, target, buffer, attrib_list);
}
EGLBoolean DmaBufServerBufferIntegration::eglDestroyImageKHR(EGLImageKHR image)
{
if (!m_egl_destroy_image) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBufferIntegration: Trying to use unresolved function eglDestroyImageKHR";
return false;
}
return m_egl_destroy_image(m_egl_display, image);
}
EGLBoolean DmaBufServerBufferIntegration::eglExportDMABUFImageQueryMESA(EGLImageKHR image,
int *fourcc,
int *num_planes,
EGLuint64KHR *modifiers)
{
if (m_egl_export_dmabuf_image_query)
return m_egl_export_dmabuf_image_query(m_egl_display, image, fourcc, num_planes, modifiers);
else
qCWarning(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBufferIntegration: Trying to use unresolved function eglExportDMABUFImageQueryMESA";
return false;
}
EGLBoolean DmaBufServerBufferIntegration::eglExportDMABUFImageMESA(EGLImageKHR image,
int *fds,
EGLint *strides,
EGLint *offsets)
{
if (m_egl_export_dmabuf_image)
return m_egl_export_dmabuf_image(m_egl_display, image, fds, strides, offsets);
else
qCWarning(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBufferIntegration: Trying to use unresolved function eglExportDMABUFImageMESA";
return false;
}
void DmaBufServerBufferIntegration::glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image)
{
if (m_gl_egl_image_target_texture_2d)
return m_gl_egl_image_target_texture_2d(target, image);
else
qCWarning(qLcWaylandCompositorHardwareIntegration) << "DmaBufServerBufferIntegration: Trying to use unresolved function glEGLImageTargetTexture2DOES";
}
QT_END_NAMESPACE
#endif
@@ -0,0 +1,166 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "drmeglserverbufferintegration.h"
#include <QtOpenGL/QOpenGLTexture>
#include <QtGui/QOpenGLContext>
QT_BEGIN_NAMESPACE
DrmEglServerBuffer::DrmEglServerBuffer(DrmEglServerBufferIntegration *integration, const QImage &qimage, QtWayland::ServerBuffer::Format format)
: QtWayland::ServerBuffer(qimage.size(),format)
, m_integration(integration)
{
m_format = format;
EGLint egl_format;
switch (m_format) {
case RGBA32:
m_drm_format = QtWaylandServer::qt_drm_egl_server_buffer::format_RGBA32;
egl_format = EGL_DRM_BUFFER_FORMAT_ARGB32_MESA;
break;
#ifdef EGL_DRM_BUFFER_FORMAT_A8_MESA
case A8:
m_drm_format = QtWaylandServer::qt_drm_egl_server_buffer::format_A8;
egl_format = EGL_DRM_BUFFER_FORMAT_A8_MESA;
break;
#endif
default:
qWarning("DrmEglServerBuffer: unsupported format");
m_drm_format = QtWaylandServer::qt_drm_egl_server_buffer::format_RGBA32;
egl_format = EGL_DRM_BUFFER_FORMAT_ARGB32_MESA;
break;
}
EGLint imageAttribs[] = {
EGL_WIDTH, m_size.width(),
EGL_HEIGHT, m_size.height(),
EGL_DRM_BUFFER_FORMAT_MESA, egl_format,
EGL_DRM_BUFFER_USE_MESA, EGL_DRM_BUFFER_USE_SHARE_MESA,
EGL_NONE
};
m_image = m_integration->eglCreateDRMImageMESA(imageAttribs);
EGLint handle;
if (!m_integration->eglExportDRMImageMESA(m_image, &m_name, &handle, &m_stride)) {
qWarning("DrmEglServerBuffer: Failed to export egl image");
}
m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
m_texture->create();
m_texture->bind();
m_integration->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, qimage.width(), qimage.height(), GL_RGBA, GL_UNSIGNED_BYTE, qimage.constBits());
m_texture->release();
m_texture->setSize(m_size.width(), m_size.height());
}
struct ::wl_resource *DrmEglServerBuffer::resourceForClient(struct ::wl_client *client)
{
auto *bufferResource = resourceMap().value(client);
if (!bufferResource) {
auto integrationResource = m_integration->resourceMap().value(client);
if (!integrationResource) {
qWarning("DrmEglServerBuffer::resourceForClient: Trying to get resource for ServerBuffer. But client is not bound to the drm_egl interface");
return nullptr;
}
struct ::wl_resource *drm_egl_integration_resource = integrationResource->handle;
Resource *resource = add(client, 1);
m_integration->send_server_buffer_created(drm_egl_integration_resource, resource->handle, m_name, m_size.width(), m_size.height(), m_stride, m_drm_format);
return resource->handle;
}
return bufferResource->handle;
}
QOpenGLTexture *DrmEglServerBuffer::toOpenGlTexture()
{
if (!m_texture) {
qWarning("DrmEglServerBuffer::toOpenGlTexture: no texture defined");
}
return m_texture;
}
bool DrmEglServerBuffer::bufferInUse()
{
return resourceMap().size() > 0;
}
DrmEglServerBufferIntegration::DrmEglServerBufferIntegration()
{
}
DrmEglServerBufferIntegration::~DrmEglServerBufferIntegration()
{
}
bool DrmEglServerBufferIntegration::initializeHardware(QWaylandCompositor *compositor)
{
Q_ASSERT(QGuiApplication::platformNativeInterface());
m_egl_display = static_cast<EGLDisplay>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay"));
if (!m_egl_display) {
qWarning("Can't initialize drm egl server buffer integration. Missing egl display from platform plugin");
return false;
}
const char *extensionString = eglQueryString(m_egl_display, EGL_EXTENSIONS);
if (!extensionString || !strstr(extensionString, "EGL_KHR_image")) {
qWarning("Failed to initialize drm egl server buffer integration. There is no EGL_KHR_image extension.\n");
return false;
}
m_egl_create_image = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
m_egl_destroy_image = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
if (!m_egl_create_image || !m_egl_destroy_image) {
qWarning("Failed to initialize drm egl server buffer integration. Could not resolve eglCreateImageKHR or eglDestroyImageKHR");
return false;
}
if (!extensionString || !strstr(extensionString, "EGL_MESA_drm_image")) {
qWarning("Failed to initialize drm egl server buffer integration. There is no EGL_MESA_drm_image extension.\n");
return false;
}
m_egl_create_drm_image = reinterpret_cast<PFNEGLCREATEDRMIMAGEMESAPROC>(eglGetProcAddress("eglCreateDRMImageMESA"));
m_egl_export_drm_image = reinterpret_cast<PFNEGLEXPORTDRMIMAGEMESAPROC>(eglGetProcAddress("eglExportDRMImageMESA"));
if (!m_egl_create_drm_image || !m_egl_export_drm_image) {
qWarning("Failed to initialize drm egl server buffer integration. Could not find eglCreateDRMImageMESA or eglExportDRMImageMESA.\n");
return false;
}
m_gl_egl_image_target_texture_2d = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
if (!m_gl_egl_image_target_texture_2d) {
qWarning("Failed to initialize drm egl server buffer integration. Could not find glEGLImageTargetTexture2DOES.\n");
return false;
}
QtWaylandServer::qt_drm_egl_server_buffer::init(compositor->display(), 1);
return true;
}
bool DrmEglServerBufferIntegration::supportsFormat(QtWayland::ServerBuffer::Format format) const
{
switch (format) {
case QtWayland::ServerBuffer::RGBA32:
return true;
case QtWayland::ServerBuffer::A8:
#ifdef EGL_DRM_BUFFER_FORMAT_A8_MESA
return true;
#else
return false;
#endif
default:
return false;
}
}
QtWayland::ServerBuffer *DrmEglServerBufferIntegration::createServerBufferFromImage(const QImage &qimage, QtWayland::ServerBuffer::Format format)
{
return new DrmEglServerBuffer(this, qimage, format);
}
QT_END_NAMESPACE
@@ -0,0 +1,139 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef DRMEGLSERVERBUFFERINTEGRATION_H
#define DRMEGLSERVERBUFFERINTEGRATION_H
#include <QtCore/QVariant>
#include <QtWaylandCompositor/private/qwlserverbufferintegration_p.h>
#include "qwayland-server-drm-egl-server-buffer.h"
#include <QtGui/QWindow>
#include <QtGui/qpa/qplatformnativeinterface.h>
#include <QtGui/QGuiApplication>
#include <QtWaylandCompositor/qwaylandcompositor.h>
#include <QtWaylandCompositor/private/qwayland-server-server-buffer-extension.h>
#include <QtCore/QDebug>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#ifndef EGL_KHR_image
typedef void *EGLImageKHR;
typedef EGLImageKHR (EGLAPIENTRYP PFNEGLCREATEIMAGEKHRPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay dpy, EGLImageKHR image);
#endif
#ifndef GL_OES_EGL_image
typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES image);
#endif
#ifndef EGL_MESA_drm_image
typedef EGLImageKHR (EGLAPIENTRYP PFNEGLCREATEDRMIMAGEMESAPROC) (EGLDisplay dpy, const EGLint *attrib_list);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLEXPORTDRMIMAGEMESAPROC) (EGLDisplay dpy, EGLImageKHR image, EGLint *name, EGLint *handle, EGLint *stride);
#endif
QT_BEGIN_NAMESPACE
class DrmEglServerBufferIntegration;
class QImage;
class DrmEglServerBuffer : public QtWayland::ServerBuffer, public QtWaylandServer::qt_server_buffer
{
public:
DrmEglServerBuffer(DrmEglServerBufferIntegration *integration, const QImage &qimage, QtWayland::ServerBuffer::Format format);
struct ::wl_resource *resourceForClient(struct ::wl_client *) override;
bool bufferInUse() override;
QOpenGLTexture *toOpenGlTexture() override;
private:
DrmEglServerBufferIntegration *m_integration = nullptr;
EGLImageKHR m_image;
int32_t m_name;
int32_t m_stride;
QOpenGLTexture *m_texture = nullptr;
QtWaylandServer::qt_drm_egl_server_buffer::format m_drm_format;
};
class DrmEglServerBufferIntegration :
public QtWayland::ServerBufferIntegration,
public QtWaylandServer::qt_drm_egl_server_buffer
{
public:
DrmEglServerBufferIntegration();
~DrmEglServerBufferIntegration() override;
bool initializeHardware(QWaylandCompositor *) override;
bool supportsFormat(QtWayland::ServerBuffer::Format format) const override;
QtWayland::ServerBuffer *createServerBufferFromImage(const QImage &qimage, QtWayland::ServerBuffer::Format format) override;
EGLDisplay display() const { return m_egl_display; }
inline EGLImageKHR eglCreateImageKHR(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);
inline EGLBoolean eglDestroyImageKHR (EGLImageKHR image);
inline EGLImageKHR eglCreateDRMImageMESA (const EGLint *attrib_list);
inline EGLBoolean eglExportDRMImageMESA (EGLImageKHR image, EGLint *name, EGLint *handle, EGLint *stride);
inline void glEGLImageTargetTexture2DOES (GLenum target, GLeglImageOES image);
private:
EGLDisplay m_egl_display = EGL_NO_DISPLAY;
PFNEGLCREATEDRMIMAGEMESAPROC m_egl_create_drm_image;
PFNEGLEXPORTDRMIMAGEMESAPROC m_egl_export_drm_image;
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_gl_egl_image_target_texture_2d;
PFNEGLCREATEIMAGEKHRPROC m_egl_create_image;
PFNEGLDESTROYIMAGEKHRPROC m_egl_destroy_image;
};
EGLImageKHR DrmEglServerBufferIntegration::eglCreateImageKHR(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list)
{
if (!m_egl_create_image) {
qWarning("DrmEglServerBufferIntegration: Trying to used unresolved function eglCreateImageKHR");
return EGL_NO_IMAGE_KHR;
}
return m_egl_create_image(m_egl_display, ctx, target, buffer,attrib_list);
}
EGLBoolean DrmEglServerBufferIntegration::eglDestroyImageKHR (EGLImageKHR image)
{
if (!m_egl_destroy_image) {
qWarning("DrmEglServerBufferIntegration: Trying to use unresolved function eglDestroyImageKHR");
return false;
}
return m_egl_destroy_image(m_egl_display, image);
}
EGLImageKHR DrmEglServerBufferIntegration::eglCreateDRMImageMESA (const EGLint *attrib_list)
{
if (m_egl_create_drm_image)
return m_egl_create_drm_image(m_egl_display, attrib_list);
else
qWarning("DrmEglServerBufferIntegration: Trying to use unresolved function eglCreateDRMImageMESA");
return EGL_NO_IMAGE_KHR;
}
EGLBoolean DrmEglServerBufferIntegration::eglExportDRMImageMESA (EGLImageKHR image, EGLint *name, EGLint *handle, EGLint *stride)
{
if (m_egl_export_drm_image)
return m_egl_export_drm_image(m_egl_display, image, name, handle, stride);
else
qWarning("DrmEglServerBufferIntegration: Trying to use unresolved function eglExportDRMImageMESA");
return 0;
}
void DrmEglServerBufferIntegration::glEGLImageTargetTexture2DOES (GLenum target, GLeglImageOES image)
{
if (m_gl_egl_image_target_texture_2d)
return m_gl_egl_image_target_texture_2d(target, image);
else
qWarning("DrmEglServerBufferIntegration: Trying to use unresolved function glEGLImageTargetTexture2DOES");
}
QT_END_NAMESPACE
#endif
@@ -0,0 +1,234 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "vsp2hardwarelayerintegration.h"
extern "C" {
#define private priv
#include <wayland-kms.h>
#undef private
}
#include <private/qwaylandquickhardwarelayer_p.h>
#include <private/qwaylandquickitem_p.h>
#include <private/qwaylandview_p.h>
#include <QWaylandQuickOutput>
#include <QQuickWindow>
#include <qpa/qlatformscreen_p.h>
using namespace QNativeInterface::Private;
QT_BEGIN_NAMESPACE
Vsp2Buffer::Vsp2Buffer(wl_kms_buffer *kmsBuffer)
: dmabufFd(kmsBuffer->fd)
, bytesPerLine(kmsBuffer->stride)
, drmPixelFormat(kmsBuffer->format)
, size(kmsBuffer->width, kmsBuffer->height)
{
}
Vsp2Layer::Vsp2Layer(QWaylandQuickHardwareLayer *hwLayer, Vsp2HardwareLayerIntegration *integration)
: m_hwLayer(hwLayer)
{
auto *wlItem = m_hwLayer->waylandItem();
m_screen = dynamic_cast<QVsp2Screen*>(wlItem->window()->screen()->handle());
Q_ASSERT(m_screen);
connect(hwLayer, &QWaylandQuickHardwareLayer::stackingLevelChanged, this, [integration](){
integration->recreateVspLayers();
});
connect(hwLayer->waylandItem(), &QWaylandQuickItem::surfaceChanged, this, &Vsp2Layer::handleSurfaceChanged);
connect(hwLayer->waylandItem(), &QQuickItem::opacityChanged, this, &Vsp2Layer::updateOpacity);
connect(hwLayer->waylandItem()->window(), &QQuickWindow::afterSynchronizing, this, &Vsp2Layer::updatePosition);
hwLayer->setSceneGraphPainting(false);
QWaylandViewPrivate::get(hwLayer->waylandItem()->view())->independentFrameCallback = true;
handleSurfaceChanged();
}
void Vsp2Layer::enableVspLayer()
{
auto *kmsBuffer = nextKmsBuffer();
if (!kmsBuffer)
return;
m_buffer = Vsp2Buffer(kmsBuffer);
updatePosition();
m_layerIndex = m_screen->addLayer(m_buffer.dmabufFd, m_buffer.size, m_position, m_buffer.drmPixelFormat, m_buffer.bytesPerLine);
auto *wlItem = m_hwLayer->waylandItem();
wlItem->surface()->frameStarted();
updateOpacity();
}
void Vsp2Layer::disableVspLayer()
{
m_screen->removeLayer(m_layerIndex);
m_layerIndex = -1;
m_screen = nullptr;
}
void Vsp2Layer::handleBufferCommitted()
{
if (!isEnabled()) {
enableVspLayer();
return;
}
auto *kmsBuffer = nextKmsBuffer();
Vsp2Buffer newBuffer(kmsBuffer);
if (m_buffer.dmabufFd != -1) {
bool formatChanged = false;
formatChanged |= newBuffer.bytesPerLine != m_buffer.bytesPerLine;
formatChanged |= newBuffer.size != m_buffer.size;
formatChanged |= newBuffer.drmPixelFormat != m_buffer.drmPixelFormat;
if (formatChanged) {
qWarning() << "The VSP2 Wayland hardware layer integration doesn't support changing"
<< "surface formats, this will most likely fail";
}
}
m_buffer = newBuffer;
m_screen->setLayerBuffer(m_layerIndex, m_buffer.dmabufFd);
auto *wlItem = m_hwLayer->waylandItem();
wlItem->surface()->frameStarted();
}
void Vsp2Layer::handleSurfaceChanged()
{
auto newSurface = m_hwLayer->waylandItem()->surface();
if (Q_UNLIKELY(newSurface == m_surface))
return;
if (this->m_surface)
disconnect(this->m_surface, &QWaylandSurface::redraw, this, &Vsp2Layer::handleBufferCommitted);
if (newSurface)
connect(newSurface, &QWaylandSurface::redraw, this, &Vsp2Layer::handleBufferCommitted, Qt::DirectConnection);
this->m_surface = newSurface;
}
void Vsp2Layer::updatePosition()
{
QWaylandQuickItem *wlItem = m_hwLayer->waylandItem();
QRectF localGeometry(0, 0, wlItem->width(), wlItem->height());
auto lastMatrix = QWaylandQuickItemPrivate::get(wlItem)->lastMatrix;
auto globalGeometry = lastMatrix.mapRect(localGeometry);
if (m_buffer.size != globalGeometry.size().toSize()) {
qWarning() << "wl_buffer size != WaylandQuickItem size and scaling has not been"
<< "implemented for the vsp2 hardware layer integration";
}
m_position = globalGeometry.topLeft().toPoint();
if (isEnabled())
m_screen->setLayerPosition(m_layerIndex, m_position);
}
void Vsp2Layer::updateOpacity()
{
if (isEnabled()) {
qreal opacity = m_hwLayer->waylandItem()->opacity();
m_screen->setLayerAlpha(m_layerIndex, opacity);
}
}
wl_kms_buffer *Vsp2Layer::nextKmsBuffer()
{
Q_ASSERT(m_hwLayer && m_hwLayer->waylandItem());
QWaylandQuickItem *wlItem = m_hwLayer->waylandItem();
auto view = wlItem->view();
Q_ASSERT(view);
view->advance();
auto wlBuffer = view->currentBuffer().wl_buffer();
if (!wlBuffer)
return nullptr;
struct wl_kms_buffer *kmsBuffer = wayland_kms_buffer_get(wlBuffer);
if (!kmsBuffer)
qWarning() << "Failed to get wl_kms_buffer for wl_buffer:" << wl_resource_get_id(wlBuffer);
return kmsBuffer;
}
void Vsp2HardwareLayerIntegration::enableVspLayers()
{
for (auto &layer : std::as_const(m_layers)) {
Q_ASSERT(!layer->isEnabled());
layer->enableVspLayer();
}
}
void Vsp2HardwareLayerIntegration::disableVspLayers()
{
for (auto it = m_layers.rbegin(); it != m_layers.rend(); ++it) {
if ((*it)->isEnabled())
(*it)->disableVspLayer();
}
}
void Vsp2HardwareLayerIntegration::sortLayersByDepth()
{
std::sort(m_layers.begin(), m_layers.end(), [](auto &l1, auto &l2){
return l1->hwLayer()->stackingLevel() < l2->hwLayer()->stackingLevel();
});
}
void Vsp2HardwareLayerIntegration::recreateVspLayers() {
disableVspLayers();
sortLayersByDepth();
enableVspLayers();
}
Vsp2HardwareLayerIntegration::Vsp2HardwareLayerIntegration()
{
if (QGuiApplication::platformName() != "eglfs") {
qWarning() << "Vsp2 layers are currently only supported on the eglfs platform plugin"
<< "with the eglfs_kms_vsp2 device integration.\n"
<< "You need to set QT_QPA_PLATFORM=eglfs and QT_QPA_EGLFS_INTEGRATION=eglfs_kms_vsp2";
}
static Vsp2HardwareLayerIntegration *s_instance = this;
auto screen = dynamic_cast<QVsp2Screen*>(QGuiApplication::primaryScreen()->handle());
screen->addBlendListener([](){
s_instance->sendFrameCallbacks();
});
}
void Vsp2HardwareLayerIntegration::add(QWaylandQuickHardwareLayer *hwLayer)
{
disableVspLayers();
m_layers.append(QSharedPointer<Vsp2Layer>(new Vsp2Layer(hwLayer, this)));
sortLayersByDepth();
enableVspLayers();
}
void Vsp2HardwareLayerIntegration::remove(QWaylandQuickHardwareLayer *hwLayer)
{
disableVspLayers();
for (auto it = m_layers.begin(); it != m_layers.end(); ++it) {
if ((*it)->hwLayer() == hwLayer) {
m_layers.erase(it);
break;
}
}
enableVspLayers();
}
void Vsp2HardwareLayerIntegration::sendFrameCallbacks()
{
for (auto &layer : std::as_const(m_layers)) {
if (auto *surface = layer->hwLayer()->waylandItem()->surface())
surface->sendFrameCallbacks();
}
}
QT_END_NAMESPACE
@@ -0,0 +1,85 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef VSP2HARDWARELAYERINTEGRATION_H
#define VSP2HARDWARELAYERINTEGRATION_H
#include <QtWaylandCompositor/private/qwlhardwarelayerintegration_p.h>
#include <private/qobject_p.h>
#include <QPoint>
#include <QSize>
struct wl_kms_buffer;
QT_BEGIN_NAMESPACE
namespace QNativeInterface::Private {
struct QVsp2Screen;
}
class QScreen;
class QWaylandSurface;
class QWaylandQuickHardwareLayer;
class Vsp2Layer;
class Vsp2HardwareLayerIntegration : public QtWayland::HardwareLayerIntegration
{
Q_OBJECT
public:
explicit Vsp2HardwareLayerIntegration();
void add(QWaylandQuickHardwareLayer *layer) override;
void remove(QWaylandQuickHardwareLayer *layer) override;
void sendFrameCallbacks();
QList<QSharedPointer<Vsp2Layer>> m_layers;
private:
void enableVspLayers();
void disableVspLayers();
void sortLayersByDepth();
void recreateVspLayers();
friend class Vsp2Layer;
};
struct Vsp2Buffer
{
explicit Vsp2Buffer() = default;
explicit Vsp2Buffer(wl_kms_buffer *kmsBuffer);
int dmabufFd = -1;
uint bytesPerLine = 0;
uint drmPixelFormat = 0;
QSize size;
};
class Vsp2Layer : public QObject
{
Q_OBJECT
public:
explicit Vsp2Layer(QWaylandQuickHardwareLayer *m_hwLayer, Vsp2HardwareLayerIntegration *integration);
void enableVspLayer();
void disableVspLayer();
bool isEnabled() { return m_layerIndex != -1; }
QWaylandQuickHardwareLayer *hwLayer() const { return m_hwLayer; }
public Q_SLOTS:
void handleBufferCommitted();
void handleSurfaceChanged();
void updatePosition();
void updateOpacity();
private:
wl_kms_buffer *nextKmsBuffer();
int m_layerIndex = -1;
QVsp2Screen *m_screen = nullptr;
QPoint m_position;
QWaylandQuickHardwareLayer *m_hwLayer = nullptr;
QWaylandSurface *m_surface = nullptr;
Vsp2Buffer m_buffer;
};
QT_END_NAMESPACE
#endif // VSP2HARDWARELAYERINTEGRATION_H
@@ -0,0 +1,166 @@
// Copyright (C) 2017 Jolla Ltd, author: <giulio.camuffo@jollamobile.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "libhybriseglserverbufferintegration.h"
#include <QtGui/QOpenGLContext>
#include <QtGui/QOpenGLTexture>
#include <hybris/eglplatformcommon/hybris_nativebufferext.h>
#include <wayland-server-core.h>
QT_BEGIN_NAMESPACE
LibHybrisEglServerBuffer::LibHybrisEglServerBuffer(LibHybrisEglServerBufferIntegration *integration, const QImage &qimage, QtWayland::ServerBuffer::Format format)
: QtWayland::ServerBuffer(qimage.size(),format)
, m_integration(integration)
{
m_format = format;
EGLint egl_format;
switch (m_format) {
case RGBA32:
m_hybris_format = QtWaylandServer::qt_libhybris_egl_server_buffer::format_RGBA32;
egl_format = HYBRIS_PIXEL_FORMAT_RGBA_8888;
break;
default:
qWarning("LibHybrisEglServerBuffer: unsupported format");
m_hybris_format = QtWaylandServer::qt_libhybris_egl_server_buffer::format_RGBA32;
egl_format = HYBRIS_PIXEL_FORMAT_RGBA_8888;
break;
}
if (!m_integration->eglHybrisCreateNativeBuffer(m_size.width(), m_size.height(), HYBRIS_USAGE_HW_TEXTURE, egl_format, &m_stride, &m_buffer)) {
qWarning("LibHybrisEglServerBuffer: Failed to create egl buffer");
return;
}
int numInts;
int numFds;
m_integration->eglHybrisGetNativeBufferInfo(m_buffer, &numInts, &numFds);
m_ints.resize(numInts);
m_fds.resize(numFds);
m_integration->eglHybrisSerializeNativeBuffer(m_buffer, m_ints.data(), m_fds.data());
m_image = m_integration->eglCreateImageKHR(EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, m_buffer, 0);
if (!QOpenGLContext::currentContext()) {
qWarning("LibHybrisEglServerBuffer: No current context when creating buffer. Texture loading will fail");
return;
}
m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
m_texture->create();
m_texture->bind();
m_integration->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, qimage.width(), qimage.height(), GL_RGBA, GL_UNSIGNED_BYTE, qimage.constBits());
m_texture->release();
m_texture->setSize(m_size.width(), m_size.height());
}
struct ::wl_resource *LibHybrisEglServerBuffer::resourceForClient(struct ::wl_client *client)
{
auto *bufferResource = resourceMap().value(client);
if (!bufferResource) {
auto integrationResource = m_integration->resourceMap().value(client);
if (!integrationResource) {
qWarning("LibHybrisEglServerBuffer::resourceForClient: Trying to get resource for ServerBuffer. But client is not bound to the libhybris_egl interface");
return 0;
}
struct ::wl_resource *egl_integration_resource = integrationResource->handle;
Resource *resource = add(client, 1);
wl_resource *bufRes = wl_resource_create(client, &qt_libhybris_buffer_interface,-1, 0);
m_integration->send_server_buffer_created(egl_integration_resource, resource->handle, bufRes, m_fds.size(), QByteArray((char *)m_ints.data(), m_ints.size() * sizeof(int32_t)),
m_name, m_size.width(), m_size.height(), m_stride, m_format);
for (int i = 0; i < m_fds.size(); ++i) {
send_add_fd(resource->handle, m_fds.at(i));
}
return resource->handle;
}
return bufferResource->handle;
}
QOpenGLTexture *LibHybrisEglServerBuffer::toOpenGlTexture()
{
return m_texture;
}
LibHybrisEglServerBufferIntegration::LibHybrisEglServerBufferIntegration()
{
}
LibHybrisEglServerBufferIntegration::~LibHybrisEglServerBufferIntegration()
{
}
bool LibHybrisEglServerBufferIntegration::initializeHardware(QWaylandCompositor *compositor)
{
Q_ASSERT(QGuiApplication::platformNativeInterface());
m_egl_display = static_cast<EGLDisplay>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay"));
if (!m_egl_display) {
qWarning("Can't initialize libhybris egl server buffer integration. Missing egl display from platform plugin");
return false;
}
m_egl_create_buffer = reinterpret_cast<PFNEGLHYBRISCREATENATIVEBUFFERPROC>(eglGetProcAddress("eglHybrisCreateNativeBuffer"));
if (!m_egl_create_buffer) {
qWarning("Failed to initialize libhybris egl server buffer integration. Could not find eglHybrisCreateNativeBuffer.\n");
return false;
}
m_egl_get_buffer_info = reinterpret_cast<PFNEGLHYBRISGETNATIVEBUFFERINFOPROC>(eglGetProcAddress("eglHybrisGetNativeBufferInfo"));
if (!m_egl_get_buffer_info) {
qWarning("Failed to initialize libhybris egl server buffer integration. Could not find eglHybrisGetNativeBufferInfo.\n");
return false;
}
m_egl_serialize_buffer = reinterpret_cast<PFNEGLHYBRISSERIALIZENATIVEBUFFERPROC>(eglGetProcAddress("eglHybrisSerializeNativeBuffer"));
if (!m_egl_serialize_buffer) {
qWarning("Failed to initialize libhybris egl server buffer integration. Could not find eglHybrisSerializeNativeBuffer.\n");
return false;
}
const char *extensionString = eglQueryString(m_egl_display, EGL_EXTENSIONS);
if (!extensionString || !strstr(extensionString, "EGL_KHR_image")) {
qWarning("Failed to initialize libhybris egl server buffer integration. There is no EGL_KHR_image extension.\n");
return false;
}
m_egl_create_image = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
m_egl_destroy_image = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
if (!m_egl_create_image || !m_egl_destroy_image) {
qWarning("Failed to initialize libhybris egl server buffer integration. Could not resolve eglCreateImageKHR or eglDestroyImageKHR");
return false;
}
m_gl_egl_image_target_texture_2d = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
if (!m_gl_egl_image_target_texture_2d) {
qWarning("Failed to initialize libhybris egl server buffer integration. Could not find glEGLImageTargetTexture2DOES.\n");
return false;
}
QtWaylandServer::qt_libhybris_egl_server_buffer::init(compositor->display(), 1);
return true;
}
bool LibHybrisEglServerBufferIntegration::supportsFormat(QtWayland::ServerBuffer::Format format) const
{
switch (format) {
case QtWayland::ServerBuffer::RGBA32:
return true;
case QtWayland::ServerBuffer::A8:
return false;
default:
return false;
}
}
QtWayland::ServerBuffer *LibHybrisEglServerBufferIntegration::createServerBufferFromImage(const QImage &qimage, QtWayland::ServerBuffer::Format format)
{
return new LibHybrisEglServerBuffer(this, qimage, format);
}
QT_END_NAMESPACE
@@ -0,0 +1,152 @@
// Copyright (C) 2017 Jolla Ltd, author: <giulio.camuffo@jollamobile.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef LIBHYBRISEGLSERVERBUFFERINTEGRATION_H
#define LIBHYBRISEGLSERVERBUFFERINTEGRATION_H
#include <QtWaylandCompositor/private/qwlserverbufferintegration_p.h>
#include "qwayland-server-libhybris-egl-server-buffer.h"
#include <QtGui/QWindow>
#include <QtGui/qpa/qplatformnativeinterface.h>
#include <QtGui/QGuiApplication>
#include <QtWaylandCompositor/qwaylandcompositor.h>
#include <QtWaylandCompositor/private/qwayland-server-server-buffer-extension.h>
#include <QtCore/QDebug>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#ifndef EGL_KHR_image
typedef void *EGLImageKHR;
typedef EGLImageKHR (EGLAPIENTRYP PFNEGLCREATEIMAGEKHRPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay dpy, EGLImageKHR image);
#endif
#ifndef EGL_HYBRIS_native_buffer
typedef EGLBoolean (EGLAPIENTRYP PFNEGLHYBRISCREATENATIVEBUFFERPROC)(EGLint width, EGLint height, EGLint usage, EGLint format, EGLint *stride, EGLClientBuffer *buffer);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLHYBRISGETNATIVEBUFFERINFOPROC)(EGLClientBuffer buffer, int *num_ints, int *num_fds);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLHYBRISSERIALIZENATIVEBUFFERPROC)(EGLClientBuffer buffer, int *ints, int *fds);
#endif
#ifndef GL_OES_EGL_image
typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES image);
#endif
QT_BEGIN_NAMESPACE
class LibHybrisEglServerBufferIntegration;
class LibHybrisEglServerBuffer : public QtWayland::ServerBuffer, public QtWaylandServer::qt_libhybris_buffer
{
public:
LibHybrisEglServerBuffer(LibHybrisEglServerBufferIntegration *integration, const QImage &qimage, QtWayland::ServerBuffer::Format format);
struct ::wl_resource *resourceForClient(struct ::wl_client *) override;
QOpenGLTexture *toOpenGlTexture() override;
private:
LibHybrisEglServerBufferIntegration *m_integration = nullptr;
EGLImageKHR m_image;
EGLClientBuffer m_buffer;
int32_t m_name;
int32_t m_stride;
QOpenGLTexture *m_texture = nullptr;
QtWaylandServer::qt_libhybris_egl_server_buffer::format m_hybris_format;
QList<int32_t> m_ints;
QList<int32_t> m_fds;
};
class LibHybrisEglServerBufferIntegration :
public QtWayland::ServerBufferIntegration,
public QtWaylandServer::qt_libhybris_egl_server_buffer
{
public:
LibHybrisEglServerBufferIntegration();
~LibHybrisEglServerBufferIntegration();
bool initializeHardware(QWaylandCompositor *);
bool supportsFormat(QtWayland::ServerBuffer::Format format) const override;
QtWayland::ServerBuffer *createServerBufferFromImage(const QImage &qimage, QtWayland::ServerBuffer::Format format) override;
EGLDisplay display() const { return m_egl_display; }
inline EGLImageKHR eglCreateImageKHR(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);
inline EGLBoolean eglDestroyImageKHR (EGLImageKHR image);
inline void glEGLImageTargetTexture2DOES (GLenum target, GLeglImageOES image);
inline EGLBoolean eglHybrisCreateNativeBuffer(EGLint width, EGLint height, EGLint usage, EGLint format, EGLint *stride, EGLClientBuffer *buffer);
inline void eglHybrisGetNativeBufferInfo(EGLClientBuffer buffer, int *num_ints, int *num_fds);
inline void eglHybrisSerializeNativeBuffer(EGLClientBuffer buffer, int *ints, int *fds);
private:
EGLDisplay m_egl_display = EGL_NO_DISPLAY;
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_gl_egl_image_target_texture_2d;
PFNEGLHYBRISCREATENATIVEBUFFERPROC m_egl_create_buffer;
PFNEGLHYBRISGETNATIVEBUFFERINFOPROC m_egl_get_buffer_info;
PFNEGLHYBRISSERIALIZENATIVEBUFFERPROC m_egl_serialize_buffer;
PFNEGLCREATEIMAGEKHRPROC m_egl_create_image;
PFNEGLDESTROYIMAGEKHRPROC m_egl_destroy_image;
};
EGLImageKHR LibHybrisEglServerBufferIntegration::eglCreateImageKHR(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list)
{
if (!m_egl_create_image) {
qWarning("LibHybrisEglServerBufferIntegration: Trying to used unresolved function eglCreateImageKHR");
return EGL_NO_IMAGE_KHR;
}
return m_egl_create_image(m_egl_display, ctx, target, buffer,attrib_list);
}
EGLBoolean LibHybrisEglServerBufferIntegration::eglDestroyImageKHR (EGLImageKHR image)
{
if (!m_egl_destroy_image) {
qWarning("LibHybrisEglServerBufferIntegration: Trying to use unresolved function eglDestroyImageKHR");
return false;
}
return m_egl_destroy_image(m_egl_display, image);
}
void LibHybrisEglServerBufferIntegration::glEGLImageTargetTexture2DOES (GLenum target, GLeglImageOES image)
{
if (m_gl_egl_image_target_texture_2d)
return m_gl_egl_image_target_texture_2d(target, image);
else
qWarning("LibHybrisEglServerBufferIntegration: Trying to use unresolved function glEGLImageTargetTexture2DOES");
}
EGLBoolean LibHybrisEglServerBufferIntegration::eglHybrisCreateNativeBuffer(EGLint width, EGLint height, EGLint usage, EGLint format, EGLint *stride, EGLClientBuffer *buffer)
{
if (m_egl_create_buffer)
return m_egl_create_buffer(width, height, usage, format, stride, buffer);
else
qWarning("LibHybrisEglServerBufferIntegration: Trying to use unresolved function eglHybrisCreateNativeBuffer");
return EGL_FALSE;
}
void LibHybrisEglServerBufferIntegration::eglHybrisGetNativeBufferInfo(EGLClientBuffer buffer, int *num_ints, int *num_fds)
{
if (m_egl_get_buffer_info)
m_egl_get_buffer_info(buffer, num_ints, num_fds);
else
qWarning("LibHybrisEglServerBufferIntegration: Trying to use unresolved function eglHybrisGetNativeBufferInfo");
}
void LibHybrisEglServerBufferIntegration::eglHybrisSerializeNativeBuffer(EGLClientBuffer buffer, int *ints, int *fds)
{
if (m_egl_serialize_buffer)
m_egl_serialize_buffer(buffer, ints, fds);
else
qWarning("LibHybrisEglServerBufferIntegration: Trying to use unresolved function eglHybrisSerializeNativeBuffer");
}
QT_END_NAMESPACE
#endif
@@ -0,0 +1,341 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "linuxdmabuf.h"
#include "linuxdmabufclientbufferintegration.h"
#include <QtWaylandCompositor/QWaylandCompositor>
#include <QtWaylandCompositor/private/qwltextureorphanage_p.h>
#include <drm_fourcc.h>
#include <drm_mode.h>
#include <unistd.h>
QT_BEGIN_NAMESPACE
LinuxDmabuf::LinuxDmabuf(wl_display *display, LinuxDmabufClientBufferIntegration *clientBufferIntegration)
: zwp_linux_dmabuf_v1(display, 3 /*version*/)
, m_clientBufferIntegration(clientBufferIntegration)
{
}
void LinuxDmabuf::setSupportedModifiers(const QHash<uint32_t, QList<uint64_t>> &modifiers)
{
Q_ASSERT(resourceMap().isEmpty());
m_modifiers = modifiers;
}
void LinuxDmabuf::zwp_linux_dmabuf_v1_bind_resource(Resource *resource)
{
for (auto it = m_modifiers.constBegin(); it != m_modifiers.constEnd(); ++it) {
auto format = it.key();
auto modifiers = it.value();
// send DRM_FORMAT_MOD_INVALID when no modifiers are supported for a format
if (modifiers.isEmpty())
modifiers << DRM_FORMAT_MOD_INVALID;
for (const auto &modifier : std::as_const(modifiers)) {
if (resource->version() >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) {
const uint32_t modifier_lo = modifier & 0xFFFFFFFF;
const uint32_t modifier_hi = modifier >> 32;
send_modifier(resource->handle, format, modifier_hi, modifier_lo);
} else if (modifier == DRM_FORMAT_MOD_LINEAR || modifier == DRM_FORMAT_MOD_INVALID) {
send_format(resource->handle, format);
}
}
}
}
void LinuxDmabuf::zwp_linux_dmabuf_v1_create_params(Resource *resource, uint32_t params_id)
{
wl_resource *r = wl_resource_create(resource->client(), &zwp_linux_buffer_params_v1_interface,
wl_resource_get_version(resource->handle), params_id);
new LinuxDmabufParams(m_clientBufferIntegration, r); // deleted by the client, or when it disconnects
}
LinuxDmabufParams::LinuxDmabufParams(LinuxDmabufClientBufferIntegration *clientBufferIntegration, wl_resource *resource)
: zwp_linux_buffer_params_v1(resource)
, m_clientBufferIntegration(clientBufferIntegration)
{
}
LinuxDmabufParams::~LinuxDmabufParams()
{
for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
if (it.value().fd != -1)
close(it.value().fd);
it.value().fd = -1;
}
}
bool LinuxDmabufParams::handleCreateParams(Resource *resource, int width, int height, uint format, uint flags)
{
if (m_used) {
wl_resource_post_error(resource->handle,
ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
"Params already used");
return false;
}
if (width <= 0 || height <= 0) {
wl_resource_post_error(resource->handle,
ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS,
"Invalid dimensions in create request");
return false;
}
if (m_planes.isEmpty()) {
wl_resource_post_error(resource->handle,
ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
"Cannot create a buffer with no planes");
return false;
}
// check for holes in plane sequence
auto planeIds = m_planes.keys();
std::sort(planeIds.begin(), planeIds.end());
for (int i = 0; i < planeIds.size(); ++i) {
if (uint(i) != planeIds[i]) {
wl_resource_post_error(resource->handle,
ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
"No dmabuf parameters provided for plane %i", i);
return false;
}
}
// check for overflows
for (auto it = m_planes.constBegin(); it != m_planes.constEnd(); ++it) {
const auto planeId = it.key();
const auto plane = it.value();
if (static_cast<int64_t>(plane.offset) + plane.stride > UINT32_MAX) {
wl_resource_post_error(resource->handle,
ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
"Size overflow for plane %i",
planeId);
return false;
}
if (planeId == 0 && static_cast<int64_t>(plane.offset) + plane.stride * static_cast<int64_t>(height) > UINT32_MAX) {
wl_resource_post_error(resource->handle,
ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
"Size overflow for plane %i",
planeId);
return false;
}
// do not report an error as it might be caused by the kernel not supporting seeking on dmabuf
off_t size = lseek(plane.fd, 0, SEEK_END);
if (size == -1) {
qCDebug(qLcWaylandCompositorHardwareIntegration) << "Seeking is not supported";
continue;
}
if (static_cast<int64_t>(plane.offset) >= size) {
wl_resource_post_error(resource->handle,
ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
"Invalid offset %i for plane %i",
plane.offset, planeId);
return false;
}
if (static_cast<int64_t>(plane.offset) + static_cast<int64_t>(plane.stride) > size) {
wl_resource_post_error(resource->handle,
ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
"Invalid stride %i for plane %i",
plane.stride, planeId);
return false;
}
// only valid for first plane as other planes might be sub-sampled
if (planeId == 0 && plane.offset + static_cast<int64_t>(plane.stride) * height > size) {
wl_resource_post_error(resource->handle,
ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
"Invalid buffer stride or height for plane %i", planeId);
return false;
}
}
m_size = QSize(width, height);
m_drmFormat = format;
m_flags = flags;
m_used = true;
return true;
}
void LinuxDmabufParams::zwp_linux_buffer_params_v1_destroy(Resource *resource)
{
wl_resource_destroy(resource->handle);
}
void LinuxDmabufParams::zwp_linux_buffer_params_v1_destroy_resource(Resource *resource)
{
Q_UNUSED(resource);
delete this;
}
void LinuxDmabufParams::zwp_linux_buffer_params_v1_add(Resource *resource, int32_t fd, uint32_t plane_idx, uint32_t offset, uint32_t stride, uint32_t modifier_hi, uint32_t modifier_lo)
{
const uint64_t modifiers = (static_cast<uint64_t>(modifier_hi) << 32) | modifier_lo;
if (plane_idx >= LinuxDmabufWlBuffer::MaxDmabufPlanes) {
wl_resource_post_error(resource->handle,
ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX,
"Plane index %i is out of bounds", plane_idx);
}
if (m_planes.contains(plane_idx)) {
wl_resource_post_error(resource->handle,
ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET,
"Plane already set");
}
Plane plane;
plane.fd = fd;
plane.modifiers = modifiers;
plane.offset = offset;
plane.stride = stride;
m_planes.insert(plane_idx, plane);
}
void LinuxDmabufParams::zwp_linux_buffer_params_v1_create(Resource *resource, int32_t width, int32_t height, uint32_t format, uint32_t flags)
{
if (!handleCreateParams(resource, width, height, format, flags))
return;
auto *buffer = new LinuxDmabufWlBuffer(resource->client(), m_clientBufferIntegration);
buffer->m_size = m_size;
buffer->m_flags = m_flags;
buffer->m_drmFormat = m_drmFormat;
buffer->m_planesNumber = m_planes.size(); // it is checked before that planes are in consecutive sequence
for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
buffer->m_planes[it.key()] = it.value();
it.value().fd = -1; // ownership is moved
}
if (!m_clientBufferIntegration->importBuffer(buffer->resource()->handle, buffer)) {
send_failed(resource->handle);
} else {
send_created(resource->handle, buffer->resource()->handle);
}
}
void LinuxDmabufParams::zwp_linux_buffer_params_v1_create_immed(Resource *resource, uint32_t buffer_id, int32_t width, int32_t height, uint32_t format, uint32_t flags)
{
if (!handleCreateParams(resource, width, height, format, flags))
return;
auto *buffer = new LinuxDmabufWlBuffer(resource->client(), m_clientBufferIntegration, buffer_id);
buffer->m_size = m_size;
buffer->m_flags = m_flags;
buffer->m_drmFormat = m_drmFormat;
buffer->m_planesNumber = m_planes.size(); // it is checked before that planes are in consecutive sequence
for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
buffer->m_planes[it.key()] = it.value();
it.value().fd = -1; // ownership is moved
}
if (!m_clientBufferIntegration->importBuffer(buffer->resource()->handle, buffer)) {
// for the 'create_immed' request, the implementation can decide
// how to handle the failure by an unknown cause; we decide
// to raise a fatal error at the client
wl_resource_post_error(resource->handle,
ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER,
"Import of the provided DMA buffer failed");
}
// note: create signal shall not be sent for the 'create_immed' request
}
LinuxDmabufWlBuffer::LinuxDmabufWlBuffer(::wl_client *client, LinuxDmabufClientBufferIntegration *clientBufferIntegration, uint id)
: wl_buffer(client, id, 1 /*version*/)
, m_clientBufferIntegration(clientBufferIntegration)
{
}
LinuxDmabufWlBuffer::~LinuxDmabufWlBuffer()
{
if (resource())
m_clientBufferIntegration->removeBuffer(resource()->handle);
deleteTextures();
}
void LinuxDmabufWlBuffer::buffer_destroy(Resource *resource)
{
m_clientBufferIntegration->removeBuffer(resource->handle);
wl_resource_destroy(resource->handle);
}
void LinuxDmabufWlBuffer::deleteTextures()
{
QMutexLocker locker(&m_texturesLock);
for (uint32_t i = 0; i < m_planesNumber; ++i) {
if (m_textures[i] != nullptr) {
QtWayland::QWaylandTextureOrphanage::instance()->admitTexture(m_textures[i],
m_texturesContext[i]);
m_textures[i] = nullptr;
m_texturesContext[i] = nullptr;
QObject::disconnect(m_texturesAboutToBeDestroyedConnection[i]);
m_texturesAboutToBeDestroyedConnection[i] = QMetaObject::Connection();
}
if (m_eglImages[i] != EGL_NO_IMAGE_KHR) {
m_clientBufferIntegration->deleteImage(m_eglImages[i]);
m_eglImages[i] = EGL_NO_IMAGE_KHR;
}
if (m_planes[i].fd != -1)
close(m_planes[i].fd);
m_planes[i].fd = -1;
}
m_planesNumber = 0;
}
void LinuxDmabufWlBuffer::initImage(uint32_t plane, EGLImageKHR image)
{
Q_ASSERT(plane < m_planesNumber);
Q_ASSERT(m_eglImages.at(plane) == EGL_NO_IMAGE_KHR);
m_eglImages[plane] = image;
}
void LinuxDmabufWlBuffer::initTexture(uint32_t plane, QOpenGLTexture *texture)
{
QMutexLocker locker(&m_texturesLock);
Q_ASSERT(plane < m_planesNumber);
Q_ASSERT(m_textures.at(plane) == nullptr);
Q_ASSERT(QOpenGLContext::currentContext());
m_textures[plane] = texture;
m_texturesContext[plane] = QOpenGLContext::currentContext();
m_texturesAboutToBeDestroyedConnection[plane] =
QObject::connect(m_texturesContext[plane], &QOpenGLContext::aboutToBeDestroyed,
m_texturesContext[plane], [this, plane]() {
QMutexLocker locker(&this->m_texturesLock);
// See above lock - there is a chance that this has already been removed from m_textures[plane]!
// Furthermore, we can trust that all the rest (e.g. disconnect) has also been properly executed!
if (this->m_textures[plane] == nullptr)
return;
delete this->m_textures[plane];
qCDebug(qLcWaylandCompositorHardwareIntegration)
<< Q_FUNC_INFO
<< "texture deleted due to QOpenGLContext::aboutToBeDestroyed!"
<< "Pointer (now dead) was:" << (void*)(this->m_textures[plane])
<< " Associated context (about to die too) is: " << (void*)(this->m_texturesContext[plane]);
this->m_textures[plane] = nullptr;
this->m_texturesContext[plane] = nullptr;
QObject::disconnect(this->m_texturesAboutToBeDestroyedConnection[plane]);
this->m_texturesAboutToBeDestroyedConnection[plane] = QMetaObject::Connection();
}, Qt::DirectConnection);
}
void LinuxDmabufWlBuffer::buffer_destroy_resource(Resource *resource)
{
// In most cases this is redundant, but for instance if a buffer has been created,
// but not committed and the client disconnects, it is vital
m_clientBufferIntegration->removeBuffer(resource->handle);
}
QT_END_NAMESPACE
@@ -0,0 +1,140 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef LINUXDMABUF_H
#define LINUXDMABUF_H
#include "qwayland-server-linux-dmabuf-unstable-v1.h"
#include <QtWaylandCompositor/private/qwayland-server-wayland.h>
#include <QtWaylandCompositor/private/qwlclientbufferintegration_p.h>
#include <QtOpenGL/QOpenGLTexture>
#include <QtCore/QObject>
#include <QtCore/QHash>
#include <QtCore/QSize>
#include <QtCore/QTextStream>
#include <array>
#include <QtGui/QOpenGLContext>
#include <QtCore/QMutex>
#include <EGL/egl.h>
#include <EGL/eglext.h>
// compatibility with libdrm <= 2.4.74
#ifndef DRM_FORMAT_RESERVED
#define DRM_FORMAT_RESERVED ((1ULL << 56) - 1)
#endif
#ifndef DRM_FORMAT_MOD_VENDOR_NONE
#define DRM_FORMAT_MOD_VENDOR_NONE 0
#endif
#ifndef DRM_FORMAT_MOD_LINEAR
#define DRM_FORMAT_MOD_LINEAR fourcc_mod_code(NONE, 0)
#endif
#ifndef DRM_FORMAT_MOD_INVALID
#define DRM_FORMAT_MOD_INVALID fourcc_mod_code(NONE, DRM_FORMAT_RESERVED)
#endif
// Copied from eglmesaext.h
#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
QT_BEGIN_NAMESPACE
class QWaylandCompositor;
class QWaylandResource;
class LinuxDmabufParams;
class LinuxDmabufClientBufferIntegration;
struct Plane {
int fd = -1;
uint32_t offset = 0;
uint32_t stride = 0;
uint64_t modifiers = 0;
};
class LinuxDmabuf : public QtWaylandServer::zwp_linux_dmabuf_v1
{
public:
explicit LinuxDmabuf(wl_display *display, LinuxDmabufClientBufferIntegration *clientBufferIntegration);
void setSupportedModifiers(const QHash<uint32_t, QList<uint64_t>> &modifiers);
protected:
void zwp_linux_dmabuf_v1_bind_resource(Resource *resource) override;
void zwp_linux_dmabuf_v1_create_params(Resource *resource, uint32_t params_id) override;
private:
QHash<uint32_t, QList<uint64_t>> m_modifiers; // key=DRM format, value=supported DRM modifiers for format
LinuxDmabufClientBufferIntegration *m_clientBufferIntegration;
};
class LinuxDmabufParams : public QtWaylandServer::zwp_linux_buffer_params_v1
{
public:
explicit LinuxDmabufParams(LinuxDmabufClientBufferIntegration *clientBufferIntegration, wl_resource *resource);
~LinuxDmabufParams() override;
private:
bool handleCreateParams(Resource *resource, int width, int height, uint format, uint flags);
uint m_drmFormat = 0;
uint m_flags = 0;
QSize m_size;
bool m_used = false;
QMap<uint, Plane> m_planes;
LinuxDmabufClientBufferIntegration *m_clientBufferIntegration;
protected:
void zwp_linux_buffer_params_v1_destroy(Resource *resource) override;
void zwp_linux_buffer_params_v1_add(Resource *resource, int32_t fd, uint32_t plane_idx, uint32_t offset, uint32_t stride, uint32_t modifier_hi, uint32_t modifier_lo) override;
void zwp_linux_buffer_params_v1_create(Resource *resource, int32_t width, int32_t height, uint32_t format, uint32_t flags) override;
void zwp_linux_buffer_params_v1_create_immed(Resource *resource, uint32_t buffer_id, int32_t width, int32_t height, uint32_t format, uint32_t flags) override;
void zwp_linux_buffer_params_v1_destroy_resource(Resource *resource) override;
friend class LinuxDmabufClientBufferIntegrationPrivate;
};
class LinuxDmabufWlBuffer : public QtWaylandServer::wl_buffer
{
public:
explicit LinuxDmabufWlBuffer(::wl_client *client, LinuxDmabufClientBufferIntegration *clientBufferIntegration, uint id = 0);
~LinuxDmabufWlBuffer() override;
void initImage(uint32_t plane, EGLImageKHR image);
void initTexture(uint32_t plane, QOpenGLTexture *texture);
inline QSize size() const { return m_size; }
inline uint32_t flags() const { return m_flags; }
inline uint32_t drmFormat() const { return m_drmFormat; }
inline Plane& plane(uint index) { return m_planes.at(index); }
inline uint32_t planesNumber() const { return m_planesNumber; }
inline EGLImageKHR image(uint32_t plane) { return m_eglImages.at(plane); }
inline QOpenGLTexture *texture(uint32_t plane) const { return m_textures.at(plane); }
void buffer_destroy_resource(Resource *resource) override;
static const uint32_t MaxDmabufPlanes = 4;
private:
QSize m_size;
uint32_t m_flags = 0;
uint32_t m_drmFormat = EGL_TEXTURE_RGBA;
std::array<Plane, MaxDmabufPlanes> m_planes;
uint32_t m_planesNumber = 1;
LinuxDmabufClientBufferIntegration *m_clientBufferIntegration = nullptr;
std::array<EGLImageKHR, MaxDmabufPlanes> m_eglImages = { {EGL_NO_IMAGE_KHR, EGL_NO_IMAGE_KHR, EGL_NO_IMAGE_KHR, EGL_NO_IMAGE_KHR} };
std::array<QOpenGLTexture *, MaxDmabufPlanes> m_textures = { {nullptr, nullptr, nullptr, nullptr} };
std::array<QOpenGLContext *, MaxDmabufPlanes> m_texturesContext = { {nullptr, nullptr, nullptr, nullptr} };
std::array<QMetaObject::Connection, MaxDmabufPlanes> m_texturesAboutToBeDestroyedConnection = { {QMetaObject::Connection(), QMetaObject::Connection(), QMetaObject::Connection(), QMetaObject::Connection()} };
QMutex m_texturesLock;
void buffer_destroy(Resource *resource) override;
void deleteTextures();
friend class LinuxDmabufParams;
};
QT_END_NAMESPACE
#endif // LINUXDMABUF_H
@@ -0,0 +1,451 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "linuxdmabufclientbufferintegration.h"
#include "linuxdmabuf.h"
#include <QtWaylandCompositor/QWaylandCompositor>
#include <QtWaylandCompositor/private/qwayland-server-wayland.h>
#include <QtWaylandCompositor/private/qwltextureorphanage_p.h>
#include <qpa/qplatformnativeinterface.h>
#include <QtOpenGL/QOpenGLTexture>
#include <QtCore/QVarLengthArray>
#include <QtGui/QGuiApplication>
#include <QtGui/QOpenGLContext>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <unistd.h>
#include <drm_fourcc.h>
QT_BEGIN_NAMESPACE
static QWaylandBufferRef::BufferFormatEgl formatFromDrmFormat(EGLint format) {
switch (format) {
case DRM_FORMAT_RGB332:
case DRM_FORMAT_BGR233:
case DRM_FORMAT_XRGB4444:
case DRM_FORMAT_XBGR4444:
case DRM_FORMAT_RGBX4444:
case DRM_FORMAT_BGRX4444:
case DRM_FORMAT_XRGB1555:
case DRM_FORMAT_XBGR1555:
case DRM_FORMAT_RGBX5551:
case DRM_FORMAT_BGRX5551:
case DRM_FORMAT_RGB565:
case DRM_FORMAT_BGR565:
case DRM_FORMAT_RGB888:
case DRM_FORMAT_BGR888:
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_RGBX8888:
case DRM_FORMAT_BGRX8888:
case DRM_FORMAT_XRGB2101010:
case DRM_FORMAT_XBGR2101010:
case DRM_FORMAT_RGBX1010102:
case DRM_FORMAT_BGRX1010102:
return QWaylandBufferRef::BufferFormatEgl_RGB;
case DRM_FORMAT_ARGB4444:
case DRM_FORMAT_ABGR4444:
case DRM_FORMAT_RGBA4444:
case DRM_FORMAT_BGRA4444:
case DRM_FORMAT_ARGB1555:
case DRM_FORMAT_ABGR1555:
case DRM_FORMAT_RGBA5551:
case DRM_FORMAT_BGRA5551:
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_BGRA8888:
case DRM_FORMAT_ARGB2101010:
case DRM_FORMAT_ABGR2101010:
case DRM_FORMAT_RGBA1010102:
case DRM_FORMAT_BGRA1010102:
return QWaylandBufferRef::BufferFormatEgl_RGBA;
case DRM_FORMAT_YUYV:
return QWaylandBufferRef::BufferFormatEgl_Y_XUXV;
default:
qCDebug(qLcWaylandCompositorHardwareIntegration) << "Buffer format" << Qt::hex << format << "not supported";
return QWaylandBufferRef::BufferFormatEgl_Null;
}
}
static QOpenGLTexture::TextureFormat openGLFormatFromBufferFormat(QWaylandBufferRef::BufferFormatEgl format) {
switch (format) {
case QWaylandBufferRef::BufferFormatEgl_RGB:
return QOpenGLTexture::RGBFormat;
case QWaylandBufferRef::BufferFormatEgl_RGBA:
return QOpenGLTexture::RGBAFormat;
default:
return QOpenGLTexture::NoFormat;
}
}
// Initialize the EGLImage for a dmabuf buffer which conceptually consists of a
// single plane. Note that depending on the modifiers, the buffer may be actually
// transported as multiple dmabuf planes which must be combined into a single
// EGLImage. For formats where the buffer needs to be represented as multiple
// EGLImages (e.g., various YUV formats) a different approach is required.
bool LinuxDmabufClientBufferIntegration::initSimpleTexture(LinuxDmabufWlBuffer *dmabufBuffer)
{
bool success = true;
// Resolving GL functions may need a context current, so do it only here.
if (!gl_egl_image_target_texture_2d)
gl_egl_image_target_texture_2d = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
if (dmabufBuffer->plane(0).modifiers != DRM_FORMAT_MOD_INVALID && !m_supportsDmabufModifiers) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Buffer uses dmabuf modifiers, which are not supported.";
success = false;
}
// 6 entries for the common attribs plus 10 per possible plane, plus 1 for
// the final EGL_NONE sentinel.
QVarLengthArray<EGLint, 6 + 10 * 4 + 1> attribs;
attribs.append(EGL_WIDTH);
attribs.append(dmabufBuffer->size().width());
attribs.append(EGL_HEIGHT);
attribs.append(dmabufBuffer->size().height());
attribs.append(EGL_LINUX_DRM_FOURCC_EXT);
attribs.append(EGLint(dmabufBuffer->drmFormat()));
#define ADD_PLANE_ATTRIBS(plane_idx) { \
attribs.append(EGL_DMA_BUF_PLANE ## plane_idx ## _FD_EXT); \
attribs.append(dmabufBuffer->plane(plane_idx).fd); \
attribs.append(EGL_DMA_BUF_PLANE ## plane_idx ## _OFFSET_EXT); \
attribs.append(EGLint(dmabufBuffer->plane(plane_idx).offset)); \
attribs.append(EGL_DMA_BUF_PLANE ## plane_idx ## _PITCH_EXT); \
attribs.append(EGLint(dmabufBuffer->plane(plane_idx).stride)); \
if (dmabufBuffer->plane(plane_idx).modifiers != DRM_FORMAT_MOD_INVALID) { \
attribs.append(EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_LO_EXT); \
attribs.append(EGLint(dmabufBuffer->plane(plane_idx).modifiers & 0xffffffff)); \
attribs.append(EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_HI_EXT); \
attribs.append(EGLint(dmabufBuffer->plane(plane_idx).modifiers >> 32)); \
} \
}
switch (dmabufBuffer->planesNumber()) {
case 4:
ADD_PLANE_ATTRIBS(3);
Q_FALLTHROUGH();
case 3:
ADD_PLANE_ATTRIBS(2);
Q_FALLTHROUGH();
case 2:
ADD_PLANE_ATTRIBS(1);
Q_FALLTHROUGH();
case 1:
ADD_PLANE_ATTRIBS(0);
break;
default:
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Buffer uses invalid number of planes:" << dmabufBuffer->planesNumber();
return false;
}
attribs.append(EGL_NONE);
// note: EGLImageKHR does NOT take ownership of the file descriptors
EGLImageKHR image = egl_create_image(m_eglDisplay,
EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT,
(EGLClientBuffer) nullptr,
attribs.constData());
if (image == EGL_NO_IMAGE_KHR) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "failed to create EGL image from" <<
dmabufBuffer->planesNumber() << "plane(s)";
success = false;
}
dmabufBuffer->initImage(0, image);
return success;
}
bool LinuxDmabufClientBufferIntegration::initYuvTexture(LinuxDmabufWlBuffer *dmabufBuffer)
{
bool success = true;
const YuvFormatConversion conversion = m_yuvFormats.value(dmabufBuffer->drmFormat());
if (conversion.inputPlanes != dmabufBuffer->planesNumber()) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Buffer for this format must provide" << conversion.inputPlanes
<< "planes but only" << dmabufBuffer->planesNumber() << "received";
return false;
}
// Resolving GL functions may need a context current, so do it only here.
if (!gl_egl_image_target_texture_2d)
gl_egl_image_target_texture_2d = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
if (dmabufBuffer->plane(0).modifiers != DRM_FORMAT_MOD_INVALID && !m_supportsDmabufModifiers) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Buffer uses dmabuf modifiers, which are not supported.";
success = false;
}
for (uint32_t i = 0; i < conversion.outputPlanes; ++i) {
const YuvPlaneConversion plane = conversion.plane[i];
QVarLengthArray<EGLint, 17> attribs = {
EGL_WIDTH, dmabufBuffer->size().width() / plane.widthDivisor,
EGL_HEIGHT, dmabufBuffer->size().height() / plane.heightDivisor,
EGL_LINUX_DRM_FOURCC_EXT, plane.format,
EGL_DMA_BUF_PLANE0_FD_EXT, dmabufBuffer->plane(plane.planeIndex).fd,
EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGLint(dmabufBuffer->plane(plane.planeIndex).offset),
EGL_DMA_BUF_PLANE0_PITCH_EXT, EGLint(dmabufBuffer->plane(plane.planeIndex).stride),
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGLint(dmabufBuffer->plane(plane.planeIndex).modifiers & 0xffffffff),
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, EGLint(dmabufBuffer->plane(plane.planeIndex).modifiers >> 32),
EGL_NONE
};
// note: EGLImageKHR does NOT take ownership of the file descriptors
EGLImageKHR image = egl_create_image(m_eglDisplay,
EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT,
(EGLClientBuffer) nullptr,
attribs.constData());
if (image == EGL_NO_IMAGE_KHR) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "failed to create EGL image for plane" << i;
success = false;
}
dmabufBuffer->initImage(i, image);
}
return success;
}
LinuxDmabufClientBufferIntegration::LinuxDmabufClientBufferIntegration()
{
YuvPlaneConversion firstPlane;
firstPlane.format = DRM_FORMAT_GR88;
firstPlane.widthDivisor = 1;
firstPlane.heightDivisor = 1;
firstPlane.planeIndex = 0;
YuvPlaneConversion secondPlane;
secondPlane.format = DRM_FORMAT_ARGB8888;
secondPlane.widthDivisor = 2;
secondPlane.heightDivisor = 1;
secondPlane.planeIndex = 0;
YuvFormatConversion formatConversion;
formatConversion.inputPlanes = 1;
formatConversion.outputPlanes = 2;
formatConversion.plane[0] = firstPlane;
formatConversion.plane[1] = secondPlane;
m_yuvFormats.insert(DRM_FORMAT_YUYV, formatConversion);
}
LinuxDmabufClientBufferIntegration::~LinuxDmabufClientBufferIntegration()
{
m_importedBuffers.clear();
if (egl_unbind_wayland_display != nullptr && m_displayBound) {
Q_ASSERT(m_wlDisplay != nullptr);
if (!egl_unbind_wayland_display(m_eglDisplay, m_wlDisplay))
qCWarning(qLcWaylandCompositorHardwareIntegration) << "eglUnbindWaylandDisplayWL failed";
}
}
void LinuxDmabufClientBufferIntegration::initializeHardware(struct ::wl_display *display)
{
m_linuxDmabuf.reset(new LinuxDmabuf(display, this));
const bool ignoreBindDisplay = !qgetenv("QT_WAYLAND_IGNORE_BIND_DISPLAY").isEmpty() && qgetenv("QT_WAYLAND_IGNORE_BIND_DISPLAY").toInt() != 0;
// initialize hardware extensions
egl_query_dmabuf_modifiers_ext = reinterpret_cast<PFNEGLQUERYDMABUFMODIFIERSEXTPROC>(eglGetProcAddress("eglQueryDmaBufModifiersEXT"));
egl_query_dmabuf_formats_ext = reinterpret_cast<PFNEGLQUERYDMABUFFORMATSEXTPROC>(eglGetProcAddress("eglQueryDmaBufFormatsEXT"));
if (!egl_query_dmabuf_modifiers_ext || !egl_query_dmabuf_formats_ext) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Failed to initialize EGL display. Could not find eglQueryDmaBufModifiersEXT and eglQueryDmaBufFormatsEXT.";
return;
}
egl_bind_wayland_display = reinterpret_cast<PFNEGLBINDWAYLANDDISPLAYWL>(eglGetProcAddress("eglBindWaylandDisplayWL"));
egl_unbind_wayland_display = reinterpret_cast<PFNEGLUNBINDWAYLANDDISPLAYWL>(eglGetProcAddress("eglUnbindWaylandDisplayWL"));
if ((!egl_bind_wayland_display || !egl_unbind_wayland_display) && !ignoreBindDisplay) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Failed to initialize EGL display. Could not find eglBindWaylandDisplayWL and eglUnbindWaylandDisplayWL.";
return;
}
egl_create_image = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
egl_destroy_image = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
if (!egl_create_image || !egl_destroy_image) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Failed to initialize EGL display. Could not find eglCreateImageKHR and eglDestroyImageKHR.";
return;
}
// initialize EGL display
QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface();
if (!nativeInterface) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Failed to initialize EGL display. No native platform interface available.";
return;
}
m_eglDisplay = nativeInterface->nativeResourceForIntegration("EglDisplay");
if (!m_eglDisplay) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Failed to initialize EGL display. Could not get EglDisplay for window.";
return;
}
const char *extensionString = eglQueryString(m_eglDisplay, EGL_EXTENSIONS);
if (!extensionString || !strstr(extensionString, "EGL_EXT_image_dma_buf_import")) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "Failed to initialize EGL display. There is no EGL_EXT_image_dma_buf_import extension.";
return;
}
if (strstr(extensionString, "EGL_EXT_image_dma_buf_import_modifiers"))
m_supportsDmabufModifiers = true;
if (egl_bind_wayland_display && egl_unbind_wayland_display) {
m_displayBound = egl_bind_wayland_display(m_eglDisplay, display);
if (!m_displayBound)
qCDebug(qLcWaylandCompositorHardwareIntegration) << "Wayland display already bound by other client buffer integration.";
m_wlDisplay = display;
}
// request and sent formats/modifiers only after egl_display is bound
QHash<uint32_t, QList<uint64_t>> modifiers;
for (const auto &format : supportedDrmFormats()) {
modifiers[format] = supportedDrmModifiers(format);
}
m_linuxDmabuf->setSupportedModifiers(modifiers);
}
QList<uint32_t> LinuxDmabufClientBufferIntegration::supportedDrmFormats()
{
if (!egl_query_dmabuf_formats_ext)
return QList<uint32_t>();
// request total number of formats
EGLint count = 0;
EGLBoolean success = egl_query_dmabuf_formats_ext(m_eglDisplay, 0, nullptr, &count);
if (success && count > 0) {
QList<uint32_t> drmFormats(count);
if (egl_query_dmabuf_formats_ext(m_eglDisplay, count, (EGLint *) drmFormats.data(), &count))
return drmFormats;
}
return QList<uint32_t>();
}
QList<uint64_t> LinuxDmabufClientBufferIntegration::supportedDrmModifiers(uint32_t format)
{
if (!egl_query_dmabuf_modifiers_ext)
return QList<uint64_t>();
// request total number of formats
EGLint count = 0;
EGLBoolean success = egl_query_dmabuf_modifiers_ext(m_eglDisplay, format, 0, nullptr, nullptr, &count);
if (success && count > 0) {
QList<uint64_t> modifiers(count);
if (egl_query_dmabuf_modifiers_ext(m_eglDisplay, format, count, modifiers.data(), nullptr, &count)) {
return modifiers;
}
}
return QList<uint64_t>();
}
void LinuxDmabufClientBufferIntegration::deleteImage(EGLImageKHR image)
{
egl_destroy_image(m_eglDisplay, image);
}
QtWayland::ClientBuffer *LinuxDmabufClientBufferIntegration::createBufferFor(wl_resource *resource)
{
auto it = m_importedBuffers.find(resource);
if (it != m_importedBuffers.end())
return new LinuxDmabufClientBuffer(this, resource, it.value());
return nullptr;
}
bool LinuxDmabufClientBufferIntegration::importBuffer(wl_resource *resource, LinuxDmabufWlBuffer *linuxDmabufBuffer)
{
if (m_importedBuffers.contains(resource)) {
qCWarning(qLcWaylandCompositorHardwareIntegration) << "buffer has already been added";
return false;
}
m_importedBuffers[resource] = linuxDmabufBuffer;
if (m_yuvFormats.contains(linuxDmabufBuffer->drmFormat()))
return initYuvTexture(linuxDmabufBuffer);
else
return initSimpleTexture(linuxDmabufBuffer);
}
void LinuxDmabufClientBufferIntegration::removeBuffer(wl_resource *resource)
{
m_importedBuffers.remove(resource);
}
LinuxDmabufClientBuffer::LinuxDmabufClientBuffer(LinuxDmabufClientBufferIntegration *integration,
wl_resource *bufferResource,
LinuxDmabufWlBuffer *dmabufBuffer)
: ClientBuffer(bufferResource)
, m_integration(integration)
{
d = dmabufBuffer;
}
QOpenGLTexture *LinuxDmabufClientBuffer::toOpenGlTexture(int plane)
{
// At this point we should have a valid OpenGL context, so it's safe to destroy textures
QtWayland::QWaylandTextureOrphanage::instance()->deleteTextures();
if (!m_buffer)
return nullptr;
QOpenGLTexture *texture = d->texture(plane);
const auto target = static_cast<QOpenGLTexture::Target>(GL_TEXTURE_2D);
if (!texture) {
texture = new QOpenGLTexture(target);
texture->setFormat(openGLFormatFromBufferFormat(formatFromDrmFormat(d->drmFormat())));
texture->setSize(d->size().width(), d->size().height());
texture->create();
d->initTexture(plane, texture);
}
if (m_textureDirty) {
m_textureDirty = false;
texture->bind();
glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
m_integration->gl_egl_image_target_texture_2d(target, d->image(plane));
}
return texture;
}
void LinuxDmabufClientBuffer::setDestroyed()
{
m_integration->removeBuffer(m_buffer);
ClientBuffer::setDestroyed();
}
LinuxDmabufClientBuffer::~LinuxDmabufClientBuffer()
{
m_buffer = nullptr;
delete d;
}
QWaylandBufferRef::BufferFormatEgl LinuxDmabufClientBuffer::bufferFormatEgl() const
{
return formatFromDrmFormat(d->drmFormat());
}
QSize LinuxDmabufClientBuffer::size() const
{
return d->size();
}
QWaylandSurface::Origin LinuxDmabufClientBuffer::origin() const
{
return (d->flags() & QtWaylandServer::zwp_linux_buffer_params_v1::flags_y_invert) ? QWaylandSurface::OriginBottomLeft : QWaylandSurface::OriginTopLeft;
}
QT_END_NAMESPACE
@@ -0,0 +1,102 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef LINUXDMABUFCLIENTBUFFERINTEGRATION_H
#define LINUXDMABUFCLIENTBUFFERINTEGRATION_H
#include "linuxdmabuf.h"
#include <QtWaylandCompositor/private/qwlclientbufferintegration_p.h>
#include <QtWaylandCompositor/private/qwlclientbuffer_p.h>
#include <QtWaylandCompositor/private/qwayland-server-wayland.h>
#include <QtCore/QMutex>
#include <drm_fourcc.h>
QT_BEGIN_NAMESPACE
typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYWAYLANDBUFFERWL_compat) (EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFFORMATSEXTPROC) (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFMODIFIERSEXTPROC) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers);
class LinuxDmabufClientBufferIntegrationPrivate;
class LinuxDmabufParams;
class LinuxDmabufClientBuffer;
// buffer conversion definitions to import YUV buffers
struct YuvPlaneConversion {
EGLint format = DRM_FORMAT_YUYV;
EGLint widthDivisor = 1;
EGLint heightDivisor = 1;
EGLint planeIndex = 0;
};
struct YuvFormatConversion {
uint32_t inputPlanes = 1;
uint32_t outputPlanes = 1;
struct YuvPlaneConversion plane[LinuxDmabufWlBuffer::MaxDmabufPlanes];
};
class LinuxDmabufClientBufferIntegration : public QtWayland::ClientBufferIntegration
{
public:
LinuxDmabufClientBufferIntegration();
~LinuxDmabufClientBufferIntegration() override;
void initializeHardware(struct ::wl_display *display) override;
QtWayland::ClientBuffer *createBufferFor(wl_resource *resource) override;
bool importBuffer(wl_resource *resource, LinuxDmabufWlBuffer *linuxDmabufBuffer);
void removeBuffer(wl_resource *resource);
void deleteImage(EGLImageKHR image);
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC gl_egl_image_target_texture_2d = nullptr;
private:
Q_DISABLE_COPY(LinuxDmabufClientBufferIntegration)
PFNEGLBINDWAYLANDDISPLAYWL egl_bind_wayland_display = nullptr;
PFNEGLUNBINDWAYLANDDISPLAYWL egl_unbind_wayland_display = nullptr;
PFNEGLCREATEIMAGEKHRPROC egl_create_image = nullptr;
PFNEGLDESTROYIMAGEKHRPROC egl_destroy_image = nullptr;
PFNEGLQUERYDMABUFMODIFIERSEXTPROC egl_query_dmabuf_modifiers_ext = nullptr;
PFNEGLQUERYDMABUFFORMATSEXTPROC egl_query_dmabuf_formats_ext = nullptr;
bool initSimpleTexture(LinuxDmabufWlBuffer *dmabufBuffer);
bool initYuvTexture(LinuxDmabufWlBuffer *dmabufBuffer);
QList<uint32_t> supportedDrmFormats();
QList<uint64_t> supportedDrmModifiers(uint32_t format);
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
::wl_display *m_wlDisplay = nullptr;
bool m_displayBound = false;
QHash<EGLint, YuvFormatConversion> m_yuvFormats;
bool m_supportsDmabufModifiers = false;
QHash<struct ::wl_resource *, LinuxDmabufWlBuffer *> m_importedBuffers;
QScopedPointer<LinuxDmabuf> m_linuxDmabuf;
};
class LinuxDmabufClientBuffer : public QtWayland::ClientBuffer
{
public:
~LinuxDmabufClientBuffer() override;
QWaylandBufferRef::BufferFormatEgl bufferFormatEgl() const override;
QSize size() const override;
QWaylandSurface::Origin origin() const override;
QOpenGLTexture *toOpenGlTexture(int plane) override;
protected:
void setDestroyed() override;
private:
friend class LinuxDmabufClientBufferIntegration;
friend class LinuxDmabufClientBufferIntegrationPrivate;
LinuxDmabufClientBuffer(LinuxDmabufClientBufferIntegration* integration, wl_resource *bufferResource, LinuxDmabufWlBuffer *dmabufBuffer);
LinuxDmabufWlBuffer *d = nullptr;
LinuxDmabufClientBufferIntegration *m_integration = nullptr;
};
QT_END_NAMESPACE
#endif // LINUXDMABUFCLIENTBUFFERINTEGRATION_H
@@ -0,0 +1,117 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "shmserverbufferintegration.h"
#include <QtOpenGL/QOpenGLTexture>
#include <QtGui/QOpenGLContext>
#include <QtCore/QSharedMemory>
#include <QtCore/QDebug>
QT_BEGIN_NAMESPACE
ShmServerBuffer::ShmServerBuffer(ShmServerBufferIntegration *integration, const QImage &qimage, QtWayland::ServerBuffer::Format format)
: QtWayland::ServerBuffer(qimage.size(),format)
, m_integration(integration)
, m_width(qimage.width())
, m_height(qimage.height())
, m_bpl(qimage.bytesPerLine())
{
m_format = format;
switch (m_format) {
case RGBA32:
m_shm_format = QtWaylandServer::qt_shm_emulation_server_buffer::format_RGBA32;
break;
case A8:
m_shm_format = QtWaylandServer::qt_shm_emulation_server_buffer::format_A8;
break;
default:
qWarning("ShmServerBuffer: unsupported format");
m_shm_format = QtWaylandServer::qt_shm_emulation_server_buffer::format_RGBA32;
break;
}
QString key = "qt_shm_emulation_" + QString::number(qimage.cacheKey());
// ### Use proper native keys the next time we can break protocol compatibility
QT_IGNORE_DEPRECATIONS(m_shm = new QSharedMemory(key);)
qsizetype shm_size = qimage.sizeInBytes();
bool ok = m_shm->create(shm_size) && m_shm->lock();
if (ok) {
memcpy(m_shm->data(), qimage.constBits(), shm_size);
m_shm->unlock();
} else {
qWarning() << "Could not create shared memory" << key << shm_size;
}
}
ShmServerBuffer::~ShmServerBuffer()
{
delete m_shm;
}
struct ::wl_resource *ShmServerBuffer::resourceForClient(struct ::wl_client *client)
{
auto *bufferResource = resourceMap().value(client);
if (!bufferResource) {
auto integrationResource = m_integration->resourceMap().value(client);
if (!integrationResource) {
qWarning("ShmServerBuffer::resourceForClient: Trying to get resource for ServerBuffer. But client is not bound to the shm_emulation interface");
return nullptr;
}
struct ::wl_resource *shm_integration_resource = integrationResource->handle;
Resource *resource = add(client, 1);
QT_IGNORE_DEPRECATIONS(const QString shmKey = m_shm->key();)
m_integration->send_server_buffer_created(shm_integration_resource, resource->handle, shmKey, m_width, m_height, m_bpl, m_shm_format);
return resource->handle;
}
return bufferResource->handle;
}
bool ShmServerBuffer::bufferInUse()
{
return resourceMap().size() > 0;
}
QOpenGLTexture *ShmServerBuffer::toOpenGlTexture()
{
if (!m_texture) {
qWarning("ShmServerBuffer::toOpenGlTexture: no texture defined");
}
return m_texture;
}
ShmServerBufferIntegration::ShmServerBufferIntegration()
{
}
ShmServerBufferIntegration::~ShmServerBufferIntegration()
{
}
bool ShmServerBufferIntegration::initializeHardware(QWaylandCompositor *compositor)
{
Q_ASSERT(QGuiApplication::platformNativeInterface());
QtWaylandServer::qt_shm_emulation_server_buffer::init(compositor->display(), 1);
return true;
}
bool ShmServerBufferIntegration::supportsFormat(QtWayland::ServerBuffer::Format format) const
{
switch (format) {
case QtWayland::ServerBuffer::RGBA32:
return true;
case QtWayland::ServerBuffer::A8:
return true;
default:
return false;
}
}
QtWayland::ServerBuffer *ShmServerBufferIntegration::createServerBufferFromImage(const QImage &qimage, QtWayland::ServerBuffer::Format format)
{
return new ShmServerBuffer(this, qimage, format);
}
QT_END_NAMESPACE
@@ -0,0 +1,64 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef SHMSERVERBUFFERINTEGRATION_H
#define SHMSERVERBUFFERINTEGRATION_H
#include <QtWaylandCompositor/private/qwlserverbufferintegration_p.h>
#include "qwayland-server-shm-emulation-server-buffer.h"
#include <QtGui/QImage>
#include <QtGui/QWindow>
#include <QtGui/qpa/qplatformnativeinterface.h>
#include <QtGui/QGuiApplication>
#include <QtWaylandCompositor/qwaylandcompositor.h>
#include <QtWaylandCompositor/private/qwayland-server-server-buffer-extension.h>
QT_BEGIN_NAMESPACE
class ShmServerBufferIntegration;
class QSharedMemory;
class ShmServerBuffer : public QtWayland::ServerBuffer, public QtWaylandServer::qt_server_buffer
{
public:
ShmServerBuffer(ShmServerBufferIntegration *integration, const QImage &qimage, QtWayland::ServerBuffer::Format format);
~ShmServerBuffer() override;
struct ::wl_resource *resourceForClient(struct ::wl_client *) override;
bool bufferInUse() override;
QOpenGLTexture *toOpenGlTexture() override;
private:
ShmServerBufferIntegration *m_integration = nullptr;
QSharedMemory *m_shm = nullptr;
int m_width;
int m_height;
int m_bpl;
QOpenGLTexture *m_texture = nullptr;
QtWaylandServer::qt_shm_emulation_server_buffer::format m_shm_format;
};
class ShmServerBufferIntegration :
public QtWayland::ServerBufferIntegration,
public QtWaylandServer::qt_shm_emulation_server_buffer
{
public:
ShmServerBufferIntegration();
~ShmServerBufferIntegration() override;
bool initializeHardware(QWaylandCompositor *) override;
bool supportsFormat(QtWayland::ServerBuffer::Format format) const override;
QtWayland::ServerBuffer *createServerBufferFromImage(const QImage &qimage, QtWayland::ServerBuffer::Format format) override;
private:
};
QT_END_NAMESPACE
#endif
@@ -0,0 +1,290 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "vulkanserverbufferintegration.h"
#include "vulkanwrapper.h"
#include <QtOpenGL/QOpenGLTexture>
#include <QtGui/QOpenGLContext>
#include <QtGui/QOffscreenSurface>
#include <QtGui/qopengl.h>
#include <unistd.h>
#include <fcntl.h>
#include <QtCore/QDebug>
QT_BEGIN_NAMESPACE
static constexpr bool vsbiExtraDebug = false;
#define DECL_GL_FUNCTION(name, type) \
type name
#define FIND_GL_FUNCTION(name, type) \
do { \
name = reinterpret_cast<type>(glContext->getProcAddress(#name)); \
if (!name) { \
qWarning() << "ERROR in GL proc lookup. Could not find " #name; \
return false; \
} \
} while (0)
struct VulkanServerBufferGlFunctions
{
DECL_GL_FUNCTION(glCreateMemoryObjectsEXT, PFNGLCREATEMEMORYOBJECTSEXTPROC);
DECL_GL_FUNCTION(glImportMemoryFdEXT, PFNGLIMPORTMEMORYFDEXTPROC);
//DECL_GL_FUNCTION(glTextureStorageMem2DEXT, PFNGLTEXTURESTORAGEMEM2DEXTPROC);
DECL_GL_FUNCTION(glTexStorageMem2DEXT, PFNGLTEXSTORAGEMEM2DEXTPROC);
DECL_GL_FUNCTION(glDeleteMemoryObjectsEXT, PFNGLDELETEMEMORYOBJECTSEXTPROC);
bool init(QOpenGLContext *glContext)
{
FIND_GL_FUNCTION(glCreateMemoryObjectsEXT, PFNGLCREATEMEMORYOBJECTSEXTPROC);
FIND_GL_FUNCTION(glImportMemoryFdEXT, PFNGLIMPORTMEMORYFDEXTPROC);
//FIND_GL_FUNCTION(glTextureStorageMem2DEXT, PFNGLTEXTURESTORAGEMEM2DEXTPROC);
FIND_GL_FUNCTION(glTexStorageMem2DEXT, PFNGLTEXSTORAGEMEM2DEXTPROC);
FIND_GL_FUNCTION(glDeleteMemoryObjectsEXT, PFNGLDELETEMEMORYOBJECTSEXTPROC);
return true;
}
static bool create(QOpenGLContext *glContext);
};
static VulkanServerBufferGlFunctions *funcs = nullptr;
//RAII
class CurrentContext
{
public:
CurrentContext()
{
if (!QOpenGLContext::currentContext()) {
if (QOpenGLContext::globalShareContext()) {
if (!localContext) {
localContext = new QOpenGLContext;
localContext->setShareContext(QOpenGLContext::globalShareContext());
localContext->create();
}
if (!offscreenSurface) {
offscreenSurface = new QOffscreenSurface;
offscreenSurface->setFormat(localContext->format());
offscreenSurface->create();
}
localContext->makeCurrent(offscreenSurface);
localContextInUse = true;
} else {
qCritical("VulkanServerBufferIntegration: no globalShareContext");
}
}
}
~CurrentContext()
{
if (localContextInUse)
localContext->doneCurrent();
}
QOpenGLContext *context() { return localContextInUse ? localContext : QOpenGLContext::currentContext(); }
private:
static QOpenGLContext *localContext;
static QOffscreenSurface *offscreenSurface;
bool localContextInUse = false;
};
QOpenGLContext *CurrentContext::localContext = nullptr;
QOffscreenSurface *CurrentContext::offscreenSurface = nullptr;
bool VulkanServerBufferGlFunctions::create(QOpenGLContext *glContext)
{
if (funcs)
return true;
funcs = new VulkanServerBufferGlFunctions;
if (!funcs->init(glContext)) {
delete funcs;
funcs = nullptr;
return false;
}
return true;
}
VulkanServerBuffer::VulkanServerBuffer(VulkanServerBufferIntegration *integration, const QImage &qimage, QtWayland::ServerBuffer::Format format)
: QtWayland::ServerBuffer(qimage.size(),format)
, m_integration(integration)
, m_width(qimage.width())
, m_height(qimage.height())
{
m_format = format;
switch (m_format) {
case RGBA32:
m_glInternalFormat = GL_RGBA8;
break;
// case A8:
// m_glInternalFormat = GL_R8;
// break;
default:
qWarning("VulkanServerBuffer: unsupported format");
m_glInternalFormat = GL_RGBA8;
break;
}
auto vulkanWrapper = m_integration->vulkanWrapper();
m_vImage = vulkanWrapper->createTextureImage(qimage);
if (m_vImage)
m_fd = vulkanWrapper->getImageInfo(m_vImage, &m_memorySize);
}
VulkanServerBuffer::VulkanServerBuffer(VulkanServerBufferIntegration *integration, VulkanImageWrapper *vImage, uint glInternalFormat, const QSize &size)
: QtWayland::ServerBuffer(size, QtWayland::ServerBuffer::Custom)
, m_integration(integration)
, m_width(size.width())
, m_height(size.height())
, m_vImage(vImage)
, m_glInternalFormat(glInternalFormat)
{
auto vulkanWrapper = m_integration->vulkanWrapper();
m_fd = vulkanWrapper->getImageInfo(m_vImage, &m_memorySize);
}
VulkanServerBuffer::~VulkanServerBuffer()
{
delete m_texture; //this is always nullptr for now
auto vulkanWrapper = m_integration->vulkanWrapper();
vulkanWrapper->freeTextureImage(m_vImage);
}
struct ::wl_resource *VulkanServerBuffer::resourceForClient(struct ::wl_client *client)
{
auto *bufferResource = resourceMap().value(client);
if (!bufferResource) {
auto integrationResource = m_integration->resourceMap().value(client);
if (!integrationResource) {
qWarning("VulkanServerBuffer::resourceForClient: Trying to get resource for ServerBuffer. But client is not bound to the vulkan interface");
return nullptr;
}
struct ::wl_resource *shm_integration_resource = integrationResource->handle;
Resource *resource = add(client, 1);
m_integration->send_server_buffer_created(shm_integration_resource, resource->handle, m_fd, m_width, m_height, m_memorySize, m_glInternalFormat);
return resource->handle;
}
return bufferResource->handle;
}
QOpenGLTexture *VulkanServerBuffer::toOpenGlTexture()
{
if (m_texture && m_texture->isCreated())
return m_texture;
CurrentContext current;
if (!funcs && !VulkanServerBufferGlFunctions::create(current.context()))
return nullptr;
funcs->glCreateMemoryObjectsEXT(1, &m_memoryObject);
if (vsbiExtraDebug) qDebug() << "glCreateMemoryObjectsEXT" << Qt::hex << glGetError();
int dupfd = fcntl(m_fd, F_DUPFD_CLOEXEC, 0);
if (dupfd < 0) {
perror("VulkanServerBuffer::toOpenGlTexture() Could not dup fd:");
return nullptr;
}
funcs->glImportMemoryFdEXT(m_memoryObject, m_memorySize, GL_HANDLE_TYPE_OPAQUE_FD_EXT, dupfd);
if (vsbiExtraDebug) qDebug() << "glImportMemoryFdEXT" << Qt::hex << glGetError();
if (!m_texture)
m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
m_texture->create();
GLuint texId = m_texture->textureId();
if (vsbiExtraDebug) qDebug() << "created texture" << texId << Qt::hex << glGetError();
m_texture->bind();
if (vsbiExtraDebug) qDebug() << "bound texture" << texId << Qt::hex << glGetError();
funcs->glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, m_glInternalFormat, m_size.width(), m_size.height(), m_memoryObject, 0 );
if (vsbiExtraDebug) qDebug() << "glTexStorageMem2DEXT" << Qt::hex << glGetError();
if (vsbiExtraDebug) qDebug() << "format" << Qt::hex << m_glInternalFormat << GL_RGBA8;
return m_texture;
}
void VulkanServerBuffer::releaseOpenGlTexture()
{
if (!m_texture || !m_texture->isCreated())
return;
CurrentContext current;
m_texture->destroy();
funcs->glDeleteMemoryObjectsEXT(1, &m_memoryObject);
}
bool VulkanServerBuffer::bufferInUse()
{
return (m_texture && m_texture->isCreated()) || resourceMap().size() > 0;
}
void VulkanServerBuffer::server_buffer_release(Resource *resource)
{
qCDebug(qLcWaylandCompositorHardwareIntegration) << "server_buffer RELEASE resource" << resource->handle << wl_resource_get_id(resource->handle) << "for client" << resource->client();
wl_resource_destroy(resource->handle);
}
VulkanServerBufferIntegration::VulkanServerBufferIntegration()
{
}
VulkanServerBufferIntegration::~VulkanServerBufferIntegration()
{
}
bool VulkanServerBufferIntegration::initializeHardware(QWaylandCompositor *compositor)
{
Q_ASSERT(QGuiApplication::platformNativeInterface());
QtWaylandServer::zqt_vulkan_server_buffer_v1::init(compositor->display(), 1);
return true;
}
bool VulkanServerBufferIntegration::supportsFormat(QtWayland::ServerBuffer::Format format) const
{
switch (format) {
case QtWayland::ServerBuffer::RGBA32:
return true;
case QtWayland::ServerBuffer::A8:
return false;
default:
return false;
}
}
QtWayland::ServerBuffer *VulkanServerBufferIntegration::createServerBufferFromImage(const QImage &qimage, QtWayland::ServerBuffer::Format format)
{
if (!m_vulkanWrapper) {
CurrentContext current;
m_vulkanWrapper = new VulkanWrapper(current.context());
}
return new VulkanServerBuffer(this, qimage, format);
}
QtWayland::ServerBuffer *
VulkanServerBufferIntegration::createServerBufferFromData(QByteArrayView view, const QSize &size,
uint glInternalFormat)
{
if (!m_vulkanWrapper) {
CurrentContext current;
m_vulkanWrapper = new VulkanWrapper(current.context());
}
auto *vImage = m_vulkanWrapper->createTextureImageFromData(
reinterpret_cast<const uchar *>(view.constData()), view.size(), size, glInternalFormat);
if (vImage)
return new VulkanServerBuffer(this, vImage, glInternalFormat, size);
qCWarning(qLcWaylandCompositorHardwareIntegration) << "could not load compressed texture";
return nullptr;
}
QT_END_NAMESPACE
@@ -0,0 +1,76 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef VULKANSERVERBUFFERINTEGRATION_H
#define VULKANSERVERBUFFERINTEGRATION_H
#include <QtWaylandCompositor/private/qwlserverbufferintegration_p.h>
#include "qwayland-server-qt-vulkan-server-buffer-unstable-v1.h"
#include <QtGui/QImage>
#include <QtGui/QWindow>
#include <QtGui/qpa/qplatformnativeinterface.h>
#include <QtGui/QGuiApplication>
#include <QtWaylandCompositor/qwaylandcompositor.h>
#include <QtWaylandCompositor/private/qwayland-server-server-buffer-extension.h>
QT_BEGIN_NAMESPACE
class VulkanServerBufferIntegration;
class VulkanWrapper;
struct VulkanImageWrapper;
class VulkanServerBuffer : public QtWayland::ServerBuffer, public QtWaylandServer::qt_server_buffer
{
public:
VulkanServerBuffer(VulkanServerBufferIntegration *integration, const QImage &qimage, QtWayland::ServerBuffer::Format format);
VulkanServerBuffer(VulkanServerBufferIntegration *integration, VulkanImageWrapper *vImage, uint glInternalFormat, const QSize &size);
~VulkanServerBuffer() override;
struct ::wl_resource *resourceForClient(struct ::wl_client *) override;
bool bufferInUse() override;
QOpenGLTexture *toOpenGlTexture() override;
void releaseOpenGlTexture() override;
protected:
void server_buffer_release(Resource *resource) override;
private:
VulkanServerBufferIntegration *m_integration = nullptr;
int m_width;
int m_height;
int m_memorySize;
int m_fd = -1;
VulkanImageWrapper *m_vImage = nullptr;
QOpenGLTexture *m_texture = nullptr;
uint m_glInternalFormat;
GLuint m_memoryObject;
};
class VulkanServerBufferIntegration :
public QtWayland::ServerBufferIntegration,
public QtWaylandServer::zqt_vulkan_server_buffer_v1
{
public:
VulkanServerBufferIntegration();
~VulkanServerBufferIntegration() override;
VulkanWrapper *vulkanWrapper() const { return m_vulkanWrapper; }
bool initializeHardware(QWaylandCompositor *) override;
bool supportsFormat(QtWayland::ServerBuffer::Format format) const override;
QtWayland::ServerBuffer *createServerBufferFromImage(const QImage &qimage, QtWayland::ServerBuffer::Format format) override;
QtWayland::ServerBuffer *createServerBufferFromData(QByteArrayView view, const QSize &size,
uint glInternalFormat) override;
private:
VulkanWrapper *m_vulkanWrapper = nullptr;
};
QT_END_NAMESPACE
#endif
@@ -0,0 +1,696 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// NOTE: Some of the code below is adapted from the public domain code at https://vulkan-tutorial.com/
#define GL_GLEXT_PROTOTYPES
#include "vulkanwrapper.h"
#include <QImage>
#include <QVarLengthArray>
#include <QOpenGLContext>
#include <QtGui/qopengl.h>
#include <QtOpenGL/private/qvkconvenience_p.h>
#include <set>
#include <unistd.h>
#include <QDebug>
QT_BEGIN_NAMESPACE
static constexpr bool vwExtraDebug = false;
#define DECL_VK_FUNCTION(name) \
PFN_ ## name name = nullptr;
#define IMPL_VK_FUNCTION(name) \
name = reinterpret_cast<PFN_ ## name>(f_glGetVkProcAddrNV(#name)); \
if (!name) { \
qCritical() << "ERROR in Vulkan proc lookup. Could not find " #name; \
}
struct QueueFamilyIndices {
int graphicsFamily = -1;
int presentFamily = -1;
bool isComplete() {
return graphicsFamily >= 0 && presentFamily >= 0;
}
};
class VulkanWrapperPrivate
{
public:
explicit VulkanWrapperPrivate(QOpenGLContext *glContext);
VulkanImageWrapper *createTextureImage(const QImage &img);
VulkanImageWrapper *createTextureImageFromData(const uchar *pixels, uint bufferSize, const QSize &size, VkFormat vkFormat);
void freeTextureImage(VulkanImageWrapper *imageWrapper);
private:
DECL_VK_FUNCTION(vkAllocateCommandBuffers);
DECL_VK_FUNCTION(vkAllocateMemory);
DECL_VK_FUNCTION(vkBeginCommandBuffer);
DECL_VK_FUNCTION(vkBindImageMemory);
DECL_VK_FUNCTION(vkCmdCopyBufferToImage);
DECL_VK_FUNCTION(vkCmdPipelineBarrier);
DECL_VK_FUNCTION(vkCreateImage);
DECL_VK_FUNCTION(vkDestroyImage);
DECL_VK_FUNCTION(vkDestroyBuffer);
DECL_VK_FUNCTION(vkEndCommandBuffer);
DECL_VK_FUNCTION(vkFreeCommandBuffers);
DECL_VK_FUNCTION(vkFreeMemory);
DECL_VK_FUNCTION(vkGetImageMemoryRequirements);
DECL_VK_FUNCTION(vkGetPhysicalDeviceMemoryProperties);
DECL_VK_FUNCTION(vkMapMemory);
DECL_VK_FUNCTION(vkQueueSubmit);
DECL_VK_FUNCTION(vkQueueWaitIdle);
DECL_VK_FUNCTION(vkUnmapMemory);
DECL_VK_FUNCTION(vkCreateBuffer);
DECL_VK_FUNCTION(vkGetBufferMemoryRequirements);
DECL_VK_FUNCTION(vkBindBufferMemory);
DECL_VK_FUNCTION(vkCreateInstance);
DECL_VK_FUNCTION(vkEnumeratePhysicalDevices);
DECL_VK_FUNCTION(vkGetPhysicalDeviceProperties);
DECL_VK_FUNCTION(vkCreateDevice);
DECL_VK_FUNCTION(vkGetPhysicalDeviceFormatProperties);
DECL_VK_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties);
DECL_VK_FUNCTION(vkCreateCommandPool);
DECL_VK_FUNCTION(vkGetDeviceQueue);
DECL_VK_FUNCTION(vkGetImageMemoryRequirements2KHR);
DECL_VK_FUNCTION(vkGetMemoryFdKHR);
//DECL_VK_FUNCTION(vkGetPhysicalDeviceSurfaceSupportKHR);
void initFunctions(PFNGLGETVKPROCADDRNVPROC f_glGetVkProcAddrNV) {
IMPL_VK_FUNCTION(vkAllocateCommandBuffers);
IMPL_VK_FUNCTION(vkAllocateMemory);
IMPL_VK_FUNCTION(vkBeginCommandBuffer);
IMPL_VK_FUNCTION(vkBindImageMemory);
IMPL_VK_FUNCTION(vkCmdCopyBufferToImage);
IMPL_VK_FUNCTION(vkCmdPipelineBarrier);
IMPL_VK_FUNCTION(vkCreateImage);
IMPL_VK_FUNCTION(vkDestroyImage);
IMPL_VK_FUNCTION(vkDestroyBuffer);
IMPL_VK_FUNCTION(vkEndCommandBuffer);
IMPL_VK_FUNCTION(vkFreeCommandBuffers);
IMPL_VK_FUNCTION(vkFreeMemory);
IMPL_VK_FUNCTION(vkGetImageMemoryRequirements);
IMPL_VK_FUNCTION(vkGetPhysicalDeviceMemoryProperties);
IMPL_VK_FUNCTION(vkMapMemory);
IMPL_VK_FUNCTION(vkQueueSubmit);
IMPL_VK_FUNCTION(vkQueueWaitIdle);
IMPL_VK_FUNCTION(vkUnmapMemory);
IMPL_VK_FUNCTION(vkCreateBuffer);
IMPL_VK_FUNCTION(vkGetBufferMemoryRequirements);
IMPL_VK_FUNCTION(vkBindBufferMemory);
IMPL_VK_FUNCTION(vkCreateInstance);
IMPL_VK_FUNCTION(vkEnumeratePhysicalDevices);
IMPL_VK_FUNCTION(vkGetPhysicalDeviceProperties);
IMPL_VK_FUNCTION(vkCreateDevice);
IMPL_VK_FUNCTION(vkGetPhysicalDeviceFormatProperties);
IMPL_VK_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties);
IMPL_VK_FUNCTION(vkCreateCommandPool);
IMPL_VK_FUNCTION(vkGetDeviceQueue);
IMPL_VK_FUNCTION(vkGetImageMemoryRequirements2KHR);
IMPL_VK_FUNCTION(vkGetMemoryFdKHR);
//IMPL_VK_FUNCTION(vkGetPhysicalDeviceSurfaceSupportKHR);
}
int findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties);
VulkanImageWrapper *createImage(VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, const QSize &size, int memSize);
bool transitionImageLayout(VkImage image, VkFormat /*format*/, VkImageLayout oldLayout, VkImageLayout newLayout);
bool createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory);
VkCommandBuffer beginSingleTimeCommands();
void endSingleTimeCommands(VkCommandBuffer commandBuffer);
void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height);
void createCommandPool();
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device);
bool createLogicalDevice();
private:
VkInstance m_instance = VK_NULL_HANDLE;
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
VkDevice m_device = VK_NULL_HANDLE;
VkCommandPool m_commandPool = VK_NULL_HANDLE;
VkQueue m_graphicsQueue = VK_NULL_HANDLE;
bool m_initFailed = false;
};
struct VulkanImageWrapper
{
VkImage textureImage = VK_NULL_HANDLE;
int imgMemSize = -1;
QSize imgSize;
int imgFd = -1;
VkDeviceMemory textureImageMemory = VK_NULL_HANDLE;
};
int VulkanWrapperPrivate::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
{
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
qCritical("VulkanWrapper: failed to find suitable memory type!");
return -1;
}
VulkanImageWrapper *VulkanWrapperPrivate::createImage(VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, const QSize &size, int memSize)
{
VkImageCreateInfo imageInfo = {};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = size.width();
imageInfo.extent.height = size.height();
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = format;
imageInfo.tiling = tiling;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = usage;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkImage image = VK_NULL_HANDLE;
if (vkCreateImage(m_device, &imageInfo, nullptr, &image) != VK_SUCCESS) {
qCritical("VulkanWrapper: failed to create image!");
return nullptr;
}
std::unique_ptr imageWrapper = std::make_unique<VulkanImageWrapper>();
imageWrapper->textureImage = image;
imageWrapper->imgMemSize = memSize;
imageWrapper->imgSize = size;
VkMemoryRequirements memRequirements;
vkGetImageMemoryRequirements(m_device, image, &memRequirements);
VkExportMemoryAllocateInfoKHR exportAllocInfo = {};
exportAllocInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR;
exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
int memoryType = findMemoryType(memRequirements.memoryTypeBits, properties);
if (memoryType < 0)
return nullptr;
allocInfo.memoryTypeIndex = memoryType;
allocInfo.pNext = &exportAllocInfo;
if (vkAllocateMemory(m_device, &allocInfo, nullptr, &imageWrapper->textureImageMemory) != VK_SUCCESS) {
qCritical("VulkanWrapper: failed to allocate image memory!");
return nullptr;
}
int res = vkBindImageMemory(m_device, image, imageWrapper->textureImageMemory, 0);
Q_UNUSED(res);
if (vwExtraDebug) qDebug() << "vkBindImageMemory res" << res;
VkMemoryGetFdInfoKHR memoryFdInfo = {};
memoryFdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
memoryFdInfo.memory = imageWrapper->textureImageMemory;
memoryFdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
res = vkGetMemoryFdKHR(m_device, &memoryFdInfo, &imageWrapper->imgFd);
if (vwExtraDebug) qDebug() << "vkGetMemoryFdKHR res" << res << "fd" << imageWrapper->imgFd;
return imageWrapper.release();
}
bool VulkanWrapperPrivate::transitionImageLayout(VkImage image, VkFormat /*format*/, VkImageLayout oldLayout, VkImageLayout newLayout)
{
VkCommandBuffer commandBuffer = beginSingleTimeCommands();
VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = oldLayout;
barrier.newLayout = newLayout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
VkPipelineStageFlags sourceStage;
VkPipelineStageFlags destinationStage;
if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
} else {
qCritical("VulkanWrapper: unsupported layout transition!");
return false;
}
vkCmdPipelineBarrier(
commandBuffer,
sourceStage, destinationStage,
0,
0, nullptr,
0, nullptr,
1, &barrier
);
endSingleTimeCommands(commandBuffer);
return true;
}
bool VulkanWrapperPrivate::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory)
{
VkBufferCreateInfo bufferInfo = {};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = size;
bufferInfo.usage = usage;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(m_device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
qCritical("VulkanWrapper: failed to create buffer!");
return false;
}
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(m_device, buffer, &memRequirements);
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
if (vkAllocateMemory(m_device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) {
qCritical("VulkanWrapper: failed to allocate buffer memory!");
return false;
}
vkBindBufferMemory(m_device, buffer, bufferMemory, 0);
return true;
}
VkCommandBuffer VulkanWrapperPrivate::beginSingleTimeCommands()
{
VkCommandBufferAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = m_commandPool;
allocInfo.commandBufferCount = 1;
if (vwExtraDebug) qDebug() << "allocating...";
VkCommandBuffer commandBuffer;
int res = vkAllocateCommandBuffers(m_device, &allocInfo, &commandBuffer);
Q_UNUSED(res);
if (vwExtraDebug) qDebug() << "vkAllocateCommandBuffers res" << res;
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
res = vkBeginCommandBuffer(commandBuffer, &beginInfo);
if (vwExtraDebug) qDebug() << "BEGIN res" << res;
return commandBuffer;
}
void VulkanWrapperPrivate::endSingleTimeCommands(VkCommandBuffer commandBuffer)
{
int res = vkEndCommandBuffer(commandBuffer);
Q_UNUSED(res);
if (vwExtraDebug) qDebug() << "END res" << res;
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(m_graphicsQueue);
vkFreeCommandBuffers(m_device, m_commandPool, 1, &commandBuffer);
}
void VulkanWrapperPrivate::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
{
VkCommandBuffer commandBuffer = beginSingleTimeCommands();
VkBufferImageCopy region = {};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = {0, 0, 0};
region.imageExtent = {
width,
height,
1
};
vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
endSingleTimeCommands(commandBuffer);
}
void VulkanWrapperPrivate::createCommandPool()
{
QueueFamilyIndices queueFamilyIndices = findQueueFamilies(m_physicalDevice);
VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily;
if (vkCreateCommandPool(m_device, &poolInfo, nullptr, &m_commandPool) != VK_SUCCESS) {
m_initFailed = true;
qCritical("VulkanWrapperPrivate: could not create command pool");
}
}
QueueFamilyIndices VulkanWrapperPrivate::findQueueFamilies(VkPhysicalDevice device)
{
QueueFamilyIndices indices;
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
if (vwExtraDebug) qDebug() << "queueFamilyCount" << queueFamilyCount;
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
#ifdef VULKAN_SERVER_BUFFER_EXTRA_DEBUG
for (const auto& queueFamily : queueFamilies) {
qDebug() << "....q" << "count" << queueFamily.queueCount << queueFamily.timestampValidBits << hex << queueFamily.queueFlags;
}
#endif
int i = 0;
for (const auto& queueFamily : queueFamilies) {
if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamily = i;
break;
}
i++;
}
return indices;
}
bool VulkanWrapperPrivate::createLogicalDevice()
{
QueueFamilyIndices indices = findQueueFamilies(m_physicalDevice);
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<int> uniqueQueueFamilies = {indices.graphicsFamily}; //////, indices.presentFamily};
float queuePriority = 1.0f;
for (int queueFamily : uniqueQueueFamilies) {
VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateInfo);
}
VkPhysicalDeviceFeatures deviceFeatures = {};
VkDeviceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
createInfo.pQueueCreateInfos = queueCreateInfos.data();
createInfo.pEnabledFeatures = &deviceFeatures;
if (vkCreateDevice(m_physicalDevice, &createInfo, nullptr, &m_device) != VK_SUCCESS) {
qCritical("VulkanWrapper: failed to create logical device!");
return false;
}
vkGetDeviceQueue(m_device, indices.graphicsFamily, 0, &m_graphicsQueue);
return true;
}
VulkanImageWrapper *VulkanWrapperPrivate::createTextureImage(const QImage &img)
{
return createTextureImageFromData(img.constBits(), img.sizeInBytes(), img.size(), VK_FORMAT_R8G8B8A8_UNORM);
}
VulkanImageWrapper *VulkanWrapperPrivate::createTextureImageFromData(const uchar *pixels, uint bufferSize, const QSize &size, VkFormat vkFormat)
{
if (m_initFailed)
return nullptr;
int texWidth = size.width();
int texHeight = size.height();
bool ok;
if (vwExtraDebug) qDebug("image load %p %dx%d", pixels, texWidth, texHeight);
if (!pixels) {
qCritical("VulkanWrapper: failed to load texture image!");
return nullptr;
}
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
ok = createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
if (!ok)
return nullptr;
void* data;
vkMapMemory(m_device, stagingBufferMemory, 0, bufferSize, 0, &data);
if (vwExtraDebug) qDebug() << "mapped" << data << bufferSize;
memcpy(data, pixels, static_cast<size_t>(bufferSize));
vkUnmapMemory(m_device, stagingBufferMemory);
if (vwExtraDebug) qDebug() << "creating image...";
std::unique_ptr<VulkanImageWrapper> imageWrapper(createImage(vkFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, size, bufferSize));
if (!imageWrapper)
return nullptr;
if (vwExtraDebug) qDebug() << "transition...";
const VkImage textureImage = imageWrapper->textureImage;
ok = transitionImageLayout(textureImage, vkFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
if (!ok)
return nullptr;
if (vwExtraDebug) qDebug() << "copyBufferToImage...";
copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight));
transitionImageLayout(textureImage, vkFormat, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
return imageWrapper.release();
}
void VulkanWrapperPrivate::freeTextureImage(VulkanImageWrapper *imageWrapper)
{
if (!imageWrapper)
return;
//"To avoid leaking resources, the application must release ownership of the file descriptor using the close system call"
::close(imageWrapper->imgFd);
// clean up the image memory
vkDestroyImage(m_device, imageWrapper->textureImage, nullptr);
vkFreeMemory(m_device, imageWrapper->textureImageMemory, nullptr);
}
VulkanWrapperPrivate::VulkanWrapperPrivate(QOpenGLContext *glContext)
{
if (vwExtraDebug) qDebug("Creating Vulkan instance");
VkApplicationInfo applicationInfo = {};
applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
applicationInfo.pNext = nullptr;
applicationInfo.pApplicationName = nullptr;
applicationInfo.applicationVersion = 0;
applicationInfo.pEngineName = nullptr;
applicationInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
applicationInfo.apiVersion = VK_MAKE_VERSION(1, 0, 5);
VkInstanceCreateInfo instanceCreateInfo = {};
instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo.pNext = nullptr;
instanceCreateInfo.flags = 0;
instanceCreateInfo.pApplicationInfo = &applicationInfo;
instanceCreateInfo.enabledLayerCount = 0;
instanceCreateInfo.ppEnabledLayerNames = nullptr;
instanceCreateInfo.enabledExtensionCount = 0;
instanceCreateInfo.ppEnabledExtensionNames = nullptr;
auto f_glGetVkProcAddrNV = reinterpret_cast<PFNGLGETVKPROCADDRNVPROC>(glContext->getProcAddress("glGetVkProcAddrNV"));
if (!f_glGetVkProcAddrNV) {
qCritical("VulkanWrapper: Could not find Vulkan/GL interop function glGetVkProcAddrNV");
m_initFailed = true;
return;
}
initFunctions(f_glGetVkProcAddrNV);
VkResult instanceCreationResult = vkCreateInstance(&instanceCreateInfo, nullptr, &m_instance);
if (vwExtraDebug) qDebug() << "result" << instanceCreationResult;
if (instanceCreationResult != VK_SUCCESS) {
qCritical() << "VulkanWrapper: Failed to create Vulkan instance: Error "
<< instanceCreationResult;
m_initFailed = true;
return;
}
uint32_t devCount;
auto res = vkEnumeratePhysicalDevices(m_instance, &devCount, nullptr);
if (vwExtraDebug) qDebug() << "vkEnumeratePhysicalDevices res =" << res << "count =" << devCount;
QVarLengthArray<VkPhysicalDevice, 5> dev(devCount);
res = vkEnumeratePhysicalDevices(m_instance, &devCount, dev.data());
if (vwExtraDebug) qDebug() << "...devs res =" << res << "count =" << devCount;
#ifdef VULKAN_SERVER_BUFFER_EXTRA_DEBUG
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(dev[0], &props);
qDebug() << "Properties " << hex
<< "apiVersion" << props.apiVersion
<< "driverVersion" << props.driverVersion
<< "vendorID" << props.vendorID
<< "deviceID" << props.deviceID
<< "deviceType" << props.deviceType
<< "deviceName" << props.deviceName;
#endif
m_physicalDevice = dev[0]; //TODO handle the case of multiple GPUs where only some support Vulkan
bool ok = createLogicalDevice();
if (!ok) {
qCritical("VulkanWrapperPrivate: could not create logical device");
m_initFailed = true;
return;
}
VkPhysicalDeviceMemoryProperties memProps;
vkGetPhysicalDeviceMemoryProperties(dev[0], &memProps);
#ifdef VULKAN_SERVER_BUFFER_EXTRA_DEBUG
qDebug() << "Physical memory properties:\n" << "types:" << memProps.memoryTypeCount << "heaps:" << memProps.memoryHeapCount;
for (uint i = 0; i < memProps.memoryTypeCount; ++i)
qDebug() << " " << i << "heap" << memProps.memoryTypes[i].heapIndex << "flags" << hex << memProps.memoryTypes[i].propertyFlags;
for (uint i = 0; i < memProps.memoryHeapCount; ++i)
qDebug() << " " << i << "size" << memProps.memoryHeaps[i].size << "flags" << hex << memProps.memoryHeaps[i].flags;
#endif
int gpuMemoryType = -1;
for (uint i = 0; i < memProps.memoryTypeCount; ++i) {
if (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
gpuMemoryType = i;
break;
}
}
if (gpuMemoryType < 0) {
qCritical("VulkanWrapper: Could not find GPU memory!");
m_initFailed = true;
return;
}
#ifdef VULKAN_SERVER_BUFFER_EXTRA_DEBUG
qDebug() << "GPU memory type:" << gpuMemoryType << "heap:" << memProps.memoryTypes[gpuMemoryType].heapIndex;
for (int f = 0; f <= VK_FORMAT_ASTC_12x12_SRGB_BLOCK; f++)
{
VkFormatProperties formatProps;
vkGetPhysicalDeviceFormatProperties(dev[0], VkFormat(f), &formatProps);
qDebug() << "format" << f << "features" << hex << formatProps.linearTilingFeatures << formatProps.optimalTilingFeatures << formatProps.bufferFeatures;
}
#endif
createCommandPool();
}
VulkanWrapper::VulkanWrapper(QOpenGLContext *glContext)
: d_ptr(new VulkanWrapperPrivate(glContext))
{
}
VulkanImageWrapper *VulkanWrapper::createTextureImage(const QImage &img)
{
return d_ptr->createTextureImage(img);
}
VulkanImageWrapper *VulkanWrapper::createTextureImageFromData(const uchar *pixels, uint bufferSize, const QSize &size, uint glInternalFormat)
{
VkFormat vkFormat = VkFormat(QVkConvenience::vkFormatFromGlFormat(glInternalFormat));
if (vkFormat == VK_FORMAT_UNDEFINED)
return nullptr;
return d_ptr->createTextureImageFromData(pixels, bufferSize, size, vkFormat);
}
int VulkanWrapper::getImageInfo(const VulkanImageWrapper *imgWrapper, int *memSize, int *w, int *h)
{
if (memSize)
*memSize = imgWrapper->imgMemSize;
if (w)
*w = imgWrapper->imgSize.width();
if (h)
*h = imgWrapper->imgSize.height();
return imgWrapper->imgFd;
}
void VulkanWrapper::freeTextureImage(VulkanImageWrapper *imageWrapper)
{
d_ptr->freeTextureImage(imageWrapper);
}
QT_END_NAMESPACE
@@ -0,0 +1,34 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef VULKANWRAPPER_H
#define VULKANWRAPPER_H
#include <QOpenGLContext>
QT_BEGIN_NAMESPACE
class VulkanWrapper;
struct VulkanImageWrapper;
class VulkanWrapperPrivate;
class QOpenGLContext;
class QImage;
class VulkanWrapper
{
public:
VulkanWrapper(QOpenGLContext *glContext);
VulkanImageWrapper *createTextureImage(const QImage &img);
VulkanImageWrapper *createTextureImageFromData(const uchar *pixels, uint bufferSize, const QSize &size, uint glInternalFormat);
int getImageInfo(const VulkanImageWrapper *imgWrapper, int *memSize, int *w = nullptr, int *h = nullptr);
void freeTextureImage(VulkanImageWrapper *imageWrapper);
private:
VulkanWrapperPrivate *d_ptr;
};
QT_END_NAMESPACE
#endif // VULKANWRAPPER_H
@@ -0,0 +1,30 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## WaylandEglCompositorHwIntegrationPrivate Module:
#####################################################################
qt_find_package(EGL) # special case
qt_internal_add_module(WaylandEglCompositorHwIntegrationPrivate
CONFIG_MODULE_NAME wayland_egl_compositor_hw_integration
INTERNAL_MODULE
SOURCES
waylandeglclientbufferintegration.cpp waylandeglclientbufferintegration_p.h
INCLUDE_DIRECTORIES
${CMAKE_CURRENT_SOURCE_DIR}
LIBRARIES
EGL::EGL
Wayland::Egl
Wayland::Server
PUBLIC_LIBRARIES
Qt::Core
Qt::Gui
Qt::WaylandCompositorPrivate
NO_GENERATE_CPP_EXPORTS
)
qt_internal_extend_target(WaylandEglCompositorHwIntegrationPrivate CONDITION NOT QT_FEATURE_egl_x11
DEFINES
QT_EGL_NO_X11
)
@@ -0,0 +1,672 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "waylandeglclientbufferintegration_p.h"
#include <QtWaylandCompositor/QWaylandCompositor>
#include <QtWaylandCompositor/private/qwltextureorphanage_p.h>
#include <qpa/qplatformnativeinterface.h>
#include <QtOpenGL/QOpenGLTexture>
#include <QtGui/QGuiApplication>
#include <QtGui/QOpenGLContext>
#include <QtGui/QOffscreenSurface>
#include <qpa/qplatformscreen.h>
#include <QtGui/QWindow>
#include <QtCore/QPointer>
#include <QDebug>
#include <QMutex>
#include <QMutexLocker>
#include <QVarLengthArray>
#include <QtCore/private/qcore_unix_p.h>
#include <QtGui/private/qeglstreamconvenience_p.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
/* 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
#ifndef EGL_KHR_image
typedef EGLImageKHR (EGLAPIENTRYP PFNEGLCREATEIMAGEKHRPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay dpy, EGLImageKHR image);
#endif
#ifndef GL_OES_EGL_image
typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES image);
typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC) (GLenum target, GLeglImageOES image);
#endif
QT_BEGIN_NAMESPACE
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;
enum EglMode {
ModeUninitialized,
ModeEGLImage,
ModeEGLStream
};
EGLint egl_format = EGL_TEXTURE_RGBA;
QVarLengthArray<EGLImageKHR, 3> egl_images;
QOpenGLTexture *textures[3] = {nullptr, nullptr, nullptr};
QOpenGLContext *texturesContext[3] = {nullptr, nullptr, nullptr};
QMetaObject::Connection texturesAboutToBeDestroyedConnection[3] = {QMetaObject::Connection(), QMetaObject::Connection(), QMetaObject::Connection()};
QMutex texturesLock;
EGLStreamKHR egl_stream = EGL_NO_STREAM_KHR;
bool isYInverted = true;
QSize size;
EglMode eglMode = ModeUninitialized;
};
class WaylandEglClientBufferIntegrationPrivate
{
public:
WaylandEglClientBufferIntegrationPrivate();
void initBuffer(WaylandEglClientBuffer *buffer);
void initEglTexture(WaylandEglClientBuffer *buffer, EGLint format);
bool ensureContext();
bool initEglStream(WaylandEglClientBuffer *buffer, struct ::wl_resource *bufferHandle);
void setupBufferAndCleanup(BufferState *bs, QOpenGLTexture *texture, int plane);
void handleEglstreamTexture(WaylandEglClientBuffer *buffer, wl_resource *bufferHandle);
void registerBuffer(struct ::wl_resource *buffer, BufferState state);
EGLDisplay egl_display = EGL_NO_DISPLAY;
bool display_bound = false;
::wl_display *wlDisplay = nullptr;
QOffscreenSurface *offscreenSurface = nullptr;
QOpenGLContext *localContext = nullptr;
PFNEGLBINDWAYLANDDISPLAYWL egl_bind_wayland_display = nullptr;
PFNEGLUNBINDWAYLANDDISPLAYWL egl_unbind_wayland_display = nullptr;
PFNEGLQUERYWAYLANDBUFFERWL_compat egl_query_wayland_buffer = nullptr;
PFNEGLCREATEIMAGEKHRPROC egl_create_image = nullptr;
PFNEGLDESTROYIMAGEKHRPROC egl_destroy_image = nullptr;
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC gl_egl_image_target_texture_2d = nullptr;
QEGLStreamConvenience *funcs = nullptr;
static WaylandEglClientBufferIntegrationPrivate *get(WaylandEglClientBufferIntegration *integration) {
return integration ? integration->d_ptr.data() : nullptr;
}
};
WaylandEglClientBufferIntegrationPrivate::WaylandEglClientBufferIntegrationPrivate()
{
}
void WaylandEglClientBufferIntegrationPrivate::initBuffer(WaylandEglClientBuffer *buffer)
{
EGLint format;
if (egl_query_wayland_buffer(egl_display, buffer->waylandBufferHandle(), EGL_TEXTURE_FORMAT, &format))
initEglTexture(buffer, format);
}
void WaylandEglClientBufferIntegrationPrivate::initEglTexture(WaylandEglClientBuffer *buffer, EGLint format)
{
// Non-streaming case
// Resolving GL functions may need a context current, so do it only here.
if (!gl_egl_image_target_texture_2d)
gl_egl_image_target_texture_2d = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
if (!gl_egl_image_target_texture_2d) {
qCWarning(qLcWaylandCompositorHardwareIntegration)
<< "BindTextureToBuffer() failed. Could not find glEGLImageTargetTexture2DOES.";
return;
}
BufferState &state = *buffer->d;
state.egl_format = format;
state.eglMode = BufferState::ModeEGLImage;
#if defined(EGL_WAYLAND_Y_INVERTED_WL)
EGLint isYInverted;
EGLBoolean ret = egl_query_wayland_buffer(egl_display, buffer->waylandBufferHandle(), EGL_WAYLAND_Y_INVERTED_WL, &isYInverted);
// Yes, this looks strange, but the specification says that EGL_FALSE return
// value (not supported) should be treated the same as EGL_TRUE return value
// and EGL_TRUE in value.
state.isYInverted = (ret == EGL_FALSE || isYInverted == EGL_TRUE);
#endif
int planes = 1;
switch (format) {
default:
case EGL_TEXTURE_RGB:
case EGL_TEXTURE_RGBA:
case EGL_TEXTURE_EXTERNAL_WL:
planes = 1;
break;
case EGL_TEXTURE_Y_UV_WL:
planes = 2;
break;
case EGL_TEXTURE_Y_U_V_WL:
planes = 3;
break;
case EGL_TEXTURE_Y_XUXV_WL:
planes = 2;
break;
}
for (int i = 0; i < planes; i++) {
EGLint attribs[5] = { EGL_WAYLAND_PLANE_WL, i, EGL_NONE };
#ifdef EGL_EXT_protected_content
if (buffer->isProtected()) {
attribs[2] = EGL_PROTECTED_CONTENT_EXT;
attribs[3] = EGL_TRUE;
attribs[4] = EGL_NONE;
}
#endif
EGLImageKHR image = egl_create_image(egl_display,
EGL_NO_CONTEXT,
EGL_WAYLAND_BUFFER_WL,
buffer->waylandBufferHandle(),
attribs);
if (image == EGL_NO_IMAGE_KHR) {
qCWarning(qLcWaylandCompositorHardwareIntegration)
<< "Failed to create EGL image for plane" << i;
}
state.egl_images << image;
QMutexLocker locker(&state.texturesLock);
state.textures[i] = nullptr;
}
}
bool WaylandEglClientBufferIntegrationPrivate::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;
}
void WaylandEglClientBufferIntegrationPrivate::setupBufferAndCleanup(BufferState *bs, QOpenGLTexture *texture, int plane)
{
QMutexLocker locker(&bs->texturesLock);
bs->textures[plane] = texture;
bs->texturesContext[plane] = QOpenGLContext::currentContext();
Q_ASSERT(bs->texturesContext[plane] != nullptr);
qCDebug(qLcWaylandCompositorHardwareIntegration)
<< Q_FUNC_INFO
<< "(egl) creating a cleanup-lambda for QOpenGLContext::aboutToBeDestroyed!"
<< ", texture: " << bs->textures[plane]
<< ", ctx: " << (void*)bs->texturesContext[plane];
bs->texturesAboutToBeDestroyedConnection[plane] =
QObject::connect(bs->texturesContext[plane], &QOpenGLContext::aboutToBeDestroyed,
bs->texturesContext[plane], [bs, plane]() {
QMutexLocker locker(&bs->texturesLock);
// See above lock - there is a chance that this has already been removed from textures[plane]!
// Furthermore, we can trust that all the rest (e.g. disconnect) has also been properly executed!
if (bs->textures[plane] == nullptr)
return;
delete bs->textures[plane];
qCDebug(qLcWaylandCompositorHardwareIntegration)
<< Q_FUNC_INFO
<< "texture deleted due to QOpenGLContext::aboutToBeDestroyed!"
<< "Pointer (now dead) was:" << (void*)(bs->textures[plane])
<< " Associated context (about to die too) is: " << (void*)(bs->texturesContext[plane]);
bs->textures[plane] = nullptr;
bs->texturesContext[plane] = nullptr;
QObject::disconnect(bs->texturesAboutToBeDestroyedConnection[plane]);
bs->texturesAboutToBeDestroyedConnection[plane] = QMetaObject::Connection();
}, Qt::DirectConnection);
}
bool WaylandEglClientBufferIntegrationPrivate::initEglStream(WaylandEglClientBuffer *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) {
qCWarning(qLcWaylandCompositorHardwareIntegration, "%s:%d: eglCreateStreamFromFileDescriptorKHR failed: 0x%x", Q_FUNC_INFO, __LINE__, eglGetError());
return false;
}
state.eglMode = BufferState::ModeEGLStream;
if (!QOpenGLContext::currentContext()) {
qCWarning(qLcWaylandCompositorHardwareIntegration)
<< "EglClientBufferIntegration: creating texture with no current context";
return false;
}
auto texture = new QOpenGLTexture(static_cast<QOpenGLTexture::Target>(GL_TEXTURE_EXTERNAL_OES));
texture->create();
setupBufferAndCleanup(buffer->d, texture, 0);
qCDebug(qLcWaylandCompositorHardwareIntegration)
<< " NEW texture! It's pointer and ctx pointer: "
<< (void*)state.textures[0] << "; " << (void*)state.texturesContext[0];
texture->bind();
auto newStream = funcs->stream_consumer_gltexture(egl_display, state.egl_stream);
if (!newStream) {
EGLint code = eglGetError();
qCWarning(qLcWaylandCompositorHardwareIntegration) << "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 WaylandEglClientBufferIntegrationPrivate::handleEglstreamTexture(WaylandEglClientBuffer *buffer, struct ::wl_resource *bufferHandle)
{
bool usingLocalContext = ensureContext();
if (buffer->d->eglMode == BufferState::ModeUninitialized) {
bool streamOK = initEglStream(buffer, bufferHandle);
if (!streamOK)
return;
}
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)
qCWarning(qLcWaylandCompositorHardwareIntegration,
"%s:%d: eglStreamConsumerAcquireKHR failed: 0x%x", Q_FUNC_INFO, __LINE__,
eglGetError());
}
if (usingLocalContext)
localContext->doneCurrent();
}
WaylandEglClientBufferIntegration::WaylandEglClientBufferIntegration()
: d_ptr(new WaylandEglClientBufferIntegrationPrivate)
{
}
WaylandEglClientBufferIntegration::~WaylandEglClientBufferIntegration()
{
Q_D(WaylandEglClientBufferIntegration);
if (d->egl_unbind_wayland_display && d->display_bound) {
Q_ASSERT(d->wlDisplay);
if (!d->egl_unbind_wayland_display(d->egl_display, d->wlDisplay))
qCWarning(qLcWaylandCompositorHardwareIntegration) << "eglUnbindWaylandDisplayWL failed";
}
}
void WaylandEglClientBufferIntegration::initializeHardware(struct wl_display *display)
{
Q_D(WaylandEglClientBufferIntegration);
const bool ignoreBindDisplay = !qgetenv("QT_WAYLAND_IGNORE_BIND_DISPLAY").isEmpty();
QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface();
if (!nativeInterface) {
qCWarning(qLcWaylandCompositorHardwareIntegration)
<< "Failed to initialize EGL display. No native platform interface available.";
return;
}
d->egl_display = nativeInterface->nativeResourceForIntegration("EglDisplay");
if (!d->egl_display) {
qCWarning(qLcWaylandCompositorHardwareIntegration)
<< "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) {
qCWarning(qLcWaylandCompositorHardwareIntegration)
<< "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) {
qCWarning(qLcWaylandCompositorHardwareIntegration)
<< "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) {
qCWarning(qLcWaylandCompositorHardwareIntegration)
<< "Failed to initialize EGL display. Could not find eglQueryWaylandBufferWL.";
return;
}
d->egl_create_image = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
d->egl_destroy_image = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
if (!d->egl_create_image || !d->egl_destroy_image) {
qCWarning(qLcWaylandCompositorHardwareIntegration)
<< "Failed to initialize EGL display. Could not find eglCreateImageKHR and eglDestroyImageKHR.";
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)
qCDebug(qLcWaylandCompositorHardwareIntegration) << "Wayland display already bound by other client buffer integration.";
d->wlDisplay = display;
}
d->funcs = new QEGLStreamConvenience;
d->funcs->initialize(d->egl_display);
}
QtWayland::ClientBuffer *WaylandEglClientBufferIntegration::createBufferFor(wl_resource *buffer)
{
Q_D(WaylandEglClientBufferIntegration);
int w = -1;
bool q = d->egl_query_wayland_buffer(d->egl_display, buffer, EGL_WIDTH, &w);
if (!q || w <= 0)
return nullptr;
return new WaylandEglClientBuffer(this, buffer);
}
WaylandEglClientBuffer::WaylandEglClientBuffer(WaylandEglClientBufferIntegration *integration, wl_resource *buffer)
: ClientBuffer(buffer)
, m_integration(integration)
{
auto *p = WaylandEglClientBufferIntegrationPrivate::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);
p->initBuffer(this);
}
}
WaylandEglClientBuffer::~WaylandEglClientBuffer()
{
auto *p = WaylandEglClientBufferIntegrationPrivate::get(m_integration);
if (p) {
for (auto image : d->egl_images)
p->egl_destroy_image(p->egl_display, image);
if (d->egl_stream)
p->funcs->destroy_stream(p->egl_display, d->egl_stream);
}
{
QMutexLocker locker(&d->texturesLock);
for (int i=0; i<3; i++) {
if (d->textures[i] != nullptr) {
qCDebug(qLcWaylandCompositorHardwareIntegration)
<< Q_FUNC_INFO << " handing over texture!"
<< (void*)d->textures[i] << "; " << (void*)d->texturesContext[i]
<< " ... current context might be the same: " << QOpenGLContext::currentContext();
QtWayland::QWaylandTextureOrphanage::instance()->admitTexture(
d->textures[i], d->texturesContext[i]);
d->textures[i] = nullptr; // in case the aboutToBeDestroyed lambda is called while we where here
d->texturesContext[i] = nullptr;
QObject::disconnect(d->texturesAboutToBeDestroyedConnection[i]);
d->texturesAboutToBeDestroyedConnection[i] = QMetaObject::Connection();
}
}
}
delete d;
}
static QWaylandBufferRef::BufferFormatEgl formatFromEglFormat(EGLint format) {
switch (format) {
case EGL_TEXTURE_RGB:
return QWaylandBufferRef::BufferFormatEgl_RGB;
case EGL_TEXTURE_RGBA:
return QWaylandBufferRef::BufferFormatEgl_RGBA;
case EGL_TEXTURE_EXTERNAL_WL:
return QWaylandBufferRef::BufferFormatEgl_EXTERNAL_OES;
case EGL_TEXTURE_Y_UV_WL:
return QWaylandBufferRef::BufferFormatEgl_Y_UV;
case EGL_TEXTURE_Y_U_V_WL:
return QWaylandBufferRef::BufferFormatEgl_Y_U_V;
case EGL_TEXTURE_Y_XUXV_WL:
return QWaylandBufferRef::BufferFormatEgl_Y_XUXV;
}
return QWaylandBufferRef::BufferFormatEgl_RGBA;
}
static QOpenGLTexture::TextureFormat openGLFormatFromEglFormat(EGLint format) {
switch (format) {
case EGL_TEXTURE_RGB:
return QOpenGLTexture::RGBFormat;
case EGL_TEXTURE_RGBA:
return QOpenGLTexture::RGBAFormat;
default:
return QOpenGLTexture::NoFormat;
}
}
QWaylandBufferRef::BufferFormatEgl WaylandEglClientBuffer::bufferFormatEgl() const
{
return formatFromEglFormat(d->egl_format);
}
QOpenGLTexture *WaylandEglClientBuffer::toOpenGlTexture(int plane)
{
auto *p = WaylandEglClientBufferIntegrationPrivate::get(m_integration);
// At this point we should have a valid OpenGL context, so it's safe to destroy textures
QtWayland::QWaylandTextureOrphanage::instance()->deleteTextures();
if (!m_buffer)
return nullptr;
auto texture = d->textures[plane];
if (d->eglMode == BufferState::ModeEGLStream)
return texture; // EGLStreams texture is maintained by handle_eglstream_texture()
const auto target = static_cast<QOpenGLTexture::Target>(d->egl_format == EGL_TEXTURE_EXTERNAL_WL ? GL_TEXTURE_EXTERNAL_OES
: GL_TEXTURE_2D);
if (!texture) {
texture = new QOpenGLTexture(target);
texture->setFormat(openGLFormatFromEglFormat(d->egl_format));
texture->setSize(d->size.width(), d->size.height());
texture->create();
p->setupBufferAndCleanup(this->d, texture, plane);
}
if (m_textureDirty) {
m_textureDirty = false;
texture->bind();
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
p->gl_egl_image_target_texture_2d(target, d->egl_images[plane]);
#ifdef GL_EXT_protected_textures
if (isProtected())
glTexParameteri(target, GL_TEXTURE_PROTECTED_EXT, GL_TRUE);
#endif
}
return texture;
}
void WaylandEglClientBuffer::setCommitted(QRegion &damage)
{
ClientBuffer::setCommitted(damage);
if (d->eglMode == BufferState::ModeEGLStream || d->eglMode == BufferState::ModeUninitialized) {
auto *p = WaylandEglClientBufferIntegrationPrivate::get(m_integration);
p->handleEglstreamTexture(this, waylandBufferHandle());
}
}
bool WaylandEglClientBuffer::isProtected()
{
if (m_integration && m_buffer)
return m_integration->isProtected(m_buffer);
return false;
}
QWaylandSurface::Origin WaylandEglClientBuffer::origin() const
{
return d->isYInverted ? QWaylandSurface::OriginTopLeft : QWaylandSurface::OriginBottomLeft;
}
quintptr WaylandEglClientBuffer::lockNativeBuffer()
{
auto *p = WaylandEglClientBufferIntegrationPrivate::get(m_integration);
if (d->egl_stream != EGL_NO_STREAM_KHR)
return 0;
EGLImageKHR image = p->egl_create_image(p->egl_display, EGL_NO_CONTEXT,
EGL_WAYLAND_BUFFER_WL,
m_buffer, nullptr);
return reinterpret_cast<quintptr>(image);
}
void WaylandEglClientBuffer::unlockNativeBuffer(quintptr native_buffer) const
{
if (!native_buffer)
return;
auto *p = WaylandEglClientBufferIntegrationPrivate::get(m_integration);
EGLImageKHR image = reinterpret_cast<EGLImageKHR>(native_buffer);
p->egl_destroy_image(p->egl_display, image);
}
QSize WaylandEglClientBuffer::size() const
{
return d->size;
}
QT_END_NAMESPACE
@@ -0,0 +1,70 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#ifndef WAYLANDEGLINTEGRATION_H
#define WAYLANDEGLINTEGRATION_H
#include <QtWaylandCompositor/private/qwlclientbufferintegration_p.h>
#include <QtCore/QScopedPointer>
#include <QtCore/QPointer>
#include <QtWaylandCompositor/private/qwlclientbuffer_p.h>
QT_BEGIN_NAMESPACE
class WaylandEglClientBufferIntegrationPrivate;
class Q_WAYLANDCOMPOSITOR_EXPORT WaylandEglClientBufferIntegration : public QObject, public QtWayland::ClientBufferIntegration
{
Q_DECLARE_PRIVATE(WaylandEglClientBufferIntegration)
public:
WaylandEglClientBufferIntegration();
~WaylandEglClientBufferIntegration() override;
void initializeHardware(struct ::wl_display *display) override;
QtWayland::ClientBuffer *createBufferFor(wl_resource *buffer) override;
private:
Q_DISABLE_COPY(WaylandEglClientBufferIntegration)
QScopedPointer<WaylandEglClientBufferIntegrationPrivate> d_ptr;
};
struct BufferState;
class Q_WAYLANDCOMPOSITOR_EXPORT WaylandEglClientBuffer : public QtWayland::ClientBuffer
{
public:
WaylandEglClientBuffer(WaylandEglClientBufferIntegration* integration, wl_resource *bufferResource);
~WaylandEglClientBuffer() override;
QWaylandBufferRef::BufferFormatEgl bufferFormatEgl() const override;
QSize size() const override;
QWaylandSurface::Origin origin() const override;
quintptr lockNativeBuffer() override;
void unlockNativeBuffer(quintptr native_buffer) const override;
QOpenGLTexture *toOpenGlTexture(int plane) override;
void setCommitted(QRegion &damage) override;
bool isProtected() override;
private:
friend class WaylandEglClientBufferIntegration;
friend class WaylandEglClientBufferIntegrationPrivate;
BufferState *d = nullptr;
QPointer<WaylandEglClientBufferIntegration> m_integration;
};
QT_END_NAMESPACE
#endif // WAYLANDEGLINTEGRATION_H
@@ -0,0 +1,27 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "waylandeglstreamcontroller.h"
#include "waylandeglstreamintegration.h"
#include <QtWaylandCompositor/QWaylandCompositor>
#include <unistd.h>
QT_BEGIN_NAMESPACE
WaylandEglStreamController::WaylandEglStreamController(wl_display *display, WaylandEglStreamClientBufferIntegration *clientBufferIntegration)
: wl_eglstream_controller(display, 1 /*version*/)
, m_clientBufferIntegration(clientBufferIntegration)
{
}
void WaylandEglStreamController::eglstream_controller_attach_eglstream_consumer(Resource *resource, struct ::wl_resource *wl_surface, struct ::wl_resource *wl_buffer)
{
Q_UNUSED(resource);
m_clientBufferIntegration->attachEglStreamConsumer(wl_surface, wl_buffer);
}
QT_END_NAMESPACE
@@ -0,0 +1,43 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef WAYLANDEGLSTREAMCONTROLLER_H
#define WAYLANDEGLSTREAMCONTROLLER_H
#include "qwayland-server-wl-eglstream-controller.h"
#include <QtWaylandCompositor/private/qwayland-server-wayland.h>
#include <QtWaylandCompositor/private/qwlclientbufferintegration_p.h>
#include <QtOpenGL/QOpenGLTexture>
#include <QtCore/QObject>
#include <QtCore/QHash>
#include <QtCore/QSize>
#include <QtCore/QTextStream>
#include <EGL/egl.h>
#include <EGL/eglext.h>
QT_BEGIN_NAMESPACE
class QWaylandCompositor;
class QWaylandResource;
class WaylandEglStreamClientBufferIntegration;
class WaylandEglStreamController : public QtWaylandServer::wl_eglstream_controller
{
public:
explicit WaylandEglStreamController(wl_display *display, WaylandEglStreamClientBufferIntegration *clientBufferIntegration);
protected:
void eglstream_controller_attach_eglstream_consumer(Resource *resource, struct ::wl_resource *wl_surface, struct ::wl_resource *wl_buffer) override;
private:
WaylandEglStreamClientBufferIntegration *m_clientBufferIntegration;
};
QT_END_NAMESPACE
#endif // WAYLANDEGLSTREAMCONTROLLER_H
@@ -0,0 +1,463 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "waylandeglstreamintegration.h"
#include "waylandeglstreamcontroller.h"
#include <QtWaylandCompositor/QWaylandCompositor>
#include <QtOpenGL/QOpenGLTexture>
#include <QtGui/QGuiApplication>
#include <QtGui/QOpenGLContext>
#include <QtGui/QOffscreenSurface>
#include <QtCore/QMutexLocker>
#include <QtGui/private/qeglstreamconvenience_p.h>
#include <qpa/qplatformnativeinterface.h>
#include <QtWaylandCompositor/private/qwaylandcompositor_p.h>
#include <QtWaylandCompositor/private/qwlbuffermanager_p.h>
#include <QtWaylandCompositor/private/qwltextureorphanage_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 ControllerBufferState
{
ControllerBufferState() = default;
EGLint egl_format = EGL_TEXTURE_EXTERNAL_WL;
QOpenGLTexture *textures[3] = {nullptr, nullptr, nullptr};
QOpenGLContext *texturesContext[3] = {nullptr, nullptr, nullptr};
QMetaObject::Connection texturesAboutToBeDestroyedConnection[3] = {QMetaObject::Connection(), QMetaObject::Connection(), QMetaObject::Connection()};
QMutex texturesLock;
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 setupBufferAndCleanup(ControllerBufferState *bs, QOpenGLTexture *texture, int plane);
void handleEglstreamTexture(WaylandEglStreamClientBuffer *buffer);
EGLDisplay egl_display = EGL_NO_DISPLAY;
bool display_bound = false;
::wl_display *wlDisplay = nullptr;
QOffscreenSurface *offscreenSurface = nullptr;
QOpenGLContext *localContext = nullptr;
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;
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;
}
void WaylandEglStreamClientBufferIntegrationPrivate::setupBufferAndCleanup(ControllerBufferState *bs, QOpenGLTexture *texture, int plane)
{
QMutexLocker locker(&bs->texturesLock);
bs->textures[plane] = texture;
bs->texturesContext[plane] = QOpenGLContext::currentContext();
Q_ASSERT(bs->texturesContext[plane] != nullptr);
qCDebug(qLcWaylandCompositorHardwareIntegration)
<< Q_FUNC_INFO
<< "(eglstream) creating a cleanup-lambda for QOpenGLContext::aboutToBeDestroyed!"
<< ", texture: " << bs->textures[plane]
<< ", ctx: " << (void*)bs->texturesContext[plane];
bs->texturesAboutToBeDestroyedConnection[plane] =
QObject::connect(bs->texturesContext[plane], &QOpenGLContext::aboutToBeDestroyed,
bs->texturesContext[plane], [bs, plane]() {
QMutexLocker locker(&bs->texturesLock);
// See above lock - there is a chance that this has already been removed from textures[plane]!
// Furthermore, we can trust that all the rest (e.g. disconnect) has also been properly executed!
if (bs->textures[plane] == nullptr)
return;
delete bs->textures[plane];
qCDebug(qLcWaylandCompositorHardwareIntegration)
<< Q_FUNC_INFO
<< "texture deleted due to QOpenGLContext::aboutToBeDestroyed!"
<< "Pointer (now dead) was:" << (void*)(bs->textures[plane])
<< " Associated context (about to die too) is: " << (void*)(bs->texturesContext[plane]);
bs->textures[plane] = nullptr;
bs->texturesContext[plane] = nullptr;
QObject::disconnect(bs->texturesAboutToBeDestroyedConnection[plane]);
bs->texturesAboutToBeDestroyedConnection[plane] = QMetaObject::Connection();
}, Qt::DirectConnection);
}
bool WaylandEglStreamClientBufferIntegrationPrivate::initEglStream(WaylandEglStreamClientBuffer *buffer, wl_resource *bufferHandle)
{
ControllerBufferState &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();
setupBufferAndCleanup(buffer->d, texture, 0);
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();
ControllerBufferState &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()
{
Q_D(WaylandEglStreamClientBufferIntegration);
WaylandEglStreamClientBufferIntegrationPrivate::shuttingDown = true;
if (d->egl_unbind_wayland_display != nullptr && d->display_bound) {
Q_ASSERT(d->wlDisplay != nullptr);
if (!d->egl_unbind_wayland_display(d->egl_display, d->wlDisplay))
qCWarning(qLcWaylandCompositorHardwareIntegration) << "eglUnbindWaylandDisplayWL failed";
}
}
void WaylandEglStreamClientBufferIntegration::attachEglStreamConsumer(struct ::wl_resource *wl_surface, struct ::wl_resource *wl_buffer)
{
Q_D(WaylandEglStreamClientBufferIntegration);
Q_UNUSED(wl_surface);
auto *clientBuffer = new WaylandEglStreamClientBuffer(this, wl_buffer);
auto *bufferManager = QWaylandCompositorPrivate::get(m_compositor)->bufferManager();
bufferManager->registerBuffer(wl_buffer, clientBuffer);
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)
qCDebug(qLcWaylandCompositorHardwareIntegration) << "Wayland display already bound by other client buffer integration.";
d->wlDisplay = display;
}
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 ControllerBufferState;
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);
}
{
QMutexLocker locker(&d->texturesLock);
for (int i=0; i<3; i++) {
if (d->textures[i] != nullptr) {
qCDebug(qLcWaylandCompositorHardwareIntegration)
<< Q_FUNC_INFO << " handing over texture!"
<< (void*)d->textures[i] << "; " << (void*)d->texturesContext[i]
<< " ... current context might be the same: " << QOpenGLContext::currentContext();
QtWayland::QWaylandTextureOrphanage::instance()->admitTexture(
d->textures[i], d->texturesContext[i]);
d->textures[i] = nullptr; // in case the aboutToBeDestroyed lambda is called while we where here
d->texturesContext[i] = nullptr;
QObject::disconnect(d->texturesAboutToBeDestroyedConnection[i]);
d->texturesAboutToBeDestroyedConnection[i] = QMetaObject::Connection();
}
}
}
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)
{
// At this point we should have a valid OpenGL context, so it's safe to destroy textures
QtWayland::QWaylandTextureOrphanage::instance()->deleteTextures();
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
@@ -0,0 +1,57 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef WAYLANDEGLSTREAMINTEGRATION_H
#define WAYLANDEGLSTREAMINTEGRATION_H
#include <QtWaylandCompositor/private/qwlclientbufferintegration_p.h>
#include <QtCore/QScopedPointer>
#include <QtWaylandCompositor/private/qwlclientbuffer_p.h>
QT_BEGIN_NAMESPACE
class WaylandEglStreamClientBufferIntegrationPrivate;
class WaylandEglStreamClientBufferIntegration : public QtWayland::ClientBufferIntegration
{
Q_DECLARE_PRIVATE(WaylandEglStreamClientBufferIntegration)
public:
WaylandEglStreamClientBufferIntegration();
~WaylandEglStreamClientBufferIntegration() override;
void initializeHardware(struct ::wl_display *display) override;
QtWayland::ClientBuffer *createBufferFor(wl_resource *buffer) override;
void attachEglStreamConsumer(struct ::wl_resource *wl_surface, struct ::wl_resource *wl_buffer);
private:
Q_DISABLE_COPY(WaylandEglStreamClientBufferIntegration)
QScopedPointer<WaylandEglStreamClientBufferIntegrationPrivate> d_ptr;
};
struct ControllerBufferState;
class WaylandEglStreamClientBuffer : public QtWayland::ClientBuffer
{
public:
~WaylandEglStreamClientBuffer() override;
QWaylandBufferRef::BufferFormatEgl bufferFormatEgl() const override;
QSize size() const override;
QWaylandSurface::Origin origin() const override;
QOpenGLTexture *toOpenGlTexture(int plane) override;
void setCommitted(QRegion &damage) override;
private:
friend class WaylandEglStreamClientBufferIntegration;
friend class WaylandEglStreamClientBufferIntegrationPrivate;
WaylandEglStreamClientBuffer(WaylandEglStreamClientBufferIntegration* integration, wl_resource *bufferResource);
ControllerBufferState *d = nullptr;
WaylandEglStreamClientBufferIntegration *m_integration = nullptr;
};
QT_END_NAMESPACE
#endif // WAYLANDEGLSTREAMINTEGRATION_H