[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 ®ion, 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 ®ion, 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));