[plasma/kwin] src: vulkan: move the queue into its own class

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

vulkan: move the queue into its own class

There can be multiple queues with different supported functionalities.

M  +7    -5    src/multigpuswapchain.cpp
M  +125  -104  src/vulkan/vulkan_device.cpp
M  +39   -15   src/vulkan/vulkan_device.h
M  +9    -9    src/vulkan/vulkan_texture.cpp

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

diff --git a/src/multigpuswapchain.cpp b/src/multigpuswapchain.cpp
index eda3966339d..3243b641d1e 100644
--- a/src/multigpuswapchain.cpp
+++ b/src/multigpuswapchain.cpp
@@ -180,6 +180,8 @@ std::optional<MultiGpuSwapchain::Ret> MultiGpuSwapchain::copyWithVulkan(Graphics
     }
 
     const auto copyVk = m_copyDevice->vulkanDevice();
+    const auto queue = copyVk->graphicsQueue();
+
     const auto srcTexture = copyVk->importBuffer(src, VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
     if (!srcTexture) {
         qCWarning(KWIN_VULKAN, "Could not import buffer for multi GPU copy!");
@@ -205,7 +207,7 @@ std::optional<MultiGpuSwapchain::Ret> MultiGpuSwapchain::copyWithVulkan(Graphics
     }
     m_journal.add(damage);
 
-    auto commandBuffer = copyVk->createCommandBuffer();
+    auto commandBuffer = queue->createCommandBuffer();
     vk::Result result = commandBuffer.begin(vk::CommandBufferBeginInfo{
         vk::CommandBufferUsageFlagBits::eOneTimeSubmit,
     });
@@ -216,7 +218,7 @@ std::optional<MultiGpuSwapchain::Ret> MultiGpuSwapchain::copyWithVulkan(Graphics
 
     std::unique_ptr<VulkanRenderTimeQuery> query;
     if (frame) {
-        query = VulkanRenderTimeQuery::begin(copyVk, commandBuffer, copyVk->graphicsQueueFamily());
+        query = VulkanRenderTimeQuery::begin(copyVk, commandBuffer, queue->familyIndex());
     }
 
     vk::ImageMemoryBarrier2 memoryBarrier{
@@ -227,7 +229,7 @@ std::optional<MultiGpuSwapchain::Ret> MultiGpuSwapchain::copyWithVulkan(Graphics
         vk::ImageLayout::eGeneral,
         vk::ImageLayout::eGeneral,
         vk::QueueFamilyExternal,
-        copyVk->graphicsQueueFamily(),
+        queue->familyIndex(),
         m_currentVulkanSlot->texture()->handle(),
         vk::ImageSubresourceRange{
             vk::ImageAspectFlagBits::eColor,
@@ -274,7 +276,7 @@ std::optional<MultiGpuSwapchain::Ret> MultiGpuSwapchain::copyWithVulkan(Graphics
                             m_currentVulkanSlot->texture()->handle(), vk::ImageLayout::eGeneral,
                             regions, vk::Filter::eNearest);
 
-    memoryBarrier.setSrcQueueFamilyIndex(copyVk->graphicsQueueFamily());
+    memoryBarrier.setSrcQueueFamilyIndex(queue->familyIndex());
     memoryBarrier.setDstQueueFamilyIndex(vk::QueueFamilyExternal);
     commandBuffer.pipelineBarrier2(vk::DependencyInfo{
         vk::DependencyFlags{},
@@ -293,7 +295,7 @@ std::optional<MultiGpuSwapchain::Ret> MultiGpuSwapchain::copyWithVulkan(Graphics
         m_journal.clear();
         return std::nullopt;
     }
-    auto completionFd = copyVk->submit(std::move(commandBuffer), std::move(sync));
+    auto completionFd = queue->submit(std::move(commandBuffer), std::move(sync));
     if (!completionFd.has_value()) {
         m_needsRecreation = true;
         return std::nullopt;
diff --git a/src/vulkan/vulkan_device.cpp b/src/vulkan/vulkan_device.cpp
index b20b6a98690..63be8157dde 100644
--- a/src/vulkan/vulkan_device.cpp
+++ b/src/vulkan/vulkan_device.cpp
@@ -27,22 +27,17 @@ VulkanDevice::VulkanDevice(vk::raii::PhysicalDevice physicalDevice, vk::raii::De
     // and sample + color attachment + transfer_dst
     , m_formats(queryFormats(VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT))
     , m_queueProperties(std::move(queueProperties))
-    , m_graphicsQueue(nullptr)
-    , m_commandPool(nullptr)
     , m_deviceLimits(m_physical.getProperties().limits)
 {
     m_memoryProperties = physicalDevice.getMemoryProperties();
     getQueue();
-    createCommandPool();
 }
 
 VulkanDevice::~VulkanDevice()
 {
     Q_EMIT deviceLost();
-    m_graphicsQueue.waitIdle();
+    m_graphicsQueue.reset();
     m_importedTextures.clear();
-    m_submittedCommandBuffers.clear();
-    m_commandPool.clear();
     m_logical.clear();
 }
 
@@ -52,22 +47,7 @@ void VulkanDevice::getQueue()
         return props.queueFlags & VK_QUEUE_GRAPHICS_BIT;
     });
     Q_ASSERT(it != m_queueProperties.end());
-    m_queueFamilyIndex = std::distance(m_queueProperties.begin(), it);
-    m_graphicsQueue = m_logical.getQueue(m_queueFamilyIndex, 0);
-}
-
-void VulkanDevice::createCommandPool()
-{
-    // only one queue for now -> also only one command pool
-    auto [result, cmdPool] = m_logical.createCommandPool(vk::CommandPoolCreateInfo{
-        vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
-        m_queueFamilyIndex,
-    });
-    if (result != vk::Result::eSuccess) {
-        qCCritical(KWIN_VULKAN) << "creating a command pool failed:" << vk::to_string(result);
-        return;
-    }
-    m_commandPool = std::move(cmdPool);
+    m_graphicsQueue = VulkanQueue::create(this, std::distance(m_queueProperties.begin(), it));
 }
 
 std::shared_ptr<VulkanTexture> VulkanDevice::importBuffer(GraphicsBuffer *buffer, VkImageUsageFlags usage)
@@ -160,7 +140,10 @@ std::shared_ptr<VulkanTexture> VulkanDevice::importDmabuf(const DmaBufAttributes
         vk::ImageTiling::eDrmFormatModifierEXT,
         vk::ImageUsageFlags(usage),
         vk::SharingMode::eExclusive,
-        m_queueFamilyIndex,
+        // the queue family index is ignored with share mode exclusive,
+        // instead Vulkan implicitly assigns ownership to the first queue
+        // the image is used in.
+        0,
         vk::ImageLayout::eUndefined,
         &externalInfo,
     };
@@ -347,14 +330,9 @@ const vk::raii::Device &VulkanDevice::logicalDevice() const
     return m_logical;
 }
 
-const vk::raii::Queue &VulkanDevice::graphicsQueue() const
-{
-    return m_graphicsQueue;
-}
-
-uint32_t VulkanDevice::graphicsQueueFamily() const
+VulkanQueue *VulkanDevice::graphicsQueue() const
 {
-    return m_queueFamilyIndex;
+    return m_graphicsQueue.get();
 }
 
 std::span<const VkQueueFamilyProperties> VulkanDevice::queueFamilyProperties() const
@@ -367,31 +345,6 @@ float VulkanDevice::nanosecondsPerQueryTick() const
     return m_deviceLimits.timestampPeriod;
 }
 
-vk::raii::CommandBuffer VulkanDevice::createCommandBuffer()
-{
-    // clean up old command buffers first
-    for (auto it = m_submittedCommandBuffers.begin(); it != m_submittedCommandBuffers.end();) {
-        const SubmittedCommand &cmd = *it;
-        // TODO use a QSocketNotifier per submission to do this asynchronously?
-        if (cmd.completionSyncFd.isReadable()) {
-            it = m_submittedCommandBuffers.erase(it);
-        } else {
-            it++;
-        }
-    }
-
-    auto [result, buffers] = m_logical.allocateCommandBuffers(vk::CommandBufferAllocateInfo{
-        m_commandPool,
-        vk::CommandBufferLevel::ePrimary,
-        1,
-    });
-    if (result != vk::Result::eSuccess) {
-        qCWarning(KWIN_VULKAN) << "Failed to create a command buffer" << vk::to_string(result);
-        return nullptr;
-    }
-    return std::move(buffers.front());
-}
-
 std::optional<vk::raii::Semaphore> VulkanDevice::importSemaphore(FileDescriptor &&syncFd) const
 {
     if (!syncFd.isValid()) {
@@ -417,54 +370,6 @@ std::optional<vk::raii::Semaphore> VulkanDevice::importSemaphore(FileDescriptor
     return std::move(semaphore);
 }
 
-std::optional<FileDescriptor> VulkanDevice::submit(vk::raii::CommandBuffer &&buffer, FileDescriptor &&syncFd)
-{
-    vk::ExportFenceCreateInfo exportInfo{
-        vk::ExternalFenceHandleTypeFlagBits::eSyncFd,
-    };
-    auto [fenceResult, fence] = m_logical.createFence(vk::FenceCreateInfo{
-        vk::FenceCreateFlags{},
-        &exportInfo,
-    });
-    if (fenceResult != vk::Result::eSuccess) {
-        return std::nullopt;
-    }
-    std::vector<vk::Semaphore> waitSemaphores;
-    std::vector<vk::PipelineStageFlags> waitFlags;
-    auto waitSemaphore = importSemaphore(std::move(syncFd));
-    if (waitSemaphore.has_value()) {
-        waitSemaphores.push_back(*waitSemaphore);
-        waitFlags.push_back(vk::PipelineStageFlagBits::eAllCommands);
-    }
-    vk::Result result = m_graphicsQueue.submit(vk::SubmitInfo{
-                                                   waitSemaphores,
-                                                   waitFlags,
-                                                   *buffer,
-                                                   {},
-                                               },
-                                               fence);
-    if (result == vk::Result::eErrorDeviceLost) {
-        handleDeviceLoss();
-        return std::nullopt;
-    } else if (result != vk::Result::eSuccess) {
-        return std::nullopt;
-    }
-    const auto [fdResult, fd] = m_logical.getFenceFdKHR(vk::FenceGetFdInfoKHR{
-        fence,
-        vk::ExternalFenceHandleTypeFlagBits::eSyncFd,
-    });
-    if (fdResult != vk::Result::eSuccess) {
-        return std::nullopt;
-    }
-    FileDescriptor ret{fd};
-    m_submittedCommandBuffers.push_back(SubmittedCommand{
-        .waitSemaphore = waitSemaphore ? std::move(*waitSemaphore) : nullptr,
-        .buffer = std::move(buffer),
-        .completionSyncFd = ret.duplicate(),
-    });
-    return ret;
-}
-
 void VulkanDevice::handleDeviceLoss()
 {
     if (m_lost) {
@@ -521,7 +426,123 @@ vk::raii::DeviceMemory VulkanDevice::allocateMemory(const vk::BufferCreateInfo &
 
 void VulkanDevice::waitIdle()
 {
-    m_graphicsQueue.waitIdle();
+    m_graphicsQueue->waitIdle();
+}
+
+VulkanQueue::VulkanQueue(VulkanDevice *device, uint32_t familyIndex, vk::raii::Queue &&handle, vk::raii::CommandPool &&commandPool)
+    : m_device(device)
+    , m_familyIndex(familyIndex)
+    , m_handle(std::move(handle))
+    , m_commandPool(std::move(commandPool))
+{
+}
+
+VulkanQueue::~VulkanQueue()
+{
+    m_handle.waitIdle();
+}
+
+uint32_t VulkanQueue::familyIndex() const
+{
+    return m_familyIndex;
+}
+
+const vk::raii::Queue &VulkanQueue::handle() const
+{
+    return m_handle;
+}
+
+vk::raii::CommandBuffer VulkanQueue::createCommandBuffer()
+{
+    // clean up old command buffers first
+    for (auto it = m_submittedCommandBuffers.begin(); it != m_submittedCommandBuffers.end();) {
+        const SubmittedCommand &cmd = *it;
+        // TODO use a QSocketNotifier per submission to do this asynchronously?
+        if (cmd.completionSyncFd.isReadable()) {
+            it = m_submittedCommandBuffers.erase(it);
+        } else {
+            it++;
+        }
+    }
+
+    auto [result, buffers] = m_device->logicalDevice().allocateCommandBuffers(vk::CommandBufferAllocateInfo{
+        m_commandPool,
+        vk::CommandBufferLevel::ePrimary,
+        1,
+    });
+    if (result != vk::Result::eSuccess) {
+        qCWarning(KWIN_VULKAN) << "Failed to create a command buffer" << vk::to_string(result);
+        return nullptr;
+    }
+    return std::move(buffers.front());
+}
+
+std::optional<FileDescriptor> VulkanQueue::submit(vk::raii::CommandBuffer &&buffer, FileDescriptor &&syncFd)
+{
+    vk::ExportFenceCreateInfo exportInfo{
+        vk::ExternalFenceHandleTypeFlagBits::eSyncFd,
+    };
+    auto [fenceResult, fence] = m_device->logicalDevice().createFence(vk::FenceCreateInfo{
+        vk::FenceCreateFlags{},
+        &exportInfo,
+    });
+    if (fenceResult != vk::Result::eSuccess) {
+        return std::nullopt;
+    }
+    std::vector<vk::Semaphore> waitSemaphores;
+    std::vector<vk::PipelineStageFlags> waitFlags;
+    auto waitSemaphore = m_device->importSemaphore(std::move(syncFd));
+    if (waitSemaphore.has_value()) {
+        waitSemaphores.push_back(*waitSemaphore);
+        waitFlags.push_back(vk::PipelineStageFlagBits::eAllCommands);
+    }
+    vk::Result result = m_handle.submit(vk::SubmitInfo{
+                                            waitSemaphores,
+                                            waitFlags,
+                                            *buffer,
+                                            {},
+                                        },
+                                        fence);
+    if (result == vk::Result::eErrorDeviceLost) {
+        m_device->handleDeviceLoss();
+        return std::nullopt;
+    } else if (result != vk::Result::eSuccess) {
+        return std::nullopt;
+    }
+    const auto [fdResult, fd] = m_device->logicalDevice().getFenceFdKHR(vk::FenceGetFdInfoKHR{
+        fence,
+        vk::ExternalFenceHandleTypeFlagBits::eSyncFd,
+    });
+    if (fdResult != vk::Result::eSuccess) {
+        return std::nullopt;
+    }
+    FileDescriptor ret{fd};
+    m_submittedCommandBuffers.push_back(SubmittedCommand{
+        .waitSemaphore = waitSemaphore ? std::move(*waitSemaphore) : nullptr,
+        .buffer = std::move(buffer),
+        .completionSyncFd = ret.duplicate(),
+    });
+    return ret;
+}
+
+void VulkanQueue::waitIdle()
+{
+    m_handle.waitIdle();
+}
+
+std::unique_ptr<VulkanQueue> VulkanQueue::create(VulkanDevice *device, uint32_t familyIndex)
+{
+    auto handle = device->logicalDevice().getQueue(familyIndex, 0);
+
+    auto [result, cmdPool] = device->logicalDevice().createCommandPool(vk::CommandPoolCreateInfo{
+        vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
+        familyIndex,
+    });
+    if (result != vk::Result::eSuccess) {
+        qCCritical(KWIN_VULKAN) << "creating a command pool failed:" << vk::to_string(result);
+        return nullptr;
+    }
+    return std::make_unique<VulkanQueue>(device, familyIndex, std::move(handle), std::move(cmdPool));
 }
 
 }
diff --git a/src/vulkan/vulkan_device.h b/src/vulkan/vulkan_device.h
index b84b964931a..a06cfdf877c 100644
--- a/src/vulkan/vulkan_device.h
+++ b/src/vulkan/vulkan_device.h
@@ -26,6 +26,41 @@ class VulkanTexture;
 class GraphicsBuffer;
 struct DmaBufAttributes;
 class RenderDevice;
+class VulkanDevice;
+
+class KWIN_EXPORT VulkanQueue
+{
+public:
+    explicit VulkanQueue(VulkanDevice *device, uint32_t familyIndex, vk::raii::Queue &&handle, vk::raii::CommandPool &&commandPool);
+    ~VulkanQueue();
+
+    uint32_t familyIndex() const;
+    const vk::raii::Queue &handle() const;
+
+    vk::raii::CommandBuffer createCommandBuffer();
+    std::optional<FileDescriptor> submit(vk::raii::CommandBuffer &&buffer, FileDescriptor &&syncFd);
+
+    /**
+     * NOTE avoid using this if at all possible, it's obviously terrible for performance!
+     */
+    void waitIdle();
+
+    static std::unique_ptr<VulkanQueue> create(VulkanDevice *device, uint32_t familyIndex);
+
+private:
+    struct SubmittedCommand
+    {
+        vk::raii::Semaphore waitSemaphore;
+        vk::raii::CommandBuffer buffer;
+        FileDescriptor completionSyncFd;
+    };
+
+    VulkanDevice *const m_device;
+    const uint32_t m_familyIndex;
+    const vk::raii::Queue m_handle;
+    const vk::raii::CommandPool m_commandPool;
+    std::deque<SubmittedCommand> m_submittedCommandBuffers;
+};
 
 class KWIN_EXPORT VulkanDevice : public QObject
 {
@@ -49,15 +84,13 @@ public:
     const FormatModifierMap &supportedFormats() const;
     const vk::raii::Device &logicalDevice() const;
 
-    const vk::raii::Queue &graphicsQueue() const;
-    uint32_t graphicsQueueFamily() const;
+    VulkanQueue *graphicsQueue() const;
+
     std::span<const VkQueueFamilyProperties> queueFamilyProperties() const;
     float nanosecondsPerQueryTick() const;
 
-    vk::raii::CommandBuffer createCommandBuffer();
     std::optional<vk::raii::Semaphore> importSemaphore(FileDescriptor &&syncFd) const;
 
-    std::optional<FileDescriptor> submit(vk::raii::CommandBuffer &&buffer, FileDescriptor &&syncFd);
     /**
      * NOTE avoid using this if at all possible, it's obviously terrible for performance!
      */
@@ -95,7 +128,6 @@ Q_SIGNALS:
 
 private:
     void getQueue();
-    void createCommandPool();
     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);
@@ -105,19 +137,11 @@ private:
     vk::raii::Device m_logical;
     FormatModifierMap m_formats;
     std::vector<VkQueueFamilyProperties> m_queueProperties;
-    vk::raii::Queue m_graphicsQueue;
-    vk::raii::CommandPool m_commandPool;
-    uint32_t m_queueFamilyIndex;
     vk::PhysicalDeviceMemoryProperties m_memoryProperties;
-    struct SubmittedCommand
-    {
-        vk::raii::Semaphore waitSemaphore;
-        vk::raii::CommandBuffer buffer;
-        FileDescriptor completionSyncFd;
-    };
-    std::deque<SubmittedCommand> m_submittedCommandBuffers;
     vk::PhysicalDeviceLimits m_deviceLimits;
 
+    std::unique_ptr<VulkanQueue> m_graphicsQueue;
+
     QHash<GraphicsBuffer *, std::shared_ptr<VulkanTexture>> m_importedTextures;
     bool m_lost = false;
 };
diff --git a/src/vulkan/vulkan_texture.cpp b/src/vulkan/vulkan_texture.cpp
index fc36d4f6407..5625decd014 100644
--- a/src/vulkan/vulkan_texture.cpp
+++ b/src/vulkan/vulkan_texture.cpp
@@ -131,7 +131,7 @@ QImage VulkanTexture::download() const
     }
     stagingBuffer.bindMemory(stagingMemory, 0);
 
-    auto commandBuffer = m_device->createCommandBuffer();
+    auto commandBuffer = m_device->graphicsQueue()->createCommandBuffer();
     commandBuffer.begin(vk::CommandBufferBeginInfo{vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
     vk::BufferImageCopy2 copyRegion{
         0,
@@ -154,8 +154,8 @@ QImage VulkanTexture::download() const
     });
     commandBuffer.end();
 
-    m_device->submit(std::move(commandBuffer), FileDescriptor{});
-    m_device->waitIdle();
+    m_device->graphicsQueue()->submit(std::move(commandBuffer), FileDescriptor{});
+    m_device->graphicsQueue()->waitIdle();
 
     // use mapMemory/unmapMemory (Vulkan 1.0) instead of mapMemory2/unmapMemory2 (Vulkan 1.4)
     // for compatibility with lavapipe and other drivers that don't support 1.4
@@ -200,7 +200,7 @@ bool VulkanTexture::update(const QImage &img, const Region &region, const QPoint
     std::memcpy(dataPtr, img.constBits(), img.sizeInBytes());
     stagingMemory.unmapMemory();
 
-    auto commandBuffer = m_device->createCommandBuffer();
+    auto commandBuffer = m_device->graphicsQueue()->createCommandBuffer();
     commandBuffer.begin(vk::CommandBufferBeginInfo{vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
     const uint32_t bytesPerPixel = img.depth() / 8;
     const auto regions = region.rects() | std::views::transform([&img, &offset, bytesPerPixel](const Rect &rect) {
@@ -225,9 +225,9 @@ bool VulkanTexture::update(const QImage &img, const Region &region, const QPoint
         regions,
     });
     commandBuffer.end();
-    m_device->submit(std::move(commandBuffer), FileDescriptor{});
+    m_device->graphicsQueue()->submit(std::move(commandBuffer), FileDescriptor{});
 
-    m_device->waitIdle();
+    m_device->graphicsQueue()->waitIdle();
     return true;
 }
 
@@ -265,7 +265,7 @@ std::unique_ptr<VulkanTexture> VulkanTexture::allocate(VulkanDevice *device, vk:
 
     // we will only use the general image layout everywhere else,
     // so transition the image here once and then never again.
-    auto commandBuffer = device->createCommandBuffer();
+    auto commandBuffer = device->graphicsQueue()->createCommandBuffer();
     commandBuffer.begin(vk::CommandBufferBeginInfo{vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
     vk::ImageMemoryBarrier toTransferSrc{
         vk::AccessFlags{},
@@ -279,11 +279,11 @@ std::unique_ptr<VulkanTexture> VulkanTexture::allocate(VulkanDevice *device, vk:
     };
     commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eFragmentShader, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, toTransferSrc);
     commandBuffer.end();
-    device->submit(std::move(commandBuffer), FileDescriptor{});
+    device->graphicsQueue()->submit(std::move(commandBuffer), FileDescriptor{});
 
     // FIXME this is terrible. Instead, pass the command buffer in as
     // an argument, and leave synchronization up to the caller.
-    device->waitIdle();
+    device->graphicsQueue()->waitIdle();
 
     std::vector<vk::raii::DeviceMemory> mem;
     mem.push_back(std::move(memory));
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.