From 6f3cb932dfccf86c1ee9b1b5e2b56de7435638c6 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Thu, 25 May 2023 14:15:57 +0300 Subject: [PATCH] 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. --- src/debug_console.cpp | 10 +- .../opengl/basiceglsurfacetexture_wayland.cpp | 30 +- .../opengl/basiceglsurfacetexture_wayland.h | 9 +- .../qpaintersurfacetexture_wayland.cpp | 15 +- src/shadow.cpp | 10 +- src/wayland/autotests/client/test_shadow.cpp | 10 +- .../autotests/client/test_wayland_seat.cpp | 2 - .../autotests/client/test_wayland_surface.cpp | 140 ++---- src/wayland/display.cpp | 2 +- src/wayland/shmclientbuffer.cpp | 399 +++++++++++------- src/wayland/shmclientbuffer.h | 40 +- src/wayland/shmclientbuffer_p.h | 79 ++++ src/wayland/tests/renderingservertest.cpp | 9 +- 13 files changed, 422 insertions(+), 333 deletions(-) create mode 100644 src/wayland/shmclientbuffer_p.h diff --git a/src/debug_console.cpp b/src/debug_console.cpp index e273963ec8..8931b20dd2 100644 --- a/src/debug_console.cpp +++ b/src/debug_console.cpp @@ -8,6 +8,7 @@ */ #include "debug_console.h" #include "composite.h" +#include "core/graphicsbufferview.h" #include "core/inputdevice.h" #include "input_event.h" #include "internalwindow.h" @@ -25,7 +26,6 @@ #include "wayland/display.h" #include "wayland/primaryselectionsource_v1_interface.h" #include "wayland/seat_interface.h" -#include "wayland/shmclientbuffer.h" #include "wayland/subcompositor_interface.h" #include "wayland/surface_interface.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) { return QStringLiteral("%1 (%2) - %3").arg(surface->client()->executablePath()).arg(surface->client()->processId()).arg(surface->id()); } else if (role == Qt::DecorationRole) { - if (auto buffer = qobject_cast(surface->buffer())) { - return buffer->data().scaled(QSize(64, 64), Qt::KeepAspectRatio); + if (surface->buffer()) { + const GraphicsBufferView view(surface->buffer()); + if (const QImage *image = view.image()) { + return image->scaled(QSize(64, 64), Qt::KeepAspectRatio); + } } + return QImage(); } } return QVariant(); diff --git a/src/platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.cpp b/src/platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.cpp index 1f6bea097f..cf7a3aacf3 100644 --- a/src/platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.cpp +++ b/src/platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.cpp @@ -5,11 +5,11 @@ */ #include "platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h" +#include "core/graphicsbufferview.h" #include "libkwineffects/kwingltexture.h" #include "platformsupport/scenes/opengl/abstract_egl_backend.h" #include "scene/surfaceitem_wayland.h" #include "utils/common.h" -#include "wayland/shmclientbuffer.h" #include @@ -36,8 +36,8 @@ bool BasicEGLSurfaceTextureWayland::create() { if (m_pixmap->buffer()->dmabufAttributes()) { return loadDmabufTexture(m_pixmap->buffer()); - } else if (auto buffer = qobject_cast(m_pixmap->buffer())) { - return loadShmTexture(buffer); + } else if (m_pixmap->buffer()->shmAttributes()) { + return loadShmTexture(m_pixmap->buffer()); } else { return false; } @@ -53,17 +53,23 @@ void BasicEGLSurfaceTextureWayland::update(const QRegion ®ion) { if (m_pixmap->buffer()->dmabufAttributes()) { updateDmabufTexture(m_pixmap->buffer()); - } else if (auto buffer = qobject_cast(m_pixmap->buffer())) { - updateShmTexture(buffer, region); + } else if (m_pixmap->buffer()->shmAttributes()) { + updateShmTexture(m_pixmap->buffer(), region); } } -bool BasicEGLSurfaceTextureWayland::loadShmTexture(KWaylandServer::ShmClientBuffer *buffer) +bool BasicEGLSurfaceTextureWayland::loadShmTexture(GraphicsBuffer *buffer) { - m_texture = GLTexture::upload(buffer->data()); - if (!m_texture) { + const GraphicsBufferView view(buffer); + if (Q_UNLIKELY(!view.image())) { return false; } + + m_texture = GLTexture::upload(*view.image()); + if (Q_UNLIKELY(!m_texture)) { + return false; + } + m_texture->setFilter(GL_LINEAR); m_texture->setWrapMode(GL_CLAMP_TO_EDGE); m_texture->setContentTransform(TextureTransform::MirrorY); @@ -72,7 +78,7 @@ bool BasicEGLSurfaceTextureWayland::loadShmTexture(KWaylandServer::ShmClientBuff return true; } -void BasicEGLSurfaceTextureWayland::updateShmTexture(KWaylandServer::ShmClientBuffer *buffer, const QRegion ®ion) +void BasicEGLSurfaceTextureWayland::updateShmTexture(GraphicsBuffer *buffer, const QRegion ®ion) { if (Q_UNLIKELY(m_bufferType != BufferType::Shm)) { destroy(); @@ -80,14 +86,14 @@ void BasicEGLSurfaceTextureWayland::updateShmTexture(KWaylandServer::ShmClientBu return; } - const QImage &image = buffer->data(); - if (Q_UNLIKELY(image.isNull())) { + const GraphicsBufferView view(buffer); + if (Q_UNLIKELY(!view.image())) { return; } const QRegion damage = mapRegion(m_pixmap->item()->surfaceToBufferMatrix(), region); for (const QRect &rect : damage) { - m_texture->update(image, rect.topLeft(), rect); + m_texture->update(*view.image(), rect.topLeft(), rect); } } diff --git a/src/platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h b/src/platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h index 2897f9def4..63a8e18de2 100644 --- a/src/platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h +++ b/src/platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h @@ -8,11 +8,6 @@ #include "openglsurfacetexture_wayland.h" -namespace KWaylandServer -{ -class ShmClientBuffer; -} - namespace KWin { @@ -31,8 +26,8 @@ public: void update(const QRegion ®ion) override; private: - bool loadShmTexture(KWaylandServer::ShmClientBuffer *buffer); - void updateShmTexture(KWaylandServer::ShmClientBuffer *buffer, const QRegion ®ion); + bool loadShmTexture(GraphicsBuffer *buffer); + void updateShmTexture(GraphicsBuffer *buffer, const QRegion ®ion); bool loadDmabufTexture(GraphicsBuffer *buffer); void updateDmabufTexture(GraphicsBuffer *buffer); void destroy(); diff --git a/src/platformsupport/scenes/qpainter/qpaintersurfacetexture_wayland.cpp b/src/platformsupport/scenes/qpainter/qpaintersurfacetexture_wayland.cpp index fd395353e8..c7452417c7 100644 --- a/src/platformsupport/scenes/qpainter/qpaintersurfacetexture_wayland.cpp +++ b/src/platformsupport/scenes/qpainter/qpaintersurfacetexture_wayland.cpp @@ -5,9 +5,9 @@ */ #include "qpaintersurfacetexture_wayland.h" +#include "core/graphicsbufferview.h" #include "scene/surfaceitem_wayland.h" #include "utils/common.h" -#include "wayland/shmclientbuffer.h" #include "wayland/surface_interface.h" #include @@ -24,23 +24,22 @@ QPainterSurfaceTextureWayland::QPainterSurfaceTextureWayland(QPainterBackend *ba bool QPainterSurfaceTextureWayland::create() { - auto buffer = qobject_cast(m_pixmap->buffer()); - if (Q_LIKELY(buffer)) { + const GraphicsBufferView view(m_pixmap->buffer()); + if (Q_LIKELY(view.image())) { // 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. - m_image = buffer->data().copy(); + m_image = view.image()->copy(); } return !m_image.isNull(); } void QPainterSurfaceTextureWayland::update(const QRegion ®ion) { - auto buffer = qobject_cast(m_pixmap->buffer()); - if (Q_UNLIKELY(!buffer)) { + const GraphicsBufferView view(m_pixmap->buffer()); + if (Q_UNLIKELY(!view.image())) { return; } - const QImage image = buffer->data(); const QRegion dirtyRegion = mapRegion(m_pixmap->item()->surfaceToBufferMatrix(), region); QPainter painter(&m_image); painter.setCompositionMode(QPainter::CompositionMode_Source); @@ -48,7 +47,7 @@ void QPainterSurfaceTextureWayland::update(const QRegion ®ion) // 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. for (const QRect &rect : dirtyRegion) { - painter.drawImage(rect, image, rect); + painter.drawImage(rect, *view.image(), rect); } } diff --git a/src/shadow.cpp b/src/shadow.cpp index 5bbfbfda12..54f8711c93 100644 --- a/src/shadow.cpp +++ b/src/shadow.cpp @@ -10,9 +10,9 @@ #include "shadow.h" // kwin #include "atoms.h" +#include "core/graphicsbufferview.h" #include "internalwindow.h" #include "wayland/shadow_interface.h" -#include "wayland/shmclientbuffer.h" #include "wayland/surface_interface.h" #include "wayland_server.h" #include "x11window.h" @@ -201,9 +201,11 @@ bool Shadow::init(KDecoration2::Decoration *decoration) static QImage shadowTileForBuffer(GraphicsBuffer *buffer) { - auto shmBuffer = qobject_cast(buffer); - if (shmBuffer) { - return shmBuffer->data().copy(); + if (buffer) { + const GraphicsBufferView view(buffer); + if (const QImage *image = view.image()) { + return image->copy(); + } } return QImage(); } diff --git a/src/wayland/autotests/client/test_shadow.cpp b/src/wayland/autotests/client/test_shadow.cpp index 42ac39d30f..52ff98b9f7 100644 --- a/src/wayland/autotests/client/test_shadow.cpp +++ b/src/wayland/autotests/client/test_shadow.cpp @@ -14,10 +14,10 @@ #include "KWayland/Client/shm_pool.h" #include "KWayland/Client/surface.h" // server +#include "core/graphicsbufferview.h" #include "wayland/compositor_interface.h" #include "wayland/display.h" #include "wayland/shadow_interface.h" -#include "wayland/shmclientbuffer.h" using namespace KWaylandServer; @@ -168,9 +168,11 @@ void ShadowTest::testCreateShadow() static QImage bufferToImage(KWin::GraphicsBuffer *clientBuffer) { - auto shmBuffer = qobject_cast(clientBuffer); - if (shmBuffer) { - return shmBuffer->data(); + if (clientBuffer) { + KWin::GraphicsBufferView view(clientBuffer); + if (QImage *image = view.image()) { + return image->copy(); + } } return QImage(); } diff --git a/src/wayland/autotests/client/test_wayland_seat.cpp b/src/wayland/autotests/client/test_wayland_seat.cpp index 880ba22c7a..c0ec3b30f9 100644 --- a/src/wayland/autotests/client/test_wayland_seat.cpp +++ b/src/wayland/autotests/client/test_wayland_seat.cpp @@ -15,7 +15,6 @@ #include "wayland/pointergestures_v1_interface.h" #include "wayland/relativepointer_v1_interface.h" #include "wayland/seat_interface.h" -#include "wayland/shmclientbuffer.h" #include "wayland/subcompositor_interface.h" #include "wayland/surface_interface.h" @@ -1351,7 +1350,6 @@ void TestWaylandSeat::testCursor() QCOMPARE(cursorChangedSpy.count(), 3); QCOMPARE(cursor->hotspot(), QPoint(1, 2)); QVERIFY(cursor->surface()); - QCOMPARE(qobject_cast(cursor->surface()->buffer())->data(), img); p->hideCursor(); QVERIFY(cursorChangedSpy.wait()); diff --git a/src/wayland/autotests/client/test_wayland_surface.cpp b/src/wayland/autotests/client/test_wayland_surface.cpp index 2e5a071f16..ff2245359c 100644 --- a/src/wayland/autotests/client/test_wayland_surface.cpp +++ b/src/wayland/autotests/client/test_wayland_surface.cpp @@ -8,11 +8,12 @@ #include #include // KWin +#include "core/graphicsbuffer.h" +#include "core/graphicsbufferview.h" #include "wayland/compositor_interface.h" #include "wayland/display.h" #include "wayland/idleinhibit_v1_interface.h" #include "wayland/output_interface.h" -#include "wayland/shmclientbuffer.h" #include "wayland/surface_interface.h" #include "KWayland/Client/compositor.h" @@ -43,7 +44,6 @@ private Q_SLOTS: void testDamage(); void testFrameCallback(); void testAttachBuffer(); - void testMultipleSurfaces(); void testOpaque(); void testInput(); void testScale(); @@ -397,11 +397,13 @@ void TestWaylandSurface::testAttachBuffer() // now the ServerSurface should have the black image attached as a buffer KWin::GraphicsBuffer *buffer = serverSurface->buffer(); buffer->ref(); - auto shmBuffer = qobject_cast(buffer); - QVERIFY(shmBuffer); - QCOMPARE(shmBuffer->data(), black); - QCOMPARE(shmBuffer->data().format(), QImage::Format_RGB32); - QCOMPARE(shmBuffer->size(), QSize(24, 24)); + { + KWin::GraphicsBufferView view(buffer); + QVERIFY(view.image()); + QCOMPARE(*view.image(), black); + QCOMPARE(view.image()->format(), QImage::Format_RGB32); + QCOMPARE(view.image()->size(), QSize(24, 24)); + } // render another frame s->attachBuffer(redBuffer); @@ -413,14 +415,16 @@ void TestWaylandSurface::testAttachBuffer() QVERIFY(unmappedSpy.isEmpty()); KWin::GraphicsBuffer *buffer2 = serverSurface->buffer(); buffer2->ref(); - auto shmBuffer2 = qobject_cast(buffer2); - QVERIFY(shmBuffer2); - QCOMPARE(shmBuffer2->data().format(), QImage::Format_ARGB32_Premultiplied); - QCOMPARE(shmBuffer2->size(), QSize(24, 24)); - for (int i = 0; i < 24; ++i) { - for (int j = 0; j < 24; ++j) { - // it's premultiplied in the format - QCOMPARE(shmBuffer2->data().pixel(i, j), qRgba(128, 0, 0, 128)); + { + KWin::GraphicsBufferView view(buffer2); + QVERIFY(view.image()); + QCOMPARE(view.image()->format(), QImage::Format_ARGB32_Premultiplied); + QCOMPARE(view.image()->size(), QSize(24, 24)); + for (int i = 0; i < 24; ++i) { + for (int j = 0; j < 24; ++j) { + // it's premultiplied in the format + QCOMPARE(view.image()->pixel(i, j), qRgba(128, 0, 0, 128)); + } } } buffer2->unref(); @@ -448,14 +452,16 @@ void TestWaylandSurface::testAttachBuffer() KWin::GraphicsBuffer *buffer3 = serverSurface->buffer(); buffer3->ref(); - auto shmBuffer3 = qobject_cast(buffer3); - QVERIFY(shmBuffer3); - QCOMPARE(shmBuffer3->data().format(), QImage::Format_ARGB32_Premultiplied); - QCOMPARE(shmBuffer3->size(), QSize(24, 24)); - for (int i = 0; i < 24; ++i) { - for (int j = 0; j < 24; ++j) { - // it's premultiplied in the format - QCOMPARE(shmBuffer3->data().pixel(i, j), qRgba(0, 0, 128, 128)); + { + KWin::GraphicsBufferView view(buffer3); + QVERIFY(view.image()); + QCOMPARE(view.image()->format(), QImage::Format_ARGB32_Premultiplied); + QCOMPARE(view.image()->size(), QSize(24, 24)); + for (int i = 0; i < 24; ++i) { + for (int j = 0; j < 24; ++j) { + // it's premultiplied in the format + QCOMPARE(view.image()->pixel(i, j), qRgba(0, 0, 128, 128)); + } } } buffer3->unref(); @@ -495,94 +501,6 @@ void TestWaylandSurface::testAttachBuffer() buffer->unref(); } -void TestWaylandSurface::testMultipleSurfaces() -{ - using namespace KWaylandServer; - KWayland::Client::Registry registry; - registry.setEventQueue(m_queue); - QSignalSpy shmSpy(®istry, &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(), shmSpy.first().last().value())); - pool2.setup(registry.bindShm(shmSpy.first().first().value(), shmSpy.first().last().value())); - QVERIFY(pool1.isValid()); - QVERIFY(pool2.isValid()); - - // create the surfaces - QSignalSpy serverSurfaceCreated(m_compositorInterface, &KWaylandServer::CompositorInterface::surfaceCreated); - std::unique_ptr s1(m_compositor->createSurface()); - QVERIFY(serverSurfaceCreated.wait()); - SurfaceInterface *serverSurface1 = serverSurfaceCreated.first().first().value(); - QVERIFY(serverSurface1); - // second surface - std::unique_ptr s2(m_compositor->createSurface()); - QVERIFY(serverSurfaceCreated.wait()); - SurfaceInterface *serverSurface2 = serverSurfaceCreated.last().first().value(); - 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(buffer1)->data(); - QCOMPARE(buffer1Data, black); - // accessing the same buffer is OK - QImage buffer1Data2 = qobject_cast(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(buffer2)->data(); - QCOMPARE(buffer2Data, red); - - // while buffer2 is accessed we cannot access buffer1 - buffer1Data = qobject_cast(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(buffer1)->data(); - QVERIFY(!buffer1Data.isNull()); - QCOMPARE(buffer1Data, black); -} - void TestWaylandSurface::testOpaque() { using namespace KWaylandServer; diff --git a/src/wayland/display.cpp b/src/wayland/display.cpp index a58243a72b..41de7c71be 100644 --- a/src/wayland/display.cpp +++ b/src/wayland/display.cpp @@ -8,7 +8,7 @@ #include "display_p.h" #include "linuxdmabufv1clientbuffer_p.h" #include "output_interface.h" -#include "shmclientbuffer.h" +#include "shmclientbuffer_p.h" #include "utils/common.h" #include diff --git a/src/wayland/shmclientbuffer.cpp b/src/wayland/shmclientbuffer.cpp index 720d3686a4..b64486ce19 100644 --- a/src/wayland/shmclientbuffer.cpp +++ b/src/wayland/shmclientbuffer.cpp @@ -4,207 +4,312 @@ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ -#include "shmclientbuffer.h" -#include "display.h" +#include "config-kwin.h" -#include -#include +#include "wayland/shmclientbuffer.h" +#include "wayland/display.h" +#include "wayland/shmclientbuffer_p.h" -#include -#include +#include +#include +#include +#include +#include +#include + +using namespace KWin; namespace KWaylandServer { -static const ShmClientBuffer *s_accessedBuffer = nullptr; -static int s_accessCounter = 0; -static QHash s_buffers; -class ShmClientBufferPrivate -{ -public: - ShmClientBufferPrivate(ShmClientBuffer *q); +static constexpr int s_version = 1; - static void buffer_destroy_callback(wl_listener *listener, void *data); - - ShmClientBuffer *q; - wl_resource *resource = nullptr; - QImage::Format format = QImage::Format_Invalid; - uint32_t width = 0; - uint32_t height = 0; - bool hasAlphaChannel = false; - QImage savedData; - - struct DestroyListener - { - wl_listener listener; - ShmClientBufferPrivate *receiver; - }; - DestroyListener destroyListener; +static constexpr uint32_t s_formats[] = { + WL_SHM_FORMAT_ARGB8888, + WL_SHM_FORMAT_XRGB8888, +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + WL_SHM_FORMAT_ARGB2101010, + WL_SHM_FORMAT_XRGB2101010, + WL_SHM_FORMAT_ABGR2101010, + WL_SHM_FORMAT_XBGR2101010, + WL_SHM_FORMAT_ABGR16161616, + WL_SHM_FORMAT_XBGR16161616, +#endif }; -ShmClientBufferPrivate::ShmClientBufferPrivate(ShmClientBuffer *q) - : q(q) +class ShmSigbusData { -} +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(poolHandle)); -} - -void ShmClientBufferPrivate::buffer_destroy_callback(wl_listener *listener, void *data) -{ - auto bufferPrivate = reinterpret_cast(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(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: + switch (shmFormat) { case WL_SHM_FORMAT_ARGB8888: - return true; - case WL_SHM_FORMAT_XBGR16161616: - case WL_SHM_FORMAT_XBGR2101010: - case WL_SHM_FORMAT_XRGB2101010: + return DRM_FORMAT_ARGB8888; case WL_SHM_FORMAT_XRGB8888: + return DRM_FORMAT_XRGB8888; 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 Q_BYTE_ORDER == Q_LITTLE_ENDIAN - case WL_SHM_FORMAT_ABGR16161616: - return QImage::Format_RGBA64_Premultiplied; - case WL_SHM_FORMAT_XBGR16161616: - return QImage::Format_RGBX64; - 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; +#if HAVE_MEMFD + const int seals = fcntl(this->fd.get(), F_GET_SEALS); + if (seals != -1) { + struct stat statbuf; + if ((seals & F_SEAL_SHRINK) && fstat(this->fd.get(), &statbuf) >= 0) { + sigbusImpossible = statbuf.st_size >= this->mapping.size(); + } + } #endif - case WL_SHM_FORMAT_ARGB8888: - return QImage::Format_ARGB32_Premultiplied; - case WL_SHM_FORMAT_XRGB8888: - return QImage::Format_RGB32; - default: - return QImage::Format_Invalid; +} + +void ShmPool::ref() +{ + ++refCount; +} + +void ShmPool::unref() +{ + --refCount; + if (refCount == 0) { + delete this; } } -ShmClientBuffer::ShmClientBuffer(wl_resource *resource) - : d(std::make_unique(this)) +void ShmPool::shm_pool_destroy_resource(Resource *resource) { - wl_shm_buffer *buffer = wl_shm_buffer_get(resource); - 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)); + unref(); +} - d->destroyListener.receiver = d.get(); - d->destroyListener.listener.notify = ShmClientBufferPrivate::buffer_destroy_callback; - wl_resource_add_destroy_listener(resource, &d->destroyListener.listener); +void ShmPool::shm_pool_destroy(Resource *resource) +{ + 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]() { - 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() { + m_shmPool->unref(); } QSize ShmClientBuffer::size() const { - return QSize(d->width, d->height); + return m_shmAttributes.size; } 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"); - s_accessCounter--; - if (s_accessCounter == 0) { - s_accessedBuffer = nullptr; - } - wl_shm_buffer_end_access(static_cast(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(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; + return &m_shmAttributes; } ShmClientBuffer *ShmClientBuffer::get(wl_resource *resource) { - if (auto buffer = s_buffers.value(resource)) { - return buffer; + if (wl_resource_instance_of(resource, &wl_buffer_interface, &implementation)) { + return static_cast(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; } +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(info->si_addr); + const uchar *mappingStart = static_cast(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(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) : 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 diff --git a/src/wayland/shmclientbuffer.h b/src/wayland/shmclientbuffer.h index 5c9dbd36b1..288fe7cc07 100644 --- a/src/wayland/shmclientbuffer.h +++ b/src/wayland/shmclientbuffer.h @@ -6,50 +6,30 @@ #pragma once -#include "core/graphicsbuffer.h" +#include "kwin_export.h" -struct wl_resource; +#include namespace KWaylandServer { class Display; -class ShmClientBufferPrivate; +class ShmClientBufferIntegrationPrivate; /** - * The ShmClientBuffer class represents a wl_shm_buffer client buffer. - * - * 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. + * The ShmClientBufferIntegration class provides support for shared memory client buffers. */ -class KWIN_EXPORT ShmClientBuffer : public KWin::GraphicsBuffer -{ - 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 d; -}; - -/** - * The ShmClientBufferIntegration class provides support for wl_shm_buffer buffers. - */ -class ShmClientBufferIntegration : public QObject +class KWIN_EXPORT ShmClientBufferIntegration : public QObject { Q_OBJECT public: explicit ShmClientBufferIntegration(Display *display); + ~ShmClientBufferIntegration() override; + +private: + friend class ShmClientBufferIntegrationPrivate; + std::unique_ptr d; }; } // namespace KWaylandServer diff --git a/src/wayland/shmclientbuffer_p.h b/src/wayland/shmclientbuffer_p.h new file mode 100644 index 0000000000..da941398a3 --- /dev/null +++ b/src/wayland/shmclientbuffer_p.h @@ -0,0 +1,79 @@ +/* + SPDX-FileCopyrightText: 2023 Vlad Zahorodnii + + 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 diff --git a/src/wayland/tests/renderingservertest.cpp b/src/wayland/tests/renderingservertest.cpp index b1b0ce544e..3f1306d707 100644 --- a/src/wayland/tests/renderingservertest.cpp +++ b/src/wayland/tests/renderingservertest.cpp @@ -3,6 +3,8 @@ 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 "../datadevicemanager_interface.h" #include "../display.h" @@ -10,7 +12,6 @@ #include "../output_interface.h" #include "../pointer_interface.h" #include "../seat_interface.h" -#include "../shmclientbuffer.h" #include "../xdgshell_interface.h" #include "fakeoutput.h" @@ -151,9 +152,9 @@ void CompositorWindow::paintEvent(QPaintEvent *event) if (!surface || !surface->isMapped()) { continue; } - auto clientBuffer = qobject_cast(surface->buffer()); - if (clientBuffer) { - p.drawImage(QPoint(0, 0), clientBuffer->data()); + KWin::GraphicsBufferView view(surface->buffer()); + if (view.image()) { + p.drawImage(QPoint(0, 0), *view.image()); } surface->frameRendered(QDateTime::currentMSecsSinceEpoch()); }