wayland: Rewrite wl-shm implementation

The main motivation for the rewrite is to properly port ShmClientBuffer
to our GraphicsBuffer abstractions. As is, libwayland implementation is
not suitable for our needs. We may need ShmClientBuffer to be alive when
its corresponding wl_shm_buffer resource is destroyed, for example to
play a window closing animation. With the existing api, we need to fight
libwayland. Besides that, libwayland doesn't provide a way to get
underlying pool file descriptor, which is needed to fill ShmAttributes.
This commit is contained in:
Vlad Zahorodnii 2023-05-25 14:15:57 +03:00
parent f1d6366d4a
commit 6f3cb932df
13 changed files with 422 additions and 333 deletions

View file

@ -8,6 +8,7 @@
*/ */
#include "debug_console.h" #include "debug_console.h"
#include "composite.h" #include "composite.h"
#include "core/graphicsbufferview.h"
#include "core/inputdevice.h" #include "core/inputdevice.h"
#include "input_event.h" #include "input_event.h"
#include "internalwindow.h" #include "internalwindow.h"
@ -25,7 +26,6 @@
#include "wayland/display.h" #include "wayland/display.h"
#include "wayland/primaryselectionsource_v1_interface.h" #include "wayland/primaryselectionsource_v1_interface.h"
#include "wayland/seat_interface.h" #include "wayland/seat_interface.h"
#include "wayland/shmclientbuffer.h"
#include "wayland/subcompositor_interface.h" #include "wayland/subcompositor_interface.h"
#include "wayland/surface_interface.h" #include "wayland/surface_interface.h"
#include "wayland_server.h" #include "wayland_server.h"
@ -1438,9 +1438,13 @@ QVariant SurfaceTreeModel::data(const QModelIndex &index, int role) const
if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
return QStringLiteral("%1 (%2) - %3").arg(surface->client()->executablePath()).arg(surface->client()->processId()).arg(surface->id()); return QStringLiteral("%1 (%2) - %3").arg(surface->client()->executablePath()).arg(surface->client()->processId()).arg(surface->id());
} else if (role == Qt::DecorationRole) { } else if (role == Qt::DecorationRole) {
if (auto buffer = qobject_cast<KWaylandServer::ShmClientBuffer *>(surface->buffer())) { if (surface->buffer()) {
return buffer->data().scaled(QSize(64, 64), Qt::KeepAspectRatio); const GraphicsBufferView view(surface->buffer());
if (const QImage *image = view.image()) {
return image->scaled(QSize(64, 64), Qt::KeepAspectRatio);
}
} }
return QImage();
} }
} }
return QVariant(); return QVariant();

View file

@ -5,11 +5,11 @@
*/ */
#include "platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h" #include "platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h"
#include "core/graphicsbufferview.h"
#include "libkwineffects/kwingltexture.h" #include "libkwineffects/kwingltexture.h"
#include "platformsupport/scenes/opengl/abstract_egl_backend.h" #include "platformsupport/scenes/opengl/abstract_egl_backend.h"
#include "scene/surfaceitem_wayland.h" #include "scene/surfaceitem_wayland.h"
#include "utils/common.h" #include "utils/common.h"
#include "wayland/shmclientbuffer.h"
#include <epoxy/egl.h> #include <epoxy/egl.h>
@ -36,8 +36,8 @@ bool BasicEGLSurfaceTextureWayland::create()
{ {
if (m_pixmap->buffer()->dmabufAttributes()) { if (m_pixmap->buffer()->dmabufAttributes()) {
return loadDmabufTexture(m_pixmap->buffer()); return loadDmabufTexture(m_pixmap->buffer());
} else if (auto buffer = qobject_cast<KWaylandServer::ShmClientBuffer *>(m_pixmap->buffer())) { } else if (m_pixmap->buffer()->shmAttributes()) {
return loadShmTexture(buffer); return loadShmTexture(m_pixmap->buffer());
} else { } else {
return false; return false;
} }
@ -53,17 +53,23 @@ void BasicEGLSurfaceTextureWayland::update(const QRegion &region)
{ {
if (m_pixmap->buffer()->dmabufAttributes()) { if (m_pixmap->buffer()->dmabufAttributes()) {
updateDmabufTexture(m_pixmap->buffer()); updateDmabufTexture(m_pixmap->buffer());
} else if (auto buffer = qobject_cast<KWaylandServer::ShmClientBuffer *>(m_pixmap->buffer())) { } else if (m_pixmap->buffer()->shmAttributes()) {
updateShmTexture(buffer, region); updateShmTexture(m_pixmap->buffer(), region);
} }
} }
bool BasicEGLSurfaceTextureWayland::loadShmTexture(KWaylandServer::ShmClientBuffer *buffer) bool BasicEGLSurfaceTextureWayland::loadShmTexture(GraphicsBuffer *buffer)
{ {
m_texture = GLTexture::upload(buffer->data()); const GraphicsBufferView view(buffer);
if (!m_texture) { if (Q_UNLIKELY(!view.image())) {
return false; return false;
} }
m_texture = GLTexture::upload(*view.image());
if (Q_UNLIKELY(!m_texture)) {
return false;
}
m_texture->setFilter(GL_LINEAR); m_texture->setFilter(GL_LINEAR);
m_texture->setWrapMode(GL_CLAMP_TO_EDGE); m_texture->setWrapMode(GL_CLAMP_TO_EDGE);
m_texture->setContentTransform(TextureTransform::MirrorY); m_texture->setContentTransform(TextureTransform::MirrorY);
@ -72,7 +78,7 @@ bool BasicEGLSurfaceTextureWayland::loadShmTexture(KWaylandServer::ShmClientBuff
return true; return true;
} }
void BasicEGLSurfaceTextureWayland::updateShmTexture(KWaylandServer::ShmClientBuffer *buffer, const QRegion &region) void BasicEGLSurfaceTextureWayland::updateShmTexture(GraphicsBuffer *buffer, const QRegion &region)
{ {
if (Q_UNLIKELY(m_bufferType != BufferType::Shm)) { if (Q_UNLIKELY(m_bufferType != BufferType::Shm)) {
destroy(); destroy();
@ -80,14 +86,14 @@ void BasicEGLSurfaceTextureWayland::updateShmTexture(KWaylandServer::ShmClientBu
return; return;
} }
const QImage &image = buffer->data(); const GraphicsBufferView view(buffer);
if (Q_UNLIKELY(image.isNull())) { if (Q_UNLIKELY(!view.image())) {
return; return;
} }
const QRegion damage = mapRegion(m_pixmap->item()->surfaceToBufferMatrix(), region); const QRegion damage = mapRegion(m_pixmap->item()->surfaceToBufferMatrix(), region);
for (const QRect &rect : damage) { for (const QRect &rect : damage) {
m_texture->update(image, rect.topLeft(), rect); m_texture->update(*view.image(), rect.topLeft(), rect);
} }
} }

View file

@ -8,11 +8,6 @@
#include "openglsurfacetexture_wayland.h" #include "openglsurfacetexture_wayland.h"
namespace KWaylandServer
{
class ShmClientBuffer;
}
namespace KWin namespace KWin
{ {
@ -31,8 +26,8 @@ public:
void update(const QRegion &region) override; void update(const QRegion &region) override;
private: private:
bool loadShmTexture(KWaylandServer::ShmClientBuffer *buffer); bool loadShmTexture(GraphicsBuffer *buffer);
void updateShmTexture(KWaylandServer::ShmClientBuffer *buffer, const QRegion &region); void updateShmTexture(GraphicsBuffer *buffer, const QRegion &region);
bool loadDmabufTexture(GraphicsBuffer *buffer); bool loadDmabufTexture(GraphicsBuffer *buffer);
void updateDmabufTexture(GraphicsBuffer *buffer); void updateDmabufTexture(GraphicsBuffer *buffer);
void destroy(); void destroy();

View file

@ -5,9 +5,9 @@
*/ */
#include "qpaintersurfacetexture_wayland.h" #include "qpaintersurfacetexture_wayland.h"
#include "core/graphicsbufferview.h"
#include "scene/surfaceitem_wayland.h" #include "scene/surfaceitem_wayland.h"
#include "utils/common.h" #include "utils/common.h"
#include "wayland/shmclientbuffer.h"
#include "wayland/surface_interface.h" #include "wayland/surface_interface.h"
#include <QPainter> #include <QPainter>
@ -24,23 +24,22 @@ QPainterSurfaceTextureWayland::QPainterSurfaceTextureWayland(QPainterBackend *ba
bool QPainterSurfaceTextureWayland::create() bool QPainterSurfaceTextureWayland::create()
{ {
auto buffer = qobject_cast<KWaylandServer::ShmClientBuffer *>(m_pixmap->buffer()); const GraphicsBufferView view(m_pixmap->buffer());
if (Q_LIKELY(buffer)) { if (Q_LIKELY(view.image())) {
// The buffer data is copied as the buffer interface returns a QImage // The buffer data is copied as the buffer interface returns a QImage
// which doesn't own the data of the underlying wl_shm_buffer object. // which doesn't own the data of the underlying wl_shm_buffer object.
m_image = buffer->data().copy(); m_image = view.image()->copy();
} }
return !m_image.isNull(); return !m_image.isNull();
} }
void QPainterSurfaceTextureWayland::update(const QRegion &region) void QPainterSurfaceTextureWayland::update(const QRegion &region)
{ {
auto buffer = qobject_cast<KWaylandServer::ShmClientBuffer *>(m_pixmap->buffer()); const GraphicsBufferView view(m_pixmap->buffer());
if (Q_UNLIKELY(!buffer)) { if (Q_UNLIKELY(!view.image())) {
return; return;
} }
const QImage image = buffer->data();
const QRegion dirtyRegion = mapRegion(m_pixmap->item()->surfaceToBufferMatrix(), region); const QRegion dirtyRegion = mapRegion(m_pixmap->item()->surfaceToBufferMatrix(), region);
QPainter painter(&m_image); QPainter painter(&m_image);
painter.setCompositionMode(QPainter::CompositionMode_Source); painter.setCompositionMode(QPainter::CompositionMode_Source);
@ -48,7 +47,7 @@ void QPainterSurfaceTextureWayland::update(const QRegion &region)
// The buffer data is copied as the buffer interface returns a QImage // The buffer data is copied as the buffer interface returns a QImage
// which doesn't own the data of the underlying wl_shm_buffer object. // which doesn't own the data of the underlying wl_shm_buffer object.
for (const QRect &rect : dirtyRegion) { for (const QRect &rect : dirtyRegion) {
painter.drawImage(rect, image, rect); painter.drawImage(rect, *view.image(), rect);
} }
} }

View file

@ -10,9 +10,9 @@
#include "shadow.h" #include "shadow.h"
// kwin // kwin
#include "atoms.h" #include "atoms.h"
#include "core/graphicsbufferview.h"
#include "internalwindow.h" #include "internalwindow.h"
#include "wayland/shadow_interface.h" #include "wayland/shadow_interface.h"
#include "wayland/shmclientbuffer.h"
#include "wayland/surface_interface.h" #include "wayland/surface_interface.h"
#include "wayland_server.h" #include "wayland_server.h"
#include "x11window.h" #include "x11window.h"
@ -201,9 +201,11 @@ bool Shadow::init(KDecoration2::Decoration *decoration)
static QImage shadowTileForBuffer(GraphicsBuffer *buffer) static QImage shadowTileForBuffer(GraphicsBuffer *buffer)
{ {
auto shmBuffer = qobject_cast<KWaylandServer::ShmClientBuffer *>(buffer); if (buffer) {
if (shmBuffer) { const GraphicsBufferView view(buffer);
return shmBuffer->data().copy(); if (const QImage *image = view.image()) {
return image->copy();
}
} }
return QImage(); return QImage();
} }

View file

@ -14,10 +14,10 @@
#include "KWayland/Client/shm_pool.h" #include "KWayland/Client/shm_pool.h"
#include "KWayland/Client/surface.h" #include "KWayland/Client/surface.h"
// server // server
#include "core/graphicsbufferview.h"
#include "wayland/compositor_interface.h" #include "wayland/compositor_interface.h"
#include "wayland/display.h" #include "wayland/display.h"
#include "wayland/shadow_interface.h" #include "wayland/shadow_interface.h"
#include "wayland/shmclientbuffer.h"
using namespace KWaylandServer; using namespace KWaylandServer;
@ -168,9 +168,11 @@ void ShadowTest::testCreateShadow()
static QImage bufferToImage(KWin::GraphicsBuffer *clientBuffer) static QImage bufferToImage(KWin::GraphicsBuffer *clientBuffer)
{ {
auto shmBuffer = qobject_cast<ShmClientBuffer *>(clientBuffer); if (clientBuffer) {
if (shmBuffer) { KWin::GraphicsBufferView view(clientBuffer);
return shmBuffer->data(); if (QImage *image = view.image()) {
return image->copy();
}
} }
return QImage(); return QImage();
} }

View file

@ -15,7 +15,6 @@
#include "wayland/pointergestures_v1_interface.h" #include "wayland/pointergestures_v1_interface.h"
#include "wayland/relativepointer_v1_interface.h" #include "wayland/relativepointer_v1_interface.h"
#include "wayland/seat_interface.h" #include "wayland/seat_interface.h"
#include "wayland/shmclientbuffer.h"
#include "wayland/subcompositor_interface.h" #include "wayland/subcompositor_interface.h"
#include "wayland/surface_interface.h" #include "wayland/surface_interface.h"
@ -1351,7 +1350,6 @@ void TestWaylandSeat::testCursor()
QCOMPARE(cursorChangedSpy.count(), 3); QCOMPARE(cursorChangedSpy.count(), 3);
QCOMPARE(cursor->hotspot(), QPoint(1, 2)); QCOMPARE(cursor->hotspot(), QPoint(1, 2));
QVERIFY(cursor->surface()); QVERIFY(cursor->surface());
QCOMPARE(qobject_cast<ShmClientBuffer *>(cursor->surface()->buffer())->data(), img);
p->hideCursor(); p->hideCursor();
QVERIFY(cursorChangedSpy.wait()); QVERIFY(cursorChangedSpy.wait());

View file

@ -8,11 +8,12 @@
#include <QPainter> #include <QPainter>
#include <QtTest> #include <QtTest>
// KWin // KWin
#include "core/graphicsbuffer.h"
#include "core/graphicsbufferview.h"
#include "wayland/compositor_interface.h" #include "wayland/compositor_interface.h"
#include "wayland/display.h" #include "wayland/display.h"
#include "wayland/idleinhibit_v1_interface.h" #include "wayland/idleinhibit_v1_interface.h"
#include "wayland/output_interface.h" #include "wayland/output_interface.h"
#include "wayland/shmclientbuffer.h"
#include "wayland/surface_interface.h" #include "wayland/surface_interface.h"
#include "KWayland/Client/compositor.h" #include "KWayland/Client/compositor.h"
@ -43,7 +44,6 @@ private Q_SLOTS:
void testDamage(); void testDamage();
void testFrameCallback(); void testFrameCallback();
void testAttachBuffer(); void testAttachBuffer();
void testMultipleSurfaces();
void testOpaque(); void testOpaque();
void testInput(); void testInput();
void testScale(); void testScale();
@ -397,11 +397,13 @@ void TestWaylandSurface::testAttachBuffer()
// now the ServerSurface should have the black image attached as a buffer // now the ServerSurface should have the black image attached as a buffer
KWin::GraphicsBuffer *buffer = serverSurface->buffer(); KWin::GraphicsBuffer *buffer = serverSurface->buffer();
buffer->ref(); buffer->ref();
auto shmBuffer = qobject_cast<KWaylandServer::ShmClientBuffer *>(buffer); {
QVERIFY(shmBuffer); KWin::GraphicsBufferView view(buffer);
QCOMPARE(shmBuffer->data(), black); QVERIFY(view.image());
QCOMPARE(shmBuffer->data().format(), QImage::Format_RGB32); QCOMPARE(*view.image(), black);
QCOMPARE(shmBuffer->size(), QSize(24, 24)); QCOMPARE(view.image()->format(), QImage::Format_RGB32);
QCOMPARE(view.image()->size(), QSize(24, 24));
}
// render another frame // render another frame
s->attachBuffer(redBuffer); s->attachBuffer(redBuffer);
@ -413,14 +415,16 @@ void TestWaylandSurface::testAttachBuffer()
QVERIFY(unmappedSpy.isEmpty()); QVERIFY(unmappedSpy.isEmpty());
KWin::GraphicsBuffer *buffer2 = serverSurface->buffer(); KWin::GraphicsBuffer *buffer2 = serverSurface->buffer();
buffer2->ref(); buffer2->ref();
auto shmBuffer2 = qobject_cast<KWaylandServer::ShmClientBuffer *>(buffer2); {
QVERIFY(shmBuffer2); KWin::GraphicsBufferView view(buffer2);
QCOMPARE(shmBuffer2->data().format(), QImage::Format_ARGB32_Premultiplied); QVERIFY(view.image());
QCOMPARE(shmBuffer2->size(), QSize(24, 24)); QCOMPARE(view.image()->format(), QImage::Format_ARGB32_Premultiplied);
for (int i = 0; i < 24; ++i) { QCOMPARE(view.image()->size(), QSize(24, 24));
for (int j = 0; j < 24; ++j) { for (int i = 0; i < 24; ++i) {
// it's premultiplied in the format for (int j = 0; j < 24; ++j) {
QCOMPARE(shmBuffer2->data().pixel(i, j), qRgba(128, 0, 0, 128)); // it's premultiplied in the format
QCOMPARE(view.image()->pixel(i, j), qRgba(128, 0, 0, 128));
}
} }
} }
buffer2->unref(); buffer2->unref();
@ -448,14 +452,16 @@ void TestWaylandSurface::testAttachBuffer()
KWin::GraphicsBuffer *buffer3 = serverSurface->buffer(); KWin::GraphicsBuffer *buffer3 = serverSurface->buffer();
buffer3->ref(); buffer3->ref();
auto shmBuffer3 = qobject_cast<KWaylandServer::ShmClientBuffer *>(buffer3); {
QVERIFY(shmBuffer3); KWin::GraphicsBufferView view(buffer3);
QCOMPARE(shmBuffer3->data().format(), QImage::Format_ARGB32_Premultiplied); QVERIFY(view.image());
QCOMPARE(shmBuffer3->size(), QSize(24, 24)); QCOMPARE(view.image()->format(), QImage::Format_ARGB32_Premultiplied);
for (int i = 0; i < 24; ++i) { QCOMPARE(view.image()->size(), QSize(24, 24));
for (int j = 0; j < 24; ++j) { for (int i = 0; i < 24; ++i) {
// it's premultiplied in the format for (int j = 0; j < 24; ++j) {
QCOMPARE(shmBuffer3->data().pixel(i, j), qRgba(0, 0, 128, 128)); // it's premultiplied in the format
QCOMPARE(view.image()->pixel(i, j), qRgba(0, 0, 128, 128));
}
} }
} }
buffer3->unref(); buffer3->unref();
@ -495,94 +501,6 @@ void TestWaylandSurface::testAttachBuffer()
buffer->unref(); buffer->unref();
} }
void TestWaylandSurface::testMultipleSurfaces()
{
using namespace KWaylandServer;
KWayland::Client::Registry registry;
registry.setEventQueue(m_queue);
QSignalSpy shmSpy(&registry, &KWayland::Client::Registry::shmAnnounced);
registry.create(m_connection->display());
QVERIFY(registry.isValid());
registry.setup();
QVERIFY(shmSpy.wait());
KWayland::Client::ShmPool pool1;
KWayland::Client::ShmPool pool2;
pool1.setup(registry.bindShm(shmSpy.first().first().value<quint32>(), shmSpy.first().last().value<quint32>()));
pool2.setup(registry.bindShm(shmSpy.first().first().value<quint32>(), shmSpy.first().last().value<quint32>()));
QVERIFY(pool1.isValid());
QVERIFY(pool2.isValid());
// create the surfaces
QSignalSpy serverSurfaceCreated(m_compositorInterface, &KWaylandServer::CompositorInterface::surfaceCreated);
std::unique_ptr<KWayland::Client::Surface> s1(m_compositor->createSurface());
QVERIFY(serverSurfaceCreated.wait());
SurfaceInterface *serverSurface1 = serverSurfaceCreated.first().first().value<KWaylandServer::SurfaceInterface *>();
QVERIFY(serverSurface1);
// second surface
std::unique_ptr<KWayland::Client::Surface> s2(m_compositor->createSurface());
QVERIFY(serverSurfaceCreated.wait());
SurfaceInterface *serverSurface2 = serverSurfaceCreated.last().first().value<KWaylandServer::SurfaceInterface *>();
QVERIFY(serverSurface2);
QVERIFY(serverSurface1->resource() != serverSurface2->resource());
// create two images
QImage black(24, 24, QImage::Format_RGB32);
black.fill(Qt::black);
QImage red(24, 24, QImage::Format_ARGB32_Premultiplied);
red.fill(QColor(255, 0, 0, 128));
auto blackBuffer = pool1.createBuffer(black);
auto redBuffer = pool2.createBuffer(red);
s1->attachBuffer(blackBuffer);
s1->damage(QRect(0, 0, 24, 24));
s1->commit(KWayland::Client::Surface::CommitFlag::None);
QSignalSpy damageSpy1(serverSurface1, &KWaylandServer::SurfaceInterface::damaged);
QVERIFY(damageSpy1.wait());
// now the ServerSurface should have the black image attached as a buffer
KWin::GraphicsBuffer *buffer1 = serverSurface1->buffer();
QVERIFY(buffer1);
QImage buffer1Data = qobject_cast<ShmClientBuffer *>(buffer1)->data();
QCOMPARE(buffer1Data, black);
// accessing the same buffer is OK
QImage buffer1Data2 = qobject_cast<ShmClientBuffer *>(buffer1)->data();
QCOMPARE(buffer1Data2, buffer1Data);
buffer1Data = QImage();
QVERIFY(buffer1Data.isNull());
buffer1Data2 = QImage();
QVERIFY(buffer1Data2.isNull());
// attach a buffer for the other surface
s2->attachBuffer(redBuffer);
s2->damage(QRect(0, 0, 24, 24));
s2->commit(KWayland::Client::Surface::CommitFlag::None);
QSignalSpy damageSpy2(serverSurface2, &KWaylandServer::SurfaceInterface::damaged);
QVERIFY(damageSpy2.wait());
KWin::GraphicsBuffer *buffer2 = serverSurface2->buffer();
QVERIFY(buffer2);
QImage buffer2Data = qobject_cast<ShmClientBuffer *>(buffer2)->data();
QCOMPARE(buffer2Data, red);
// while buffer2 is accessed we cannot access buffer1
buffer1Data = qobject_cast<ShmClientBuffer *>(buffer1)->data();
QVERIFY(buffer1Data.isNull());
// a deep copy can be kept around
QImage deepCopy = buffer2Data.copy();
QCOMPARE(deepCopy, red);
buffer2Data = QImage();
QVERIFY(buffer2Data.isNull());
QCOMPARE(deepCopy, red);
// now that buffer2Data is destroyed we can access buffer1 again
buffer1Data = qobject_cast<ShmClientBuffer *>(buffer1)->data();
QVERIFY(!buffer1Data.isNull());
QCOMPARE(buffer1Data, black);
}
void TestWaylandSurface::testOpaque() void TestWaylandSurface::testOpaque()
{ {
using namespace KWaylandServer; using namespace KWaylandServer;

View file

@ -8,7 +8,7 @@
#include "display_p.h" #include "display_p.h"
#include "linuxdmabufv1clientbuffer_p.h" #include "linuxdmabufv1clientbuffer_p.h"
#include "output_interface.h" #include "output_interface.h"
#include "shmclientbuffer.h" #include "shmclientbuffer_p.h"
#include "utils/common.h" #include "utils/common.h"
#include <QAbstractEventDispatcher> #include <QAbstractEventDispatcher>

View file

@ -4,207 +4,312 @@
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/ */
#include "shmclientbuffer.h" #include "config-kwin.h"
#include "display.h"
#include <QHash> #include "wayland/shmclientbuffer.h"
#include <QImage> #include "wayland/display.h"
#include "wayland/shmclientbuffer_p.h"
#include <wayland-server-core.h> #include <drm_fourcc.h>
#include <wayland-server-protocol.h> #include <fcntl.h>
#include <mutex>
#include <signal.h>
#include <sys/mman.h>
#include <sys/stat.h>
using namespace KWin;
namespace KWaylandServer namespace KWaylandServer
{ {
static const ShmClientBuffer *s_accessedBuffer = nullptr;
static int s_accessCounter = 0;
static QHash<wl_resource *, ShmClientBuffer *> s_buffers;
class ShmClientBufferPrivate static constexpr int s_version = 1;
{
public:
ShmClientBufferPrivate(ShmClientBuffer *q);
static void buffer_destroy_callback(wl_listener *listener, void *data); static constexpr uint32_t s_formats[] = {
WL_SHM_FORMAT_ARGB8888,
ShmClientBuffer *q; WL_SHM_FORMAT_XRGB8888,
wl_resource *resource = nullptr; #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
QImage::Format format = QImage::Format_Invalid; WL_SHM_FORMAT_ARGB2101010,
uint32_t width = 0; WL_SHM_FORMAT_XRGB2101010,
uint32_t height = 0; WL_SHM_FORMAT_ABGR2101010,
bool hasAlphaChannel = false; WL_SHM_FORMAT_XBGR2101010,
QImage savedData; WL_SHM_FORMAT_ABGR16161616,
WL_SHM_FORMAT_XBGR16161616,
struct DestroyListener #endif
{
wl_listener listener;
ShmClientBufferPrivate *receiver;
};
DestroyListener destroyListener;
}; };
ShmClientBufferPrivate::ShmClientBufferPrivate(ShmClientBuffer *q) class ShmSigbusData
: q(q)
{ {
} public:
ShmPool *pool = nullptr;
int accessCount = 0;
};
static void cleanupShmPool(void *poolHandle) static thread_local ShmSigbusData sigbusData;
static struct sigaction prevSigbusAction;
static uint32_t shmFormatToDrmFormat(uint32_t shmFormat)
{ {
wl_shm_pool_unref(static_cast<wl_shm_pool *>(poolHandle)); switch (shmFormat) {
}
void ShmClientBufferPrivate::buffer_destroy_callback(wl_listener *listener, void *data)
{
auto bufferPrivate = reinterpret_cast<ShmClientBufferPrivate::DestroyListener *>(listener)->receiver;
wl_shm_buffer *buffer = wl_shm_buffer_get(bufferPrivate->resource);
wl_shm_pool *pool = wl_shm_buffer_ref_pool(buffer);
s_buffers.remove(bufferPrivate->resource);
wl_list_remove(&bufferPrivate->destroyListener.listener.link);
wl_list_init(&bufferPrivate->destroyListener.listener.link);
bufferPrivate->resource = nullptr;
bufferPrivate->savedData = QImage(static_cast<const uchar *>(wl_shm_buffer_get_data(buffer)),
bufferPrivate->width,
bufferPrivate->height,
wl_shm_buffer_get_stride(buffer),
bufferPrivate->format,
cleanupShmPool,
pool);
bufferPrivate->q->drop();
}
static bool alphaChannelFromFormat(uint32_t format)
{
switch (format) {
case WL_SHM_FORMAT_ABGR16161616:
case WL_SHM_FORMAT_ABGR2101010:
case WL_SHM_FORMAT_ARGB2101010:
case WL_SHM_FORMAT_ARGB8888: case WL_SHM_FORMAT_ARGB8888:
return true; return DRM_FORMAT_ARGB8888;
case WL_SHM_FORMAT_XBGR16161616:
case WL_SHM_FORMAT_XBGR2101010:
case WL_SHM_FORMAT_XRGB2101010:
case WL_SHM_FORMAT_XRGB8888: case WL_SHM_FORMAT_XRGB8888:
return DRM_FORMAT_XRGB8888;
default: default:
return false; return shmFormat; // other wl_shm formats match the drm formats
} }
} }
static QImage::Format imageFormatForShmFormat(uint32_t format) ShmPool::ShmPool(ShmClientBufferIntegration *integration, wl_client *client, int id, uint32_t version, FileDescriptor &&fd, MemoryMap &&mapping)
: QtWaylandServer::wl_shm_pool(client, id, version)
, integration(integration)
, mapping(std::move(mapping))
, fd(std::move(fd))
{ {
switch (format) { #if HAVE_MEMFD
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN const int seals = fcntl(this->fd.get(), F_GET_SEALS);
case WL_SHM_FORMAT_ABGR16161616: if (seals != -1) {
return QImage::Format_RGBA64_Premultiplied; struct stat statbuf;
case WL_SHM_FORMAT_XBGR16161616: if ((seals & F_SEAL_SHRINK) && fstat(this->fd.get(), &statbuf) >= 0) {
return QImage::Format_RGBX64; sigbusImpossible = statbuf.st_size >= this->mapping.size();
case WL_SHM_FORMAT_ARGB2101010: }
return QImage::Format_A2RGB30_Premultiplied; }
case WL_SHM_FORMAT_XRGB2101010:
return QImage::Format_RGB30;
case WL_SHM_FORMAT_ABGR2101010:
return QImage::Format_A2BGR30_Premultiplied;
case WL_SHM_FORMAT_XBGR2101010:
return QImage::Format_BGR30;
#endif #endif
case WL_SHM_FORMAT_ARGB8888: }
return QImage::Format_ARGB32_Premultiplied;
case WL_SHM_FORMAT_XRGB8888: void ShmPool::ref()
return QImage::Format_RGB32; {
default: ++refCount;
return QImage::Format_Invalid; }
void ShmPool::unref()
{
--refCount;
if (refCount == 0) {
delete this;
} }
} }
ShmClientBuffer::ShmClientBuffer(wl_resource *resource) void ShmPool::shm_pool_destroy_resource(Resource *resource)
: d(std::make_unique<ShmClientBufferPrivate>(this))
{ {
wl_shm_buffer *buffer = wl_shm_buffer_get(resource); unref();
d->resource = resource; }
d->width = wl_shm_buffer_get_width(buffer);
d->height = wl_shm_buffer_get_height(buffer);
d->hasAlphaChannel = alphaChannelFromFormat(wl_shm_buffer_get_format(buffer));
d->format = imageFormatForShmFormat(wl_shm_buffer_get_format(buffer));
d->destroyListener.receiver = d.get(); void ShmPool::shm_pool_destroy(Resource *resource)
d->destroyListener.listener.notify = ShmClientBufferPrivate::buffer_destroy_callback; {
wl_resource_add_destroy_listener(resource, &d->destroyListener.listener); wl_resource_destroy(resource->handle);
}
void ShmPool::shm_pool_create_buffer(Resource *resource, uint32_t id, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format)
{
if (std::find(std::begin(s_formats), std::end(s_formats), format) == std::end(s_formats)) {
wl_resource_post_error(resource->handle,
WL_SHM_ERROR_INVALID_FORMAT,
"invalid format 0x%x",
format);
return;
}
if (offset < 0 || width <= 0 || height <= 0 || stride < width
|| INT32_MAX / stride < height || offset > mapping.size() - stride * height) {
wl_resource_post_error(resource->handle,
WL_SHM_ERROR_INVALID_STRIDE,
"invalid width, height or stride (%dx%d, %u)",
width, height, stride);
return;
}
ShmAttributes attributes{
.fd = fd.duplicate(),
.stride = stride,
.offset = offset,
.size = QSize(width, height),
.format = shmFormatToDrmFormat(format),
};
new ShmClientBuffer(this, std::move(attributes), resource->client(), id);
}
void ShmPool::shm_pool_resize(Resource *resource, int32_t size)
{
if (size < mapping.size()) {
wl_resource_post_error(resource->handle, WL_SHM_ERROR_INVALID_FD, "shrinking pool invalid");
return;
}
auto remapping = MemoryMap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0);
if (remapping.isValid()) {
mapping = std::move(remapping);
} else {
wl_resource_post_error(resource->handle, WL_SHM_ERROR_INVALID_FD, "failed to map shm pool with the new size");
}
}
void ShmClientBuffer::buffer_destroy_resource(wl_resource *resource)
{
if (ShmClientBuffer *buffer = ShmClientBuffer::get(resource)) {
buffer->m_resource = nullptr;
buffer->drop();
}
}
void ShmClientBuffer::buffer_destroy(wl_client *client, wl_resource *resource)
{
wl_resource_destroy(resource);
}
const struct wl_buffer_interface ShmClientBuffer::implementation = {
.destroy = buffer_destroy,
};
ShmClientBuffer::ShmClientBuffer(ShmPool *pool, ShmAttributes attributes, wl_client *client, uint32_t id)
: m_shmPool(pool)
, m_shmAttributes(std::move(attributes))
{
m_shmPool->ref();
connect(this, &GraphicsBuffer::released, [this]() { connect(this, &GraphicsBuffer::released, [this]() {
wl_buffer_send_release(d->resource); wl_buffer_send_release(m_resource);
}); });
m_resource = wl_resource_create(client, &wl_buffer_interface, 1, id);
wl_resource_set_implementation(m_resource, &implementation, this, buffer_destroy_resource);
} }
ShmClientBuffer::~ShmClientBuffer() ShmClientBuffer::~ShmClientBuffer()
{ {
m_shmPool->unref();
} }
QSize ShmClientBuffer::size() const QSize ShmClientBuffer::size() const
{ {
return QSize(d->width, d->height); return m_shmAttributes.size;
} }
bool ShmClientBuffer::hasAlphaChannel() const bool ShmClientBuffer::hasAlphaChannel() const
{ {
return d->hasAlphaChannel; return alphaChannelFromDrmFormat(m_shmAttributes.format);
} }
static void cleanupShmData(void *bufferHandle) const ShmAttributes *ShmClientBuffer::shmAttributes() const
{ {
Q_ASSERT_X(s_accessCounter > 0, "cleanup", "access counter must be positive"); return &m_shmAttributes;
s_accessCounter--;
if (s_accessCounter == 0) {
s_accessedBuffer = nullptr;
}
wl_shm_buffer_end_access(static_cast<wl_shm_buffer *>(bufferHandle));
}
QImage ShmClientBuffer::data() const
{
if (s_accessedBuffer && s_accessedBuffer != this) {
return QImage();
}
if (wl_shm_buffer *buffer = wl_shm_buffer_get(d->resource)) {
s_accessedBuffer = this;
s_accessCounter++;
wl_shm_buffer_begin_access(buffer);
const uchar *data = static_cast<const uchar *>(wl_shm_buffer_get_data(buffer));
const uint32_t stride = wl_shm_buffer_get_stride(buffer);
return QImage(data, d->width, d->height, stride, d->format, cleanupShmData, buffer);
}
return d->savedData;
} }
ShmClientBuffer *ShmClientBuffer::get(wl_resource *resource) ShmClientBuffer *ShmClientBuffer::get(wl_resource *resource)
{ {
if (auto buffer = s_buffers.value(resource)) { if (wl_resource_instance_of(resource, &wl_buffer_interface, &implementation)) {
return buffer; return static_cast<ShmClientBuffer *>(wl_resource_get_user_data(resource));
} }
if (wl_shm_buffer_get(resource)) {
auto buffer = new ShmClientBuffer(resource);
s_buffers[resource] = buffer;
return buffer;
}
return nullptr; return nullptr;
} }
static void sigbusHandler(int signum, siginfo_t *info, void *context)
{
auto reraise = [&]() {
if (prevSigbusAction.sa_flags & SA_SIGINFO) {
prevSigbusAction.sa_sigaction(signum, info, context);
} else {
prevSigbusAction.sa_handler(signum);
}
};
const ShmPool *pool = sigbusData.pool;
if (!pool) {
reraise();
return;
}
const uchar *addr = static_cast<uchar *>(info->si_addr);
const uchar *mappingStart = static_cast<uchar *>(pool->mapping.data());
if (addr < mappingStart || addr >= mappingStart + pool->mapping.size()) {
reraise();
return;
}
// Replace the faulty mapping with a new one that's filled with zeros.
if (mmap(pool->mapping.data(), pool->mapping.size(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED) {
reraise();
return;
}
}
void *ShmClientBuffer::map()
{
if (!m_shmPool->sigbusImpossible) {
// A SIGBUS signal may be emitted if the backing file is shrinked and we access now
// removed pages. Install a signal handler to handle this case. Note that if the
// backing file has F_SEAL_SHRINK seal, then we don't need to do anything.
static std::once_flag sigbusOnce;
std::call_once(sigbusOnce, []() {
struct sigaction action;
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
action.sa_sigaction = sigbusHandler;
action.sa_flags = SA_SIGINFO | SA_NODEFER;
sigaction(SIGBUS, &action, &prevSigbusAction);
});
Q_ASSERT(!sigbusData.pool || sigbusData.pool == m_shmPool);
sigbusData.pool = m_shmPool;
++sigbusData.accessCount;
}
return reinterpret_cast<uchar *>(m_shmPool->mapping.data()) + m_shmAttributes.offset;
}
void ShmClientBuffer::unmap()
{
if (m_shmPool->sigbusImpossible) {
return;
}
Q_ASSERT(sigbusData.accessCount > 0);
--sigbusData.accessCount;
if (sigbusData.accessCount == 0) {
sigbusData.pool = nullptr;
}
}
ShmClientBufferIntegrationPrivate::ShmClientBufferIntegrationPrivate(Display *display, ShmClientBufferIntegration *q)
: QtWaylandServer::wl_shm(*display, s_version)
, q(q)
{
}
void ShmClientBufferIntegrationPrivate::shm_bind_resource(Resource *resource)
{
for (const uint32_t &format : s_formats) {
send_format(resource->handle, format);
}
}
void ShmClientBufferIntegrationPrivate::shm_create_pool(Resource *resource, uint32_t id, int32_t fd, int32_t size)
{
FileDescriptor fileDescriptor{fd};
if (size <= 0) {
wl_resource_post_error(resource->handle, error_invalid_stride, "invalid size (%d)", size);
return;
}
auto mapping = MemoryMap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (!mapping.isValid()) {
wl_resource_post_error(resource->handle, error_invalid_fd, "failed to map shm pool");
return;
}
new ShmPool(q, resource->client(), id, resource->version(), std::move(fileDescriptor), std::move(mapping));
}
ShmClientBufferIntegration::ShmClientBufferIntegration(Display *display) ShmClientBufferIntegration::ShmClientBufferIntegration(Display *display)
: QObject(display) : QObject(display)
, d(new ShmClientBufferIntegrationPrivate(display, this))
{
}
ShmClientBufferIntegration::~ShmClientBufferIntegration()
{ {
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
wl_display_add_shm_format(*display, WL_SHM_FORMAT_ARGB2101010);
wl_display_add_shm_format(*display, WL_SHM_FORMAT_XRGB2101010);
wl_display_add_shm_format(*display, WL_SHM_FORMAT_ABGR2101010);
wl_display_add_shm_format(*display, WL_SHM_FORMAT_XBGR2101010);
wl_display_add_shm_format(*display, WL_SHM_FORMAT_ABGR16161616);
wl_display_add_shm_format(*display, WL_SHM_FORMAT_XBGR16161616);
#endif
wl_display_init_shm(*display);
} }
} // namespace KWaylandServer } // namespace KWaylandServer

View file

@ -6,50 +6,30 @@
#pragma once #pragma once
#include "core/graphicsbuffer.h" #include "kwin_export.h"
struct wl_resource; #include <QObject>
namespace KWaylandServer namespace KWaylandServer
{ {
class Display; class Display;
class ShmClientBufferPrivate; class ShmClientBufferIntegrationPrivate;
/** /**
* The ShmClientBuffer class represents a wl_shm_buffer client buffer. * The ShmClientBufferIntegration class provides support for shared memory client buffers.
*
* The buffer's data can be accessed using the data() function. Note that it is not allowed
* to access data of several shared memory buffers simultaneously.
*/ */
class KWIN_EXPORT ShmClientBuffer : public KWin::GraphicsBuffer class KWIN_EXPORT ShmClientBufferIntegration : public QObject
{
Q_OBJECT
public:
explicit ShmClientBuffer(wl_resource *resource);
~ShmClientBuffer() override;
QImage data() const;
QSize size() const override;
bool hasAlphaChannel() const override;
static ShmClientBuffer *get(wl_resource *resource);
private:
std::unique_ptr<ShmClientBufferPrivate> d;
};
/**
* The ShmClientBufferIntegration class provides support for wl_shm_buffer buffers.
*/
class ShmClientBufferIntegration : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit ShmClientBufferIntegration(Display *display); explicit ShmClientBufferIntegration(Display *display);
~ShmClientBufferIntegration() override;
private:
friend class ShmClientBufferIntegrationPrivate;
std::unique_ptr<ShmClientBufferIntegrationPrivate> d;
}; };
} // namespace KWaylandServer } // namespace KWaylandServer

View file

@ -0,0 +1,79 @@
/*
SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include "core/graphicsbuffer.h"
#include "wayland/shmclientbuffer.h"
#include "utils/filedescriptor.h"
#include "utils/memorymap.h"
#include "qwayland-server-wayland.h"
namespace KWaylandServer
{
class ShmClientBufferIntegrationPrivate : public QtWaylandServer::wl_shm
{
public:
ShmClientBufferIntegrationPrivate(Display *display, ShmClientBufferIntegration *q);
ShmClientBufferIntegration *q;
protected:
void shm_bind_resource(Resource *resource) override;
void shm_create_pool(Resource *resource, uint32_t id, int32_t fd, int32_t size) override;
};
class ShmPool : public QtWaylandServer::wl_shm_pool
{
public:
ShmPool(ShmClientBufferIntegration *integration, wl_client *client, int id, uint32_t version, KWin::FileDescriptor &&fd, KWin::MemoryMap &&mapping);
void ref();
void unref();
ShmClientBufferIntegration *integration;
KWin::MemoryMap mapping;
KWin::FileDescriptor fd;
int refCount = 1;
bool sigbusImpossible = false;
protected:
void shm_pool_destroy_resource(Resource *resource) override;
void shm_pool_create_buffer(Resource *resource, uint32_t id, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format) override;
void shm_pool_destroy(Resource *resource) override;
void shm_pool_resize(Resource *resource, int32_t size) override;
};
class KWIN_EXPORT ShmClientBuffer : public KWin::GraphicsBuffer
{
Q_OBJECT
public:
ShmClientBuffer(ShmPool *pool, KWin::ShmAttributes attributes, wl_client *client, uint32_t id);
~ShmClientBuffer() override;
void *map() override;
void unmap() override;
QSize size() const override;
bool hasAlphaChannel() const override;
const KWin::ShmAttributes *shmAttributes() const override;
static ShmClientBuffer *get(wl_resource *resource);
private:
static void buffer_destroy_resource(wl_resource *resource);
static void buffer_destroy(wl_client *client, wl_resource *resource);
static const struct wl_buffer_interface implementation;
wl_resource *m_resource = nullptr;
ShmPool *m_shmPool;
KWin::ShmAttributes m_shmAttributes;
};
} // namespace KWaylandServer

View file

@ -3,6 +3,8 @@
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/ */
#include "core/graphicsbufferview.h"
#include "../compositor_interface.h" #include "../compositor_interface.h"
#include "../datadevicemanager_interface.h" #include "../datadevicemanager_interface.h"
#include "../display.h" #include "../display.h"
@ -10,7 +12,6 @@
#include "../output_interface.h" #include "../output_interface.h"
#include "../pointer_interface.h" #include "../pointer_interface.h"
#include "../seat_interface.h" #include "../seat_interface.h"
#include "../shmclientbuffer.h"
#include "../xdgshell_interface.h" #include "../xdgshell_interface.h"
#include "fakeoutput.h" #include "fakeoutput.h"
@ -151,9 +152,9 @@ void CompositorWindow::paintEvent(QPaintEvent *event)
if (!surface || !surface->isMapped()) { if (!surface || !surface->isMapped()) {
continue; continue;
} }
auto clientBuffer = qobject_cast<KWaylandServer::ShmClientBuffer *>(surface->buffer()); KWin::GraphicsBufferView view(surface->buffer());
if (clientBuffer) { if (view.image()) {
p.drawImage(QPoint(0, 0), clientBuffer->data()); p.drawImage(QPoint(0, 0), *view.image());
} }
surface->frameRendered(QDateTime::currentMSecsSinceEpoch()); surface->frameRendered(QDateTime::currentMSecsSinceEpoch());
} }