From 41242ae392e67f77839d7d5bf41d67ed98573c5d Mon Sep 17 00:00:00 2001 From: guoyao Date: Mon, 13 Jul 2026 15:25:49 +0800 Subject: [PATCH] fix: import dmabuf as vulkan render target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Port wlroots 0.20 vulkan_import_dmabuf(for_render=true) to waylib 2. Replace wlr_texture-based VkImage in acquireRenderTarget with direct dmabuf import so the buffer is a valid render target 3. New VkImage gets VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT for valid render target usage instead of reusing a wlr_texture 4. Add VkDmabufImage lifecycle management in BufferData destructor 5. Fix BufferData memory leak in onBufferDestroy 6. Keep the verbatim wlroots 0.20 formats[] table; guard the 8 DRM_FORMAT_* entries added in libdrm >= 2.4.129 with #if defined() so it compiles on CI's older libdrm (2.4.123) 7. Use Q_BYTE_ORDER == Q_LITTLE_ENDIAN instead of a custom endian macro 8. Make isYCbCr, kVkFormats and vkFormatFromDrm constexpr 9. Use QT_WARNING_PUSH/POP instead of pragma GCC diagnostic 10. Return an empty target when get_dmabuf fails (else branch) Log: Fixed Vulkan render target to use a proper color-attachment VkImage Influence: 1. Test Vulkan rendering with various dmabuf formats 2. Verify render target creation without validation errors 3. Test buffer creation and destruction for memory leaks 4. Verify GL and pixman rendering paths are unaffected 5. Confirm the build passes on libdrm 2.4.123 and >= 2.4.129 fix: 使用 dmabuf 导入创建 vulkan 渲染目标 1. 将 wlroots 0.20 的 vulkan_import_dmabuf(for_render=true) 移植到 waylib 2. 在 acquireRenderTarget 中用直接 dmabuf 导入替代 wlr_texture 创建 VkImage,使缓冲可作为有效渲染目标 3. 新建 VkImage 带 VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,替代复用 wlr_texture 的做法,获得合法的渲染目标用途 4. 在 BufferData 析构中添加 VkDmabufImage 生命周期管理 5. 修复 onBufferDestroy 中 BufferData 的内存泄漏 6. 保留 wlroots 0.20 原始 formats[] 表;对 libdrm >= 2.4.129 新增的 8 个 DRM_FORMAT_* 条目用 #if defined() 守卫,使其在 CI 较旧的 libdrm (2.4.123) 上也能编译 7. 用 Q_BYTE_ORDER == Q_LITTLE_ENDIAN 替代自定义大小端宏 8. 将 isYCbCr、kVkFormats、vkFormatFromDrm 改为 constexpr 9. 用 QT_WARNING_PUSH/POP 替代 pragma GCC diagnostic 10. get_dmabuf 失败时返回空目标(else 分支) Log: 修复 Vulkan 渲染目标,使用合法的颜色附件 VkImage Influence: 1. 测试 Vulkan 渲染路径下各种 dmabuf 格式 2. 验证渲染目标创建成功,无 validation 错误 3. 测试 buffer 创建和销毁,确认无内存泄漏 4. 验证 GL 和 pixman 渲染路径不受影响 5. 确认在 libdrm 2.4.123 与 >= 2.4.129 上均能编译通过 --- .../workflows/treeland-archlinux-build.yml | 2 +- waylib/src/server/CMakeLists.txt | 8 + .../qtquick/private/wvulkandmabufimport.cpp | 597 ++++++++++++++++++ .../qtquick/private/wvulkandmabufimport_p.h | 49 ++ waylib/src/server/qtquick/wrenderhelper.cpp | 36 +- 5 files changed, 685 insertions(+), 7 deletions(-) create mode 100644 waylib/src/server/qtquick/private/wvulkandmabufimport.cpp create mode 100644 waylib/src/server/qtquick/private/wvulkandmabufimport_p.h diff --git a/.github/workflows/treeland-archlinux-build.yml b/.github/workflows/treeland-archlinux-build.yml index 93a4baeb02..986d790c40 100644 --- a/.github/workflows/treeland-archlinux-build.yml +++ b/.github/workflows/treeland-archlinux-build.yml @@ -34,7 +34,7 @@ jobs: - name: Install graphics and wayland dependencies run: | # Core graphics and wayland dependencies (matching qwlroots and waylib builds) - pacman -S --noconfirm --noprogressbar pixman vulkan-headers wayland wayland-protocols wlr-protocols libxcb mesa + pacman -S --noconfirm --noprogressbar pixman vulkan-headers wayland wayland-protocols wlr-protocols libxcb pacman -S --noconfirm --noprogressbar wlroots0.19 libinput pam systemd-libs jemalloc gcc-libs glibc - name: Install additional waylib/qwlroots dependencies diff --git a/waylib/src/server/CMakeLists.txt b/waylib/src/server/CMakeLists.txt index 90111db5fb..d697205318 100644 --- a/waylib/src/server/CMakeLists.txt +++ b/waylib/src/server/CMakeLists.txt @@ -31,6 +31,9 @@ pkg_search_module(PIXMAN REQUIRED IMPORTED_TARGET pixman-1) pkg_search_module(XKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon) pkg_search_module(XCB REQUIRED IMPORTED_TARGET xcb) pkg_search_module(EGL REQUIRED IMPORTED_TARGET egl) +# Optional: wvulkandmabufimport.cpp calls Vulkan loader functions directly. +# Only linked when present; the code is compiled out without ENABLE_VULKAN_RENDER. +pkg_search_module(VULKAN IMPORTED_TARGET vulkan) add_compile_definitions(WLR_PRIVATE=) @@ -151,6 +154,7 @@ set(SOURCES qtquick/private/wqmlhelper.cpp qtquick/private/wbufferrenderer.cpp qtquick/private/wrenderbuffernode.cpp + qtquick/private/wvulkandmabufimport.cpp ${WAYLAND_PROTOCOLS_OUTPUTDIR}/text-input-unstable-v1-protocol.c ${WAYLAND_PROTOCOLS_OUTPUTDIR}/text-input-unstable-v2-protocol.c @@ -332,6 +336,7 @@ set(PRIVATE_HEADERS qtquick/private/wbufferrenderer_p.h qtquick/private/wrenderbuffernode_p.h qtquick/private/wsurfaceitem_p.h + qtquick/private/wvulkandmabufimport_p.h ${WAYLAND_PROTOCOLS_OUTPUTDIR}/text-input-unstable-v1-protocol.h ${WAYLAND_PROTOCOLS_OUTPUTDIR}/text-input-unstable-v2-protocol.h @@ -407,6 +412,9 @@ target_link_libraries(${TARGET} PkgConfig::XCB PkgConfig::EGL ) +if(TARGET PkgConfig::VULKAN) + target_link_libraries(${TARGET} PRIVATE PkgConfig::VULKAN) +endif() target_link_libraries(${TARGET} PUBLIC diff --git a/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp b/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp new file mode 100644 index 0000000000..e99f4c5c75 --- /dev/null +++ b/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp @@ -0,0 +1,597 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// This file adapts wlroots 0.20's internal vulkan_import_dmabuf() and its +// helpers so waylib can import a dmabuf as a VkImage with render-target usage +// (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT). The wlroots function lives in a +// private, uninstalled header (include/render/vulkan.h) and reaches into +// private renderer structs (wlr_vk_device::format_props, ::api). To avoid +// linking against those internals, the queries below use only the public +// Vulkan and public wlroots renderer API. The logic mirrors wlroots 0.20 +// (render/vulkan/texture.c, pixel_format.c, util.c) so it can be updated by +// diffing against upstream. + +#include "wvulkandmabufimport_p.h" + +#ifdef ENABLE_VULKAN_RENDER + +#include "wayliblogging.h" + +#include +#include +#include + +#include + +#include + +QT_WARNING_PUSH +QT_WARNING_DISABLE_GCC("-Wmissing-field-initializers") + +WAYLIB_SERVER_BEGIN_NAMESPACE + +namespace { + +// DRM <-> VkFormat mapping for render-target import (adapted from wlroots +// 0.20 render/vulkan/pixel_format.c). +struct WlrVkFormat { + uint32_t drm; + VkFormat vk; + VkFormat vk_srgb; // sRGB variant, or 0 if nonexistent +}; + +// Copy from wlroots 0.20 render/vulkan/pixel_format.c: the DRM <-> VkFormat +// mapping table. Only the formats wlroots knows about are listed; the GPU may +// support a subset. +const WlrVkFormat kVkFormats[] = { + // Vulkan non-packed 8-bits-per-channel formats have an inverted channel + // order compared to the DRM formats, because DRM format channel order + // is little-endian while Vulkan format channel order is in memory byte + // order. + {.drm = DRM_FORMAT_R8, .vk = VK_FORMAT_R8_UNORM, .vk_srgb = VK_FORMAT_R8_SRGB}, +#if defined(DRM_FORMAT_R16F) + {.drm = DRM_FORMAT_R16F, .vk = VK_FORMAT_R16_SFLOAT, .vk_srgb = VK_FORMAT_UNDEFINED}, +#endif +#if defined(DRM_FORMAT_R32F) + {.drm = DRM_FORMAT_R32F, .vk = VK_FORMAT_R32_SFLOAT, .vk_srgb = VK_FORMAT_UNDEFINED}, +#endif + {.drm = DRM_FORMAT_GR88, .vk = VK_FORMAT_R8G8_UNORM, .vk_srgb = VK_FORMAT_R8G8_SRGB}, +#if defined(DRM_FORMAT_GR1616F) + {.drm = DRM_FORMAT_GR1616F, .vk = VK_FORMAT_R16G16_SFLOAT, .vk_srgb = VK_FORMAT_UNDEFINED}, +#endif +#if defined(DRM_FORMAT_GR3232F) + {.drm = DRM_FORMAT_GR3232F, .vk = VK_FORMAT_R32G32_SFLOAT, .vk_srgb = VK_FORMAT_UNDEFINED}, +#endif + {.drm = DRM_FORMAT_RGB888, .vk = VK_FORMAT_B8G8R8_UNORM, .vk_srgb = VK_FORMAT_B8G8R8_SRGB}, + {.drm = DRM_FORMAT_BGR888, .vk = VK_FORMAT_R8G8B8_UNORM, .vk_srgb = VK_FORMAT_R8G8B8_SRGB}, + {.drm = DRM_FORMAT_XRGB8888, .vk = VK_FORMAT_B8G8R8A8_UNORM, .vk_srgb = VK_FORMAT_B8G8R8A8_SRGB}, + {.drm = DRM_FORMAT_XBGR8888, .vk = VK_FORMAT_R8G8B8A8_UNORM, .vk_srgb = VK_FORMAT_R8G8B8A8_SRGB}, + // The Vulkan _SRGB formats correspond to unpremultiplied alpha, but + // the Wayland protocol specifies premultiplied alpha on electrical values + {.drm = DRM_FORMAT_ARGB8888, .vk = VK_FORMAT_B8G8R8A8_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_ABGR8888, .vk = VK_FORMAT_R8G8B8A8_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, + // Vulkan packed formats have the same channel order as DRM formats on + // little endian systems. +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + {.drm = DRM_FORMAT_RGBA4444, .vk = VK_FORMAT_R4G4B4A4_UNORM_PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_RGBX4444, .vk = VK_FORMAT_R4G4B4A4_UNORM_PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_BGRA4444, .vk = VK_FORMAT_B4G4R4A4_UNORM_PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_BGRX4444, .vk = VK_FORMAT_B4G4R4A4_UNORM_PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_RGB565, .vk = VK_FORMAT_R5G6B5_UNORM_PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_BGR565, .vk = VK_FORMAT_B5G6R5_UNORM_PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_RGBA5551, .vk = VK_FORMAT_R5G5B5A1_UNORM_PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_RGBX5551, .vk = VK_FORMAT_R5G5B5A1_UNORM_PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_BGRA5551, .vk = VK_FORMAT_B5G5R5A1_UNORM_PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_BGRX5551, .vk = VK_FORMAT_B5G5R5A1_UNORM_PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_ARGB1555, .vk = VK_FORMAT_A1R5G5B5_UNORM_PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_XRGB1555, .vk = VK_FORMAT_A1R5G5B5_UNORM_PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_ARGB2101010, .vk = VK_FORMAT_A2R10G10B10_UNORM_PACK32, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_XRGB2101010, .vk = VK_FORMAT_A2R10G10B10_UNORM_PACK32, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_ABGR2101010, .vk = VK_FORMAT_A2B10G10R10_UNORM_PACK32, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_XBGR2101010, .vk = VK_FORMAT_A2B10G10R10_UNORM_PACK32, .vk_srgb = VK_FORMAT_UNDEFINED}, +#endif + + // Vulkan 16-bits-per-channel formats have an inverted channel order + // compared to DRM formats, just like the 8-bits-per-channel ones. + // On little endian systems the memory representation of each channel + // matches the DRM formats'. +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN +#if defined(DRM_FORMAT_BGR161616) + {.drm = DRM_FORMAT_BGR161616, .vk = VK_FORMAT_R16G16B16_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, +#endif +#if defined(DRM_FORMAT_BGR161616F) + {.drm = DRM_FORMAT_BGR161616F, .vk = VK_FORMAT_R16G16B16_SFLOAT, .vk_srgb = VK_FORMAT_UNDEFINED}, +#endif + {.drm = DRM_FORMAT_ABGR16161616, .vk = VK_FORMAT_R16G16B16A16_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_XBGR16161616, .vk = VK_FORMAT_R16G16B16A16_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_ABGR16161616F, .vk = VK_FORMAT_R16G16B16A16_SFLOAT, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_XBGR16161616F, .vk = VK_FORMAT_R16G16B16A16_SFLOAT, .vk_srgb = VK_FORMAT_UNDEFINED}, +#if defined(DRM_FORMAT_BGR323232F) + {.drm = DRM_FORMAT_BGR323232F, .vk = VK_FORMAT_R32G32B32_SFLOAT, .vk_srgb = VK_FORMAT_UNDEFINED}, +#endif +#if defined(DRM_FORMAT_ABGR32323232F) + {.drm = DRM_FORMAT_ABGR32323232F, .vk = VK_FORMAT_R32G32B32A32_SFLOAT, .vk_srgb = VK_FORMAT_UNDEFINED}, +#endif +#endif + + // YCbCr formats + // R -> V, G -> Y, B -> U + // 420 -> 2x2 subsampled, 422 -> 2x1 subsampled, 444 -> non-subsampled + {.drm = DRM_FORMAT_UYVY, .vk = VK_FORMAT_B8G8R8G8_422_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_YUYV, .vk = VK_FORMAT_G8B8G8R8_422_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_NV12, .vk = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_NV16, .vk = VK_FORMAT_G8_B8R8_2PLANE_422_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_YUV420, .vk = VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_YUV422, .vk = VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_YUV444, .vk = VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, + // 3PACK16 formats split the memory in three 16-bit words, so they have an + // inverted channel order compared to DRM formats. +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + {.drm = DRM_FORMAT_P010, .vk = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_P210, .vk = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_P012, .vk = VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_P016, .vk = VK_FORMAT_G16_B16R16_2PLANE_420_UNORM, .vk_srgb = VK_FORMAT_UNDEFINED}, + {.drm = DRM_FORMAT_Q410, .vk = VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, .vk_srgb = VK_FORMAT_UNDEFINED}, +#endif + // TODO: add DRM_FORMAT_NV24/VK_FORMAT_G8_B8R8_2PLANE_444_UNORM (requires + // Vulkan 1.3 or VK_EXT_ycbcr_2plane_444_formats) +}; + +const WlrVkFormat *vkFormatFromDrm(uint32_t drm) +{ + for (const WlrVkFormat &f : kVkFormats) { + if (f.drm == drm) + return &f; + } + return nullptr; +} + +constexpr bool isYCbCr(uint32_t drm) +{ + switch (drm) { + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV24: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV61: + case DRM_FORMAT_NV42: + case DRM_FORMAT_P010: + case DRM_FORMAT_P012: + case DRM_FORMAT_P016: + case DRM_FORMAT_P030: + case DRM_FORMAT_P210: + case DRM_FORMAT_Q401: + case DRM_FORMAT_Q410: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YUV444: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VUY101010: + case DRM_FORMAT_XYUV8888: + case DRM_FORMAT_Y210: + case DRM_FORMAT_Y212: + case DRM_FORMAT_Y216: + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_VYUY: + return true; + default: + return false; + } +} + +// Copy from wlroots 0.20 render/vulkan/texture.c: a dmabuf is disjoint when +// its planes live in different dmabuf fds (each plane needs its own +// VkDeviceMemory binding in that case). +bool isDmabufDisjoint(const wlr_dmabuf_attributes *attribs) +{ + if (attribs->n_planes == 1) + return false; + + struct stat firstStat; + if (fstat(attribs->fd[0], &firstStat) != 0) { + qCWarning(lcWlRenderHelper, "isDmabufDisjoint: fstat failed"); + return true; + } + + for (int i = 1; i < attribs->n_planes; i++) { + struct stat planeStat; + if (fstat(attribs->fd[i], &planeStat) != 0) { + qCWarning(lcWlRenderHelper, "isDmabufDisjoint: fstat failed"); + return true; + } + if (firstStat.st_ino != planeStat.st_ino) + return true; + } + + return false; +} + +VkImageAspectFlagBits memPlaneAspect(unsigned i) +{ + switch (i) { + case 0: return VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT; + case 1: return VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT; + case 2: return VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT; + case 3: return VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT; + default: Q_UNREACHABLE(); break; + } +} + +// Copy from wlroots 0.20 render/vulkan/util.c: find a memory type that +// satisfies req_bits and the requested property flags. +int findMemType(VkPhysicalDevice phdev, VkMemoryPropertyFlags flags, uint32_t reqBits) +{ + VkPhysicalDeviceMemoryProperties props; + vkGetPhysicalDeviceMemoryProperties(phdev, &props); + + for (unsigned i = 0; i < props.memoryTypeCount; ++i) { + if (reqBits & (1u << i)) { + if ((props.memoryTypes[i].propertyFlags & flags) == flags) + return static_cast(i); + } + } + + return -1; +} + +// Render-target usage and the format features wlroots requires for it. +constexpr VkImageUsageFlags kRenderUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +constexpr VkFormatFeatureFlags kRenderFeatures = + VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT; + +// Adapted from wlroots 0.20 render/vulkan/pixel_format.c +// (query_modifier_usage_support): verify that a specific modifier can host a +// render-target image importable from a dmabuf, and report its max extent. +bool queryModifierRenderSupport(VkPhysicalDevice phdev, VkFormat vkFormat, + uint64_t modifier, VkExtent2D *outMaxExtent) +{ + VkFormat viewFormats[1] = {vkFormat}; + VkImageFormatListCreateInfoKHR listInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR, + .viewFormatCount = 1, + .pViewFormats = viewFormats, + }; + VkPhysicalDeviceImageDrmFormatModifierInfoEXT modInfo = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT, + .pNext = &listInfo, + .drmFormatModifier = modifier, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + }; + VkPhysicalDeviceExternalImageFormatInfo extInfo = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO, + .pNext = &modInfo, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + VkPhysicalDeviceImageFormatInfo2 fmtInfo = {}; + fmtInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2; + fmtInfo.pNext = &extInfo; + fmtInfo.type = VK_IMAGE_TYPE_2D; + fmtInfo.format = vkFormat; + fmtInfo.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + fmtInfo.usage = kRenderUsage; + fmtInfo.flags = 0; + VkExternalImageFormatProperties extProps = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES, + }; + VkImageFormatProperties2 imgProps = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, + .pNext = &extProps, + }; + + VkResult res = vkGetPhysicalDeviceImageFormatProperties2(phdev, &fmtInfo, &imgProps); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper, + "vkGetPhysicalDeviceImageFormatProperties2 failed for render import (res=%d)", res); + return false; + } + + if (!(extProps.externalMemoryProperties.externalMemoryFeatures + & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT)) { + qCWarning(lcWlRenderHelper, "dmabuf modifier 0x%016llx is not importable", + static_cast(modifier)); + return false; + } + + outMaxExtent->width = imgProps.imageFormatProperties.maxExtent.width; + outMaxExtent->height = imgProps.imageFormatProperties.maxExtent.height; + return true; +} + +// Combined query for the modifier matching the dmabuf: mirrors wlroots 0.20 +// vulkan_format_props_from_drm + vulkan_format_props_find_modifier + +// query_modifier_support, but only for the single modifier we need. Fills the +// modifier plane count, tiling features and max extent. +struct ModifierQueryResult { + uint32_t planeCount = 0; + VkFormatFeatureFlags tilingFeatures = 0; + VkExtent2D maxExtent = {}; + bool supported = false; +}; + +ModifierQueryResult findRenderModifier(VkPhysicalDevice phdev, VkFormat vkFormat, + uint64_t modifier) +{ + ModifierQueryResult result; + + VkDrmFormatModifierPropertiesListEXT modList = { + .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT, + }; + VkFormatProperties2 fmtProps = { + .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2, + .pNext = &modList, + }; + vkGetPhysicalDeviceFormatProperties2(phdev, vkFormat, &fmtProps); + + if (modList.drmFormatModifierCount == 0) { + qCWarning(lcWlRenderHelper, "no drm format modifiers for vk format %d", vkFormat); + return result; + } + + QList mods(modList.drmFormatModifierCount); + modList.pDrmFormatModifierProperties = mods.data(); + vkGetPhysicalDeviceFormatProperties2(phdev, vkFormat, &fmtProps); + + for (uint32_t i = 0; i < modList.drmFormatModifierCount; ++i) { + const VkDrmFormatModifierPropertiesEXT &m = modList.pDrmFormatModifierProperties[i]; + if (m.drmFormatModifier != modifier) + continue; + + if ((m.drmFormatModifierTilingFeatures & kRenderFeatures) != kRenderFeatures) { + qCWarning(lcWlRenderHelper, + "modifier 0x%016llx lacks render features for vk format %d", + static_cast(modifier), vkFormat); + return result; + } + + VkExtent2D maxExtent = {}; + if (!queryModifierRenderSupport(phdev, vkFormat, modifier, &maxExtent)) + return result; + + result.planeCount = m.drmFormatModifierPlaneCount; + result.tilingFeatures = m.drmFormatModifierTilingFeatures; + result.maxExtent = maxExtent; + result.supported = true; + return result; + } + + qCWarning(lcWlRenderHelper, "modifier 0x%016llx not advertised for vk format %d", + static_cast(modifier), vkFormat); + return result; +} + +} // namespace + +VkDmabufImage vulkanImportDmabufForRender(VkPhysicalDevice physicalDevice, VkDevice device, + const wlr_dmabuf_attributes *attribs) +{ + VkDmabufImage result; + result.device = device; + + const WlrVkFormat *fmt = vkFormatFromDrm(attribs->format); + if (!fmt) { + qCWarning(lcWlRenderHelper, "Unsupported pixel format 0x%08X for vulkan render import", + attribs->format); + return result; + } + + if (isYCbCr(attribs->format)) { + qCWarning(lcWlRenderHelper, "YCbCr format 0x%08X cannot be used as a render target", + attribs->format); + return result; + } + + const uint32_t planeCount = attribs->n_planes; + if (planeCount >= WLR_DMABUF_MAX_PLANES) { + qCWarning(lcWlRenderHelper, "Too many dmabuf planes (%u)", planeCount); + return result; + } + + ModifierQueryResult mod = findRenderModifier(physicalDevice, fmt->vk, attribs->modifier); + if (!mod.supported) + return result; + + if (static_cast(attribs->width) > mod.maxExtent.width + || static_cast(attribs->height) > mod.maxExtent.height) { + qCWarning(lcWlRenderHelper, + "dmabuf too large to import (%dx%d > %ux%u)", + attribs->width, attribs->height, mod.maxExtent.width, mod.maxExtent.height); + return result; + } + + if (mod.planeCount != planeCount) { + qCWarning(lcWlRenderHelper, "Plane count mismatch (dmabuf %u, format %u)", + planeCount, mod.planeCount); + return result; + } + + const bool disjoint = isDmabufDisjoint(attribs); + if (disjoint && !(mod.tilingFeatures & VK_FORMAT_FEATURE_DISJOINT_BIT)) { + qCWarning(lcWlRenderHelper, "Format/modifier does not support disjoint images"); + return result; + } + + const VkExternalMemoryHandleTypeFlagBits handleType = + VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; + + VkImageCreateInfo imgInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .format = fmt->vk, + .extent = {static_cast(attribs->width), static_cast(attribs->height), 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .usage = kRenderUsage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + if (disjoint) + imgInfo.flags = VK_IMAGE_CREATE_DISJOINT_BIT; + + VkExternalMemoryImageCreateInfo extImg = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .handleTypes = handleType, + }; + imgInfo.pNext = &extImg; + + VkSubresourceLayout planeLayouts[WLR_DMABUF_MAX_PLANES] = {}; + imgInfo.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + for (unsigned i = 0; i < planeCount; ++i) { + planeLayouts[i].offset = attribs->offset[i]; + planeLayouts[i].rowPitch = attribs->stride[i]; + planeLayouts[i].size = 0; + } + + VkImageDrmFormatModifierExplicitCreateInfoEXT modInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT, + .drmFormatModifier = attribs->modifier, + .drmFormatModifierPlaneCount = planeCount, + .pPlaneLayouts = planeLayouts, + }; + extImg.pNext = &modInfo; + + VkImage image = VK_NULL_HANDLE; + VkResult res = vkCreateImage(device, &imgInfo, nullptr, &image); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper, "vkCreateImage failed (res=%d)", res); + return result; + } + + // vkGetMemoryFdPropertiesKHR is an extension function wlroots loads into + // its private device struct; load it ourselves via the device command table. + PFN_vkGetMemoryFdPropertiesKHR getMemoryFdProperties = + reinterpret_cast( + vkGetDeviceProcAddr(device, "vkGetMemoryFdPropertiesKHR")); + if (!getMemoryFdProperties) { + qCWarning(lcWlRenderHelper, "vkGetMemoryFdPropertiesKHR not available"); + vkDestroyImage(device, image, nullptr); + return result; + } + + const unsigned memCount = disjoint ? planeCount : 1u; + VkBindImageMemoryInfo bindInfo[WLR_DMABUF_MAX_PLANES] = {}; + VkBindImagePlaneMemoryInfo planeInfo[WLR_DMABUF_MAX_PLANES] = {}; + + for (unsigned i = 0; i < memCount; ++i) { + VkMemoryFdPropertiesKHR fdProps = { + .sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR, + }; + res = getMemoryFdProperties(device, handleType, attribs->fd[i], &fdProps); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper, "vkGetMemoryFdPropertiesKHR failed (res=%d)", res); + goto error; + } + + VkImageMemoryRequirementsInfo2 memReqInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2, + .image = image, + }; + + VkImagePlaneMemoryRequirementsInfo planeReq; + if (disjoint) { + planeReq = { + .sType = VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO, + .planeAspect = memPlaneAspect(i), + }; + memReqInfo.pNext = &planeReq; + } + + VkMemoryRequirements2 memReq = { + .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, + }; + vkGetImageMemoryRequirements2(device, &memReqInfo, &memReq); + + int memType = findMemType(physicalDevice, 0, + memReq.memoryRequirements.memoryTypeBits & fdProps.memoryTypeBits); + if (memType < 0) { + qCWarning(lcWlRenderHelper, "no valid memory type index for plane %u", i); + goto error; + } + + // Importing a dmabuf fd transfers ownership of the fd to Vulkan, so + // duplicate it: the attribs still belong to the caller and Vulkan will + // close the duplicate on vkFreeMemory. + int dupFd = fcntl(attribs->fd[i], F_DUPFD_CLOEXEC, 0); + if (dupFd < 0) { + qCWarning(lcWlRenderHelper, "fcntl(F_DUPFD_CLOEXEC) failed for plane %u", i); + goto error; + } + + VkMemoryAllocateInfo memAlloc = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memReq.memoryRequirements.size, + .memoryTypeIndex = static_cast(memType), + }; + VkImportMemoryFdInfoKHR importInfo = { + .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, + .handleType = handleType, + .fd = dupFd, + }; + memAlloc.pNext = &importInfo; + VkMemoryDedicatedAllocateInfo dedicated = { + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, + .image = image, + }; + importInfo.pNext = &dedicated; + + res = vkAllocateMemory(device, &memAlloc, nullptr, &result.memories[i]); + if (res != VK_SUCCESS) { + close(dupFd); + qCWarning(lcWlRenderHelper, "vkAllocateMemory failed for plane %u (res=%d)", i, res); + goto error; + } + + ++result.memoryCount; + + bindInfo[i].sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; + bindInfo[i].image = image; + bindInfo[i].memory = result.memories[i]; + bindInfo[i].memoryOffset = 0; + + if (disjoint) { + planeInfo[i].sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO; + planeInfo[i].planeAspect = planeReq.planeAspect; + bindInfo[i].pNext = &planeInfo[i]; + } + } + + res = vkBindImageMemory2(device, memCount, bindInfo); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper, "vkBindImageMemory2 failed (res=%d)", res); + goto error; + } + + result.image = image; + result.format = fmt->vk; + return result; + +error: + vkDestroyImage(device, image, nullptr); + for (uint32_t i = 0; i < result.memoryCount; ++i) { + vkFreeMemory(device, result.memories[i], nullptr); + result.memories[i] = VK_NULL_HANDLE; + } + result.memoryCount = 0; + return result; +} + +void vulkanReleaseDmabufImage(VkDmabufImage &import) +{ + if (import.image != VK_NULL_HANDLE) { + vkDestroyImage(import.device, import.image, nullptr); + import.image = VK_NULL_HANDLE; + } + for (uint32_t i = 0; i < import.memoryCount; ++i) { + if (import.memories[i] != VK_NULL_HANDLE) { + vkFreeMemory(import.device, import.memories[i], nullptr); + import.memories[i] = VK_NULL_HANDLE; + } + } + import.memoryCount = 0; +} + +WAYLIB_SERVER_END_NAMESPACE + +QT_WARNING_POP +#endif // ENABLE_VULKAN_RENDER diff --git a/waylib/src/server/qtquick/private/wvulkandmabufimport_p.h b/waylib/src/server/qtquick/private/wvulkandmabufimport_p.h new file mode 100644 index 0000000000..3ce688cbd6 --- /dev/null +++ b/waylib/src/server/qtquick/private/wvulkandmabufimport_p.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include + +struct wlr_dmabuf_attributes; + +#ifdef ENABLE_VULKAN_RENDER +#include + +#include +#endif + +WAYLIB_SERVER_BEGIN_NAMESPACE + +#ifdef ENABLE_VULKAN_RENDER + +// Holds a Vulkan image imported from a dmabuf so it can be used as a render +// target (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT). Unlike the image produced by +// wlr_texture_from_buffer (which is sampled-only), this image is suitable for +// QRhiTextureRenderTarget. The caller owns the resources and must release them +// with vulkanReleaseDmabufImage() before the VkDevice is destroyed. +struct VkDmabufImage { + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memories[WLR_DMABUF_MAX_PLANES] = {}; + uint32_t memoryCount = 0; + VkFormat format = VK_FORMAT_UNDEFINED; + VkDevice device = VK_NULL_HANDLE; + + bool isNull() const { return image == VK_NULL_HANDLE; } +}; + +// Imports a dmabuf as a VkImage with render-target usage. Returns a non-null +// image on success; on failure image is VK_NULL_HANDLE and no resources are +// leaked. Implementation is adapted from wlroots 0.20 vulkan_import_dmabuf +// (render/vulkan/texture.c) but uses only the public Vulkan and wlroots API, +// so it does not depend on wlroots' private renderer structs. +VkDmabufImage vulkanImportDmabufForRender(VkPhysicalDevice physicalDevice, + VkDevice device, + const wlr_dmabuf_attributes *attribs); + +// Releases the image and memory held by an imported dmabuf image. Safe to call +// on a default-constructed (null) instance. +void vulkanReleaseDmabufImage(VkDmabufImage &import); +#endif // ENABLE_VULKAN_RENDER + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/qtquick/wrenderhelper.cpp b/waylib/src/server/qtquick/wrenderhelper.cpp index 0f00631593..5fabe24734 100644 --- a/waylib/src/server/qtquick/wrenderhelper.cpp +++ b/waylib/src/server/qtquick/wrenderhelper.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2023 JiDe Zhang . +// Copyright (C) 2023-2026 JiDe Zhang . // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "wrenderhelper.h" @@ -6,6 +6,7 @@ #include "wayliblogging.h" #include "private/wqmlhelper_p.h" #include "private/wglobal_p.h" +#include "private/wvulkandmabufimport_p.h" #include #include @@ -58,6 +59,9 @@ struct Q_DECL_HIDDEN BufferData { ~BufferData() { resetWindowRenderTarget(); +#ifdef ENABLE_VULKAN_RENDER + vulkanReleaseDmabufImage(vkDmabufImage); +#endif } qw_buffer *buffer = nullptr; @@ -65,6 +69,11 @@ struct Q_DECL_HIDDEN BufferData { WImageRenderTarget paintDevice; QQuickRenderTarget renderTarget; QQuickWindowRenderTarget windowRenderTarget; +#ifdef ENABLE_VULKAN_RENDER + // Vulkan image imported with COLOR_ATTACHMENT usage for the render target. + // Released after the RHI wrapper in ~BufferData(). + VkDmabufImage vkDmabufImage; +#endif inline void resetWindowRenderTarget() { #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) @@ -269,6 +278,7 @@ void WRenderHelperPrivate::onBufferDestroy() if (lastBuffer == data) lastBuffer = nullptr; buffers.removeAt(i); + delete data; break; } } @@ -553,11 +563,11 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q std::unique_ptr bufferData(new BufferData); bufferData->buffer = buffer; - auto texture = qw_texture::from_buffer(*d->renderer, *buffer); QQuickRenderTarget rt; if (wlr_renderer_is_pixman(d->renderer->handle())) { + std::unique_ptr texture(qw_texture::from_buffer(*d->renderer, *buffer)); pixman_image_t *image = wlr_pixman_texture_get_image(texture->handle()); void *data = pixman_image_get_data(image); if (bufferData->paintDevice.constBits() != data) @@ -567,12 +577,27 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q } #ifdef ENABLE_VULKAN_RENDER else if (wlr_renderer_is_vk(d->renderer->handle())) { - wlr_vk_image_attribs attribs; - wlr_vk_texture_get_image_attribs(texture->handle(), &attribs); - rt = QQuickRenderTarget::fromVulkanImage(attribs.image, attribs.layout, attribs.format, d->size); + // wlr_texture_from_buffer creates a sampled-only VkImage that cannot be + // used as a color attachment. Import the dmabuf directly with + // COLOR_ATTACHMENT usage so QRhiTextureRenderTarget can render into it. + wlr_dmabuf_attributes dmabuf; + if (buffer->get_dmabuf(&dmabuf)) { + wlr_renderer *renderer = d->renderer->handle(); + bufferData->vkDmabufImage = vulkanImportDmabufForRender( + wlr_vk_renderer_get_physical_device(renderer), + wlr_vk_renderer_get_device(renderer), &dmabuf); + if (!bufferData->vkDmabufImage.isNull()) { + rt = QQuickRenderTarget::fromVulkanImage( + bufferData->vkDmabufImage.image, VK_IMAGE_LAYOUT_UNDEFINED, + bufferData->vkDmabufImage.format, d->size); + } + } else { + return {}; + } } #endif else if (wlr_renderer_is_gles2(d->renderer->handle())) { + std::unique_ptr texture(qw_texture::from_buffer(*d->renderer, *buffer)); wlr_gles2_texture_attribs attribs; wlr_gles2_texture_get_attribs(texture->handle(), &attribs); @@ -580,7 +605,6 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q rt.setMirrorVertically(true); } - delete texture; bufferData->renderTarget = rt; if (QSGRendererInterface::isApiRhiBased(getGraphicsApi(rc))) {