wayland/screencopy: support dmabufs in vulkan mode

This commit is contained in:
reakjra 2026-02-09 18:20:14 +01:00 committed by outfoxxed
parent 158db16b93
commit a99519c3ad
No known key found for this signature in database
GPG key ID: 4C88A185FB89301E
8 changed files with 375 additions and 2 deletions

View file

@ -50,6 +50,7 @@ jobs:
wayland-protocols \
wayland \
libdrm \
vulkan-headers \
libxcb \
libpipewire \
cli11 \

View file

@ -146,6 +146,7 @@ To disable: `-DSCREENCOPY=OFF`
Dependencies:
- `libdrm`
- `libgbm`
- `vulkan-headers` (build-time)
Specific protocols can also be disabled:
- `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1]

View file

@ -25,6 +25,7 @@ set shell id.
- Added support for IPC signal listeners.
- Added Quickshell version checking and version gated preprocessing.
- Added a way to detect if an icon is from the system icon theme or not.
- Added vulkan support to screencopy.
## Other Changes
@ -51,3 +52,4 @@ set shell id.
## Packaging Changes
`glib` and `polkit` have been added as dependencies when compiling with polkit agent support.
`vulkan-headers` has been added as a build-time dependency for screencopy (Vulkan backend support).

View file

@ -19,6 +19,7 @@
xorg,
libdrm,
libgbm ? null,
vulkan-headers,
pipewire,
pam,
polkit,
@ -77,7 +78,7 @@
++ lib.optional withJemalloc jemalloc
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
++ lib.optionals withWayland [ wayland wayland-protocols ]
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ]
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm vulkan-headers ]
++ lib.optional withX11 xorg.libxcb
++ lib.optional withPam pam
++ lib.optional withPipewire pipewire

View file

@ -1,6 +1,8 @@
find_package(PkgConfig REQUIRED)
pkg_check_modules(dmabuf-deps REQUIRED IMPORTED_TARGET libdrm gbm egl)
find_package(VulkanHeaders REQUIRED)
qt_add_library(quickshell-wayland-buffer STATIC
manager.cpp
dmabuf.cpp
@ -10,9 +12,10 @@ qt_add_library(quickshell-wayland-buffer STATIC
wl_proto(wlp-linux-dmabuf linux-dmabuf-v1 "${WAYLAND_PROTOCOLS}/stable/linux-dmabuf")
target_link_libraries(quickshell-wayland-buffer PRIVATE
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
Qt::Quick Qt::QuickPrivate Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
PkgConfig::dmabuf-deps
wlp-linux-dmabuf
Vulkan::Headers
)
qs_pch(quickshell-wayland-buffer SET large)

View file

@ -14,6 +14,7 @@
#include <fcntl.h>
#include <gbm.h>
#include <libdrm/drm_fourcc.h>
#include <private/qquickwindow_p.h>
#include <qcontainerfwd.h>
#include <qdebug.h>
#include <qlist.h>
@ -24,12 +25,17 @@
#include <qpair.h>
#include <qquickwindow.h>
#include <qscopedpointer.h>
#include <qsgrendererinterface.h>
#include <qsgtexture_platform.h>
#include <qvulkanfunctions.h>
#include <qvulkaninstance.h>
#include <qwayland-linux-dmabuf-v1.h>
#include <qwaylandclientextension.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <qtypes.h>
#include <vulkan/vulkan_core.h>
#include <wayland-client-protocol.h>
#include <wayland-linux-dmabuf-v1-client-protocol.h>
#include <wayland-util.h>
@ -48,6 +54,25 @@ QS_LOGGING_CATEGORY(logDmabuf, "quickshell.wayland.buffer.dmabuf", QtWarningMsg)
LinuxDmabufManager* MANAGER = nullptr; // NOLINT
VkFormat drmFormatToVkFormat(uint32_t drmFormat) {
// NOLINTBEGIN(bugprone-branch-clone): XRGB/ARGB intentionally map to the same VK format
switch (drmFormat) {
case DRM_FORMAT_ARGB8888: return VK_FORMAT_B8G8R8A8_UNORM;
case DRM_FORMAT_XRGB8888: return VK_FORMAT_B8G8R8A8_UNORM;
case DRM_FORMAT_ABGR8888: return VK_FORMAT_R8G8B8A8_UNORM;
case DRM_FORMAT_XBGR8888: return VK_FORMAT_R8G8B8A8_UNORM;
case DRM_FORMAT_ARGB2101010: return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
case DRM_FORMAT_XRGB2101010: return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
case DRM_FORMAT_ABGR2101010: return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
case DRM_FORMAT_XBGR2101010: return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
case DRM_FORMAT_ABGR16161616F: return VK_FORMAT_R16G16B16A16_SFLOAT;
case DRM_FORMAT_RGB565: return VK_FORMAT_R5G6B5_UNORM_PACK16;
case DRM_FORMAT_BGR565: return VK_FORMAT_B5G6R5_UNORM_PACK16;
default: return VK_FORMAT_UNDEFINED;
}
// NOLINTEND(bugprone-branch-clone)
}
} // namespace
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc) {
@ -532,6 +557,15 @@ bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const {
}
WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) const {
auto* ri = window->rendererInterface();
if (ri && ri->graphicsApi() == QSGRendererInterface::Vulkan) {
return this->createQsgTextureVulkan(window);
}
return this->createQsgTextureGl(window);
}
WlBufferQSGTexture* WlDmaBuffer::createQsgTextureGl(QQuickWindow* window) const {
static auto* glEGLImageTargetTexture2DOES = []() {
auto* fn = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(
eglGetProcAddress("glEGLImageTargetTexture2DOES")
@ -662,6 +696,291 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) const {
return tex;
}
WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) const {
auto* ri = window->rendererInterface();
auto* vkInst = window->vulkanInstance();
if (!vkInst) {
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: no QVulkanInstance.";
return nullptr;
}
auto* vkDevicePtr =
static_cast<VkDevice*>(ri->getResource(window, QSGRendererInterface::DeviceResource));
auto* vkPhysDevicePtr = static_cast<VkPhysicalDevice*>(
ri->getResource(window, QSGRendererInterface::PhysicalDeviceResource)
);
if (!vkDevicePtr || !vkPhysDevicePtr) {
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: could not get Vulkan device.";
return nullptr;
}
VkDevice device = *vkDevicePtr;
VkPhysicalDevice physDevice = *vkPhysDevicePtr;
auto* devFuncs = vkInst->deviceFunctions(device);
auto* instFuncs = vkInst->functions();
if (!devFuncs || !instFuncs) {
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: "
"could not get Vulkan functions.";
return nullptr;
}
auto getMemoryFdPropertiesKHR = reinterpret_cast<PFN_vkGetMemoryFdPropertiesKHR>(
instFuncs->vkGetDeviceProcAddr(device, "vkGetMemoryFdPropertiesKHR")
);
if (!getMemoryFdPropertiesKHR) {
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: "
"vkGetMemoryFdPropertiesKHR not available. "
"Missing VK_KHR_external_memory_fd extension.";
return nullptr;
}
const VkFormat vkFormat = drmFormatToVkFormat(this->format);
if (vkFormat == VK_FORMAT_UNDEFINED) {
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: unsupported DRM format"
<< FourCCStr(this->format);
return nullptr;
}
if (this->planeCount > 4) {
qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: too many planes"
<< this->planeCount;
return nullptr;
}
std::array<VkSubresourceLayout, 4> planeLayouts = {};
for (int i = 0; i < this->planeCount; ++i) {
planeLayouts[i].offset = this->planes[i].offset; // NOLINT
planeLayouts[i].rowPitch = this->planes[i].stride; // NOLINT
planeLayouts[i].size = 0;
planeLayouts[i].arrayPitch = 0;
planeLayouts[i].depthPitch = 0;
}
const bool useModifier = this->modifier != DRM_FORMAT_MOD_INVALID;
VkExternalMemoryImageCreateInfo externalInfo = {};
externalInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
VkImageDrmFormatModifierExplicitCreateInfoEXT modifierInfo = {};
modifierInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT;
modifierInfo.drmFormatModifier = this->modifier;
modifierInfo.drmFormatModifierPlaneCount = static_cast<uint32_t>(this->planeCount);
modifierInfo.pPlaneLayouts = planeLayouts.data();
if (useModifier) {
externalInfo.pNext = &modifierInfo;
}
VkImageCreateInfo imageInfo = {};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.pNext = &externalInfo;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.format = vkFormat;
imageInfo.extent = {.width = this->width, .height = this->height, .depth = 1};
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = useModifier ? VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT : VK_IMAGE_TILING_LINEAR;
imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImage image = VK_NULL_HANDLE;
VkResult result = devFuncs->vkCreateImage(device, &imageInfo, nullptr, &image);
if (result != VK_SUCCESS) {
qCWarning(logDmabuf) << "Failed to create VkImage for DMA-BUF import, result:" << result;
return nullptr;
}
VkDeviceMemory memory = VK_NULL_HANDLE;
// dup() is required because vkAllocateMemory with VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT
// takes ownership of the fd on succcess. Without dup, WlDmaBuffer would double-close.
const int dupFd = dup(this->planes[0].fd); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
if (dupFd < 0) {
qCWarning(logDmabuf) << "Failed to dup() fd for DMA-BUF import";
goto cleanup_fail; // NOLINT
}
{
VkMemoryRequirements memReqs = {};
devFuncs->vkGetImageMemoryRequirements(device, image, &memReqs);
VkMemoryFdPropertiesKHR fdProps = {};
fdProps.sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR;
result = getMemoryFdPropertiesKHR(
device,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
dupFd,
&fdProps
);
if (result != VK_SUCCESS) {
close(dupFd);
qCWarning(logDmabuf) << "vkGetMemoryFdPropertiesKHR failed, result:" << result;
goto cleanup_fail; // NOLINT
}
const uint32_t memTypeBits = memReqs.memoryTypeBits & fdProps.memoryTypeBits;
VkPhysicalDeviceMemoryProperties memProps = {};
instFuncs->vkGetPhysicalDeviceMemoryProperties(physDevice, &memProps);
uint32_t memTypeIndex = UINT32_MAX;
for (uint32_t j = 0; j < memProps.memoryTypeCount; ++j) {
if (memTypeBits & (1u << j)) {
memTypeIndex = j;
break;
}
}
if (memTypeIndex == UINT32_MAX) {
close(dupFd);
qCWarning(logDmabuf) << "No compatible memory type for DMA-BUF import";
goto cleanup_fail; // NOLINT
}
VkImportMemoryFdInfoKHR importInfo = {};
importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR;
importInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
importInfo.fd = dupFd;
VkMemoryDedicatedAllocateInfo dedicatedInfo = {};
dedicatedInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
dedicatedInfo.image = image;
dedicatedInfo.pNext = &importInfo;
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.pNext = &dedicatedInfo;
allocInfo.allocationSize = memReqs.size;
allocInfo.memoryTypeIndex = memTypeIndex;
result = devFuncs->vkAllocateMemory(device, &allocInfo, nullptr, &memory);
if (result != VK_SUCCESS) {
close(dupFd);
qCWarning(logDmabuf) << "vkAllocateMemory failed, result:" << result;
goto cleanup_fail; // NOLINT
}
result = devFuncs->vkBindImageMemory(device, image, memory, 0);
if (result != VK_SUCCESS) {
qCWarning(logDmabuf) << "vkBindImageMemory failed, result:" << result;
goto cleanup_fail; // NOLINT
}
}
{
// acquire the DMA-BUF from the foreign (compositor) queue and transition
// to shader-read layout. oldLayout must be GENERAL (not UNDEFINED) to
// preserve the DMA-BUF contents written by the external producer. Hopefully.
window->beginExternalCommands();
auto* cmdBufPtr = static_cast<VkCommandBuffer*>(
ri->getResource(window, QSGRendererInterface::CommandListResource)
);
if (cmdBufPtr && *cmdBufPtr) {
VkCommandBuffer cmdBuf = *cmdBufPtr;
// find the graphics queue family index for the ownrship transfer.
uint32_t graphicsQueueFamily = 0;
uint32_t queueFamilyCount = 0;
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(
physDevice, &queueFamilyCount, nullptr
);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(
physDevice, &queueFamilyCount, queueFamilies.data()
);
for (uint32_t i = 0; i < queueFamilyCount; ++i) {
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
graphicsQueueFamily = i;
break;
}
}
VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT;
barrier.dstQueueFamilyIndex = graphicsQueueFamily;
barrier.image = image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
devFuncs->vkCmdPipelineBarrier(
cmdBuf,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0,
0,
nullptr,
0,
nullptr,
1,
&barrier
);
}
window->endExternalCommands();
auto* qsgTexture = QQuickWindowPrivate::get(window)->createTextureFromNativeTexture(
reinterpret_cast<quint64>(image),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
static_cast<uint>(vkFormat),
QSize(static_cast<int>(this->width), static_cast<int>(this->height)),
{}
);
auto* tex = new WlDmaBufferVulkanQSGTexture(
devFuncs,
device,
image,
memory,
qsgTexture
);
qCDebug(logDmabuf) << "Created WlDmaBufferVulkanQSGTexture" << tex << "from" << this;
return tex;
}
cleanup_fail:
if (image != VK_NULL_HANDLE) {
devFuncs->vkDestroyImage(device, image, nullptr);
}
if (memory != VK_NULL_HANDLE) {
devFuncs->vkFreeMemory(device, memory, nullptr);
}
return nullptr;
}
WlDmaBufferVulkanQSGTexture::~WlDmaBufferVulkanQSGTexture() {
delete this->qsgTexture;
if (this->image != VK_NULL_HANDLE) {
this->devFuncs->vkDestroyImage(this->device, this->image, nullptr);
}
if (this->memory != VK_NULL_HANDLE) {
this->devFuncs->vkFreeMemory(this->device, this->memory, nullptr);
}
qCDebug(logDmabuf) << "WlDmaBufferVulkanQSGTexture" << this << "destroyed.";
}
WlDmaBufferQSGTexture::~WlDmaBufferQSGTexture() {
auto* context = QOpenGLContext::currentContext();
auto* display = context->nativeInterface<QNativeInterface::QEGLContext>()->display();

View file

@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <cstdint>
#include <EGL/egl.h>
@ -12,9 +13,11 @@
#include <qsize.h>
#include <qtclasshelpermacros.h>
#include <qtypes.h>
#include <qvulkanfunctions.h>
#include <qwayland-linux-dmabuf-v1.h>
#include <qwaylandclientextension.h>
#include <sys/types.h>
#include <vulkan/vulkan_core.h>
#include <wayland-linux-dmabuf-v1-client-protocol.h>
#include <wayland-util.h>
#include <xf86drm.h>
@ -114,6 +117,36 @@ private:
friend class WlDmaBuffer;
};
class WlDmaBufferVulkanQSGTexture: public WlBufferQSGTexture {
public:
~WlDmaBufferVulkanQSGTexture() override;
Q_DISABLE_COPY_MOVE(WlDmaBufferVulkanQSGTexture);
[[nodiscard]] QSGTexture* texture() const override { return this->qsgTexture; }
private:
WlDmaBufferVulkanQSGTexture(
QVulkanDeviceFunctions* devFuncs,
VkDevice device,
VkImage image,
VkDeviceMemory memory,
QSGTexture* qsgTexture
)
: devFuncs(devFuncs)
, device(device)
, image(image)
, memory(memory)
, qsgTexture(qsgTexture) {}
QVulkanDeviceFunctions* devFuncs = nullptr;
VkDevice device = VK_NULL_HANDLE;
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
QSGTexture* qsgTexture = nullptr;
friend class WlDmaBuffer;
};
class WlDmaBuffer: public WlBuffer {
public:
~WlDmaBuffer() override;
@ -151,6 +184,9 @@ private:
friend class LinuxDmabufManager;
friend QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);
[[nodiscard]] WlBufferQSGTexture* createQsgTextureGl(QQuickWindow* window) const;
[[nodiscard]] WlBufferQSGTexture* createQsgTextureVulkan(QQuickWindow* window) const;
};
QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);

View file

@ -13,6 +13,7 @@
#include <qqmlengine.h>
#include <qqmlinfo.h>
#include <qqmllist.h>
#include <qquickgraphicsconfiguration.h>
#include <qquickitem.h>
#include <qquickwindow.h>
#include <qregion.h>
@ -147,6 +148,15 @@ void ProxyWindowBase::ensureQWindow() {
this->window = nullptr; // createQQuickWindow may indirectly reference this->window
this->window = this->createQQuickWindow();
this->window->setFormat(format);
// needed for vulkan dmabuf import, qt ignores these if not applicable
auto graphicsConfig = this->window->graphicsConfiguration();
graphicsConfig.setDeviceExtensions({
"VK_KHR_external_memory_fd",
"VK_EXT_external_memory_dma_buf",
"VK_EXT_image_drm_format_modifier",
});
this->window->setGraphicsConfiguration(graphicsConfig);
}
void ProxyWindowBase::createWindow() {