[plasma/kwin] src: scene: use linux-dmabuf v6 to improve multi GPU performance

Xaver Hugl <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 2ca7a19a74628039933f4de63aa61a199fed5743 by Xaver Hugl.
Committed on 28/07/2026 at 10:59.
Pushed by zamundaaa into branch 'master'.

scene: use linux-dmabuf v6 to improve multi GPU performance

Instead of the client doing multi GPU copies and providing a buffer for the
main device, the client provides the original buffer on the GPU it's actually
doing its rendering, and hints the device we should import the buffer on.
Whenever KWin needs to render on a different GPU than the one the buffer resides
on, it now does those multi GPU copies explicitly.

The main benefit of doing this right now is that when KWin does direct scanout,
it can use the original buffer without doing any copies, with can be hugely
beneficial to performance. On my eGPU setup, a simple fullscreen
eglgears_wayland with a copy to the integrated GPU and back to the external
GPU reaches only about 40fps, and it easily reaches 120Hz with this commit and
the matching implementation in Mesa.

In the future, we should also do compositing on the GPU the display is actually
connected to, but this is a big step in the right direction.

M  +9    -7    src/backends/drm/drm_gpu.cpp
M  +1    -1    src/core/renderbackend.cpp
M  +1    -1    src/core/renderbackend.h
M  +23   -4    src/multigpuswapchain.cpp
M  +6    -2    src/multigpuswapchain.h
M  +70   -15   src/opengl/eglbackend.cpp
M  +2    -1    src/opengl/eglbackend.h
M  +56   -9    src/scene/opengl/texture.cpp
M  +5    -1    src/scene/opengl/texture.h
M  +41   -4    src/wayland/linuxdmabufv1clientbuffer.cpp
M  +2    -1    src/wayland/linuxdmabufv1clientbuffer_p.h

https://invent.kde.org/plasma/kwin/-/commit/2ca7a19a74628039933f4de63aa61a199fed5743

diff --git a/src/backends/drm/drm_gpu.cpp b/src/backends/drm/drm_gpu.cpp
index e54e3767c9e..c186e1ac8f2 100644
--- a/src/backends/drm/drm_gpu.cpp
+++ b/src/backends/drm/drm_gpu.cpp
@@ -125,13 +125,15 @@ DrmGpu::DrmGpu(DrmBackend *backend, int fd, std::unique_ptr<DrmDevice> &&device)
     // Make sure the render device list is up to date, otherwise we may first
     // select software rendering and only later switch to the render node
     GpuManager::self()->scanForRenderDevices();
-    // fallback for software rendering
-    auto fallbackDevice = RenderDevice::open(m_drmDevice->path(), m_fd);
-    m_kmsRenderDevice = fallbackDevice.get();
-    if (!fallbackDevice) {
-        qCWarning(KWIN_DRM, "Opening render device for %s failed", qPrintable(m_drmDevice->path()));
-    } else {
-        GpuManager::self()->addDevice(std::move(fallbackDevice));
+    if (!GpuManager::self()->softwareDevice()) {
+        // fallback for software rendering
+        auto fallbackDevice = RenderDevice::open(m_drmDevice->path(), m_fd);
+        m_kmsRenderDevice = fallbackDevice.get();
+        if (!fallbackDevice) {
+            qCWarning(KWIN_DRM, "Opening render device for %s failed", qPrintable(m_drmDevice->path()));
+        } else {
+            GpuManager::self()->addDevice(std::move(fallbackDevice));
+        }
     }
     updateRenderDevice();
     connect(GpuManager::self(), &GpuManager::renderDeviceAdded, this, &DrmGpu::updateRenderDevice);
diff --git a/src/core/renderbackend.cpp b/src/core/renderbackend.cpp
index 7fd28fd88de..aa1015743a2 100644
--- a/src/core/renderbackend.cpp
+++ b/src/core/renderbackend.cpp
@@ -178,7 +178,7 @@ RenderDevice *RenderBackend::renderDevice() const
     return nullptr;
 }
 
-bool RenderBackend::testImportBuffer(GraphicsBuffer *buffer)
+bool RenderBackend::testImportBuffer(GraphicsBuffer *buffer, dev_t targetDevice)
 {
     return false;
 }
diff --git a/src/core/renderbackend.h b/src/core/renderbackend.h
index ce2e1a2f2ad..de9116086ce 100644
--- a/src/core/renderbackend.h
+++ b/src/core/renderbackend.h
@@ -133,7 +133,7 @@ public:
 
     virtual RenderDevice *renderDevice() const;
 
-    virtual bool testImportBuffer(GraphicsBuffer *buffer);
+    virtual bool testImportBuffer(GraphicsBuffer *buffer, dev_t targetDevice);
     virtual FormatModifierMap supportedFormats() const;
 };
 
diff --git a/src/multigpuswapchain.cpp b/src/multigpuswapchain.cpp
index 330feeaea35..118d54bcd4e 100644
--- a/src/multigpuswapchain.cpp
+++ b/src/multigpuswapchain.cpp
@@ -89,7 +89,7 @@ std::unique_ptr<MultiGpuSwapchain> MultiGpuSwapchain::create(RenderDevice *copyD
             }
             auto swapchain = VulkanSwapchain::create(copyDevice->vulkanDevice(), targetDevice->allocator(), options);
             if (swapchain) {
-                return std::make_unique<MultiGpuSwapchain>(copyDevice, targetDevice, std::move(swapchain));
+                return std::make_unique<MultiGpuSwapchain>(copyDevice, targetDevice, std::move(swapchain), format);
             }
         }
     }
@@ -117,13 +117,14 @@ std::unique_ptr<MultiGpuSwapchain> MultiGpuSwapchain::create(RenderDevice *copyD
         };
         auto eglSwapchain = EglSwapchain::create(copyDevice->allocator(), context.get(), options);
         if (eglSwapchain) {
-            return std::make_unique<MultiGpuSwapchain>(copyDevice, targetDevice, context, std::move(eglSwapchain));
+            return std::make_unique<MultiGpuSwapchain>(copyDevice, targetDevice, context, std::move(eglSwapchain), format);
         }
     }
     return nullptr;
 }
 
-MultiGpuSwapchain::MultiGpuSwapchain(RenderDevice *copyDevice, DrmDevice *targetDevice, const std::shared_ptr<EglContext> &eglContext, std::shared_ptr<EglSwapchain> &&eglSwapchain)
+MultiGpuSwapchain::MultiGpuSwapchain(RenderDevice *copyDevice, DrmDevice *targetDevice, const std::shared_ptr<EglContext> &eglContext,
+                                     std::shared_ptr<EglSwapchain> &&eglSwapchain, uint32_t sourceFormat)
     : m_targetDevice(targetDevice)
     , m_copyDevice(copyDevice)
     , m_copyContext(eglContext)
@@ -131,17 +132,19 @@ MultiGpuSwapchain::MultiGpuSwapchain(RenderDevice *copyDevice, DrmDevice *target
     , m_format(m_eglSwapchain->format())
     , m_modifier(m_eglSwapchain->modifier())
     , m_size(m_eglSwapchain->size())
+    , m_sourceFormat(sourceFormat)
 {
     connect(GpuManager::self(), &GpuManager::renderDeviceRemoved, this, &MultiGpuSwapchain::handleDeviceRemoved);
 }
 
-MultiGpuSwapchain::MultiGpuSwapchain(RenderDevice *copyDevice, DrmDevice *targetDevice, std::unique_ptr<VulkanSwapchain> &&swapchain)
+MultiGpuSwapchain::MultiGpuSwapchain(RenderDevice *copyDevice, DrmDevice *targetDevice, std::unique_ptr<VulkanSwapchain> &&swapchain, uint32_t sourceFormat)
     : m_targetDevice(targetDevice)
     , m_copyDevice(copyDevice)
     , m_vulkanSwapchain(std::move(swapchain))
     , m_format(m_vulkanSwapchain->format())
     , m_modifier(m_vulkanSwapchain->modifier())
     , m_size(m_vulkanSwapchain->size())
+    , m_sourceFormat(sourceFormat)
 {
     connect(GpuManager::self(), &GpuManager::renderDeviceRemoved, this, &MultiGpuSwapchain::handleDeviceRemoved);
     connect(m_copyDevice->vulkanDevice(), &VulkanDevice::deviceLost, this, &MultiGpuSwapchain::handleGpuReset);
@@ -444,4 +447,20 @@ bool MultiGpuSwapchain::needsRecreation() const
     return m_needsRecreation;
 }
 
+bool MultiGpuSwapchain::isSuitableFor(GraphicsBuffer *buffer) const
+{
+    if (!m_copyDevice || !m_targetDevice || needsRecreation()) {
+        return false;
+    }
+    const auto attrs = buffer->dmabufAttributes();
+    if (!attrs || attrs->format != m_sourceFormat || buffer->size() != m_size) {
+        return false;
+    }
+    if (m_vulkanSwapchain) {
+        return m_copyDevice->vulkanDevice()->supportedFormats().containsFormat(attrs->format, attrs->modifier);
+    } else {
+        return m_copyDevice->eglDisplay()->allSupportedDrmFormats().containsFormat(attrs->format, attrs->modifier);
+    }
+}
+
 }
diff --git a/src/multigpuswapchain.h b/src/multigpuswapchain.h
index d1ef771e80f..00d258ce4fa 100644
--- a/src/multigpuswapchain.h
+++ b/src/multigpuswapchain.h
@@ -34,8 +34,9 @@ class KWIN_EXPORT MultiGpuSwapchain : public QObject
 {
     Q_OBJECT
 public:
-    explicit MultiGpuSwapchain(RenderDevice *copyDevice, DrmDevice *targetDevice, const std::shared_ptr<EglContext> &eglContext, std::shared_ptr<EglSwapchain> &&eglSwapchain);
-    explicit MultiGpuSwapchain(RenderDevice *copyDevice, DrmDevice *targetDevice, std::unique_ptr<VulkanSwapchain> &&swapchain);
+    explicit MultiGpuSwapchain(RenderDevice *copyDevice, DrmDevice *targetDevice, const std::shared_ptr<EglContext> &eglContext,
+                               std::shared_ptr<EglSwapchain> &&eglSwapchain, uint32_t sourceFormat);
+    explicit MultiGpuSwapchain(RenderDevice *copyDevice, DrmDevice *targetDevice, std::unique_ptr<VulkanSwapchain> &&swapchain, uint32_t sourceFormat);
     ~MultiGpuSwapchain() override;
 
     struct Ret
@@ -54,6 +55,8 @@ public:
     QSize size() const;
     bool needsRecreation() const;
 
+    bool isSuitableFor(GraphicsBuffer *buffer) const;
+
     /**
      * NOTE that the copyDevice needs to be chosen carefully. Importing a buffer to a given device
      * (even if just for rendering) causes the kernel to possibly migrate the buffer to that device.
@@ -83,6 +86,7 @@ private:
     const uint32_t m_format;
     const uint64_t m_modifier;
     const QSize m_size;
+    const uint32_t m_sourceFormat;
     bool m_needsRecreation = false;
 };
 }
diff --git a/src/opengl/eglbackend.cpp b/src/opengl/eglbackend.cpp
index 81172f89139..67d988c3a38 100644
--- a/src/opengl/eglbackend.cpp
+++ b/src/opengl/eglbackend.cpp
@@ -19,6 +19,7 @@
 #include "opengl/eglimagetexture.h"
 #include "opengl/eglutils_p.h"
 #include "utils/common.h"
+#include "vulkan/vulkan_device.h"
 #include "wayland/linux_drm_syncobj_v1.h"
 #include "wayland_server.h"
 
@@ -34,6 +35,8 @@ namespace KWin
 EglBackend::EglBackend(RenderDevice *device)
     : m_renderDevice(device)
 {
+    connect(GpuManager::s_self.get(), &GpuManager::renderDeviceAdded, this, &EglBackend::updateDmabufTranches);
+    connect(GpuManager::s_self.get(), &GpuManager::renderDeviceRemoved, this, &EglBackend::updateDmabufTranches);
 }
 
 CompositingType EglBackend::compositingType() const
@@ -100,10 +103,24 @@ void EglBackend::cleanup()
 
 void EglBackend::initWayland()
 {
-    auto filterFormats = [this](std::optional<uint32_t> bpc, bool withExternalOnlyYUV) {
+    updateDmabufTranches();
+    waylandServer()->setRenderBackend(this);
+}
+
+void EglBackend::updateDmabufTranches()
+{
+    enum class DeviceType {
+        EGL,
+        Vulkan,
+    };
+    auto filterFormats = [this](RenderDevice *device, std::optional<uint32_t> bpc, bool withExternalOnlyYUV, DeviceType type) {
         FormatModifierMap set;
-        const auto &allFormats = m_renderDevice->eglDisplay()->allSupportedDrmFormats();
-        const auto &nonExternalOnly = m_renderDevice->eglDisplay()->nonExternalOnlySupportedDrmFormats();
+        const auto &allFormats = type == DeviceType::Vulkan
+            ? device->vulkanDevice()->supportedFormats()
+            : device->eglDisplay()->allSupportedDrmFormats();
+        const auto &nonExternalOnly = type == DeviceType::Vulkan
+            ? device->vulkanDevice()->supportedFormats()
+            : device->eglDisplay()->nonExternalOnlySupportedDrmFormats();
         for (auto it = allFormats.constBegin(); it != allFormats.constEnd(); it++) {
             const auto info = FormatInfo::get(it.key());
             if (bpc && (!info || bpc != info->bitsPerColor)) {
@@ -127,6 +144,9 @@ void EglBackend::initWayland()
                 if (modifiers.empty()) {
                     break;
                 }
+                if (tranche.device != device->deviceId()) {
+                    continue;
+                }
                 const auto trancheModifiers = tranche.formatTable.value(it.key());
                 for (auto trancheModifier : trancheModifiers) {
                     modifiers.erase(trancheModifier);
@@ -150,27 +170,55 @@ void EglBackend::initWayland()
         return formats;
     };
 
+    m_tranches.clear();
+
+    // put the "main" device first, with EGL format+modifiers
     m_tranches.append({
         .device = m_renderDevice->deviceId(),
         .flags = LinuxDmaBufV1Feedback::TrancheFlag::Sampling,
-        .formatTable = filterFormats(10, false),
+        .formatTable = filterFormats(m_renderDevice, 10, false, DeviceType::EGL),
     });
     m_tranches.append({
         .device = m_renderDevice->deviceId(),
         .flags = LinuxDmaBufV1Feedback::TrancheFlag::Sampling,
-        .formatTable = filterFormats(8, false),
+        .formatTable = filterFormats(m_renderDevice, 8, false, DeviceType::EGL),
     });
     m_tranches.append({
         .device = m_renderDevice->deviceId(),
         .flags = LinuxDmaBufV1Feedback::TrancheFlag::Sampling,
-        .formatTable = includeShaderConversions(filterFormats(std::nullopt, true)),
+        .formatTable = includeShaderConversions(filterFormats(m_renderDevice, std::nullopt, true, DeviceType::EGL)),
     });
 
+    // Other GPUs come afterwards, in no particular order.
+    // Until the copy code can handle them, YUV formats are excluded from this
+    const auto &devices = GpuManager::s_self->renderDevices();
+    for (const auto &device : devices) {
+        if (device.get() == m_renderDevice) {
+            continue;
+        }
+        // If available, prefer Vulkan for better performance.
+        // EGL formats can still be imported, but should be avoided by clients this way
+        const DeviceType type = device->vulkanDevice() ? DeviceType::Vulkan : DeviceType::EGL;
+        m_tranches.append({
+            .device = device->deviceId(),
+            .flags = LinuxDmaBufV1Feedback::TrancheFlag::Sampling,
+            .formatTable = filterFormats(device.get(), 10, false, type),
+        });
+        m_tranches.append({
+            .device = device->deviceId(),
+            .flags = LinuxDmaBufV1Feedback::TrancheFlag::Sampling,
+            .formatTable = filterFormats(device.get(), 8, false, type),
+        });
+        m_tranches.push_back({
+            .device = device->deviceId(),
+            .flags = LinuxDmaBufV1Feedback::TrancheFlag::Sampling,
+            .formatTable = filterFormats(device.get(), std::nullopt, false, type),
+        });
+    }
+
     LinuxDmaBufV1ClientBufferIntegration *dmabuf = waylandServer()->linuxDmabuf();
     dmabuf->setRenderBackend(this);
     dmabuf->setSupportedFormatsWithModifiers(m_tranches);
-
-    waylandServer()->setRenderBackend(this);
 }
 
 bool EglBackend::initClientExtensions()
@@ -232,16 +280,23 @@ std::shared_ptr<GLTexture> EglBackend::importDmaBufAsTexture(const DmaBufAttribu
     return m_context->importDmaBufAsTexture(attributes);
 }
 
-bool EglBackend::testImportBuffer(GraphicsBuffer *buffer)
+bool EglBackend::testImportBuffer(GraphicsBuffer *buffer, dev_t targetDevice)
 {
-    RenderDevice *compat = GpuManager::self()->compatibleRenderDevice(buffer->dmabufAttributes()->device);
-    if (compat != m_renderDevice) {
-        // TODO import the buffer into the correct device instead
+    RenderDevice *device = GpuManager::self()->compatibleRenderDevice(targetDevice);
+    if (!device) {
         return false;
     }
-    const auto nonExternalOnly = m_renderDevice->eglDisplay()->nonExternalOnlySupportedDrmFormats();
+
+    if (device != m_renderDevice && device->vulkanDevice() && device->vulkanDevice()->supportedFormats().containsFormat(buffer->dmabufAttributes()->format, buffer->dmabufAttributes()->modifier)) {
+        if (device->vulkanDevice()->importBuffer(buffer, VK_IMAGE_USAGE_TRANSFER_SRC_BIT)) {
+            return true;
+        }
+        // allow falling back to EGL
+    }
+
+    const auto nonExternalOnly = device->eglDisplay()->nonExternalOnlySupportedDrmFormats();
     if (auto it = nonExternalOnly.find(buffer->dmabufAttributes()->format); it != nonExternalOnly.end() && it->contains(buffer->dmabufAttributes()->modifier)) {
-        return importBufferAsImage(buffer) != EGL_NO_IMAGE_KHR;
+        return device->eglDisplay()->importBufferAsImage(buffer) != EGL_NO_IMAGE_KHR;
     }
     // external_only buffers aren't used as a single EGLImage, import them separately
     const auto info = FormatInfo::get(buffer->dmabufAttributes()->format);
@@ -253,7 +308,7 @@ bool EglBackend::testImportBuffer(GraphicsBuffer *buffer)
         return false;
     }
     for (int i = 0; i < planes.size(); i++) {
-        if (!importBufferAsImage(buffer, i, planes[i].format, QSize(buffer->size().width() / planes[i].widthDivisor, buffer->size().height() / planes[i].heightDivisor))) {
+        if (!device->eglDisplay()->importBufferAsImage(buffer, i, planes[i].format, QSize(buffer->size().width() / planes[i].widthDivisor, buffer->size().height() / planes[i].heightDivisor))) {
             return false;
         }
     }
diff --git a/src/opengl/eglbackend.h b/src/opengl/eglbackend.h
index 75d5f9971fb..a7f6fe658cb 100644
--- a/src/opengl/eglbackend.h
+++ b/src/opengl/eglbackend.h
@@ -46,7 +46,7 @@ public:
     EglContext *openglShareContext() const;
     RenderDevice *renderDevice() const override;
 
-    bool testImportBuffer(GraphicsBuffer *buffer) override;
+    bool testImportBuffer(GraphicsBuffer *buffer, dev_t targetDevice) override;
     FormatModifierMap supportedFormats() const override;
 
     QList<LinuxDmaBufV1Feedback::Tranche> tranches() const;
@@ -61,6 +61,7 @@ protected:
     void initWayland();
     bool hasClientExtension(const QByteArray &ext) const;
     bool createContext();
+    void updateDmabufTranches();
 
     bool ensureGlobalShareContext();
     ::EGLContext createContextInternal(::EGLContext sharedContext);
diff --git a/src/scene/opengl/texture.cpp b/src/scene/opengl/texture.cpp
index 275c39ee6a0..8910b199738 100644
--- a/src/scene/opengl/texture.cpp
+++ b/src/scene/opengl/texture.cpp
@@ -12,7 +12,9 @@
 #include "core/graphicsbufferview.h"
 #include "core/renderdevice.h"
 #include "core/syncobjtimeline.h"
+#include "multigpuswapchain.h"
 #include "opengl/eglbackend.h"
+#include "opengl/eglnativefence.h"
 #include "opengl/gltexture.h"
 #include "utils/common.h"
 
@@ -72,6 +74,10 @@ BufferTextureOpenGL::BufferTextureOpenGL(EglBackend *backend)
 {
 }
 
+BufferTextureOpenGL::~BufferTextureOpenGL()
+{
+}
+
 std::unique_ptr<BufferTextureOpenGL> BufferTextureOpenGL::create(GraphicsBuffer *buffer, const std::shared_ptr<SyncReleasePoint> &releasePoint)
 {
     auto texture = std::make_unique<BufferTextureOpenGL>(static_cast<EglBackend *>(Compositor::self()->backend()));
@@ -103,7 +109,7 @@ bool BufferTextureOpenGL::attach(GraphicsBuffer *buffer, const std::shared_ptr<S
 void BufferTextureOpenGL::attach(GraphicsBuffer *buffer, const Region &region, const std::shared_ptr<SyncReleasePoint> &releasePoint)
 {
     if (buffer->dmabufAttributes()) {
-        updateDmabufTexture(buffer, releasePoint);
+        updateDmabufTexture(buffer, region, releasePoint);
     } else if (buffer->shmAttributes()) {
         if (EGLImageKHR image = m_backend->importBufferAsImage(buffer)) {
             updateUDmabufTexture(buffer, image, releasePoint);
@@ -207,13 +213,38 @@ void BufferTextureOpenGL::updateShmTexture(GraphicsBuffer *buffer, const Region
 
 bool BufferTextureOpenGL::loadDmabufTexture(GraphicsBuffer *buffer, const std::shared_ptr<SyncReleasePoint> &releasePoint)
 {
-    RenderDevice *compat = GpuManager::self()->compatibleRenderDevice(buffer->dmabufAttributes()->device);
-    if (compat != m_backend->renderDevice()) {
-        // TODO do multi gpu copies instead
+    auto attribs = buffer->dmabufAttributes();
+    m_dmabufDevice = attribs->device;
+    RenderDevice *compat = GpuManager::self()->compatibleRenderDevice(attribs->device);
+    if (!compat) {
+        qCCritical(KWIN_OPENGL, "Couldn't find a compatible GPU for a buffer");
         return false;
+    } else if (compat == m_backend->renderDevice()) {
+        m_mgpuSwapchain.reset();
+        m_releasePoint = releasePoint;
+    } else {
+        // need to do a multi gpu copy
+        m_mgpuSwapchain = MultiGpuSwapchain::create(compat, m_backend->renderDevice()->drmDevice(), attribs->format, attribs->modifier, buffer->size(),
+                                                    m_backend->renderDevice()->eglDisplay()->nonExternalOnlySupportedDrmFormats(), false);
+        if (!m_mgpuSwapchain) {
+            qCCritical(KWIN_OPENGL, "Couldn't create multi gpu swapchain for a buffer %s 0x%lx", qPrintable(FormatInfo::drmFormatName(attribs->format)), attribs->modifier);
+            return false;
+        }
+        EGLNativeFence releaseFence(m_backend->eglDisplayObject());
+        auto imported = m_mgpuSwapchain->copyRgbBuffer(buffer, Region::infinite(), releaseFence.takeFileDescriptor(),
+                                                       nullptr, releasePoint);
+        if (!imported.has_value()) {
+            return false;
+        }
+        const auto fence = EGLNativeFence::importFence(m_backend->eglDisplayObject(), std::move(imported->sync));
+        if (!fence.waitSync()) {
+            return false;
+        }
+        buffer = imported->buffer;
+        attribs = buffer->dmabufAttributes();
+        m_releasePoint = imported->releasePoint;
     }
 
-    const auto attribs = buffer->dmabufAttributes();
     if (auto itConv = FormatInfo::s_drmConversions.find(buffer->dmabufAttributes()->format); itConv != FormatInfo::s_drmConversions.end()) {
         std::vector<std::unique_ptr<GLTexture>> textures;
         Q_ASSERT(itConv->plane.count() == uint(buffer->dmabufAttributes()->planeCount));
@@ -247,18 +278,35 @@ bool BufferTextureOpenGL::loadDmabufTexture(GraphicsBuffer *buffer, const std::s
     m_size = buffer->size();
     const auto info = FormatInfo::get(buffer->dmabufAttributes()->format);
     m_isFloatingPoint = info && info->floatingPoint;
-    m_releasePoint = releasePoint;
 
     return true;
 }
 
-void BufferTextureOpenGL::updateDmabufTexture(GraphicsBuffer *buffer, const std::shared_ptr<SyncReleasePoint> &releasePoint)
+void BufferTextureOpenGL::updateDmabufTexture(GraphicsBuffer *buffer, const Region &region, const std::shared_ptr<SyncReleasePoint> &releasePoint)
 {
-    if (Q_UNLIKELY(m_bufferType != BufferType::DmaBuf)) {
+    if (Q_UNLIKELY(m_bufferType != BufferType::DmaBuf)
+        || Q_UNLIKELY(m_dmabufDevice != buffer->dmabufAttributes()->device)
+        || (m_mgpuSwapchain && !m_mgpuSwapchain->isSuitableFor(buffer))) {
         reset();
         attach(buffer, releasePoint);
         return;
     }
+    if (m_mgpuSwapchain) {
+        EGLNativeFence releaseFence(m_backend->eglDisplayObject());
+        auto imported = m_mgpuSwapchain->copyRgbBuffer(buffer, region, releaseFence.takeFileDescriptor(),
+                                                       nullptr, releasePoint);
+        if (!imported.has_value()) {
+            return;
+        }
+        const auto fence = EGLNativeFence::importFence(m_backend->eglDisplayObject(), std::move(imported->sync));
+        if (!fence.waitSync()) {
+            return;
+        }
+        buffer = imported->buffer;
+        m_releasePoint = imported->releasePoint;
+    } else {
+        m_releasePoint = releasePoint;
+    }
 
     RenderDevice *compat = GpuManager::self()->compatibleRenderDevice(buffer->dmabufAttributes()->device);
     if (compat != m_backend->renderDevice()) {
@@ -287,7 +335,6 @@ void BufferTextureOpenGL::updateDmabufTexture(GraphicsBuffer *buffer, const std:
     }
     const auto info = FormatInfo::get(buffer->dmabufAttributes()->format);
     m_isFloatingPoint = info && info->floatingPoint;
-    m_releasePoint = releasePoint;
 }
 
 bool BufferTextureOpenGL::loadSinglePixelTexture(GraphicsBuffer *buffer)
diff --git a/src/scene/opengl/texture.h b/src/scene/opengl/texture.h
index c705284a1f1..9117e5a086e 100644
--- a/src/scene/opengl/texture.h
+++ b/src/scene/opengl/texture.h
@@ -20,6 +20,7 @@ class GraphicsBuffer;
 class Region;
 class Rect;
 typedef void *EGLImageKHR;
+class MultiGpuSwapchain;
 
 class TextureOpenGL : public Texture
 {
@@ -49,6 +50,7 @@ public:
     static std::unique_ptr<BufferTextureOpenGL> create(GraphicsBuffer *buffer, const std::shared_ptr<SyncReleasePoint> &releasePoint);
 
     explicit BufferTextureOpenGL(EglBackend *backend);
+    ~BufferTextureOpenGL() override;
 
     bool attach(GraphicsBuffer *buffer, const std::shared_ptr<SyncReleasePoint> &releasePoint);
     void attach(GraphicsBuffer *buffer, const Region &region, const std::shared_ptr<SyncReleasePoint> &releasePoint) override;
@@ -61,7 +63,7 @@ private:
     bool loadShmTexture(GraphicsBuffer *buffer);
     void updateShmTexture(GraphicsBuffer *buffer, const Region &region, const std::shared_ptr<SyncReleasePoint> &releasePoint);
     bool loadDmabufTexture(GraphicsBuffer *buffer, const std::shared_ptr<SyncReleasePoint> &releasePoint);
-    void updateDmabufTexture(GraphicsBuffer *buffer, const std::shared_ptr<SyncReleasePoint> &releasePoint);
+    void updateDmabufTexture(GraphicsBuffer *buffer, const Region &region, const std::shared_ptr<SyncReleasePoint> &releasePoint);
     bool loadSinglePixelTexture(GraphicsBuffer *buffer);
     void updateSinglePixelTexture(GraphicsBuffer *buffer, const std::shared_ptr<SyncReleasePoint> &releasePoint);
     bool loadUDmabufTexture(GraphicsBuffer *buffer, EGLImageKHR image);
@@ -77,6 +79,8 @@ private:
 
     BufferType m_bufferType = BufferType::None;
     EglBackend *m_backend;
+    std::unique_ptr<MultiGpuSwapchain> m_mgpuSwapchain;
+    std::optional<dev_t> m_dmabufDevice;
 };
 
 } // namespace KWin
diff --git a/src/wayland/linuxdmabufv1clientbuffer.cpp b/src/wayland/linuxdmabufv1clientbuffer.cpp
index ae4935e8106..989bb5fccd9 100644
--- a/src/wayland/linuxdmabufv1clientbuffer.cpp
+++ b/src/wayland/linuxdmabufv1clientbuffer.cpp
@@ -176,10 +176,26 @@ void LinuxDmaBufParamsV1::zwp_linux_buffer_params_v1_create(Resource *resource,
     m_attrs.width = width;
     m_attrs.height = height;
     m_attrs.format = format;
-    m_attrs.device = m_targetDevice.value_or(m_integration->mainDevice(resource->client()));
 
     auto clientBuffer = new LinuxDmaBufV1ClientBuffer(std::move(m_attrs));
-    if (!renderBackend->testImportBuffer(clientBuffer)) {
+    const dev_t target = m_targetDevice.value_or(m_integration->mainDevice(resource->client()));
+    const auto &devices = GpuManager::s_self->renderDevices();
+    bool success = renderBackend->testImportBuffer(clientBuffer, target);
+    if (success) {
+        clientBuffer->setDevice(target);
+    } else {
+        for (const auto &device : devices) {
+            if (device->drmDevice()->deviceId() == target) {
+                continue;
+            }
+            success = renderBackend->testImportBuffer(clientBuffer, device->drmDevice()->deviceId());
+            if (success) {
+                clientBuffer->setDevice(device->drmDevice()->deviceId());
+                break;
+            }
+        }
+    }
+    if (!success) {
         send_failed(resource->handle);
         clientBuffer->drop();
         return;
@@ -228,10 +244,26 @@ void LinuxDmaBufParamsV1::zwp_linux_buffer_params_v1_create_immed(Resource *reso
     m_attrs.width = width;
     m_attrs.height = height;
     m_attrs.format = format;
-    m_attrs.device = m_targetDevice.value_or(m_integration->mainDevice(resource->client()));
 
     auto clientBuffer = new LinuxDmaBufV1ClientBuffer(std::move(m_attrs));
-    if (!renderBackend->testImportBuffer(clientBuffer)) {
+    const dev_t target = m_targetDevice.value_or(m_integration->mainDevice(resource->client()));
+    const auto &devices = GpuManager::s_self->renderDevices();
+    bool success = renderBackend->testImportBuffer(clientBuffer, target);
+    if (success) {
+        clientBuffer->setDevice(target);
+    } else {
+        for (const auto &device : devices) {
+            if (device->drmDevice()->deviceId() == target) {
+                continue;
+            }
+            success = renderBackend->testImportBuffer(clientBuffer, device->drmDevice()->deviceId());
+            if (success) {
+                clientBuffer->setDevice(device->drmDevice()->deviceId());
+                break;
+            }
+        }
+    }
+    if (!success) {
         wl_resource_post_error(resource->handle, error_invalid_wl_buffer, "importing the supplied dmabufs failed");
         clientBuffer->drop();
         return;
@@ -418,6 +450,11 @@ const DmaBufAttributes *LinuxDmaBufV1ClientBuffer::dmabufAttributes() const
     return &m_attrs;
 }
 
+void LinuxDmaBufV1ClientBuffer::setDevice(dev_t deviceId)
+{
+    m_attrs.device = deviceId;
+}
+
 QSize LinuxDmaBufV1ClientBuffer::size() const
 {
     return QSize(m_attrs.width, m_attrs.height);
diff --git a/src/wayland/linuxdmabufv1clientbuffer_p.h b/src/wayland/linuxdmabufv1clientbuffer_p.h
index c3efedfa2e2..6d95fc744af 100644
--- a/src/wayland/linuxdmabufv1clientbuffer_p.h
+++ b/src/wayland/linuxdmabufv1clientbuffer_p.h
@@ -20,7 +20,6 @@
 #include <QDebug>
 #include <QList>
 #include <QPointer>
-
 #include <drm_fourcc.h>
 #include <sys/types.h>
 
@@ -93,6 +92,8 @@ public:
     bool hasAlphaChannel() const override;
     const DmaBufAttributes *dmabufAttributes() const override;
 
+    void setDevice(dev_t deviceId);
+
     static LinuxDmaBufV1ClientBuffer *get(wl_resource *resource);
 
 private:
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.