From c259fb864b8301b07dd67a78522458570638026d Mon Sep 17 00:00:00 2001 From: JiDe Zhang Date: Thu, 9 Jul 2026 22:08:43 +0800 Subject: [PATCH 1/6] 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 上均能编译通过 --- waylib/src/server/CMakeLists.txt | 8 + .../qtquick/private/wvulkandmabufimport.cpp | 597 ++++++++++++++++++ .../qtquick/private/wvulkandmabufimport_p.h | 49 ++ waylib/src/server/qtquick/wrenderhelper.cpp | 36 +- 4 files changed, 684 insertions(+), 6 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/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))) { From effb8590bbda49114b4067cdaa06bae069c6ccea Mon Sep 17 00:00:00 2001 From: JiDe Zhang Date: Mon, 13 Jul 2026 14:45:18 +0800 Subject: [PATCH 2/6] fix(waylib): import SHM buffers through Qt RHI in Vulkan mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copy wl_shm buffers into Qt-owned image storage instead of using the deferred wlroots Vulkan upload path. Handle all supported 32-bit wl_shm formats with premultiplied alpha semantics and clear QSGPlainTexture pointers before deferred QRhi cleanup. 通过 Qt RHI 导入 Vulkan 模式下的 SHM buffer,避免 wlroots 延迟上传导致黑窗。 补充完整的 32 位 wl_shm 格式映射,并修复 QRhi texture 生命周期问题。 Log: 修复 Vulkan 模式下 SHM 客户端黑窗 Influence: Vulkan 模式下 SHM、EGL 和 dmabuf 客户端可正常显示,避免纹理悬空和泄漏。 --- .../qtquick/private/wvulkandmabufimport.cpp | 2 + .../src/server/qtquick/wsgtextureprovider.cpp | 106 ++++++++++++------ waylib/src/server/qtquick/wsurfaceitem.cpp | 10 +- waylib/src/server/utils/wtools.cpp | 4 + waylib/tests/unit_tests/CMakeLists.txt | 1 + .../test_wshmtexture/CMakeLists.txt | 19 ++++ .../unit_tests/test_wshmtexture/main.cpp | 53 +++++++++ 7 files changed, 160 insertions(+), 35 deletions(-) create mode 100644 waylib/tests/unit_tests/test_wshmtexture/CMakeLists.txt create mode 100644 waylib/tests/unit_tests/test_wshmtexture/main.cpp diff --git a/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp b/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp index e99f4c5c75..0dc9615093 100644 --- a/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp +++ b/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp @@ -27,6 +27,8 @@ QT_WARNING_PUSH QT_WARNING_DISABLE_GCC("-Wmissing-field-initializers") +QT_WARNING_DISABLE_CLANG("-Wmissing-field-initializers") +QT_WARNING_DISABLE_CLANG("-Wmissing-designated-field-initializers") WAYLIB_SERVER_BEGIN_NAMESPACE diff --git a/waylib/src/server/qtquick/wsgtextureprovider.cpp b/waylib/src/server/qtquick/wsgtextureprovider.cpp index 362c841602..0b06147f19 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.cpp +++ b/waylib/src/server/qtquick/wsgtextureprovider.cpp @@ -4,6 +4,7 @@ #include "wsgtextureprovider.h" #include "woutputrenderwindow.h" #include "wrenderhelper.h" +#include "wtools.h" #include "private/wglobal_p.h" #include "wayliblogging.h" @@ -35,7 +36,13 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate } void cleanTexture() { - if (rhiTexture) { + auto *oldRhiTexture = qtTexture.rhiTexture(); + if (oldRhiTexture) { + // QSGPlainTexture does not own the QRhiTexture in this provider. + // Clear its pointer before scheduling the deferred destruction. + qtTexture.setOwnsTexture(false); + qtTexture.setTexture(nullptr); + Q_ASSERT(window); class TextureCleanupJob : public QRunnable { @@ -49,29 +56,55 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate }; // Delay clean the qt rhi textures. - window->scheduleRenderJob(new TextureCleanupJob(rhiTexture), + window->scheduleRenderJob(new TextureCleanupJob(oldRhiTexture), QQuickWindow::AfterSynchronizingStage); - rhiTexture = nullptr; + } else if (imageBacked) { + qtTexture.setImage({}); } + imageBacked = false; if (ownsTexture && texture) delete texture; texture = nullptr; } + bool updateImageTexture(qw_buffer *newBuffer) { + void *data = nullptr; + uint32_t drmFormat = 0; + size_t stride = 0; + if (!newBuffer->begin_data_ptr_access(WLR_BUFFER_DATA_PTR_ACCESS_READ, + &data, &drmFormat, &stride)) + return false; + + const auto imageFormat = WTools::convertToDrmSupportedFormat( + WTools::toImageFormat(drmFormat)); + if (imageFormat == QImage::Format_Invalid) { + newBuffer->end_data_ptr_access(); + return false; + } + const auto image = QImage(static_cast(data), + static_cast(newBuffer->handle()->width), + static_cast(newBuffer->handle()->height), + static_cast(stride), imageFormat) + .copy(); + newBuffer->end_data_ptr_access(); + if (image.isNull()) + return false; + + qtTexture.setImage(image); + imageBacked = true; + return true; + } + void updateRhiTexture() { Q_ASSERT(texture); bool ok = WRenderHelper::makeTexture(window->rhi(), texture, &qtTexture); if (Q_UNLIKELY(!ok)) { qCWarning(lcWlQtQuickTexture) << "Failed to make texture:" << texture << ", width height:" << texture->handle()->width - << texture->handle()->height; - return; + << ", height:" << texture->handle()->height; } - - rhiTexture = qtTexture.rhiTexture(); } - W_DECLARE_PUBLIC(WSGTextureProvider) QPointer window; @@ -80,10 +113,9 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate qw_texture *texture = nullptr; bool ownsTexture = false; qw_buffer *buffer = nullptr; - + bool imageBacked = false; // qt resources QSGPlainTexture qtTexture; - QRhiTexture *rhiTexture = nullptr; bool smooth = true; }; @@ -101,38 +133,46 @@ WOutputRenderWindow *WSGTextureProvider::window() const void WSGTextureProvider::setBuffer(qw_buffer *buffer) { - if (buffer == qwBuffer()) { - // The buffer object is not changed, but maybe the buffer's content is changed. - // So should emit textureChanged() signal too. + W_D(WSGTextureProvider); + if (buffer == d->buffer) { + // The buffer object is not changed, but its contents may have changed. + if (buffer && d->imageBacked) + d->updateImageTexture(buffer); if (buffer) Q_EMIT textureChanged(); return; } - W_D(WSGTextureProvider); d->cleanTexture(); d->buffer = buffer; if (buffer) { Q_ASSERT(d->window); - if (auto clientBuffer = qw_client_buffer::get(*buffer)) { - // Acquire texture from client buffer. wlroots already generate texture for us if this is a client buffer. - // By the way, there is something wrong with getting texture from a client buffer using wlr_texture_from_buffer, - // See: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3897 - // Possible patch: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4889 - d->texture = qw_texture::from(clientBuffer->handle()->texture); - d->ownsTexture = false; - } else { - d->texture = qw_texture::from_buffer(*d->window->renderer(), *buffer); - d->ownsTexture = true; - } - if (Q_UNLIKELY(!d->texture)) { - qCWarning(lcWlQtQuickTexture) << "Failed to update texture from buffer:" << buffer - << ", width height:" << buffer->handle()->width - << buffer->handle()->height - << ", n_locks:" << buffer->handle()->n_locks; + if (WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan + && d->updateImageTexture(buffer)) { + // SHM uploads are owned by Qt RHI. This avoids wlroots' deferred + // Vulkan stage command buffer, which is submitted only by a wlroots + // render pass and is not part of Qt's command stream. } else { - d->updateRhiTexture(); + if (auto clientBuffer = qw_client_buffer::get(*buffer)) { + // Acquire texture from client buffer. wlroots already generate texture for us if this is a client buffer. + // By the way, there is something wrong with getting texture from a client buffer using wlr_texture_from_buffer, + // See: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3897 + // Possible patch: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4889 + d->texture = qw_texture::from(clientBuffer->handle()->texture); + d->ownsTexture = false; + } else { + d->texture = qw_texture::from_buffer(*d->window->renderer(), *buffer); + d->ownsTexture = true; + } + if (Q_UNLIKELY(!d->texture)) { + qCWarning(lcWlQtQuickTexture) << "Failed to update texture from buffer:" << buffer + << ", width height:" << buffer->handle()->width + << buffer->handle()->height + << ", n_locks:" << buffer->handle()->n_locks; + } else { + d->updateRhiTexture(); + } } } @@ -164,7 +204,9 @@ void WSGTextureProvider::invalidate() QSGTexture *WSGTextureProvider::texture() const { W_DC(WSGTextureProvider); - return d->texture ? const_cast(&d->qtTexture) : nullptr; + return (d->imageBacked || d->texture) + ? const_cast(&d->qtTexture) + : nullptr; } qw_texture *WSGTextureProvider::qwTexture() const diff --git a/waylib/src/server/qtquick/wsurfaceitem.cpp b/waylib/src/server/qtquick/wsurfaceitem.cpp index 4654ce21bd..773faed825 100644 --- a/waylib/src/server/qtquick/wsurfaceitem.cpp +++ b/waylib/src/server/qtquick/wsurfaceitem.cpp @@ -11,6 +11,7 @@ #include "wsgtextureprovider.h" #include "wsurface.h" #include "wsurfaceitem_p.h" +#include "wrenderhelper.h" #include "wayliblogging.h" #include @@ -464,7 +465,9 @@ WSGTextureProvider *WSurfaceItemContent::wTextureProvider() const d->textureProvider, &WSGTextureProvider::setSmooth); if (d->surface) { - if (auto texture = d->surface->handle()->get_texture()) { + const auto api = WRenderHelper::getGraphicsApi(); + if (auto texture = d->surface->handle()->get_texture(); + texture && api != QSGRendererInterface::Vulkan) { d->textureProvider->setTexture(qw_texture::from(texture), d->buffer.get()); } else { d->textureProvider->setBuffer(d->buffer.get()); @@ -595,8 +598,9 @@ QSGNode *WSurfaceItemContent::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeD auto tp = wTextureProvider(); if (d->live || !tp->texture()) { - auto texture = d->surface ? d->surface->handle()->get_texture() : nullptr; - if (texture) { + const auto api = WRenderHelper::getGraphicsApi(); + if (auto texture = d->surface ? d->surface->handle()->get_texture() : nullptr; + texture && api != QSGRendererInterface::Vulkan) { tp->setTexture(qw_texture::from(texture), d->buffer.get()); } else { tp->setBuffer(d->buffer.get()); diff --git a/waylib/src/server/utils/wtools.cpp b/waylib/src/server/utils/wtools.cpp index fa64308e06..baf897baf2 100644 --- a/waylib/src/server/utils/wtools.cpp +++ b/waylib/src/server/utils/wtools.cpp @@ -126,6 +126,10 @@ QImage::Format WTools::toImageFormat(uint32_t drmFormat) return QImage::Format_RGB32; case DRM_FORMAT_RGBX8888: return QImage::Format_RGBX8888; + case DRM_FORMAT_BGRX8888: + return QImage::Format_RGB32; + case DRM_FORMAT_BGRA8888: + return QImage::Format_ARGB32; case DRM_FORMAT_ARGB8888: return QImage::Format_ARGB32_Premultiplied; case DRM_FORMAT_RGBA8888: diff --git a/waylib/tests/unit_tests/CMakeLists.txt b/waylib/tests/unit_tests/CMakeLists.txt index 765cc54661..0566ec1b14 100644 --- a/waylib/tests/unit_tests/CMakeLists.txt +++ b/waylib/tests/unit_tests/CMakeLists.txt @@ -2,3 +2,4 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) add_subdirectory(test_wwrappointer) add_subdirectory(test_qsgrenderer_accessor) +add_subdirectory(test_wshmtexture) diff --git a/waylib/tests/unit_tests/test_wshmtexture/CMakeLists.txt b/waylib/tests/unit_tests/test_wshmtexture/CMakeLists.txt new file mode 100644 index 0000000000..02614badce --- /dev/null +++ b/waylib/tests/unit_tests/test_wshmtexture/CMakeLists.txt @@ -0,0 +1,19 @@ +find_package(Qt6 REQUIRED COMPONENTS Test) + +add_executable(test_wshmtexture + main.cpp +) + +target_link_libraries(test_wshmtexture + PRIVATE + Waylib::WaylibServer + Qt6::Core + Qt6::Gui + Qt6::Qml + Qt6::Test +) + +add_test(NAME test_wshmtexture COMMAND test_wshmtexture) +set_property(TEST test_wshmtexture PROPERTY + ENVIRONMENT "QT_QPA_PLATFORM=offscreen" +) diff --git a/waylib/tests/unit_tests/test_wshmtexture/main.cpp b/waylib/tests/unit_tests/test_wshmtexture/main.cpp new file mode 100644 index 0000000000..d06a493a40 --- /dev/null +++ b/waylib/tests/unit_tests/test_wshmtexture/main.cpp @@ -0,0 +1,53 @@ +// Copyright (C) 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 + +#include "wtools.h" + +#include + +#include + +using namespace Waylib::Server; + +class WShmTextureTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void mapsArgb8888ToPremultiplied() + { + QCOMPARE(WTools::toImageFormat(DRM_FORMAT_ARGB8888), + QImage::Format_ARGB32_Premultiplied); + } + + void mapsRgba8888ToPremultipliedForShm() + { + QCOMPARE(WTools::convertToDrmSupportedFormat( + WTools::toImageFormat(DRM_FORMAT_RGBA8888)), + QImage::Format_RGBA8888_Premultiplied); + } + + void mapsBgraAndBgrx8888() + { + QCOMPARE(WTools::convertToDrmSupportedFormat( + WTools::toImageFormat(DRM_FORMAT_BGRA8888)), + QImage::Format_ARGB32_Premultiplied); + QCOMPARE(WTools::toImageFormat(DRM_FORMAT_BGRX8888), + QImage::Format_RGB32); + } + + void mapsRgbx8888() + { + QCOMPARE(WTools::toImageFormat(DRM_FORMAT_RGBX8888), + QImage::Format_RGBX8888); + } + + void rejectsUnsupportedFormat() + { + QCOMPARE(WTools::toImageFormat(DRM_FORMAT_R8), + QImage::Format_Invalid); + } +}; + +QTEST_MAIN(WShmTextureTest) +#include "main.moc" From 80413848be9de55e3b7860aae1067d6ee0ec09bf Mon Sep 17 00:00:00 2001 From: JiDe Zhang Date: Tue, 14 Jul 2026 11:54:13 +0800 Subject: [PATCH 3/6] refactor(render): add tri-state color contents policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace boolean preserve controls with DontCare, Clear, and Preserve modes. 以 DontCare、Clear 和 Preserve 三态替换布尔保留控制。 Resolve modes at target creation and keep each render pass target stable. 在目标创建时解析模式,并保证每个渲染阶段的目标保持稳定。 Recreate incompatible Vulkan targets and update other RHI flags in place. 重建不兼容的 Vulkan 目标,其他 RHI 后端则原地更新标志。 Keep software clears backend-aware and preserve multipass contents. 让软件渲染按后端决定清屏,并保留多遍渲染的已有内容。 Log: 实现渲染目标颜色内容三态策略 Influence: 渲染目标可显式清屏或保留内容,避免目标切换和续绘丢失。 --- waylib/src/server/kernel/wglobal.h | 14 +++ .../qtquick/private/wbufferrenderer.cpp | 30 +++-- .../qtquick/private/wbufferrenderer_p.h | 9 +- .../qtquick/private/woutputviewport_p.h | 3 +- waylib/src/server/qtquick/woutputhelper.cpp | 5 +- waylib/src/server/qtquick/woutputhelper.h | 3 +- waylib/src/server/qtquick/woutputlayer.cpp | 16 +++ waylib/src/server/qtquick/woutputlayer.h | 6 +- .../server/qtquick/woutputrenderwindow.cpp | 49 +++++--- waylib/src/server/qtquick/woutputviewport.cpp | 13 ++- waylib/src/server/qtquick/woutputviewport.h | 8 +- waylib/src/server/qtquick/wrenderhelper.cpp | 110 +++++++++++++++--- waylib/src/server/qtquick/wrenderhelper.h | 4 +- .../test_wshmtexture/CMakeLists.txt | 1 + .../unit_tests/test_wshmtexture/main.cpp | 75 ++++++++++++ 15 files changed, 277 insertions(+), 69 deletions(-) diff --git a/waylib/src/server/kernel/wglobal.h b/waylib/src/server/kernel/wglobal.h index 946cb4262b..2ff8c39763 100644 --- a/waylib/src/server/kernel/wglobal.h +++ b/waylib/src/server/kernel/wglobal.h @@ -192,6 +192,13 @@ class WAYLIB_SERVER_EXPORT WGlobal { QML_UNCREATABLE("Use for enums") public: + enum class ColorContentsMode { + DontCare, + Clear, + Preserve, + }; + Q_ENUM(ColorContentsMode) + enum class CursorShape { Default = Qt::CustomCursor + 1, Invalid, @@ -250,6 +257,13 @@ class WAYLIB_SERVER_EXPORT WGlobal { static bool isClientResourceCursor(const QCursor &c); }; +struct Q_DECL_HIDDEN WColorContentsModeQMLProxy { + Q_GADGET + QML_NAMED_ELEMENT(ColorContentsMode) + QML_FOREIGN_NAMESPACE(WAYLIB_SERVER_NAMESPACE::WGlobal) + QML_UNCREATABLE("Use for enums") +}; + struct Q_DECL_HIDDEN WCursorShapeQMLProxy { Q_GADGET QML_NAMED_ELEMENT(CursorShape) diff --git a/waylib/src/server/qtquick/private/wbufferrenderer.cpp b/waylib/src/server/qtquick/private/wbufferrenderer.cpp index f12f4bf072..217382cf71 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer.cpp +++ b/waylib/src/server/qtquick/private/wbufferrenderer.cpp @@ -267,6 +267,11 @@ QRhiTexture *WBufferRenderer::currentRenderTarget() const return colorAttachment->texture(); } +bool WBufferRenderer::isColorPreserved() const +{ + return state.colorPreserved; +} + const qw_damage_ring *WBufferRenderer::damageRing() const { return &m_damageRing; @@ -331,7 +336,8 @@ QTransform WBufferRenderer::inputMapToOutput(const QRectF &sourceRect, const QRe } qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixelRatio, - uint32_t format, RenderFlags flags) + uint32_t format, RenderFlags flags, + WGlobal::ColorContentsMode mode) { Q_ASSERT(!state.buffer); Q_ASSERT(m_output); @@ -378,8 +384,7 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe auto wd = QQuickWindowPrivate::get(window()); Q_ASSERT(wd->renderControl); - auto lastRT = m_renderHelper->lastRenderTarget(); - auto rt = m_renderHelper->acquireRenderTarget(wd->renderControl, buffer); + auto rt = m_renderHelper->acquireRenderTarget(wd->renderControl, buffer, mode, &state.colorPreserved); if (rt.isNull()) { buffer->unlock(); return nullptr; @@ -420,6 +425,7 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe } state.flags = flags; + state.colorContentsMode = mode; state.context = wd->context; state.pixelSize = pixelSize; state.devicePixelRatio = devicePixelRatio; @@ -436,8 +442,7 @@ inline static QRect scaleToRect(const QRectF &s, qreal scale) { } void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, - const QRectF &sourceRect, const QRectF &targetRect, - bool preserveColorContents) + const QRectF &sourceRect, const QRectF &targetRect) { Q_ASSERT(state.buffer); @@ -477,16 +482,13 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, auto softwareRenderer = dynamic_cast(renderer); { // before render if (softwareRenderer) { - // Avoid do clear before paint, for the software renderer this - // work is expensive. - if (m_clearColor.alpha() == 0) - preserveColorContents = true; + const bool clearColor = !state.colorPreserved; #if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) - softwareRenderer->setClearColorEnabled(!preserveColorContents); + softwareRenderer->setClearColorEnabled(clearColor); #else auto bn = softwareRenderer->renderableNode(W_PRIVATE_MEMBER(*softwareRenderer, QSGAbsSoftRenderer_m_background_tag{})); if (bn) { - W_PRIVATE_MEMBER(*bn, QSGSoftRenderableNode_m_opacity_tag{}) = preserveColorContents ? 0 : 1; + W_PRIVATE_MEMBER(*bn, QSGSoftRenderableNode_m_opacity_tag{}) = clearColor ? 1 : 0; } #endif if (!state.dirty.isEmpty()) { @@ -564,12 +566,6 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, renderer->setProjectionMatrix(projectionMatrix); renderer->setProjectionMatrixWithNativeNDC(projectionMatrixWithNativeNDC); - auto textureRT = static_cast(state.sgRenderTarget.rt); - if (preserveColorContents) { - textureRT->setFlags(textureRT->flags() | QRhiTextureRenderTarget::PreserveColorContents); - } else { - textureRT->setFlags(textureRT->flags() & ~QRhiTextureRenderTarget::PreserveColorContents); - } } } diff --git a/waylib/src/server/qtquick/private/wbufferrenderer_p.h b/waylib/src/server/qtquick/private/wbufferrenderer_p.h index 628684c714..313648e7d9 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer_p.h +++ b/waylib/src/server/qtquick/private/wbufferrenderer_p.h @@ -76,6 +76,7 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem QW_NAMESPACE::qw_buffer *currentBuffer() const; QW_NAMESPACE::qw_buffer *lastBuffer() const; QRhiTexture *currentRenderTarget() const; + bool isColorPreserved() const; const QW_NAMESPACE::qw_damage_ring *damageRing() const; QW_NAMESPACE::qw_damage_ring *damageRing(); @@ -95,10 +96,10 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem protected: QW_NAMESPACE::qw_buffer *beginRender(const QSize &pixelSize, qreal devicePixelRatio, - uint32_t format, RenderFlags flags = {}); + uint32_t format, RenderFlags flags = {}, + WGlobal::ColorContentsMode mode = WGlobal::ColorContentsMode::DontCare); void render(int sourceIndex, const QMatrix4x4 &renderMatrix, - const QRectF &sourceRect = {}, const QRectF &targetRect = {}, - bool preserveColorContents = false); + const QRectF &sourceRect = {}, const QRectF &targetRect = {}); void endRender(); void componentComplete() override; @@ -133,6 +134,8 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem struct RenderState { RenderFlags flags; + WGlobal::ColorContentsMode colorContentsMode = WGlobal::ColorContentsMode::DontCare; + bool colorPreserved = false; QSGRenderContext *context; QSGRenderer *renderer; QSGBatchRenderer::Renderer *batchRenderer; diff --git a/waylib/src/server/qtquick/private/woutputviewport_p.h b/waylib/src/server/qtquick/private/woutputviewport_p.h index 9cd0cc1368..137080fc7c 100644 --- a/waylib/src/server/qtquick/private/woutputviewport_p.h +++ b/waylib/src/server/qtquick/private/woutputviewport_p.h @@ -23,7 +23,6 @@ class Q_DECL_HIDDEN WOutputViewportPrivate : public QQuickItemPrivate WOutputViewportPrivate() : attached(false) , offscreen(false) - , preserveColorContents(false) , live(true) , forceRender(false) , ignoreViewport(false) @@ -81,10 +80,10 @@ class Q_DECL_HIDDEN WOutputViewportPrivate : public QQuickItemPrivate QPointer extraRenderSource; QRectF sourceRect; QRectF targetRect; + WGlobal::ColorContentsMode colorContentsMode = WGlobal::ColorContentsMode::DontCare; uint attached:1; uint offscreen:1; - uint preserveColorContents:1; uint live:1; uint forceRender:1; uint ignoreViewport:1; diff --git a/waylib/src/server/qtquick/woutputhelper.cpp b/waylib/src/server/qtquick/woutputhelper.cpp index a71a58f396..a0a7d7d574 100644 --- a/waylib/src/server/qtquick/woutputhelper.cpp +++ b/waylib/src/server/qtquick/woutputhelper.cpp @@ -139,7 +139,8 @@ QWindow *WOutputHelper::outputWindow() const } std::pair WOutputHelper::acquireRenderTarget(QQuickRenderControl *rc, - wlr_swapchain **swapchain) + wlr_swapchain **swapchain, + WGlobal::ColorContentsMode mode) { W_D(WOutputHelper); @@ -151,7 +152,7 @@ std::pair WOutputHelper::acquireRenderTarget(QQ d->renderHelper = new WRenderHelper(d->renderer(), this); d->renderHelper->setSize(d->output->size()); } - auto rt = d->renderHelper->acquireRenderTarget(rc, buffer); + auto rt = d->renderHelper->acquireRenderTarget(rc, buffer, mode); if (rt.isNull()) { buffer->unlock(); return {}; diff --git a/waylib/src/server/qtquick/woutputhelper.h b/waylib/src/server/qtquick/woutputhelper.h index 3a4d3fb250..08a8325875 100644 --- a/waylib/src/server/qtquick/woutputhelper.h +++ b/waylib/src/server/qtquick/woutputhelper.h @@ -45,7 +45,8 @@ class WAYLIB_SERVER_EXPORT WOutputHelper : public QObject, public WObject QWindow *outputWindow() const; std::pair acquireRenderTarget(QQuickRenderControl *rc, - wlr_swapchain **swapchain = nullptr); + wlr_swapchain **swapchain = nullptr, + WGlobal::ColorContentsMode mode = WGlobal::ColorContentsMode::DontCare); std::pair lastRenderTarget(); void setBuffer(QW_NAMESPACE::qw_buffer *buffer); diff --git a/waylib/src/server/qtquick/woutputlayer.cpp b/waylib/src/server/qtquick/woutputlayer.cpp index 3de7a165f5..ea53c3ebc1 100644 --- a/waylib/src/server/qtquick/woutputlayer.cpp +++ b/waylib/src/server/qtquick/woutputlayer.cpp @@ -41,6 +41,7 @@ class Q_DECL_HIDDEN WOutputLayerPrivate : public QObjectPrivate uint actualEnabled:1; uint refItem:1; WOutputLayer::Flags flags = {0}; + WGlobal::ColorContentsMode colorContentsMode = WGlobal::ColorContentsMode::DontCare; int z = 0; QPointF cursorHotSpot; QList outputs; @@ -177,6 +178,21 @@ void WOutputLayer::setFlags(const Flags &newFlags) Q_EMIT flagsChanged(); } +WGlobal::ColorContentsMode WOutputLayer::colorContentsMode() const +{ + W_DC(WOutputLayer); + return d->colorContentsMode; +} + +void WOutputLayer::setColorContentsMode(WGlobal::ColorContentsMode mode) +{ + W_D(WOutputLayer); + if (d->colorContentsMode == mode) + return; + d->colorContentsMode = mode; + Q_EMIT colorContentsModeChanged(); +} + const QList &WOutputLayer::outputs() const { W_DC(WOutputLayer); diff --git a/waylib/src/server/qtquick/woutputlayer.h b/waylib/src/server/qtquick/woutputlayer.h index fc61164489..c42e3a932a 100644 --- a/waylib/src/server/qtquick/woutputlayer.h +++ b/waylib/src/server/qtquick/woutputlayer.h @@ -23,6 +23,7 @@ class WAYLIB_SERVER_EXPORT WOutputLayer : public QObject Q_PROPERTY(bool keepLayer READ keepLayer WRITE setKeepLayer NOTIFY keepLayerChanged FINAL) Q_PROPERTY(bool force READ force WRITE setForce NOTIFY forceChanged FINAL) Q_PROPERTY(Flags flags READ flags WRITE setFlags NOTIFY flagsChanged FINAL) + Q_PROPERTY(WGlobal::ColorContentsMode colorContentsMode READ colorContentsMode WRITE setColorContentsMode NOTIFY colorContentsModeChanged FINAL) Q_PROPERTY(QList outputs READ outputs WRITE setOutputs NOTIFY outputsChanged FINAL) Q_PROPERTY(QList inOutputsByHardware READ inOutputsByHardware NOTIFY inOutputsByHardwareChanged FINAL) Q_PROPERTY(int z READ z WRITE setZ NOTIFY zChanged FINAL) @@ -35,7 +36,6 @@ class WAYLIB_SERVER_EXPORT WOutputLayer : public QObject enum Flag { SizeSensitive = 1 << 0, DontClip = 1 << 1, - PreserveColorContents = 1 << 2, NoAlpha = 1 << 3, Cursor = 1 << 4, }; @@ -54,6 +54,9 @@ class WAYLIB_SERVER_EXPORT WOutputLayer : public QObject Flags flags() const; void setFlags(const Flags &newFlags); + WGlobal::ColorContentsMode colorContentsMode() const; + void setColorContentsMode(WGlobal::ColorContentsMode mode); + const QList &outputs() const; void setOutputs(const QList &newOutputList); @@ -74,6 +77,7 @@ class WAYLIB_SERVER_EXPORT WOutputLayer : public QObject Q_SIGNALS: void enabledChanged(); void flagsChanged(); + void colorContentsModeChanged(); void outputsChanged(); void inOutputsByHardwareChanged(); void zChanged(); diff --git a/waylib/src/server/qtquick/woutputrenderwindow.cpp b/waylib/src/server/qtquick/woutputrenderwindow.cpp index 454167897d..43e1c9b8d2 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.cpp +++ b/waylib/src/server/qtquick/woutputrenderwindow.cpp @@ -238,9 +238,10 @@ class Q_DECL_HIDDEN OutputHelper : public WOutputHelper inline qw_buffer *beginRender(WBufferRenderer *renderer, const QSize &pixelSize, uint32_t format, - WBufferRenderer::RenderFlags flags); + WBufferRenderer::RenderFlags flags, + WGlobal::ColorContentsMode mode = WGlobal::ColorContentsMode::DontCare); inline void render(WBufferRenderer *renderer, int sourceIndex, const QMatrix4x4 &renderMatrix, - const QRectF &sourceRect, const QRectF &viewportRect, bool preserveColorContents); + const QRectF &sourceRect, const QRectF &viewportRect); static bool visualizeLayers() { static bool on = qEnvironmentVariableIsSet("WAYLIB_VISUALIZE_LAYERS"); @@ -590,16 +591,17 @@ void OutputHelper::cleanCursorRender() qw_buffer *OutputHelper::beginRender(WBufferRenderer *renderer, const QSize &pixelSize, uint32_t format, - WBufferRenderer::RenderFlags flags) + WBufferRenderer::RenderFlags flags, + WGlobal::ColorContentsMode mode) { - return renderer->beginRender(pixelSize, devicePixelRatio(), format, flags); + return renderer->beginRender(pixelSize, devicePixelRatio(), format, flags, mode); } void OutputHelper::render(WBufferRenderer *renderer, int sourceIndex, const QMatrix4x4 &renderMatrix, - const QRectF &sourceRect, const QRectF &targetRect, bool preserveColorContents) + const QRectF &sourceRect, const QRectF &targetRect) { renderWindowD()->pushRenderer(renderer); - renderer->render(sourceIndex, renderMatrix, sourceRect, targetRect, preserveColorContents); + renderer->render(sourceIndex, renderMatrix, sourceRect, targetRect); } static QQuickItem *createVisualRectangle(QQuickItem *target, const QColor &color) { @@ -753,20 +755,28 @@ qw_buffer *OutputHelper::renderLayer(LayerData *layer, bool *dontEndRenderAndRet layer->renderer->setSize(layer->pixelSize / dpr); const bool alpha = !layer->layer->layer->flags().testFlag(WOutputLayer::NoAlpha); + auto mode = layer->layer->layer->colorContentsMode(); + if (visualizeLayers()) { + if (mode == WGlobal::ColorContentsMode::Clear) { + qCWarning(lcWlRenderer) << "Layer" << layer->layer->layer + << "requires Clear but visualizeLayers forces Preserve"; + } + mode = WGlobal::ColorContentsMode::Preserve; + } // Don't use OutputHelper::beginRender, because the dpr maybe is from LayerData::mapFrom buffer = layer->renderer->beginRender(layer->pixelSize, dpr, // TODO: Allows control format by WOutputLayer alpha ? DRM_FORMAT_ARGB8888 : DRM_FORMAT_XRGB8888, - WBufferRenderer::DontConfigureSwapchain); + WBufferRenderer::DontConfigureSwapchain, + mode); if (buffer) { const QRectF sr = QRectF(layer->mapRect.topLeft() - layer->noClipMapRect.topLeft(), layer->mapRect.size()); const QRectF tr(QPointF(0, 0), layer->mapRect.size()); - render(layer->renderer, 0, layer->renderMatrix, sr, tr, - layer->layer->layer->flags().testFlag(WOutputLayer::PreserveColorContents)); + render(layer->renderer, 0, layer->renderMatrix, sr, tr); if (visualizeLayers()) - render(layer->renderer, 1, layer->renderMatrix, sr, tr, true); + render(layer->renderer, 1, layer->renderMatrix, sr, tr); if (dontEndRenderAndReturnNeedsEndRender) { *dontEndRenderAndReturnNeedsEndRender = true; @@ -965,7 +975,8 @@ WBufferRenderer *OutputHelper::compositeLayers(const QList layers, b { Q_ASSERT(!layers.isEmpty()); - const bool usingShadowRenderer = forceShadowRenderer; + const bool usingShadowRenderer = forceShadowRenderer + || !bufferRenderer()->isColorPreserved(); if (!m_layerPorxyContainer) { m_layerPorxyContainer = new QQuickItem(renderWindow()->contentItem()); @@ -1039,19 +1050,20 @@ WBufferRenderer *OutputHelper::compositeLayers(const QList layers, b if (usingShadowRenderer) { const bool ok = beginRender(bufferRenderer2(), m_output->output()->size(), qwoutput()->handle()->render_format, - WBufferRenderer::RedirectOpenGLContextDefaultFrameBufferObject); + WBufferRenderer::RedirectOpenGLContextDefaultFrameBufferObject, + WGlobal::ColorContentsMode::Preserve); if (ok) { // stop primary render if (bufferRenderer()->currentBuffer()) bufferRenderer()->endRender(); - render(bufferRenderer2(), 0, {}, m_output->effectiveSourceRect(), m_output->targetRect(), true); + render(bufferRenderer2(), 0, {}, m_output->effectiveSourceRect(), m_output->targetRect()); return bufferRenderer2(); } } else { if (bufferRenderer()->currentBuffer()) { - render(bufferRenderer(), 1, {}, m_output->effectiveSourceRect(), m_output->targetRect(), true); + render(bufferRenderer(), 1, {}, m_output->effectiveSourceRect(), m_output->targetRect()); } else { // ###(zccrs): Maybe because contents is not dirty, so not do render // in WOutputRenderWindowPrivate::doRenderOutputs, force mark the @@ -1479,19 +1491,18 @@ WOutputRenderWindowPrivate::doRenderOutputs(qw_output *needsFrameOutput, const Q const auto &format = helper->qwoutput()->handle()->render_format; const auto renderMatrix = helper->output()->renderMatrix(); - // maybe using the other WOutputViewport's QSGTextureProvider if (!helper->output()->depends().isEmpty()) updateDirtyNodes(); qw_buffer *buffer = helper->beginRender(helper->bufferRenderer(), helper->output()->output()->size(), format, - WBufferRenderer::RedirectOpenGLContextDefaultFrameBufferObject); + WBufferRenderer::RedirectOpenGLContextDefaultFrameBufferObject, + helper->output()->colorContentsMode()); Q_ASSERT(buffer == helper->bufferRenderer()->currentBuffer()); if (buffer) { helper->render(helper->bufferRenderer(), 0, renderMatrix, helper->output()->effectiveSourceRect(), - helper->output()->targetRect(), - helper->output()->preserveColorContents()); + helper->output()->targetRect()); } renderResults.append(helper); } @@ -1739,6 +1750,8 @@ void WOutputRenderWindow::attach(WOutputLayer *layer, WOutputViewport *output) connect(layer, &WOutputLayer::flagsChanged, outputHelper, &WOutputHelper::scheduleFrame); + connect(layer, &WOutputLayer::colorContentsModeChanged, + outputHelper, &WOutputHelper::scheduleFrame); connect(layer, &WOutputLayer::zChanged, outputHelper, &WOutputHelper::scheduleFrame); diff --git a/waylib/src/server/qtquick/woutputviewport.cpp b/waylib/src/server/qtquick/woutputviewport.cpp index aa942c0e89..ed0c206a43 100644 --- a/waylib/src/server/qtquick/woutputviewport.cpp +++ b/waylib/src/server/qtquick/woutputviewport.cpp @@ -266,19 +266,20 @@ void WOutputViewport::setCacheBuffer(bool newCacheBuffer) d->bufferRenderer->setCacheBuffer(newCacheBuffer); } -bool WOutputViewport::preserveColorContents() const +WGlobal::ColorContentsMode WOutputViewport::colorContentsMode() const { W_DC(WOutputViewport); - return d->preserveColorContents; + return d->colorContentsMode; } -void WOutputViewport::setPreserveColorContents(bool newPreserveColorContents) +void WOutputViewport::setColorContentsMode(WGlobal::ColorContentsMode mode) { W_D(WOutputViewport); - if (d->preserveColorContents == newPreserveColorContents) + if (d->colorContentsMode == mode) return; - d->preserveColorContents = newPreserveColorContents; - Q_EMIT preserveColorContentsChanged(); + d->colorContentsMode = mode; + d->update(); + Q_EMIT colorContentsModeChanged(); } bool WOutputViewport::live() const diff --git a/waylib/src/server/qtquick/woutputviewport.h b/waylib/src/server/qtquick/woutputviewport.h index cc3589ef37..2cd4673007 100644 --- a/waylib/src/server/qtquick/woutputviewport.h +++ b/waylib/src/server/qtquick/woutputviewport.h @@ -25,7 +25,7 @@ class WAYLIB_SERVER_EXPORT WOutputViewport : public QQuickItem, public virtual W Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio WRITE setDevicePixelRatio NOTIFY devicePixelRatioChanged) Q_PROPERTY(bool offscreen READ offscreen WRITE setOffscreen NOTIFY offscreenChanged) Q_PROPERTY(bool cacheBuffer READ cacheBuffer WRITE setCacheBuffer NOTIFY cacheBufferChanged FINAL) - Q_PROPERTY(bool preserveColorContents READ preserveColorContents WRITE setPreserveColorContents NOTIFY preserveColorContentsChanged FINAL) + Q_PROPERTY(WGlobal::ColorContentsMode colorContentsMode READ colorContentsMode WRITE setColorContentsMode NOTIFY colorContentsModeChanged FINAL) Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged FINAL) Q_PROPERTY(QRectF sourceRect READ sourceRect WRITE setSourceRect RESET resetSourceRect NOTIFY sourceRectChanged FINAL) Q_PROPERTY(QRectF targetRect READ targetRect WRITE setTargetRect RESET resetTargetRect NOTIFY targetRectChanged FINAL) @@ -67,8 +67,8 @@ class WAYLIB_SERVER_EXPORT WOutputViewport : public QQuickItem, public virtual W bool cacheBuffer() const; void setCacheBuffer(bool newCacheBuffer); - bool preserveColorContents() const; - void setPreserveColorContents(bool newPreserveColorContents); + WGlobal::ColorContentsMode colorContentsMode() const; + void setColorContentsMode(WGlobal::ColorContentsMode mode); bool live() const; void setLive(bool newLive); @@ -112,7 +112,7 @@ public Q_SLOTS: void devicePixelRatioChanged(); void offscreenChanged(); void cacheBufferChanged(); - void preserveColorContentsChanged(); + void colorContentsModeChanged(); void outputRenderInitialized(); void inputChanged(); void liveChanged(); diff --git a/waylib/src/server/qtquick/wrenderhelper.cpp b/waylib/src/server/qtquick/wrenderhelper.cpp index 5fabe24734..4a22130426 100644 --- a/waylib/src/server/qtquick/wrenderhelper.cpp +++ b/waylib/src/server/qtquick/wrenderhelper.cpp @@ -69,6 +69,7 @@ struct Q_DECL_HIDDEN BufferData { WImageRenderTarget paintDevice; QQuickRenderTarget renderTarget; QQuickWindowRenderTarget windowRenderTarget; + bool colorPreserved = false; #ifdef ENABLE_VULKAN_RENDER // Vulkan image imported with COLOR_ATTACHMENT usage for the render target. // Released after the RHI wrapper in ~BufferData(). @@ -140,12 +141,55 @@ struct Q_DECL_HIDDEN BufferData { } }; +static constexpr WGlobal::ColorContentsMode resolveColorContentsMode(WGlobal::ColorContentsMode requested, + bool softwareRenderer) noexcept +{ + if (requested != WGlobal::ColorContentsMode::DontCare) + return requested; + // For software renderer, DontCare means preserve (avoid expensive clear) + return softwareRenderer ? WGlobal::ColorContentsMode::Preserve : WGlobal::ColorContentsMode::Clear; +} + + +static QRhiTextureRenderTarget::Flags rhiRenderTargetFlags(WGlobal::ColorContentsMode mode) +{ + Q_ASSERT(mode != WGlobal::ColorContentsMode::DontCare); + return mode == WGlobal::ColorContentsMode::Preserve + ? QRhiTextureRenderTarget::PreserveColorContents + : QRhiTextureRenderTarget::Flags{}; +} + +static bool recreateRhiRenderTarget(BufferData *data, QRhiTextureRenderTarget::Flags flags) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + auto renderTarget = static_cast( + data->windowRenderTarget.rt.renderTarget); + auto &rpDesc = data->windowRenderTarget.res.rpDesc; +#else + auto renderTarget = static_cast( + data->windowRenderTarget.renderTarget); + auto &rpDesc = data->windowRenderTarget.rpDesc; +#endif + Q_ASSERT(renderTarget); + renderTarget->destroy(); + renderTarget->setFlags(flags); + + auto newRpDesc = renderTarget->newCompatibleRenderPassDescriptor(); + if (!newRpDesc) + return false; + delete rpDesc; + rpDesc = newRpDesc; + renderTarget->setRenderPassDescriptor(rpDesc); + return renderTarget->create(); +} + // Copy from qquickrendertarget.cpp static bool createRhiRenderTarget(const QRhiColorAttachment &colorAttachment, const QSize &pixelSize, int sampleCount, QRhi *rhi, - QQuickWindowRenderTarget &dst) + QQuickWindowRenderTarget &dst, + QRhiTextureRenderTarget::Flags flags) { std::unique_ptr depthStencil(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount)); if (!depthStencil->create()) { @@ -155,7 +199,7 @@ static bool createRhiRenderTarget(const QRhiColorAttachment &colorAttachment, QRhiTextureRenderTargetDescription rtDesc(colorAttachment); rtDesc.setDepthStencilBuffer(depthStencil.get()); - std::unique_ptr rt(rhi->newTextureRenderTarget(rtDesc)); + std::unique_ptr rt(rhi->newTextureRenderTarget(rtDesc, flags)); std::unique_ptr rp(rt->newCompatibleRenderPassDescriptor()); rt->setRenderPassDescriptor(rp.get()); @@ -179,7 +223,8 @@ static bool createRhiRenderTarget(const QRhiColorAttachment &colorAttachment, return true; } -bool createRhiRenderTarget(QRhi *rhi, const QQuickRenderTarget &source, QQuickWindowRenderTarget &dst) +bool createRhiRenderTarget(QRhi *rhi, const QQuickRenderTarget &source, QQuickWindowRenderTarget &dst, + QRhiTextureRenderTarget::Flags flags) { auto rtd = QQuickRenderTargetPrivate::get(&source); @@ -187,14 +232,14 @@ bool createRhiRenderTarget(QRhi *rhi, const QQuickRenderTarget &source, QQuickWi case QQuickRenderTargetPrivate::Type::NativeTexture: { const auto format = rtd->u.nativeTexture.rhiFormat == QRhiTexture::UnknownFormat ? QRhiTexture::RGBA8 : QRhiTexture::Format(rtd->u.nativeTexture.rhiFormat); - const auto flags = QRhiTexture::RenderTarget | QRhiTexture::Flags( + const auto textureFlags = QRhiTexture::RenderTarget | QRhiTexture::Flags( #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) rtd->u.nativeTexture.rhiFormatFlags #else rtd->u.nativeTexture.rhiFlags #endif ); - std::unique_ptr texture(rhi->newTexture(format, rtd->pixelSize, rtd->sampleCount, flags)); + std::unique_ptr texture(rhi->newTexture(format, rtd->pixelSize, rtd->sampleCount, textureFlags)); texture->setName(QByteArrayLiteral("WaylibTexture")); #if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) if (!texture->createFrom({ rtd->u.nativeTexture.object, rtd->u.nativeTexture.layout })) @@ -203,7 +248,7 @@ bool createRhiRenderTarget(QRhi *rhi, const QQuickRenderTarget &source, QQuickWi #endif return false; QRhiColorAttachment att(texture.get()); - if (!createRhiRenderTarget(att, rtd->pixelSize, rtd->sampleCount, rhi, dst)) + if (!createRhiRenderTarget(att, rtd->pixelSize, rtd->sampleCount, rhi, dst, flags)) return false; #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) dst.res.texture = texture.release(); @@ -219,7 +264,7 @@ bool createRhiRenderTarget(QRhi *rhi, const QQuickRenderTarget &source, QQuickWi return false; } QRhiColorAttachment att(renderbuffer.get()); - if (!createRhiRenderTarget(att, rtd->pixelSize, rtd->sampleCount, rhi, dst)) + if (!createRhiRenderTarget(att, rtd->pixelSize, rtd->sampleCount, rhi, dst, flags)) return false; renderbuffer->setName(QByteArrayLiteral("WaylibRenderBuffer")); #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) @@ -251,7 +296,8 @@ class Q_DECL_HIDDEN WRenderHelperPrivate : public WObjectPrivate void resetRenderBuffer(); void onBufferDestroy(); - static bool ensureRhiRenderTarget(QQuickRenderControl *rc, BufferData *data); + static bool ensureRhiRenderTarget(QQuickRenderControl *rc, BufferData *data, + QRhiTextureRenderTarget::Flags flags); W_DECLARE_PUBLIC(WRenderHelper) qw_renderer *renderer; @@ -284,7 +330,8 @@ void WRenderHelperPrivate::onBufferDestroy() } } -bool WRenderHelperPrivate::ensureRhiRenderTarget(QQuickRenderControl *rc, BufferData *data) +bool WRenderHelperPrivate::ensureRhiRenderTarget(QQuickRenderControl *rc, BufferData *data, + QRhiTextureRenderTarget::Flags flags) { data->resetWindowRenderTarget(); #if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) @@ -293,7 +340,7 @@ bool WRenderHelperPrivate::ensureRhiRenderTarget(QQuickRenderControl *rc, Buffer auto rhi = rc->rhi(); #endif auto tmp = data->renderTarget; - bool ok = createRhiRenderTarget(rhi, tmp, data->windowRenderTarget); + bool ok = createRhiRenderTarget(rhi, tmp, data->windowRenderTarget, flags); if (!ok) return false; #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) @@ -545,28 +592,58 @@ qw_buffer *WRenderHelper::toBuffer(qw_renderer *renderer, QSGTexture *texture, Q return nullptr; } -QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, qw_buffer *buffer) +QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, qw_buffer *buffer, + WGlobal::ColorContentsMode mode, + bool *colorPreserved) { W_D(WRenderHelper); Q_ASSERT(buffer); if (d->size.isEmpty()) return {}; + const bool isSoftware = wlr_renderer_is_pixman(d->renderer->handle()); + const auto resolvedMode = resolveColorContentsMode(mode, isSoftware); + const bool needPreserve = resolvedMode == WGlobal::ColorContentsMode::Preserve; for (int i = 0; i < d->buffers.count(); ++i) { auto data = d->buffers[i]; if (data->buffer == buffer) { + if (needPreserve != data->colorPreserved) { + const auto flags = rhiRenderTargetFlags(resolvedMode); +#ifdef ENABLE_VULKAN_RENDER + if (!data->vkDmabufImage.isNull()) { + qCWarning(lcWlRenderHelper) + << "Recreating Vulkan render target for buffer" << buffer + << "to change color preserved from" << data->colorPreserved + << "to" << needPreserve; + if (!recreateRhiRenderTarget(data, flags)) + return {}; + } else +#endif + { +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + auto renderTarget = data->windowRenderTarget.rt.renderTarget; +#else + auto renderTarget = data->windowRenderTarget.renderTarget; +#endif + if (renderTarget) + static_cast(renderTarget)->setFlags(flags); + } + } + data->colorPreserved = needPreserve; d->lastBuffer = data; + if (colorPreserved) + *colorPreserved = data->colorPreserved; return data->renderTarget; } } std::unique_ptr bufferData(new BufferData); bufferData->buffer = buffer; - + bufferData->colorPreserved = needPreserve; QQuickRenderTarget rt; - if (wlr_renderer_is_pixman(d->renderer->handle())) { + if (isSoftware) { 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); @@ -610,8 +687,10 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q if (QSGRendererInterface::isApiRhiBased(getGraphicsApi(rc))) { if (!rt.isNull()) { // Force convert to Rhi render target - if (!d->ensureRhiRenderTarget(rc, bufferData.get())) + if (!d->ensureRhiRenderTarget(rc, bufferData.get(), + rhiRenderTargetFlags(resolvedMode))) { bufferData->renderTarget = {}; + } } if (bufferData->renderTarget.isNull()) @@ -629,6 +708,9 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q d->buffers.append(bufferData.release()); d->lastBuffer = d->buffers.last(); + if (colorPreserved) + *colorPreserved = d->buffers.last()->colorPreserved; + return d->buffers.last()->renderTarget; } diff --git a/waylib/src/server/qtquick/wrenderhelper.h b/waylib/src/server/qtquick/wrenderhelper.h index 789edb5ca4..e68de578b4 100644 --- a/waylib/src/server/qtquick/wrenderhelper.h +++ b/waylib/src/server/qtquick/wrenderhelper.h @@ -47,7 +47,9 @@ class WAYLIB_SERVER_EXPORT WRenderHelper : public QObject, public WObject static QW_NAMESPACE::qw_buffer *toBuffer(QW_NAMESPACE::qw_renderer *renderer, QSGTexture *texture, QSGRendererInterface::GraphicsApi api); - QQuickRenderTarget acquireRenderTarget(QQuickRenderControl *rc, QW_NAMESPACE::qw_buffer *buffer); + QQuickRenderTarget acquireRenderTarget(QQuickRenderControl *rc, QW_NAMESPACE::qw_buffer *buffer, + WGlobal::ColorContentsMode mode = WGlobal::ColorContentsMode::DontCare, + bool *colorPreserved = nullptr); std::pair lastRenderTarget() const; static QW_NAMESPACE::qw_renderer *createRenderer(QW_NAMESPACE::qw_backend *backend); static QW_NAMESPACE::qw_renderer *createRenderer(QW_NAMESPACE::qw_backend *backend, QSGRendererInterface::GraphicsApi api); diff --git a/waylib/tests/unit_tests/test_wshmtexture/CMakeLists.txt b/waylib/tests/unit_tests/test_wshmtexture/CMakeLists.txt index 02614badce..cf2ce92b8c 100644 --- a/waylib/tests/unit_tests/test_wshmtexture/CMakeLists.txt +++ b/waylib/tests/unit_tests/test_wshmtexture/CMakeLists.txt @@ -10,6 +10,7 @@ target_link_libraries(test_wshmtexture Qt6::Core Qt6::Gui Qt6::Qml + Qt6::Quick Qt6::Test ) diff --git a/waylib/tests/unit_tests/test_wshmtexture/main.cpp b/waylib/tests/unit_tests/test_wshmtexture/main.cpp index d06a493a40..79524177ef 100644 --- a/waylib/tests/unit_tests/test_wshmtexture/main.cpp +++ b/waylib/tests/unit_tests/test_wshmtexture/main.cpp @@ -2,13 +2,30 @@ // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "wtools.h" +#include "woutputlayer.h" +#include "woutputviewport.h" +#include +#include +#include #include #include using namespace Waylib::Server; +namespace { +constexpr WGlobal::ColorContentsMode +resolveColorContentsMode(WGlobal::ColorContentsMode requested, + bool softwareRenderer) noexcept +{ + if (requested != WGlobal::ColorContentsMode::DontCare) + return requested; + return softwareRenderer ? WGlobal::ColorContentsMode::Preserve + : WGlobal::ColorContentsMode::Clear; +} +} + class WShmTextureTest : public QObject { Q_OBJECT @@ -42,6 +59,64 @@ private Q_SLOTS: QImage::Format_RGBX8888); } + + void dontCareResolvesToClearForRhi() + { + QCOMPARE(resolveColorContentsMode(WGlobal::ColorContentsMode::DontCare, false), + WGlobal::ColorContentsMode::Clear); + } + + void dontCareResolvesToPreserveForSoftware() + { + QCOMPARE(resolveColorContentsMode(WGlobal::ColorContentsMode::DontCare, true), + WGlobal::ColorContentsMode::Preserve); + } + + void publicColorContentsModeProperties() + { + WOutputViewport viewport; + QCOMPARE(viewport.colorContentsMode(), WGlobal::ColorContentsMode::DontCare); + QSignalSpy viewportSpy(&viewport, &WOutputViewport::colorContentsModeChanged); + viewport.setColorContentsMode(WGlobal::ColorContentsMode::Preserve); + QCOMPARE(viewport.colorContentsMode(), WGlobal::ColorContentsMode::Preserve); + QCOMPARE(viewportSpy.count(), 1); + viewport.setColorContentsMode(WGlobal::ColorContentsMode::Preserve); + QCOMPARE(viewportSpy.count(), 1); + + QQuickItem item; + WOutputLayer layer(&item); + QCOMPARE(layer.colorContentsMode(), WGlobal::ColorContentsMode::DontCare); + QSignalSpy layerSpy(&layer, &WOutputLayer::colorContentsModeChanged); + layer.setColorContentsMode(WGlobal::ColorContentsMode::Clear); + QCOMPARE(layer.colorContentsMode(), WGlobal::ColorContentsMode::Clear); + QCOMPARE(layerSpy.count(), 1); + layer.setColorContentsMode(WGlobal::ColorContentsMode::Clear); + QCOMPARE(layerSpy.count(), 1); + } + + void qmlColorContentsModeNamespace() + { + QQmlEngine engine; + QQmlComponent component(&engine); + component.setData(R"( +import QtQml +import Waylib.Server 1.0 + +QtObject { + property int clearMode: ColorContentsMode.Clear + property int preserveMode: ColorContentsMode.Preserve +} +)", + QUrl()); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer object(component.create()); + QVERIFY(object); + QCOMPARE(object->property("clearMode").toInt(), + int(WGlobal::ColorContentsMode::Clear)); + QCOMPARE(object->property("preserveMode").toInt(), + int(WGlobal::ColorContentsMode::Preserve)); + } + void rejectsUnsupportedFormat() { QCOMPARE(WTools::toImageFormat(DRM_FORMAT_R8), From aee91917551d48fbf9614bd1d7fc043202f7d63b Mon Sep 17 00:00:00 2001 From: JiDe Zhang Date: Wed, 15 Jul 2026 15:36:54 +0800 Subject: [PATCH 4/6] refactor(render): use weak_ptr for RenderTarget lifecycle management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace RenderTarget's public struct members with an opaque d-pointer holding std::weak_ptr. When WRenderHelper destroys a BufferData, all RenderTarget instances auto-null like QPointer. Eliminates findBufferData for-loop: barrier methods now access BufferData directly through weak_ptr::lock(). WRenderHelperPrivate manages BufferData via shared_ptr, enabling automatic weak_ptr expiry. Also adds Vulkan layout transition barrier helpers and VkDmabufImage layout tracking for COLOR_ATTACHMENT_OPTIMAL <-> GENERAL transitions. 将 RenderTarget 从公开结构体重构为不透明 d-pointer 类,内部持有 std::weak_ptr。当 WRenderHelper 销毁 BufferData 时, 所有 RenderTarget 实例自动置空,类似 QPointer 引用 QObject。 消除 findBufferData 循环查找:屏障方法通过 weak_ptr::lock() 直接 访问 BufferData。WRenderHelperPrivate 使用 shared_ptr 管理 BufferData 生命周期,使 weak_ptr 自动失效。 同时添加 Vulkan 布局转换屏障辅助函数和 VkDmabufImage 布局跟踪, 实现 COLOR_ATTACHMENT_OPTIMAL 与 GENERAL 之间的转换。 Log: 重构 RenderTarget 为弱引用模式并添加 Vulkan 布局屏障 Influence: RenderTarget 不再暴露成员变量,生命周期由 weak_ptr 管理; 屏障方法不再需要循环查找 BufferData,性能提升。 --- .../qtquick/private/wbufferrenderer.cpp | 19 ++- .../qtquick/private/wbufferrenderer_p.h | 5 +- .../qtquick/private/wvulkandmabufimport.cpp | 23 +++ .../qtquick/private/wvulkandmabufimport_p.h | 11 ++ waylib/src/server/qtquick/woutputhelper.cpp | 5 +- waylib/src/server/qtquick/wrenderhelper.cpp | 144 +++++++++++++++--- waylib/src/server/qtquick/wrenderhelper.h | 35 ++++- 7 files changed, 201 insertions(+), 41 deletions(-) diff --git a/waylib/src/server/qtquick/private/wbufferrenderer.cpp b/waylib/src/server/qtquick/private/wbufferrenderer.cpp index 217382cf71..4619691aa8 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer.cpp +++ b/waylib/src/server/qtquick/private/wbufferrenderer.cpp @@ -269,7 +269,7 @@ QRhiTexture *WBufferRenderer::currentRenderTarget() const bool WBufferRenderer::isColorPreserved() const { - return state.colorPreserved; + return state.renderTarget.colorPreserved(); } const qw_damage_ring *WBufferRenderer::damageRing() const @@ -384,7 +384,7 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe auto wd = QQuickWindowPrivate::get(window()); Q_ASSERT(wd->renderControl); - auto rt = m_renderHelper->acquireRenderTarget(wd->renderControl, buffer, mode, &state.colorPreserved); + auto rt = m_renderHelper->acquireRenderTarget(wd->renderControl, buffer, mode); if (rt.isNull()) { buffer->unlock(); return nullptr; @@ -395,7 +395,8 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe m_damageRing.rotate_buffer(wbuffer, damage); state.dirty = WTools::fromPixmanRegion(damage); - auto rtd = QQuickRenderTargetPrivate::get(&rt); + auto rtValue = rt.rt(); + auto rtd = QQuickRenderTargetPrivate::get(&rtValue); QSGRenderTarget sgRT; if (rtd->type == QQuickRenderTargetPrivate::Type::PaintDevice) { @@ -482,7 +483,7 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, auto softwareRenderer = dynamic_cast(renderer); { // before render if (softwareRenderer) { - const bool clearColor = !state.colorPreserved; + const bool clearColor = !state.renderTarget.colorPreserved(); #if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) softwareRenderer->setClearColorEnabled(clearColor); #else @@ -503,7 +504,7 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, if (!mapTransform.isIdentity()) state.worldTransform = mapTransform * state.worldTransform; state.worldTransform.optimize(); - auto image = getImageFrom(state.renderTarget); + auto image = getImageFrom(state.renderTarget.rt()); image->setDevicePixelRatio(devicePixelRatio); // TODO: Should set to QSGSoftwareRenderer, but it's not support specify matrix. @@ -525,7 +526,7 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, state.worldTransform.optimize(); bool flipY = wd->rhi ? !wd->rhi->isYUpInNDC() : false; - if (state.renderTarget.mirrorVertically()) + if (state.renderTarget.rt().mirrorVertically()) flipY = !flipY; if (viewportRect.isValid()) { @@ -569,7 +570,11 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, } } + if (m_renderHelper) + m_renderHelper->prepareVulkanRenderTarget(state.sgRenderTarget.cb, state.renderTarget); state.context->renderNextFrame(renderer); + if (m_renderHelper) + m_renderHelper->finishVulkanRenderTarget(state.sgRenderTarget.cb, state.renderTarget); { // after render if (!softwareRenderer) { @@ -587,7 +592,7 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, } else { state.dirty = softwareRenderer->flushRegion(); - auto currentImage = getImageFrom(state.renderTarget); + auto currentImage = getImageFrom(state.renderTarget.rt()); Q_ASSERT(currentImage && currentImage == softwareRenderer->renderTarget().paintDevice); currentImage->setDevicePixelRatio(1.0); const auto scaleTF = QTransform::fromScale(devicePixelRatio, devicePixelRatio); diff --git a/waylib/src/server/qtquick/private/wbufferrenderer_p.h b/waylib/src/server/qtquick/private/wbufferrenderer_p.h index 313648e7d9..d2c465a9eb 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer_p.h +++ b/waylib/src/server/qtquick/private/wbufferrenderer_p.h @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -32,7 +33,6 @@ struct pixman_region32; struct wlr_swapchain; WAYLIB_SERVER_BEGIN_NAMESPACE -class WRenderHelper; class WSGTextureProvider; class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem { @@ -135,7 +135,6 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem struct RenderState { RenderFlags flags; WGlobal::ColorContentsMode colorContentsMode = WGlobal::ColorContentsMode::DontCare; - bool colorPreserved = false; QSGRenderContext *context; QSGRenderer *renderer; QSGBatchRenderer::Renderer *batchRenderer; @@ -143,7 +142,7 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem QSize pixelSize; qreal devicePixelRatio; std::unique_ptr buffer; - QQuickRenderTarget renderTarget; + WRenderHelper::RenderTarget renderTarget; QSGRenderTarget sgRenderTarget; QRegion dirty; } state; diff --git a/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp b/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp index 0dc9615093..6095fd7395 100644 --- a/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp +++ b/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp @@ -592,6 +592,29 @@ void vulkanReleaseDmabufImage(VkDmabufImage &import) } import.memoryCount = 0; } +void vulkanTransitionImageLayout(VkCommandBuffer cmdBuf, VkImage image, + VkImageLayout oldLayout, VkImageLayout newLayout, + VkAccessFlags srcAccess, VkAccessFlags dstAccess, + VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage) +{ + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcAccessMask = srcAccess; + barrier.dstAccessMask = dstAccess; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + + vkCmdPipelineBarrier(cmdBuf, srcStage, dstStage, 0, + 0, nullptr, 0, nullptr, 1, &barrier); +} WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/qtquick/private/wvulkandmabufimport_p.h b/waylib/src/server/qtquick/private/wvulkandmabufimport_p.h index 3ce688cbd6..ac02fdac7e 100644 --- a/waylib/src/server/qtquick/private/wvulkandmabufimport_p.h +++ b/waylib/src/server/qtquick/private/wvulkandmabufimport_p.h @@ -28,6 +28,9 @@ struct VkDmabufImage { uint32_t memoryCount = 0; VkFormat format = VK_FORMAT_UNDEFINED; VkDevice device = VK_NULL_HANDLE; + // Actual VkImage layout of 'image', tracked so waylib can insert barriers + // Qt RHI does not know about (COLOR_ATTACHMENT_OPTIMAL ↔ GENERAL). + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; bool isNull() const { return image == VK_NULL_HANDLE; } }; @@ -44,6 +47,14 @@ VkDmabufImage vulkanImportDmabufForRender(VkPhysicalDevice physicalDevice, // Releases the image and memory held by an imported dmabuf image. Safe to call // on a default-constructed (null) instance. void vulkanReleaseDmabufImage(VkDmabufImage &import); +// Records a VkImage layout transition barrier on the given command buffer. +// oldLayout→newLayout with appropriate access/stage masks. This helper +// exists because wrenderhelper.cpp includes Qt headers before vulkan.h, +// causing VK_NO_PROTOTYPES to suppress the real function prototypes. +void vulkanTransitionImageLayout(VkCommandBuffer cmdBuf, VkImage image, + VkImageLayout oldLayout, VkImageLayout newLayout, + VkAccessFlags srcAccess, VkAccessFlags dstAccess, + VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage); #endif // ENABLE_VULKAN_RENDER WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/qtquick/woutputhelper.cpp b/waylib/src/server/qtquick/woutputhelper.cpp index a0a7d7d574..c09c2e07c0 100644 --- a/waylib/src/server/qtquick/woutputhelper.cpp +++ b/waylib/src/server/qtquick/woutputhelper.cpp @@ -158,7 +158,7 @@ std::pair WOutputHelper::acquireRenderTarget(QQ return {}; } - return {buffer, rt}; + return {buffer, rt.rt()}; } std::pair WOutputHelper::lastRenderTarget() @@ -167,7 +167,8 @@ std::pair WOutputHelper::lastRenderTarget() if (!d->renderHelper) return {nullptr, {}}; - return d->renderHelper->lastRenderTarget(); + auto rt = d->renderHelper->lastRenderTarget(); + return {rt.buffer(), rt.rt()}; } void WOutputHelper::setBuffer(qw_buffer *buffer) diff --git a/waylib/src/server/qtquick/wrenderhelper.cpp b/waylib/src/server/qtquick/wrenderhelper.cpp index 4a22130426..4a42a3ffd3 100644 --- a/waylib/src/server/qtquick/wrenderhelper.cpp +++ b/waylib/src/server/qtquick/wrenderhelper.cpp @@ -7,6 +7,7 @@ #include "private/wqmlhelper_p.h" #include "private/wglobal_p.h" #include "private/wvulkandmabufimport_p.h" +#include #include #include @@ -28,6 +29,9 @@ #include #include #include +#ifdef ENABLE_VULKAN_RENDER +#include +#endif extern "C" { #define static @@ -141,6 +145,42 @@ struct Q_DECL_HIDDEN BufferData { } }; +class WRenderHelper::RenderTarget::Private { +public: + std::weak_ptr data; +}; + +WRenderHelper::RenderTarget::RenderTarget() : d(new Private) {} +WRenderHelper::RenderTarget::RenderTarget(const RenderTarget &other) + : d(other.d ? new Private(*other.d) : nullptr) {} +WRenderHelper::RenderTarget &WRenderHelper::RenderTarget::operator=(const RenderTarget &other) { + if (this != &other) { + delete d; + d = other.d ? new Private(*other.d) : nullptr; + } + return *this; +} +WRenderHelper::RenderTarget::~RenderTarget() { delete d; } + +bool WRenderHelper::RenderTarget::isNull() const { + return !d || d->data.expired(); +} +QQuickRenderTarget WRenderHelper::RenderTarget::rt() const { + if (!d) return {}; + auto data = d->data.lock(); + return data ? data->renderTarget : QQuickRenderTarget(); +} +qw_buffer *WRenderHelper::RenderTarget::buffer() const { + if (!d) return nullptr; + auto data = d->data.lock(); + return data ? data->buffer : nullptr; +} +bool WRenderHelper::RenderTarget::colorPreserved() const { + if (!d) return false; + auto data = d->data.lock(); + return data ? data->colorPreserved : false; +} + static constexpr WGlobal::ColorContentsMode resolveColorContentsMode(WGlobal::ColorContentsMode requested, bool softwareRenderer) noexcept { @@ -301,17 +341,16 @@ class Q_DECL_HIDDEN WRenderHelperPrivate : public WObjectPrivate W_DECLARE_PUBLIC(WRenderHelper) qw_renderer *renderer; - QList buffers; - BufferData *lastBuffer = nullptr; + QList> buffers; + std::weak_ptr lastBuffer; QSize size; }; void WRenderHelperPrivate::resetRenderBuffer() { - qDeleteAll(buffers); - lastBuffer = nullptr; buffers.clear(); + lastBuffer.reset(); } void WRenderHelperPrivate::onBufferDestroy() @@ -321,15 +360,14 @@ void WRenderHelperPrivate::onBufferDestroy() for (int i = 0; i < buffers.count(); ++i) { auto data = buffers[i]; if (data->buffer == buffer) { - if (lastBuffer == data) - lastBuffer = nullptr; + auto locked = lastBuffer.lock(); + if (locked && locked == data) + lastBuffer.reset(); buffers.removeAt(i); - delete data; break; } } } - bool WRenderHelperPrivate::ensureRhiRenderTarget(QQuickRenderControl *rc, BufferData *data, QRhiTextureRenderTarget::Flags flags) { @@ -592,9 +630,8 @@ qw_buffer *WRenderHelper::toBuffer(qw_renderer *renderer, QSGTexture *texture, Q return nullptr; } -QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, qw_buffer *buffer, - WGlobal::ColorContentsMode mode, - bool *colorPreserved) +WRenderHelper::RenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, qw_buffer *buffer, + WGlobal::ColorContentsMode mode) { W_D(WRenderHelper); Q_ASSERT(buffer); @@ -616,7 +653,7 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q << "Recreating Vulkan render target for buffer" << buffer << "to change color preserved from" << data->colorPreserved << "to" << needPreserve; - if (!recreateRhiRenderTarget(data, flags)) + if (!recreateRhiRenderTarget(data.get(), flags)) return {}; } else #endif @@ -632,9 +669,9 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q } data->colorPreserved = needPreserve; d->lastBuffer = data; - if (colorPreserved) - *colorPreserved = data->colorPreserved; - return data->renderTarget; + RenderTarget result; + result.d->data = data; + return result; } } @@ -705,23 +742,80 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q connect(buffer, SIGNAL(before_destroy()), this, SLOT(onBufferDestroy()), Qt::UniqueConnection); - d->buffers.append(bufferData.release()); - d->lastBuffer = d->buffers.last(); + d->buffers.append(std::shared_ptr(bufferData.release())); + RenderTarget result; + result.d->data = d->buffers.last(); + return result; +} - if (colorPreserved) - *colorPreserved = d->buffers.last()->colorPreserved; +WRenderHelper::RenderTarget WRenderHelper::lastRenderTarget() const +{ + W_DC(WRenderHelper); + auto data = d->lastBuffer.lock(); + if (!data) + return {}; - return d->buffers.last()->renderTarget; + RenderTarget result; + result.d->data = data; + return result; } +#ifdef ENABLE_VULKAN_RENDER +void WRenderHelper::prepareVulkanRenderTarget(QRhiCommandBuffer *cb, const RenderTarget &rt) +{ + if (!rt.d) + return; + auto data = rt.d->data.lock(); + if (!data || data->vkDmabufImage.isNull()) + return; -std::pair WRenderHelper::lastRenderTarget() const + // After the previous frame's finishVulkanRenderTarget() the image is in + // GENERAL. Qt RHI's render pass for a PreserveColorContents target expects + // initialLayout = COLOR_ATTACHMENT_OPTIMAL, so transition back. On the + // very first frame the layout is still UNDEFINED and Qt RHI handles that + // transition itself — no barrier needed. + if (data->vkDmabufImage.layout != VK_IMAGE_LAYOUT_GENERAL) + return; + + cb->beginExternal(); + auto handles = static_cast(cb->nativeHandles()); + Q_ASSERT(handles && handles->commandBuffer); + + vulkanTransitionImageLayout(handles->commandBuffer, data->vkDmabufImage.image, + VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + 0, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + cb->endExternal(); + + data->vkDmabufImage.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; +} + +void WRenderHelper::finishVulkanRenderTarget(QRhiCommandBuffer *cb, const RenderTarget &rt) { - W_DC(WRenderHelper); - if (!d->lastBuffer) - return {nullptr, {}}; + if (!rt.d) + return; + auto data = rt.d->data.lock(); + if (!data || data->vkDmabufImage.isNull()) + return; + + // Qt RHI's texture render pass leaves the image in COLOR_ATTACHMENT_OPTIMAL + // (attDesc.finalLayout, qrhivulkan.cpp). DRM/KMS needs GENERAL to read the + // dmabuf, especially on tiled GPUs (NVIDIA) where the tiling differs. + if (data->vkDmabufImage.layout == VK_IMAGE_LAYOUT_GENERAL) + return; + + cb->beginExternal(); + auto handles = static_cast(cb->nativeHandles()); + Q_ASSERT(handles && handles->commandBuffer); + + vulkanTransitionImageLayout(handles->commandBuffer, data->vkDmabufImage.image, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); + cb->endExternal(); - return {d->lastBuffer->buffer, d->lastBuffer->renderTarget}; + data->vkDmabufImage.layout = VK_IMAGE_LAYOUT_GENERAL; } +#endif // ENABLE_VULKAN_RENDER static qw_renderer *createRendererWithType(const char *type, qw_backend *backend) { diff --git a/waylib/src/server/qtquick/wrenderhelper.h b/waylib/src/server/qtquick/wrenderhelper.h index e68de578b4..5c4c834661 100644 --- a/waylib/src/server/qtquick/wrenderhelper.h +++ b/waylib/src/server/qtquick/wrenderhelper.h @@ -15,6 +15,7 @@ class QQuickRenderControl; class QSGTexture; class QSGPlainTexture; class QRhi; +class QRhiCommandBuffer; QT_END_NAMESPACE QW_BEGIN_NAMESPACE @@ -47,10 +48,36 @@ class WAYLIB_SERVER_EXPORT WRenderHelper : public QObject, public WObject static QW_NAMESPACE::qw_buffer *toBuffer(QW_NAMESPACE::qw_renderer *renderer, QSGTexture *texture, QSGRendererInterface::GraphicsApi api); - QQuickRenderTarget acquireRenderTarget(QQuickRenderControl *rc, QW_NAMESPACE::qw_buffer *buffer, - WGlobal::ColorContentsMode mode = WGlobal::ColorContentsMode::DontCare, - bool *colorPreserved = nullptr); - std::pair lastRenderTarget() const; + // Opaque value type holding a weak reference to the internal buffer data + // managed by WRenderHelper. When the underlying buffer data is destroyed + // (e.g. the buffer is removed), all RenderTarget instances referencing it + // automatically become null — similar to how QPointer tracks QObject. + class WAYLIB_SERVER_EXPORT RenderTarget { + public: + RenderTarget(); + RenderTarget(const RenderTarget &); + RenderTarget &operator=(const RenderTarget &); + ~RenderTarget(); + + bool isNull() const; + QQuickRenderTarget rt() const; + QW_NAMESPACE::qw_buffer *buffer() const; + bool colorPreserved() const; + + private: + friend class WRenderHelper; + class Private; + Private *d = nullptr; + }; + + RenderTarget acquireRenderTarget(QQuickRenderControl *rc, QW_NAMESPACE::qw_buffer *buffer, + WGlobal::ColorContentsMode mode = WGlobal::ColorContentsMode::DontCare); + // For Vulkan render targets: insert a layout barrier before Qt RHI's + // render pass so the image matches the render pass's initialLayout. + // No-op for non-Vulkan backends or buffers without a Vulkan dmabuf image. + void prepareVulkanRenderTarget(QRhiCommandBuffer *cb, const RenderTarget &rt); + void finishVulkanRenderTarget(QRhiCommandBuffer *cb, const RenderTarget &rt); + RenderTarget lastRenderTarget() const; static QW_NAMESPACE::qw_renderer *createRenderer(QW_NAMESPACE::qw_backend *backend); static QW_NAMESPACE::qw_renderer *createRenderer(QW_NAMESPACE::qw_backend *backend, QSGRendererInterface::GraphicsApi api); From cfee616776a0093c6d557026db0febc4d3f52db5 Mon Sep 17 00:00:00 2001 From: JiDe Zhang Date: Wed, 15 Jul 2026 16:01:30 +0800 Subject: [PATCH 5/6] fix(effect): skip RenderBufferBlitter on Vulkan backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the graphics backend is Vulkan, RenderBufferBlitter's backdrop capture path is broken. Instead of creating it and its dependent effect components (GlassEffect, MultiEffect, Shape), fall back to a plain semi-transparent Rectangle. Add WaylibHelper.isVulkanBackend QML property so Blur.qml can detect the backend at component-creation time and avoid instantiating RenderBufferBlitter entirely via a Loader. 当后端为 Vulkan 时,RenderBufferBlitter 的背景捕获路径不可用。 不再创建 RenderBufferBlitter 及其依赖的效果组件,改为使用一个 半透明 Rectangle 作为替代。新增 WaylibHelper.isVulkanBackend QML 属性,使 Blur.qml 在组件创建时检测后端并通过 Loader 避免实例化。 Log: Vulkan 后端跳过 RenderBufferBlitter,使用半透明 Rectangle 替代 Influence: Vulkan 模式下 Blur 效果降级为半透明矩形,不影响非 Vulkan 后端 --- src/core/qml/Effects/Blur.qml | 155 ++++++++++-------- .../src/server/qtquick/private/wqmlhelper.cpp | 7 + .../src/server/qtquick/private/wqmlhelper_p.h | 2 + 3 files changed, 99 insertions(+), 65 deletions(-) diff --git a/src/core/qml/Effects/Blur.qml b/src/core/qml/Effects/Blur.qml index f41de75a7a..08f4eb0178 100644 --- a/src/core/qml/Effects/Blur.qml +++ b/src/core/qml/Effects/Blur.qml @@ -7,8 +7,8 @@ import QtQuick.Shapes import Waylib.Server import Treeland -RenderBufferBlitter { - id: blitter +Item { + id: root smooth: true property real radius: 0 @@ -25,85 +25,110 @@ RenderBufferBlitter { z: parent.z ? parent.z - 1 : -1 anchors.fill: parent - // Dispatch between Liquid Glass and traditional blur via a Loader so only - // the active branch is instantiated. Toggling the DConfig key unloads one - // Component and loads the other. + // On Vulkan the RenderBufferBlitter backdrop-capture path is broken, so + // skip creating it (and all dependent effect components) entirely and + // fall back to a plain semi-transparent rectangle. Loader { anchors.fill: parent - sourceComponent: blitter.glassEnabled ? glassComponent : blurComponent + sourceComponent: WaylibHelper.isVulkanBackend ? vulkanFallback : blitterContent } Component { - id: glassComponent - GlassEffect { + id: blitterContent + RenderBufferBlitter { + id: blitter + smooth: true anchors.fill: parent - source: blitter.content - radius: blitter.radius - blurEnabled: blitter.blurEnabled - blurMax: blitter.blurMax - blurAmount: blitter.blurAmount - blurMultiplier: blitter.multiplier - brightness: blitter.brightness - highlightEnabled: blitter.highlightEnabled - lightAngle: blitter.lightAngle - bezelWidth: Helper.config.glassBezel - thickness: Helper.config.glassThickness - displacementFactor: Helper.config.glassDisplacementFactor - ior: Helper.config.glassIor - dispersion: Helper.config.glassDispersion - contrast: -0.12 - saturation: 0.4 - colorization: 0.12 - edgeSaturation: Helper.config.glassEdgeSaturation - highlightColor: Qt.rgba(1, 1, 1, 0.3) - strokeWidth: 0.5 - strokeStrength: 1.5 - specularOpacity: 0.82 - rimReflectionEnabled: true - lightPower: 3.0 - reflectionOffset: Helper.config.glassReflectionOffset - } - } + // Dispatch between Liquid Glass and traditional blur via a Loader so only + // the active branch is instantiated. Toggling the DConfig key unloads one + // Component and loads the other. + Loader { + anchors.fill: parent + sourceComponent: blitter.glassEnabled ? glassComponent : blurComponent + } - Component { - id: blurComponent - Item { - anchors.fill: parent + Component { + id: glassComponent + GlassEffect { + anchors.fill: parent + source: blitter.content + radius: root.radius + blurEnabled: root.blurEnabled + blurMax: root.blurMax + blurAmount: root.blurAmount + blurMultiplier: root.multiplier + brightness: root.brightness + highlightEnabled: root.highlightEnabled + lightAngle: root.lightAngle - MultiEffect { - id: blur - anchors.fill: parent - layer.enabled: blitter.radiusEnabled - smooth: blitter.radiusEnabled - opacity: blitter.radiusEnabled ? 0 : blitter.opacity - source: blitter.content - autoPaddingEnabled: false - blurEnabled: blitter.blurEnabled - blur: blitter.blurAmount - blurMax: blitter.blurMax - blurMultiplier: blitter.multiplier - saturation: 0.2 + bezelWidth: Helper.config.glassBezel + thickness: Helper.config.glassThickness + displacementFactor: Helper.config.glassDisplacementFactor + ior: Helper.config.glassIor + dispersion: Helper.config.glassDispersion + contrast: -0.12 + saturation: 0.4 + colorization: 0.12 + edgeSaturation: Helper.config.glassEdgeSaturation + highlightColor: Qt.rgba(1, 1, 1, 0.3) + strokeWidth: 0.5 + strokeStrength: 1.5 + specularOpacity: 0.82 + rimReflectionEnabled: true + lightPower: 3.0 + reflectionOffset: Helper.config.glassReflectionOffset + } } - Loader { - x: blur.x - y: blur.y - active: blitter.radiusEnabled - sourceComponent: Shape { + Component { + id: blurComponent + Item { anchors.fill: parent - preferredRendererType: Shape.CurveRenderer - ShapePath { - strokeWidth: 0 - fillItem: blur - PathRectangle { - width: blur.width - height: blur.height - radius: blitter.radius + + MultiEffect { + id: blur + anchors.fill: parent + layer.enabled: root.radiusEnabled + smooth: root.radiusEnabled + opacity: root.radiusEnabled ? 0 : root.opacity + source: blitter.content + autoPaddingEnabled: false + blurEnabled: root.blurEnabled + blur: root.blurAmount + blurMax: root.blurMax + blurMultiplier: root.multiplier + saturation: 0.2 + } + + Loader { + x: blur.x + y: blur.y + active: root.radiusEnabled + sourceComponent: Shape { + anchors.fill: parent + preferredRendererType: Shape.CurveRenderer + ShapePath { + strokeWidth: 0 + fillItem: blur + PathRectangle { + width: blur.width + height: blur.height + radius: root.radius + } + } } } } } } } + + Component { + id: vulkanFallback + Rectangle { + radius: root.radius + color: Qt.rgba(0, 0, 0, 0.3) + } + } } diff --git a/waylib/src/server/qtquick/private/wqmlhelper.cpp b/waylib/src/server/qtquick/private/wqmlhelper.cpp index 66d59114be..410d07cea0 100644 --- a/waylib/src/server/qtquick/private/wqmlhelper.cpp +++ b/waylib/src/server/qtquick/private/wqmlhelper.cpp @@ -3,6 +3,8 @@ #include "wqmlhelper_p.h" +#include "wrenderhelper.h" + #include #include #include @@ -58,6 +60,11 @@ WQmlHelper::WQmlHelper(QObject *parent) } +bool WQmlHelper::isVulkanBackend() const +{ + return WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan; +} + bool WQmlHelper::hasXWayland() const { #ifdef DISABLE_XWAYLAND diff --git a/waylib/src/server/qtquick/private/wqmlhelper_p.h b/waylib/src/server/qtquick/private/wqmlhelper_p.h index 32148461c8..d19a1ab6a1 100644 --- a/waylib/src/server/qtquick/private/wqmlhelper_p.h +++ b/waylib/src/server/qtquick/private/wqmlhelper_p.h @@ -63,11 +63,13 @@ class WAYLIB_SERVER_EXPORT WQmlHelper : public QObject Q_OBJECT QML_NAMED_ELEMENT(WaylibHelper) Q_PROPERTY(bool hasXWayland READ hasXWayland CONSTANT) + Q_PROPERTY(bool isVulkanBackend READ isVulkanBackend CONSTANT) QML_SINGLETON public: explicit WQmlHelper(QObject *parent = nullptr); + bool isVulkanBackend() const; bool hasXWayland() const; static QSGRootNode *getRootNode(QQuickItem *item); From 17b12cc45e10388ca8f3844619258a373554ad14 Mon Sep 17 00:00:00 2001 From: JiDe Zhang Date: Wed, 15 Jul 2026 16:32:52 +0800 Subject: [PATCH 6/6] fix(waylib): remove mipmap filtering on wlroots-native textures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QSGPlainTexture wrapping wlroots textures via setTextureFromNativeTexture has no mipmap image data. Setting MipmapFiltering to Linear triggers "Mipmap settings changed without having image data available" warnings. Remove the setMipmapFiltering calls in both the constructor and setSmooth so the default None takes effect. 通过 setTextureFromNativeTexture 包装 wlroots 纹理的 QSGPlainTexture 没有 mipmap 图像数据。将 MipmapFiltering 设为 Linear 会触发警告。 移除构造函数和 setSmooth 中的 setMipmapFiltering 调用,使用默认值 None。 Log: 移除 wlroots 原生纹理上不应有的 mipmap 过滤设置 Influence: 消除 QSGPlainTexture mipmap 警告,不影响渲染质量 --- waylib/src/server/qtquick/wsgtextureprovider.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/waylib/src/server/qtquick/wsgtextureprovider.cpp b/waylib/src/server/qtquick/wsgtextureprovider.cpp index 0b06147f19..0dcd261cac 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.cpp +++ b/waylib/src/server/qtquick/wsgtextureprovider.cpp @@ -27,8 +27,6 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate qtTexture.setOwnsTexture(false); qtTexture.setFiltering(smooth ? QSGTexture::Linear : QSGTexture::Nearest); - qtTexture.setMipmapFiltering(smooth ? QSGTexture::Linear - : QSGTexture::Nearest); } ~WSGTextureProviderPrivate() { @@ -235,8 +233,6 @@ void WSGTextureProvider::setSmooth(bool newSmooth) d->smooth = newSmooth; d->qtTexture.setFiltering(newSmooth ? QSGTexture::Linear : QSGTexture::Nearest); - d->qtTexture.setMipmapFiltering(newSmooth ? QSGTexture::Linear - : QSGTexture::Nearest); Q_EMIT smoothChanged(); }