[plasma/kwin] src: multigpuswapchain: use transfer operations whenever possible

Xaver Hugl <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit f29392e57a53ed89f15bf3491a43dd4fa83925d1 by Xaver Hugl.
Committed on 03/08/2026 at 12:04.
Pushed by zamundaaa into branch 'master'.

multigpuswapchain: use transfer operations whenever possible

vkCmdCopyImage should generally be faster than vkCmdBlitImage, and in testing
with my eGPU setup, showed about a 10% performance improvement for Cyberpunk
2077 being copied back and forth.
The only reason we didn't do it before is that the texel block size needs to
match between the buffers, so we also need to keep the fallback to blitting
with the graphics queue.

M  +57   -28   src/multigpuswapchain.cpp
M  +19   -2    src/vulkan/vulkan_device.cpp
M  +3    -1    src/vulkan/vulkan_device.h

https://invent.kde.org/plasma/kwin/-/commit/f29392e57a53ed89f15bf3491a43dd4fa83925d1

diff --git a/src/multigpuswapchain.cpp b/src/multigpuswapchain.cpp
index 3243b641d1e..82219c77ace 100644
--- a/src/multigpuswapchain.cpp
+++ b/src/multigpuswapchain.cpp
@@ -180,7 +180,9 @@ std::optional<MultiGpuSwapchain::Ret> MultiGpuSwapchain::copyWithVulkan(Graphics
     }
 
     const auto copyVk = m_copyDevice->vulkanDevice();
-    const auto queue = copyVk->graphicsQueue();
+
+    const bool useTransferQueue = copyVk->transferQueue() && FormatInfo::get(src->dmabufAttributes()->format)->bitsPerPixel == FormatInfo::get(m_format)->bitsPerPixel;
+    const auto queue = useTransferQueue ? copyVk->transferQueue() : copyVk->graphicsQueue();
 
     const auto srcTexture = copyVk->importBuffer(src, VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
     if (!srcTexture) {
@@ -246,35 +248,62 @@ std::optional<MultiGpuSwapchain::Ret> MultiGpuSwapchain::copyWithVulkan(Graphics
         memoryBarrier,
     });
 
-    const std::vector<vk::ImageBlit> regions = toRender.rects() | std::views::transform([&completeRect](const Rect &rect) {
-        return vk::ImageBlit{
-            // src
-            vk::ImageSubresourceLayers{
-                vk::ImageAspectFlagBits::eColor,
-                0,
-                0,
-                1,
-            },
-            std::array{
+    if (useTransferQueue) {
+        const std::vector<vk::ImageCopy> regions = toRender.rects() | std::views::transform([&completeRect](const Rect &rect) {
+            return vk::ImageCopy{
+                // src
+                vk::ImageSubresourceLayers{
+                    vk::ImageAspectFlagBits::eColor,
+                    0,
+                    0,
+                    1,
+                },
                 vk::Offset3D{rect.left(), rect.top(), 0},
-                vk::Offset3D{rect.right(), rect.bottom(), 1},
-            },
-            // dst
-            vk::ImageSubresourceLayers{
-                vk::ImageAspectFlagBits::eColor,
-                0,
-                0,
-                1,
-            },
-            std::array{
+                // dst
+                vk::ImageSubresourceLayers{
+                    vk::ImageAspectFlagBits::eColor,
+                    0,
+                    0,
+                    1,
+                },
                 vk::Offset3D{rect.left(), rect.top(), 0},
-                vk::Offset3D{rect.right(), rect.bottom(), 1},
-            },
-        };
-    }) | std::ranges::to<std::vector>();
-    commandBuffer.blitImage(srcTexture->handle(), vk::ImageLayout::eGeneral,
-                            m_currentVulkanSlot->texture()->handle(), vk::ImageLayout::eGeneral,
-                            regions, vk::Filter::eNearest);
+                vk::Extent3D{uint32_t(rect.width()), uint32_t(rect.height()), 1},
+            };
+        }) | std::ranges::to<std::vector>();
+        commandBuffer.copyImage(srcTexture->handle(), vk::ImageLayout::eGeneral,
+                                m_currentVulkanSlot->texture()->handle(), vk::ImageLayout::eGeneral,
+                                regions);
+    } else {
+        const std::vector<vk::ImageBlit> regions = toRender.rects() | std::views::transform([&completeRect](const Rect &rect) {
+            return vk::ImageBlit{
+                // src
+                vk::ImageSubresourceLayers{
+                    vk::ImageAspectFlagBits::eColor,
+                    0,
+                    0,
+                    1,
+                },
+                std::array{
+                    vk::Offset3D{rect.left(), rect.top(), 0},
+                    vk::Offset3D{rect.right(), rect.bottom(), 1},
+                },
+                // dst
+                vk::ImageSubresourceLayers{
+                    vk::ImageAspectFlagBits::eColor,
+                    0,
+                    0,
+                    1,
+                },
+                std::array{
+                    vk::Offset3D{rect.left(), rect.top(), 0},
+                    vk::Offset3D{rect.right(), rect.bottom(), 1},
+                },
+            };
+        }) | std::ranges::to<std::vector>();
+        commandBuffer.blitImage(srcTexture->handle(), vk::ImageLayout::eGeneral,
+                                m_currentVulkanSlot->texture()->handle(), vk::ImageLayout::eGeneral,
+                                regions, vk::Filter::eNearest);
+    }
 
     memoryBarrier.setSrcQueueFamilyIndex(queue->familyIndex());
     memoryBarrier.setDstQueueFamilyIndex(vk::QueueFamilyExternal);
diff --git a/src/vulkan/vulkan_device.cpp b/src/vulkan/vulkan_device.cpp
index 63be8157dde..b754d858d66 100644
--- a/src/vulkan/vulkan_device.cpp
+++ b/src/vulkan/vulkan_device.cpp
@@ -30,19 +30,31 @@ VulkanDevice::VulkanDevice(vk::raii::PhysicalDevice physicalDevice, vk::raii::De
     , m_deviceLimits(m_physical.getProperties().limits)
 {
     m_memoryProperties = physicalDevice.getMemoryProperties();
-    getQueue();
+    getQueues();
 }
 
 VulkanDevice::~VulkanDevice()
 {
     Q_EMIT deviceLost();
     m_graphicsQueue.reset();
+    m_transferQueue.reset();
     m_importedTextures.clear();
     m_logical.clear();
 }
 
-void VulkanDevice::getQueue()
+void VulkanDevice::getQueues()
 {
+    // prefer the most minimal capabilities for the transfer queue
+    auto transfer = m_queueProperties | std::views::filter([](const VkQueueFamilyProperties &props) {
+        return props.queueFlags & VK_QUEUE_TRANSFER_BIT;
+    });
+    const auto transferIt = std::ranges::min_element(transfer, [](const VkQueueFamilyProperties &left, const VkQueueFamilyProperties &right) {
+        return std::popcount(left.queueFlags) < std::popcount(right.queueFlags);
+    });
+    if (transferIt != transfer.end()) {
+        m_transferQueue = VulkanQueue::create(this, std::distance(m_queueProperties.begin(), transferIt.base()));
+    }
+
     auto it = std::ranges::find_if(m_queueProperties, [](const VkQueueFamilyProperties &props) {
         return props.queueFlags & VK_QUEUE_GRAPHICS_BIT;
     });
@@ -335,6 +347,11 @@ VulkanQueue *VulkanDevice::graphicsQueue() const
     return m_graphicsQueue.get();
 }
 
+VulkanQueue *VulkanDevice::transferQueue() const
+{
+    return m_transferQueue.get();
+}
+
 std::span<const VkQueueFamilyProperties> VulkanDevice::queueFamilyProperties() const
 {
     return m_queueProperties;
diff --git a/src/vulkan/vulkan_device.h b/src/vulkan/vulkan_device.h
index a06cfdf877c..0a007cc11dd 100644
--- a/src/vulkan/vulkan_device.h
+++ b/src/vulkan/vulkan_device.h
@@ -85,6 +85,7 @@ public:
     const vk::raii::Device &logicalDevice() const;
 
     VulkanQueue *graphicsQueue() const;
+    VulkanQueue *transferQueue() const;
 
     std::span<const VkQueueFamilyProperties> queueFamilyProperties() const;
     float nanosecondsPerQueryTick() const;
@@ -127,7 +128,7 @@ Q_SIGNALS:
     void deviceLost();
 
 private:
-    void getQueue();
+    void getQueues();
     FormatModifierMap queryFormats(VkImageUsageFlags flags) const;
     std::optional<uint32_t> findMemoryType(uint32_t typeBits, vk::MemoryPropertyFlags memoryPropertyFlags) const;
     std::shared_ptr<VulkanTexture> importDmabuf(const DmaBufAttributes *attributes, VkImageUsageFlags usage);
@@ -141,6 +142,7 @@ private:
     vk::PhysicalDeviceLimits m_deviceLimits;
 
     std::unique_ptr<VulkanQueue> m_graphicsQueue;
+    std::unique_ptr<VulkanQueue> m_transferQueue;
 
     QHash<GraphicsBuffer *, std::shared_ptr<VulkanTexture>> m_importedTextures;
     bool m_lost = false;
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.