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/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/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..4619691aa8 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.renderTarget.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); if (rt.isNull()) { buffer->unlock(); return nullptr; @@ -390,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) { @@ -420,6 +426,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 +443,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 +483,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.renderTarget.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()) { @@ -501,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. @@ -523,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()) { @@ -564,16 +567,14 @@ 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); - } } } + 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) { @@ -591,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 628684c714..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 { @@ -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,7 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem struct RenderState { RenderFlags flags; + WGlobal::ColorContentsMode colorContentsMode = WGlobal::ColorContentsMode::DontCare; QSGRenderContext *context; QSGRenderer *renderer; QSGBatchRenderer::Renderer *batchRenderer; @@ -140,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/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/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); diff --git a/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp b/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp new file mode 100644 index 0000000000..6095fd7395 --- /dev/null +++ b/waylib/src/server/qtquick/private/wvulkandmabufimport.cpp @@ -0,0 +1,622 @@ +// 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") +QT_WARNING_DISABLE_CLANG("-Wmissing-field-initializers") +QT_WARNING_DISABLE_CLANG("-Wmissing-designated-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; +} +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 + +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..ac02fdac7e --- /dev/null +++ b/waylib/src/server/qtquick/private/wvulkandmabufimport_p.h @@ -0,0 +1,60 @@ +// 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; + // 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; } +}; + +// 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); +// 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 a71a58f396..c09c2e07c0 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,13 +152,13 @@ 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 {}; } - return {buffer, rt}; + return {buffer, rt.rt()}; } std::pair WOutputHelper::lastRenderTarget() @@ -166,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/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 0f00631593..4a42a3ffd3 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,8 @@ #include "wayliblogging.h" #include "private/wqmlhelper_p.h" #include "private/wglobal_p.h" +#include "private/wvulkandmabufimport_p.h" +#include #include #include @@ -27,6 +29,9 @@ #include #include #include +#ifdef ENABLE_VULKAN_RENDER +#include +#endif extern "C" { #define static @@ -58,6 +63,9 @@ struct Q_DECL_HIDDEN BufferData { ~BufferData() { resetWindowRenderTarget(); +#ifdef ENABLE_VULKAN_RENDER + vulkanReleaseDmabufImage(vkDmabufImage); +#endif } qw_buffer *buffer = nullptr; @@ -65,6 +73,12 @@ 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(). + VkDmabufImage vkDmabufImage; +#endif inline void resetWindowRenderTarget() { #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) @@ -131,12 +145,91 @@ 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 +{ + 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()) { @@ -146,7 +239,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()); @@ -170,7 +263,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); @@ -178,14 +272,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 })) @@ -194,7 +288,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(); @@ -210,7 +304,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) @@ -242,21 +336,21 @@ 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; - 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() @@ -266,15 +360,16 @@ 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); break; } } } - -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) @@ -283,7 +378,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) @@ -535,29 +630,58 @@ qw_buffer *WRenderHelper::toBuffer(qw_renderer *renderer, QSGTexture *texture, Q return nullptr; } -QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, qw_buffer *buffer) +WRenderHelper::RenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, qw_buffer *buffer, + WGlobal::ColorContentsMode mode) { 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.get(), 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; - return data->renderTarget; + RenderTarget result; + result.d->data = data; + return result; } } std::unique_ptr bufferData(new BufferData); bufferData->buffer = buffer; - auto texture = qw_texture::from_buffer(*d->renderer, *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); if (bufferData->paintDevice.constBits() != data) @@ -567,12 +691,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,14 +719,15 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q rt.setMirrorVertically(true); } - delete texture; bufferData->renderTarget = rt; 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()) @@ -602,20 +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(); - - return d->buffers.last()->renderTarget; + d->buffers.append(std::shared_ptr(bufferData.release())); + RenderTarget result; + result.d->data = d->buffers.last(); + return result; } -std::pair WRenderHelper::lastRenderTarget() const +WRenderHelper::RenderTarget WRenderHelper::lastRenderTarget() const { W_DC(WRenderHelper); - if (!d->lastBuffer) - return {nullptr, {}}; + auto data = d->lastBuffer.lock(); + if (!data) + return {}; + + 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; + + // 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) +{ + 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 789edb5ca4..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,8 +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); - 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); diff --git a/waylib/src/server/qtquick/wsgtextureprovider.cpp b/waylib/src/server/qtquick/wsgtextureprovider.cpp index 362c841602..0dcd261cac 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" @@ -26,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() { @@ -35,7 +34,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 +54,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 +111,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 +131,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 +202,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 @@ -193,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(); } 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..cf2ce92b8c --- /dev/null +++ b/waylib/tests/unit_tests/test_wshmtexture/CMakeLists.txt @@ -0,0 +1,20 @@ +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::Quick + 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..79524177ef --- /dev/null +++ b/waylib/tests/unit_tests/test_wshmtexture/main.cpp @@ -0,0 +1,128 @@ +// 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 "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 + +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 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), + QImage::Format_Invalid); + } +}; + +QTEST_MAIN(WShmTextureTest) +#include "main.moc"