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()); }