From 606d6aa0d536aacfab0ee2312fa4f29c41a36f08 Mon Sep 17 00:00:00 2001 From: LFRon Date: Thu, 2 Jul 2026 20:42:26 +0800 Subject: [PATCH 1/3] feat: enable Vulkan QRhi composition through wlroots When WLR_RENDERER=vulkan, initialize Qt Quick on the wlroots Vulkan device and keep the existing GLES2 path unchanged otherwise. Clear QSG_RHI_BACKEND for child processes so OpenGL clients keep rendering with their own drivers and arrive at the compositor as buffers. Import committed client buffers into Qt RHI via dmabuf-backed texture providers instead of relying on GL-specific surface render semantics. Add the qwlroots wrapper coverage needed for Vulkan renderer handles, textures, render passes, buffer state, and syncobj release, then route output submission back through wlroots Vulkan. Render the Qt Quick composition canvas into a dmabuf output layer, release it for wlroots scanout/import, and prefer explicit non-linear modifiers for the Vulkan intermediate layer with INVALID/LINEAR only as fallbacks. --- qwlroots/src/CMakeLists.txt | 1 + qwlroots/src/render/qwrenderer.h | 20 + qwlroots/src/render/qwrenderpass.h | 22 + qwlroots/src/render/qwtexture.h | 20 + qwlroots/src/types/qwbuffer.h | 4 + qwlroots/src/types/qwcompositor.h | 21 + qwlroots/src/types/qwlinuxdrmsyncobjv1.h | 29 +- qwlroots/src/types/qwoutput.h | 1 + src/core/qml/Animations/NewAnimation.qml | 3 +- src/core/qml/PrimaryOutput.qml | 5 +- src/main.cpp | 14 +- src/output/output.cpp | 22 +- src/surface/surfacewrapper.cpp | 24 + src/surface/surfacewrapper.h | 2 + waylib/src/server/CMakeLists.txt | 2 +- waylib/src/server/kernel/private/wsurface_p.h | 12 + waylib/src/server/kernel/woutput.cpp | 5 +- waylib/src/server/kernel/wsurface.cpp | 101 +- waylib/src/server/kernel/wsurface.h | 4 + .../qtquick/private/wbufferrenderer.cpp | 449 +- .../qtquick/private/wbufferrenderer_p.h | 3 + .../qtquick/private/wrenderbuffernode.cpp | 31 +- .../server/qtquick/private/wsurfaceitem_p.h | 1 - waylib/src/server/qtquick/wbufferitem.cpp | 20 +- waylib/src/server/qtquick/woutputhelper.cpp | 193 +- waylib/src/server/qtquick/woutputhelper.h | 6 + .../server/qtquick/woutputrenderwindow.cpp | 235 +- .../src/server/qtquick/woutputrenderwindow.h | 4 + waylib/src/server/qtquick/wquickcursor.cpp | 124 +- waylib/src/server/qtquick/wrenderhelper.cpp | 5358 ++++++++++++++++- waylib/src/server/qtquick/wrenderhelper.h | 70 +- .../src/server/qtquick/wsgtextureprovider.cpp | 705 ++- .../src/server/qtquick/wsgtextureprovider.h | 22 +- waylib/src/server/qtquick/wsurfaceitem.cpp | 331 +- waylib/src/server/utils/wbufferdumper.cpp | 17 +- .../utils/wextimagecapturesourcev1impl.cpp | 14 +- waylib/src/server/wayliblogging.cpp | 1 + waylib/src/server/wayliblogging.h | 1 + 38 files changed, 7464 insertions(+), 433 deletions(-) create mode 100644 qwlroots/src/render/qwrenderpass.h diff --git a/qwlroots/src/CMakeLists.txt b/qwlroots/src/CMakeLists.txt index f2013d8f1..4548554bf 100644 --- a/qwlroots/src/CMakeLists.txt +++ b/qwlroots/src/CMakeLists.txt @@ -81,6 +81,7 @@ set(HEADERS qwdisplay.h qwsession.h render/qwrenderer.h + render/qwrenderpass.h render/qwtexture.h render/qwallocator.h render/qwegl.h diff --git a/qwlroots/src/render/qwrenderer.h b/qwlroots/src/render/qwrenderer.h index ee2faf8df..00d68d192 100644 --- a/qwlroots/src/render/qwrenderer.h +++ b/qwlroots/src/render/qwrenderer.h @@ -11,7 +11,14 @@ extern "C" { #include #include #undef static +#if WLR_HAVE_GLES2_RENDERER +#include +#endif +#include #include +#if WLR_HAVE_VULKAN_RENDERER +#include +#endif } struct wlr_renderer; @@ -37,6 +44,19 @@ class QW_CLASS_OBJECT(renderer) QW_FUNC_MEMBER(renderer, init_wl_shm, bool, wl_display *wl_display) QW_FUNC_MEMBER(renderer, get_drm_fd, int) QW_FUNC_MEMBER(renderer, get_texture_formats, const wlr_drm_format_set *, uint32_t buffer_caps) +#if WLR_HAVE_GLES2_RENDERER + QW_FUNC_MEMBER(renderer, is_gles2, bool) +#endif + QW_FUNC_MEMBER(renderer, is_pixman, bool) +#if WLR_HAVE_VULKAN_RENDERER + QW_FUNC_MEMBER(renderer, is_vk, bool) + // Access the wlroots-adopted Vulkan device handles. Used by compositors + // (e.g. waylib) that adopt the wlroots VkDevice into Qt RHI. + QW_FUNC_MEMBER(vk_renderer, get_instance, VkInstance) + QW_FUNC_MEMBER(vk_renderer, get_physical_device, VkPhysicalDevice) + QW_FUNC_MEMBER(vk_renderer, get_device, VkDevice) + QW_FUNC_MEMBER(vk_renderer, get_queue_family, uint32_t) +#endif protected: QW_FUNC_MEMBER(renderer, destroy, void) diff --git a/qwlroots/src/render/qwrenderpass.h b/qwlroots/src/render/qwrenderpass.h new file mode 100644 index 000000000..6b0a07742 --- /dev/null +++ b/qwlroots/src/render/qwrenderpass.h @@ -0,0 +1,22 @@ +// 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 + +#pragma once + +#include + +extern "C" { +#include +} + +QW_BEGIN_NAMESPACE + +class QW_CLASS_REINTERPRET_CAST(render_pass) +{ +public: + QW_FUNC_MEMBER(render_pass, submit, bool) + QW_FUNC_MEMBER(render_pass, add_texture, void, const wlr_render_texture_options *options) + QW_FUNC_MEMBER(render_pass, add_rect, void, const wlr_render_rect_options *options) +}; + +QW_END_NAMESPACE diff --git a/qwlroots/src/render/qwtexture.h b/qwlroots/src/render/qwtexture.h index 963554c3a..ca39f022c 100644 --- a/qwlroots/src/render/qwtexture.h +++ b/qwlroots/src/render/qwtexture.h @@ -7,6 +7,13 @@ extern "C" { #include +#if WLR_HAVE_GLES2_RENDERER +#include +#endif +#include +#if WLR_HAVE_VULKAN_RENDERER +#include +#endif } QW_BEGIN_NAMESPACE @@ -17,6 +24,19 @@ class QW_CLASS_REINTERPRET_CAST(texture) QW_FUNC_STATIC(texture, from_pixels, qw_texture *, wlr_renderer *renderer, uint32_t fmt, uint32_t stride, uint32_t width, uint32_t height, const void *data) QW_FUNC_STATIC(texture, from_dmabuf, qw_texture *, wlr_renderer *renderer, wlr_dmabuf_attributes *attribs) QW_FUNC_STATIC(texture, from_buffer, qw_texture *, wlr_renderer *renderer, wlr_buffer *buffer) + QW_FUNC_MEMBER(texture, read_pixels, bool, const wlr_texture_read_pixels_options *options) + QW_FUNC_MEMBER(texture, preferred_read_format, uint32_t) +#if WLR_HAVE_GLES2_RENDERER + QW_FUNC_MEMBER(texture, is_gles2, bool) + QW_FUNC_MEMBER(gles2_texture, get_attribs, void, wlr_gles2_texture_attribs *attribs) +#endif + QW_FUNC_MEMBER(texture, is_pixman, bool) + QW_FUNC_MEMBER(pixman_texture, get_image, pixman_image_t *) +#if WLR_HAVE_VULKAN_RENDERER + QW_FUNC_MEMBER(texture, is_vk, bool) + QW_FUNC_MEMBER(vk_texture, get_image_attribs, void, wlr_vk_image_attribs *attribs) + QW_FUNC_MEMBER(vk_texture, has_alpha, bool) +#endif QW_FUNC_MEMBER(texture, update_from_buffer, bool, wlr_buffer *buffer, const pixman_region32_t *damage) diff --git a/qwlroots/src/types/qwbuffer.h b/qwlroots/src/types/qwbuffer.h index 7bc64a2c3..4007e1abe 100644 --- a/qwlroots/src/types/qwbuffer.h +++ b/qwlroots/src/types/qwbuffer.h @@ -48,6 +48,10 @@ class QW_CLASS_OBJECT(buffer) QW_FUNC_MEMBER(buffer, get_shm, bool, wlr_shm_attributes *attribs) QW_FUNC_MEMBER(buffer, begin_data_ptr_access, bool, uint32_t flags, void **data, uint32_t *format, size_t *stride) QW_FUNC_MEMBER(buffer, end_data_ptr_access, void) + + QW_ALWAYS_INLINE size_t lock_count() const { + return handle() ? handle()->n_locks : 0; + } }; class QW_CLASS_REINTERPRET_CAST(client_buffer) diff --git a/qwlroots/src/types/qwcompositor.h b/qwlroots/src/types/qwcompositor.h index 3a72be5c1..849653486 100644 --- a/qwlroots/src/types/qwcompositor.h +++ b/qwlroots/src/types/qwcompositor.h @@ -4,6 +4,7 @@ #pragma once #include +#include extern "C" { #include @@ -51,6 +52,26 @@ class QW_CLASS_OBJECT(surface) QW_FUNC_MEMBER(surface, for_each_surface, void, wlr_surface_iterator_func_t iterator, void *user_data) QW_FUNC_MEMBER(surface, get_buffer_source_box, void, wlr_fbox *box) QW_FUNC_MEMBER(surface, get_effective_damage, void, pixman_region32_t *damage) + QW_ALWAYS_INLINE qw_buffer *pending_buffer() const { + return handle() && handle()->pending.buffer + ? qw_buffer::from(handle()->pending.buffer) + : nullptr; + } + QW_ALWAYS_INLINE uint32_t pending_committed() const { + return handle() ? handle()->pending.committed : 0; + } + QW_ALWAYS_INLINE uint32_t pending_seq() const { + return handle() ? handle()->pending.seq : 0; + } + QW_ALWAYS_INLINE uint32_t current_committed() const { + return handle() ? handle()->current.committed : 0; + } + QW_ALWAYS_INLINE uint32_t current_seq() const { + return handle() ? handle()->current.seq : 0; + } + QW_ALWAYS_INLINE const pixman_region32_t *buffer_damage() const { + return &handle()->buffer_damage; + } #if WLR_VERSION_MINOR < 19 QW_FUNC_MEMBER(surface, get_extends, void, wlr_box *box) #else diff --git a/qwlroots/src/types/qwlinuxdrmsyncobjv1.h b/qwlroots/src/types/qwlinuxdrmsyncobjv1.h index 222e6089b..af67bcca2 100644 --- a/qwlroots/src/types/qwlinuxdrmsyncobjv1.h +++ b/qwlroots/src/types/qwlinuxdrmsyncobjv1.h @@ -8,6 +8,7 @@ #include "qwbuffer.h" extern "C" { +#include #include #include } @@ -25,7 +26,33 @@ class QW_CLASS_REINTERPRET_CAST(linux_drm_syncobj_surface_v1_state) { public: #if WLR_VERSION_MINOR >= 19 - QW_FUNC_MEMBER(linux_drm_syncobj_surface_v1_state, signal_release_with_buffer, bool, wlr_buffer *buffer) + QW_ALWAYS_INLINE static qw_linux_drm_syncobj_surface_v1_state *get_surface_state(wlr_surface *surface) { + auto *state = wlr_linux_drm_syncobj_v1_get_surface_state(surface); + return state ? reinterpret_cast(state) : nullptr; + } + QW_ALWAYS_INLINE bool has_acquire_timeline() const { + return handle() && handle()->acquire_timeline; + } + QW_ALWAYS_INLINE uint64_t acquire_point() const { + return handle() ? handle()->acquire_point : 0; + } + QW_ALWAYS_INLINE int export_acquire_sync_file() const { + return has_acquire_timeline() + ? wlr_drm_syncobj_timeline_export_sync_file(handle()->acquire_timeline, + handle()->acquire_point) + : -1; + } + QW_ALWAYS_INLINE bool has_release_timeline() const { + return handle() && handle()->release_timeline; + } + QW_ALWAYS_INLINE uint64_t release_point() const { + return handle() ? handle()->release_point : 0; + } + QW_ALWAYS_INLINE bool signal_release_with_buffer(qw_buffer *buffer) const { + return handle() && buffer && buffer->handle() + ? wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer(handle(), buffer->handle()) + : false; + } #endif }; diff --git a/qwlroots/src/types/qwoutput.h b/qwlroots/src/types/qwoutput.h index de8cdf400..0010adbb5 100644 --- a/qwlroots/src/types/qwoutput.h +++ b/qwlroots/src/types/qwoutput.h @@ -94,6 +94,7 @@ class QW_CLASS_OBJECT(output) #endif QW_FUNC_MEMBER(output, get_primary_formats, const wlr_drm_format_set *, uint32_t buffer_caps) + QW_FUNC_MEMBER(output, begin_render_pass, wlr_render_pass *, wlr_output_state *state, const wlr_buffer_pass_options *options) QW_FUNC_MEMBER(output, add_software_cursors_to_render_pass, void, wlr_render_pass *render_pass, const pixman_region32_t *damage) QW_FUNC_MEMBER(output, configure_primary_swapchain, bool, const wlr_output_state *state, wlr_swapchain **swapchain) diff --git a/src/core/qml/Animations/NewAnimation.qml b/src/core/qml/Animations/NewAnimation.qml index b4207227a..b071e5d2c 100644 --- a/src/core/qml/Animations/NewAnimation.qml +++ b/src/core/qml/Animations/NewAnimation.qml @@ -20,6 +20,7 @@ Item { required property var direction property int duration: 400 * Helper.animationSpeed property var enableBlur: false + property bool liveSource: direction === NewAnimation.Direction.Show x: target.x y: target.y @@ -60,7 +61,7 @@ Item { width: root.target.width + 100 height: root.target.height + 100 anchors.centerIn: parent - live: direction === NewAnimation.Direction.Show + live: root.liveSource hideSource: true sourceItem: root.target sourceRect: Qt.rect(effect.x, effect.y, effect.width, effect.height) diff --git a/src/core/qml/PrimaryOutput.qml b/src/core/qml/PrimaryOutput.qml index c8ce8f9f5..b93203e3f 100644 --- a/src/core/qml/PrimaryOutput.qml +++ b/src/core/qml/PrimaryOutput.qml @@ -19,6 +19,9 @@ OutputItem { required property QtObject outputCursor readonly property point rawPosition: parent.mapFromGlobal(cursor.position.x, cursor.position.y) readonly property real effectiveScale: rootOutputItem.devicePixelRatio || 1.0 + readonly property bool useCursorOutputLayer: GraphicsInfo.api !== GraphicsInfo.Vulkan + && GraphicsInfo.api !== GraphicsInfo.VulkanRhi + && !outputCursor.output.forceSoftwareCursor // Align cursor position to pixel grid to prevent blur on fractional DPR displays function alignToPixelGrid(value) { @@ -35,7 +38,7 @@ OutputItem { x: position.x - hotSpot.x y: position.y - hotSpot.y visible: valid && outputCursor.visible - OutputLayer.enabled: !outputCursor.output.forceSoftwareCursor + OutputLayer.enabled: useCursorOutputLayer OutputLayer.keepLayer: true OutputLayer.outputs: [screenViewport] OutputLayer.flags: OutputLayer.Cursor diff --git a/src/main.cpp b/src/main.cpp index 9d3909978..cba8aa952 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -54,7 +55,15 @@ int main(int argc, char *argv[]) }); // QQuickStyle::setStyle("Material"); - QGuiApplication::setAttribute(Qt::AA_UseOpenGLES); + const QByteArray wlrRenderer = qgetenv("WLR_RENDERER"); + const bool hasExplicitWlrRenderer = !wlrRenderer.isEmpty() && wlrRenderer != "auto"; + if (wlrRenderer != "vulkan") + QGuiApplication::setAttribute(Qt::AA_UseOpenGLES); + if (hasExplicitWlrRenderer) { + WRenderHelper::setupRendererBackend(); + if (wlrRenderer == "vulkan") + qunsetenv("QSG_RHI_BACKEND"); + } QGuiApplication::setHighDpiScaleFactorRoundingPolicy( Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); QGuiApplication::setQuitOnLastWindowClosed(false); @@ -77,7 +86,8 @@ int main(int argc, char *argv[]) #endif DLogManager::registerJournalAppender(); - WRenderHelper::setupRendererBackend(); + if (!hasExplicitWlrRenderer) + WRenderHelper::setupRendererBackend(); if (CmdLine::ref().tryExec()) return 0; Q_ASSERT(qw_buffer::get_objects().isEmpty()); diff --git a/src/output/output.cpp b/src/output/output.cpp index 475bd472b..64eb64dc0 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -30,7 +30,9 @@ #include #include +#include #include +#include #include #include @@ -39,14 +41,28 @@ #define DIFF_APP_OFFSET_FACTOR 2.0 #define POPUP_EDGE_MARGIN 10 +namespace { + +bool shouldForceSoftwareCursorForVulkanRhi() +{ + const auto graphicsApi = QQuickWindow::graphicsApi(); + return qgetenv("WLR_RENDERER") == "vulkan" + && graphicsApi == QSGRendererInterface::Vulkan; +} + +} + Output *Output::create(WOutput *output, QQmlEngine *engine, QObject *parent) { auto isSoftwareCursor = [](WOutput *output) -> bool { - return output->handle()->is_x11() || Helper::instance()->globalConfig()->forceSoftwareCursor(); + return output->handle()->is_x11() + || Helper::instance()->globalConfig()->forceSoftwareCursor() + || shouldForceSoftwareCursorForVulkanRhi(); }; QQmlComponent delegate(engine, "Treeland", "PrimaryOutput"); + const bool forceSoftwareCursor = isSoftwareCursor(output); QObject *obj = delegate.beginCreate(engine->rootContext()); - delegate.setInitialProperties(obj, { { "forceSoftwareCursor", isSoftwareCursor(output) } }); + delegate.setInitialProperties(obj, { { "forceSoftwareCursor", forceSoftwareCursor } }); delegate.completeCreate(); WOutputItem *outputItem = qobject_cast(obj); Q_ASSERT(outputItem); @@ -55,6 +71,7 @@ Output *Output::create(WOutput *output, QQmlEngine *engine, QObject *parent) auto contentItem = Helper::instance()->window()->contentItem(); outputItem->setParentItem(contentItem); outputItem->setOutput(output); + output->setForceSoftwareCursor(forceSoftwareCursor); connect(Helper::instance()->globalConfig(), &TreelandConfig::forceSoftwareCursorChanged, @@ -63,6 +80,7 @@ Output *Output::create(WOutput *output, QQmlEngine *engine, QObject *parent) auto forceSoftwareCursor = isSoftwareCursor(output); qCInfo(lcTlOutput) << "forceSoftwareCursor changed to" << forceSoftwareCursor; obj->setProperty("forceSoftwareCursor", forceSoftwareCursor); + output->setForceSoftwareCursor(forceSoftwareCursor); }); auto o = new Output(outputItem, parent); diff --git a/src/surface/surfacewrapper.cpp b/src/surface/surfacewrapper.cpp index 6c5a2cc19..76f6eacf8 100644 --- a/src/surface/surfacewrapper.cpp +++ b/src/surface/surfacewrapper.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -33,6 +34,21 @@ #define CLOSE_ANIMATION 2 #define ALWAYSONTOPLAYER 1 +namespace { +bool isVulkanRendererBackend() +{ +#ifdef ENABLE_VULKAN_RENDER + auto *helper = Helper::instance(); + if (!helper || !helper->window() || !helper->window()->renderer()) + return false; + + return wlr_renderer_is_vk(helper->window()->renderer()->handle()); +#else + return false; +#endif +} +} + SurfaceWrapper::SurfaceWrapper(QmlEngine *qmlEngine, WToplevelSurface *shellSurface, Type type, @@ -612,6 +628,7 @@ void SurfaceWrapper::startPrelaunchSplashHideSequence() const QRectF toGeometry(toTopLeft, targetImplicitSize); m_geometryAnimation = m_engine->createGeometryAnimation(this, fromGeometry, toGeometry, container()); + Q_EMIT animationRunningChanged(); bool ok = connect(m_geometryAnimation, SIGNAL(ready()), @@ -650,6 +667,7 @@ void SurfaceWrapper::onPrelaunchGeometryAnimationFinished() m_geometryAnimation->disconnect(this); m_geometryAnimation->deleteLater(); m_geometryAnimation = nullptr; + Q_EMIT animationRunningChanged(); if (m_decoration) m_decoration->setVisible(true); @@ -1007,6 +1025,7 @@ void SurfaceWrapper::setSurfaceState(State newSurfaceState) m_geometryAnimation->disconnect(this); m_geometryAnimation->deleteLater(); m_geometryAnimation = nullptr; + Q_EMIT animationRunningChanged(); } doSetSurfaceState(newSurfaceState); @@ -1273,6 +1292,8 @@ void SurfaceWrapper::createNewOrClose(uint direction) case Type::XWayland: { m_windowAnimation = m_engine->createNewAnimation(this, container(), direction); m_windowAnimation->setProperty("enableBlur", m_blur); + if (m_type == Type::SplashScreen && isVulkanRendererBackend()) + m_windowAnimation->setProperty("liveSource", false); } break; case Type::Layer: { auto scope = QString(static_cast(m_surfaceItem) @@ -1443,6 +1464,7 @@ void SurfaceWrapper::onAnimationReady() m_geometryAnimation->disconnect(this); m_geometryAnimation->deleteLater(); m_geometryAnimation = nullptr; + Q_EMIT animationRunningChanged(); return; } @@ -1459,6 +1481,7 @@ void SurfaceWrapper::onAnimationFinished() m_geometryAnimation->disconnect(this); m_geometryAnimation->deleteLater(); m_geometryAnimation = nullptr; + Q_EMIT animationRunningChanged(); } bool SurfaceWrapper::startStateChangeAnimation(State targetState, const QRectF &targetGeometry) @@ -1468,6 +1491,7 @@ bool SurfaceWrapper::startStateChangeAnimation(State targetState, const QRectF & m_geometryAnimation = m_engine->createGeometryAnimation(this, geometry(), targetGeometry, container()); + Q_EMIT animationRunningChanged(); m_geometryAnimation->setProperty("enableBlur", m_blur); m_pendingState = targetState; m_pendingGeometry = targetGeometry; diff --git a/src/surface/surfacewrapper.h b/src/surface/surfacewrapper.h index 5febbaefa..1c1b1d52d 100644 --- a/src/surface/surfacewrapper.h +++ b/src/surface/surfacewrapper.h @@ -77,6 +77,7 @@ class SurfaceWrapper : public QQuickItem // through treeland_dde_shell_surface_v1.set_surface_position Q_PROPERTY(QPoint clientRequstPos READ clientRequstPos NOTIFY clientRequstPosChanged FINAL) Q_PROPERTY(bool blur READ blur NOTIFY blurChanged FINAL) + Q_PROPERTY(bool isAnimationRunning READ isAnimationRunning NOTIFY animationRunningChanged FINAL) Q_PROPERTY(bool isWindowAnimationRunning READ isWindowAnimationRunning NOTIFY windowAnimationRunningChanged FINAL) Q_PROPERTY(bool coverEnabled READ coverEnabled NOTIFY coverEnabledChanged FINAL) Q_PROPERTY(bool acceptKeyboardFocus READ acceptKeyboardFocus NOTIFY acceptKeyboardFocusChanged FINAL) @@ -353,6 +354,7 @@ public Q_SLOTS: void autoPlaceYOffsetChanged(); void clientRequstPosChanged(); void blurChanged(); + void animationRunningChanged(); void windowAnimationRunningChanged(); void coverEnabledChanged(); void aboutToBeInvalidated(); diff --git a/waylib/src/server/CMakeLists.txt b/waylib/src/server/CMakeLists.txt index 90111db5f..17918422c 100644 --- a/waylib/src/server/CMakeLists.txt +++ b/waylib/src/server/CMakeLists.txt @@ -12,7 +12,7 @@ set(WAYLIB_INCLUDE_INSTALL_DIR if(Qt6_VERSION VERSION_GREATER_EQUAL 6.10) find_package(Qt6 REQUIRED COMPONENTS GuiPrivate QuickPrivate) endif() -find_package(Qt6 COMPONENTS Core Gui Quick REQUIRED) +find_package(Qt6 COMPONENTS Core Gui Quick ShaderTools REQUIRED) qt_standard_project_setup(REQUIRES 6.8) diff --git a/waylib/src/server/kernel/private/wsurface_p.h b/waylib/src/server/kernel/private/wsurface_p.h index aadbbd896..c9a1dfeab 100644 --- a/waylib/src/server/kernel/private/wsurface_p.h +++ b/waylib/src/server/kernel/private/wsurface_p.h @@ -12,6 +12,9 @@ #include #include +#include +#include + struct wlr_surface; struct wlr_subsurface; @@ -40,7 +43,10 @@ class Q_DECL_HIDDEN WSurfacePrivate : public WWrapObjectPrivate { void instantRelease() override; // release qwobject etc. void updateOutputs(); void setBuffer(QW_NAMESPACE::qw_buffer *newBuffer); + void setCommittedBuffer(QW_NAMESPACE::qw_buffer *newBuffer); + void capturePendingBuffer(); void updateBuffer(); + void updateCommittedBuffer(); void updateBufferOffset(); void updatePreferredBufferScale(); void preferredBufferScaleChange(); @@ -60,6 +66,12 @@ class Q_DECL_HIDDEN WSurfacePrivate : public WWrapObjectPrivate { bool needsFrame = false; std::unique_ptr buffer; + std::unique_ptr committedBuffer; + struct PendingCommittedBuffer { + uint32_t seq = 0; + std::unique_ptr buffer; + }; + std::vector pendingCommittedBuffers; QList outputs; WOutput *framePacingOutput = nullptr; QMetaObject::Connection frameDoneConnection; diff --git a/waylib/src/server/kernel/woutput.cpp b/waylib/src/server/kernel/woutput.cpp index 3b87d5d47..30c4baade 100644 --- a/waylib/src/server/kernel/woutput.cpp +++ b/waylib/src/server/kernel/woutput.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -314,7 +315,7 @@ static struct wlr_swapchain *create_swapchain(struct wlr_output *output, static bool test_swapchain(struct wlr_output *output, struct wlr_swapchain *swapchain, const struct wlr_output_state *state) { - struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain); + struct wlr_buffer *buffer = qw_swapchain::from(swapchain)->acquire(); if (buffer == NULL) { return false; } @@ -323,7 +324,7 @@ static bool test_swapchain(struct wlr_output *output, copy.committed |= WLR_OUTPUT_STATE_BUFFER; copy.buffer = buffer; bool ok = wlr_output_test_state(output, ©); - wlr_buffer_unlock(buffer); + qw_buffer::from(buffer)->unlock(); return ok; } diff --git a/waylib/src/server/kernel/wsurface.cpp b/waylib/src/server/kernel/wsurface.cpp index 1ddd113db..3a80400de 100644 --- a/waylib/src/server/kernel/wsurface.cpp +++ b/waylib/src/server/kernel/wsurface.cpp @@ -6,6 +6,7 @@ #include "wseat.h" #include "private/wsurface_p.h" #include "woutput.h" +#include "wayliblogging.h" #include #include @@ -46,16 +47,30 @@ void WSurfacePrivate::on_commit() needsFrame = !wl_list_empty(&nativeHandle()->current.frame_callback_list); - if (nativeHandle()->current.committed & WLR_SURFACE_STATE_BUFFER) + const uint32_t committed = handle()->current_committed(); + if (committed & WLR_SURFACE_STATE_BUFFER) { updateBuffer(); + updateCommittedBuffer(); + } - if (nativeHandle()->current.committed & WLR_SURFACE_STATE_OFFSET) + if (committed & WLR_SURFACE_STATE_OFFSET) updateBufferOffset(); if (hasSubsurface) // Will make to true when qw_surface::newSubsurface updateHasSubsurface(); - Q_EMIT q->commit(nativeHandle()->current.committed); + Q_EMIT q->commit(committed); + + if (committed & WLR_SURFACE_STATE_BUFFER) + setCommittedBuffer(nullptr); +} + +void WSurfacePrivate::on_client_commit() +{ + if (!(handle()->pending_committed() & WLR_SURFACE_STATE_BUFFER)) + return; + + capturePendingBuffer(); } void WSurfacePrivate::init() @@ -85,6 +100,9 @@ void WSurfacePrivate::connect() { W_Q(WSurface); + QObject::connect(handle(), &qw_surface::notify_client_commit, q, [this] { + on_client_commit(); + }); QObject::connect(handle(), &qw_surface::notify_commit, q, [this] { on_commit(); }); @@ -146,6 +164,47 @@ void WSurfacePrivate::setBuffer(qw_buffer *newBuffer) } } +void WSurfacePrivate::setCommittedBuffer(qw_buffer *newBuffer) +{ + if (committedBuffer.get() == newBuffer) + return; + + if (newBuffer) + newBuffer->lock(); + + committedBuffer.reset(newBuffer); +} + +void WSurfacePrivate::capturePendingBuffer() +{ + PendingCommittedBuffer pending; + pending.seq = handle()->pending_seq(); + + if (auto *newBuffer = handle()->pending_buffer()) { + newBuffer->lock(); + pending.buffer.reset(newBuffer); + } + + pendingCommittedBuffers.push_back(std::move(pending)); + + static constexpr size_t maxPendingCommittedBuffers = 16; + if (pendingCommittedBuffers.size() > maxPendingCommittedBuffers) { + qCDebug(lcWlSurface) + << "Dropping stale captured raw surface buffer" + << "surface" << q_func() + << "seq" << pendingCommittedBuffers.front().seq + << "pendingCount" << pendingCommittedBuffers.size(); + pendingCommittedBuffers.erase(pendingCommittedBuffers.begin()); + } + + qCDebug(lcWlSurface) + << "Captured pending raw surface buffer" + << "surface" << q_func() + << "seq" << pendingCommittedBuffers.back().seq + << "rawBuffer" << pendingCommittedBuffers.back().buffer.get() + << "pendingCount" << pendingCommittedBuffers.size(); +} + void WSurfacePrivate::updateBuffer() { qw_buffer *buffer = nullptr; @@ -155,6 +214,36 @@ void WSurfacePrivate::updateBuffer() setBuffer(buffer); } +void WSurfacePrivate::updateCommittedBuffer() +{ + const uint32_t seq = handle()->current_seq(); + for (auto it = pendingCommittedBuffers.begin(); it != pendingCommittedBuffers.end(); ++it) { + if (it->seq != seq) + continue; + + auto eraseEnd = it; + ++eraseEnd; + committedBuffer = std::move(it->buffer); + pendingCommittedBuffers.erase(pendingCommittedBuffers.begin(), eraseEnd); + qCDebug(lcWlSurface) + << "Activated committed raw surface buffer" + << "surface" << q_func() + << "seq" << seq + << "rawBuffer" << committedBuffer.get() + << "wrapperBuffer" << buffer.get() + << "remainingPending" << pendingCommittedBuffers.size(); + return; + } + + setCommittedBuffer(nullptr); + qCDebug(lcWlSurface) + << "No captured raw surface buffer matched commit" + << "surface" << q_func() + << "seq" << seq + << "wrapperBuffer" << buffer.get() + << "pendingCount" << pendingCommittedBuffers.size(); +} + void WSurfacePrivate::updateBufferOffset() { W_Q(WSurface); @@ -308,6 +397,12 @@ qw_buffer *WSurface::buffer() const return d->buffer.get(); } +qw_buffer *WSurface::committedBuffer() const +{ + W_DC(WSurface); + return d->committedBuffer.get(); +} + void WSurface::notifyFrameDone() { W_D(WSurface); diff --git a/waylib/src/server/kernel/wsurface.h b/waylib/src/server/kernel/wsurface.h index b4d6b6873..e02df1e0d 100644 --- a/waylib/src/server/kernel/wsurface.h +++ b/waylib/src/server/kernel/wsurface.h @@ -52,6 +52,10 @@ class WAYLIB_SERVER_EXPORT WSurface : public WWrapObject int bufferScale() const; QPoint bufferOffset() const; QW_NAMESPACE::qw_buffer *buffer() const; + // Raw producer buffer captured from the last committed attach. The legacy + // buffer() accessor intentionally keeps returning wlroots' client-buffer + // wrapper for existing texture paths. + QW_NAMESPACE::qw_buffer *committedBuffer() const; void notifyFrameDone(); diff --git a/waylib/src/server/qtquick/private/wbufferrenderer.cpp b/waylib/src/server/qtquick/private/wbufferrenderer.cpp index f12f4bf07..9800fe217 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer.cpp +++ b/waylib/src/server/qtquick/private/wbufferrenderer.cpp @@ -38,6 +38,12 @@ #include #include +#include + +extern "C" { +#include +} + using QSGAbsSoftRenderer_NodesMap = QHash; W_DECLARE_PRIVATE_MEMBER(QSGAbsSoftRenderer_m_nodes_tag, QSGAbstractSoftwareRenderer, m_nodes, QSGAbsSoftRenderer_NodesMap); W_DECLARE_PRIVATE_MEMBER(QSGAbsSoftRenderer_m_background_tag, QSGAbstractSoftwareRenderer, m_background, QSGSimpleRectNode*); @@ -55,6 +61,16 @@ inline static WImageRenderTarget *getImageFrom(const QQuickRenderTarget &rt) return static_cast(d->u.paintDevice); } +inline static QRhiTexture *getColorTextureFrom(QRhiRenderTarget *rt) +{ + if (!rt) + return nullptr; + + auto textureRT = static_cast(rt); + auto colorAttachment = textureRT->description().colorAttachmentAt(0); + return colorAttachment ? colorAttachment->texture() : nullptr; +} + static const wlr_drm_format *pickFormat(qw_renderer *renderer, uint32_t format) { auto r = renderer->handle(); @@ -68,6 +84,134 @@ static const wlr_drm_format *pickFormat(qw_renderer *renderer, uint32_t format) return wlr_drm_format_set_get(format_set, format); } +static QByteArray drmFormatNameForLog(uint32_t format) +{ + char *name = drmGetFormatName(format); + QByteArray result = name ? QByteArray(name) : QByteArray::number(format, 16); + free(name); + return result; +} + +static QByteArray drmModifierNameForLog(uint64_t modifier) +{ + if (modifier == DRM_FORMAT_MOD_INVALID) + return QByteArrayLiteral("INVALID"); + + char *name = drmGetFormatModifierName(modifier); + QByteArray result = name ? QByteArray(name) : QByteArray::number(modifier, 16); + free(name); + return result; +} + +static bool drmFormatHasModifier(const wlr_drm_format *format, uint64_t modifier) +{ + if (!format) + return false; + + for (size_t i = 0; i < format->len; ++i) { + if (format->modifiers[i] == modifier) + return true; + } + + return false; +} + +static bool drmFormatsEqual(const wlr_drm_format *a, const wlr_drm_format *b) +{ + if (!a || !b || a->format != b->format || a->len != b->len) + return false; + + for (size_t i = 0; i < a->len; ++i) { + if (a->modifiers[i] != b->modifiers[i]) + return false; + } + + return true; +} + +static bool copyDrmFormatWithSingleModifier(wlr_drm_format *dst, uint32_t format, uint64_t modifier) +{ + if (!dst) + return false; + + auto *modifiers = static_cast(malloc(sizeof(uint64_t))); + if (!modifiers) + return false; + + modifiers[0] = modifier; + wlr_drm_format_finish(dst); + dst->format = format; + dst->len = 1; + dst->capacity = 1; + dst->modifiers = modifiers; + return true; +} + +static bool copyDrmFormatWithoutImplicitFallbacks(wlr_drm_format *dst, const wlr_drm_format *src) +{ + if (!dst || !src || src->len == 0) + return false; + + size_t filteredLen = 0; + for (size_t i = 0; i < src->len; ++i) { + const uint64_t modifier = src->modifiers[i]; + if (modifier != DRM_FORMAT_MOD_INVALID && modifier != DRM_FORMAT_MOD_LINEAR) + ++filteredLen; + } + if (filteredLen == 0) + return false; + + auto *modifiers = static_cast(malloc(sizeof(uint64_t) * filteredLen)); + if (!modifiers) + return false; + + size_t outIndex = 0; + for (size_t i = 0; i < src->len; ++i) { + const uint64_t modifier = src->modifiers[i]; + if (modifier != DRM_FORMAT_MOD_INVALID && modifier != DRM_FORMAT_MOD_LINEAR) + modifiers[outIndex++] = modifier; + } + + wlr_drm_format_finish(dst); + dst->format = src->format; + dst->len = filteredLen; + dst->capacity = filteredLen; + dst->modifiers = modifiers; + return true; +} + +static bool pickTextureInteropFormat(qw_renderer *renderer, uint32_t format, wlr_drm_format *out) +{ + if (!renderer || !renderer->handle() || !out) + return false; + + auto *r = renderer->handle(); + const wlr_drm_format_set *renderFormats = + r->impl->get_render_formats ? r->impl->get_render_formats(r) : nullptr; + const wlr_drm_format_set *textureFormats = + wlr_renderer_get_texture_formats(r, WLR_BUFFER_CAP_DMABUF); + if (!renderFormats || !textureFormats) + return false; + + wlr_drm_format_set intersection = {}; + if (!wlr_drm_format_set_intersect(&intersection, textureFormats, renderFormats)) + return false; + + const wlr_drm_format *interopFormat = wlr_drm_format_set_get(&intersection, format); + bool ok = false; + if (interopFormat && interopFormat->len > 0) { + ok = copyDrmFormatWithoutImplicitFallbacks(out, interopFormat); + if (!ok && drmFormatHasModifier(interopFormat, DRM_FORMAT_MOD_INVALID)) { + ok = copyDrmFormatWithSingleModifier(out, format, DRM_FORMAT_MOD_INVALID); + } else if (!ok && drmFormatHasModifier(interopFormat, DRM_FORMAT_MOD_LINEAR)) { + ok = copyDrmFormatWithSingleModifier(out, format, DRM_FORMAT_MOD_LINEAR); + } + } + + wlr_drm_format_set_finish(&intersection); + return ok; +} + static void applyTransform(QSGSoftwareRenderer *renderer, const QTransform &t) { if (t.isIdentity()) @@ -256,15 +400,14 @@ qw_buffer *WBufferRenderer::lastBuffer() const return m_lastBuffer; } +bool WBufferRenderer::currentBufferReadyForScanout() const +{ + return state.scanoutReady; +} + QRhiTexture *WBufferRenderer::currentRenderTarget() const { - if (!state.sgRenderTarget.rt) - return nullptr; - auto textureRT = static_cast(state.sgRenderTarget.rt); - auto colorAttachment = textureRT->description().colorAttachmentAt(0); - if (!colorAttachment) - return nullptr; - return colorAttachment->texture(); + return getColorTextureFrom(state.sgRenderTarget.rt); } const qw_damage_ring *WBufferRenderer::damageRing() const @@ -298,7 +441,20 @@ WSGTextureProvider *WBufferRenderer::wTextureProvider() const if (!m_textureProvider) { m_textureProvider.reset(new WSGTextureProvider(w)); - m_textureProvider->setBuffer(m_lastBuffer); + // WBufferRenderer cache/output buffers are wlroots buffers too. The + // provider decides whether direct dmabuf import is safe for the active + // Qt RHI; otherwise it falls back to a Qt-owned texture. + m_textureProvider->setDirectBufferImportAllowed(true); + bool publishInitialBuffer = shouldCacheBuffer() && m_lastBuffer; +#ifdef ENABLE_VULKAN_RENDER + auto wd = QQuickWindowPrivate::get(window()); + if (publishInitialBuffer && wd && wd->rhi && wd->rhi->backend() == QRhi::Vulkan + && m_cacheBufferLocker.isEmpty()) { + publishInitialBuffer = false; + } +#endif + if (publishInitialBuffer) + m_textureProvider->setBuffer(m_lastBuffer); } return m_textureProvider.get(); @@ -343,18 +499,47 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe // configure swapchain if (flags.testFlag(RenderFlag::DontConfigureSwapchain)) { - auto renderFormat = pickFormat(m_output->renderer(), format); + wlr_drm_format interopFormat = {}; + const bool pickedInteropFormat = + pickTextureInteropFormat(m_output->renderer(), format, &interopFormat); + const wlr_drm_format *renderFormat = + pickedInteropFormat ? &interopFormat : pickFormat(m_output->renderer(), format); if (!renderFormat) { - qCWarning(lcWlBufferRenderer, "wlr_renderer doesn't support format 0x%s", drmGetFormatName(format)); + qCWarning(lcWlBufferRenderer) + << "wlr_renderer doesn't support format" + << drmFormatNameForLog(format); + wlr_drm_format_finish(&interopFormat); return nullptr; } - if (!m_swapchain || QSize(m_swapchain->handle()->width, m_swapchain->handle()->height) != pixelSize - || m_swapchain->handle()->format.format != renderFormat->format) { + if (!m_swapchain + || QSize(m_swapchain->handle()->width, m_swapchain->handle()->height) != pixelSize + || !drmFormatsEqual(&m_swapchain->handle()->format, renderFormat)) { if (m_swapchain) delete m_swapchain; m_swapchain = qw_swapchain::create(m_output->allocator()->handle(), pixelSize.width(), pixelSize.height(), renderFormat); } + if (!m_swapchain) { + qCWarning(lcWlBufferRenderer) + << "Failed to create output swapchain" + << "size" << pixelSize + << "format" << drmFormatNameForLog(renderFormat->format) + << "pickedTextureInteropFormat" << pickedInteropFormat + << "modifiers" << renderFormat->len + << "preferredModifier" + << (renderFormat->len > 0 ? drmModifierNameForLog(renderFormat->modifiers[0]) : QByteArrayLiteral("")); + wlr_drm_format_finish(&interopFormat); + return nullptr; + } + if (pickedInteropFormat) { + qCDebug(lcWlBufferRenderer) + << "Vulkan intermediate compositor layer picked texture-compatible format" + << "format" << drmFormatNameForLog(renderFormat->format) + << "modifiers" << renderFormat->len + << "preferredModifier" + << (renderFormat->len > 0 ? drmModifierNameForLog(renderFormat->modifiers[0]) : QByteArrayLiteral("")); + } + wlr_drm_format_finish(&interopFormat); } else if (flags.testFlag(RenderFlag::UseCursorFormats)) { bool ok = m_output->configureCursorSwapchain(pixelSize, format, &m_swapchain); if (!ok) @@ -372,6 +557,16 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe return nullptr; auto buffer = qw_buffer::from(wbuffer); +#ifdef ENABLE_VULKAN_RENDER + if (m_textureProvider && m_textureProvider->qwBuffer() == buffer) { + qCDebug(lcWlBufferRenderer) + << "Vulkan RHI output cache import released before rendering into the same buffer" + << "buffer" << buffer + << "size" << buffer->handle()->width << "x" << buffer->handle()->height; + m_textureProvider->setBuffer(nullptr); + } +#endif + if (!m_renderHelper) m_renderHelper = new WRenderHelper(m_output->renderer()); m_renderHelper->setSize(pixelSize); @@ -392,6 +587,7 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe auto rtd = QQuickRenderTargetPrivate::get(&rt); QSGRenderTarget sgRT; + bool scanoutReady = true; if (rtd->type == QQuickRenderTargetPrivate::Type::PaintDevice) { sgRT.paintDevice = rtd->u.paintDevice; @@ -417,6 +613,20 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe QOpenGLContextPrivate::get(glContext)->defaultFboRedirect = glRT->framebuffer; } #endif + +#ifdef ENABLE_VULKAN_RENDER + if (wd->rhi && wd->rhi->backend() == QRhi::Vulkan) { + QRhiTexture *targetTexture = getColorTextureFrom(sgRT.rt); + if (!m_renderHelper->prepareVulkanRenderTargetForQt(wd->rhi, targetTexture, buffer)) { + qCWarning(lcWlBufferRenderer) + << "Failed to acquire Vulkan output render target for Qt rendering" + << "buffer size" << buffer->handle()->width << "x" << buffer->handle()->height; + buffer->unlock(); + return nullptr; + } + scanoutReady = false; + } +#endif } state.flags = flags; @@ -426,6 +636,7 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe state.buffer.reset(buffer); state.renderTarget = rt; state.sgRenderTarget = sgRT; + state.scanoutReady = scanoutReady; return buffer; } @@ -469,12 +680,31 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, // how Shape fills the QSGTexture provided by fillItem. Therefore, we need to ensure // that QSGRenderer::devicePixelRatio and QQuickWindow::effectiveDevicePixelRatio // are always consistent. Otherwise, some Items might render incorrectly. + auto softwareRenderer = dynamic_cast(renderer); +#ifdef ENABLE_VULKAN_RENDER + if (!softwareRenderer && wd->rhi && wd->rhi->backend() == QRhi::Vulkan && m_renderHelper) { + QQuickRenderTarget passRenderTarget = + m_renderHelper->renderTargetForBuffer(state.buffer.get(), preserveColorContents); + if (!passRenderTarget.isNull()) { + const auto *passRtData = QQuickRenderTargetPrivate::get(&passRenderTarget); + Q_ASSERT(passRtData->type == QQuickRenderTargetPrivate::Type::RhiRenderTarget); + state.renderTarget = passRenderTarget; + state.sgRenderTarget.rt = passRtData->u.rhiRt; + state.sgRenderTarget.rpDesc = passRtData->u.rhiRt->renderPassDescriptor(); + } else { + qCWarning(lcWlBufferRenderer) + << "Vulkan buffer renderer could not select render target for pass" + << "sourceIndex" << sourceIndex + << "preserveColorContents" << preserveColorContents + << "bufferPtr" << quintptr(state.buffer.get()); + } + } +#endif renderer->setDevicePixelRatio(window()->effectiveDevicePixelRatio()); renderer->setDeviceRect(QRect(QPoint(0, 0), state.pixelSize)); renderer->setRenderTarget(state.sgRenderTarget); const auto viewportRect = scaleToRect(targetRect, devicePixelRatio); - auto softwareRenderer = dynamic_cast(renderer); { // before render if (softwareRenderer) { // Avoid do clear before paint, for the software renderer this @@ -526,13 +756,17 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, if (state.renderTarget.mirrorVertically()) flipY = !flipY; + QRect usedViewportRect; if (viewportRect.isValid()) { - QRect vr = viewportRect; + usedViewportRect = viewportRect; if (flipY) - vr.moveTop(-vr.y() + state.pixelSize.height() - vr.height()); - renderer->setViewportRect(vr); + usedViewportRect.moveTop(-usedViewportRect.y() + + state.pixelSize.height() + - usedViewportRect.height()); + renderer->setViewportRect(usedViewportRect); } else { - renderer->setViewportRect(QRect(QPoint(0, 0), state.pixelSize)); + usedViewportRect = QRect(QPoint(0, 0), state.pixelSize); + renderer->setViewportRect(usedViewportRect); } QRectF rect = sourceRect; @@ -564,12 +798,54 @@ 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 (!wd->rhi || wd->rhi->backend() != QRhi::Vulkan) { + auto textureRT = static_cast(state.sgRenderTarget.rt); + if (preserveColorContents) { + textureRT->setFlags(textureRT->flags() | QRhiTextureRenderTarget::PreserveColorContents); + } else { + textureRT->setFlags(textureRT->flags() & ~QRhiTextureRenderTarget::PreserveColorContents); + } + } + +#ifdef ENABLE_VULKAN_RENDER + if (Q_UNLIKELY(wd->rhi && wd->rhi->backend() == QRhi::Vulkan + && lcWlBufferRenderer().isDebugEnabled())) { + QRhiTexture *targetTexture = currentRenderTarget(); + const auto *sourceItem = source.source ? source.source : wd->contentItem; + qCDebug(lcWlBufferRenderer) + << "Vulkan buffer renderer projection configured" + << "sourceIndex" << sourceIndex + << "rootSource" << isRootItem(source.source) + << "sourceItemPtr" << quintptr(sourceItem) + << "rendererItemSize" << size() + << "bufferPtr" << quintptr(state.buffer.get()) + << "bufferSize" << (state.buffer + ? QSize(state.buffer->handle()->width, + state.buffer->handle()->height) + : QSize()) + << "pixelSize" << state.pixelSize + << "devicePixelRatio" << devicePixelRatio + << "windowDevicePixelRatio" << window()->effectiveDevicePixelRatio() + << "sourceRectInput" << sourceRect + << "sourceRectUsed" << rect + << "targetRect" << targetRect + << "targetRectValid" << targetRect.isValid() + << "viewportInput" << viewportRect + << "viewportUsed" << usedViewportRect + << "flipY" << flipY + << "rhiYUpInNDC" << wd->rhi->isYUpInNDC() + << "mirrorVertically" << state.renderTarget.mirrorVertically() + << "preserveColorContents" << preserveColorContents + << "renderFlags" << int(state.flags) + << "clearColor" << m_clearColor + << "rhiRenderTargetPtr" << quintptr(state.sgRenderTarget.rt) + << "targetTexturePtr" << quintptr(targetTexture) + << "targetTextureSize" << (targetTexture ? targetTexture->pixelSize() : QSize()) + << "worldTransform" << state.worldTransform + << "projectionMatrix" << projectionMatrix + << "nativeProjectionMatrix" << projectionMatrixWithNativeNDC; } +#endif } } @@ -588,6 +864,17 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, // complete the results of this drawing here to ensure the current // drawing result is available for use. wd->rhi->finish(); +#ifdef ENABLE_VULKAN_RENDER + // The primary/output render target must stay Qt-owned until + // QQuickRenderControl::endFrame() submits the complete offscreen frame. + // Smaller wlroots layer/cursor targets are committed outside the primary + // output path and still need the legacy immediate release before their + // current buffer is ended. + if (wd->rhi->backend() == QRhi::Vulkan && m_renderHelper + && !state.flags.testFlag(RedirectOpenGLContextDefaultFrameBufferObject)) { + releaseCurrentBufferForScanout(); + } +#endif } else { state.dirty = softwareRenderer->flushRegion(); @@ -627,10 +914,58 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, dr->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates); } - if (shouldCacheBuffer()) +#ifdef ENABLE_VULKAN_RENDER + const bool deferCacheBuffer = wd->rhi && wd->rhi->backend() == QRhi::Vulkan; +#else + const bool deferCacheBuffer = false; +#endif + if (shouldCacheBuffer() && !deferCacheBuffer) wTextureProvider()->setBuffer(state.buffer.get()); } +bool WBufferRenderer::releaseCurrentBufferForScanout() +{ +#ifdef ENABLE_VULKAN_RENDER + if (!state.buffer) { + state.scanoutReady = true; + return true; + } + + auto wd = QQuickWindowPrivate::get(window()); + if (!wd || !wd->rhi || wd->rhi->backend() != QRhi::Vulkan || !m_renderHelper) { + state.scanoutReady = true; + return true; + } + + QRhiTexture *targetTexture = currentRenderTarget(); + if (!targetTexture) { + state.scanoutReady = false; + qCWarning(lcWlBufferRenderer) + << "Failed to release Vulkan output render target for scanout:" + << "current render target has no color texture" + << "buffer size" << state.buffer->handle()->width + << "x" << state.buffer->handle()->height; + return false; + } + + state.scanoutReady = + m_renderHelper->releaseVulkanRenderTargetToScanout(wd->rhi, + targetTexture, + state.buffer.get()); + if (!state.scanoutReady) { + qCWarning(lcWlBufferRenderer) + << "Failed to release Vulkan output render target for scanout" + << "buffer size" << state.buffer->handle()->width + << "x" << state.buffer->handle()->height; + } + + return state.scanoutReady; +#else + state.scanoutReady = true; + return true; +#endif +} + void WBufferRenderer::endRender() { Q_ASSERT(state.buffer.get()); @@ -643,6 +978,30 @@ void WBufferRenderer::endRender() m_lastBuffer = buffer.get(); } +#ifdef ENABLE_VULKAN_RENDER + auto windowPrivate = QQuickWindowPrivate::get(window()); + if (windowPrivate && windowPrivate->rhi && windowPrivate->rhi->backend() == QRhi::Vulkan) { + if (!m_cacheBufferLocker.isEmpty()) { + qCDebug(lcWlBufferRenderer) + << "Vulkan RHI output cache buffer published after rendering" + << "buffer" << m_lastBuffer + << "size" << m_lastBuffer->handle()->width << "x" << m_lastBuffer->handle()->height + << "cacheBuffer" << m_cacheBuffer + << "cacheLockers" << m_cacheBufferLocker.size() + << "renderFlags" << int(state.flags) + << "scanoutReady" << state.scanoutReady; + wTextureProvider()->setBuffer(m_lastBuffer); + } else if (m_textureProvider && m_textureProvider->qwBuffer()) { + qCDebug(lcWlBufferRenderer) + << "Vulkan RHI output cache buffer cleared after rendering without consumers" + << "cacheBuffer" << m_cacheBuffer + << "renderFlags" << int(state.flags) + << "scanoutReady" << state.scanoutReady; + m_textureProvider->setBuffer(nullptr); + } + } +#endif + #ifndef QT_NO_OPENGL auto wd = QQuickWindowPrivate::get(window()); if (state.flags.testFlag(RedirectOpenGLContextDefaultFrameBufferObject) @@ -666,35 +1025,71 @@ void WBufferRenderer::updateTextureProvider() if (!m_textureProvider) return; - if (shouldCacheBuffer()) { + bool publishCache = shouldCacheBuffer(); +#ifdef ENABLE_VULKAN_RENDER + auto wd = QQuickWindowPrivate::get(window()); + if (publishCache && wd && wd->rhi && wd->rhi->backend() == QRhi::Vulkan + && m_cacheBufferLocker.isEmpty()) { + publishCache = false; + } +#endif + + if (publishCache) { const bool hasCachedBuffer = m_textureProvider->qwBuffer(); // Ensure only update the buffer when the "shouldCacheBuffer" state is changed. // If the state is not changed, the buffer is update in the WBufferRenderer::render. - if (!hasCachedBuffer && m_lastBuffer) + if (!hasCachedBuffer && m_lastBuffer) { + qCDebug(lcWlBufferRenderer) + << "Output cache texture provider restored last buffer" + << "buffer" << m_lastBuffer + << "size" << m_lastBuffer->handle()->width << "x" << m_lastBuffer->handle()->height + << "cacheBuffer" << m_cacheBuffer + << "cacheLockers" << m_cacheBufferLocker.size(); m_textureProvider->setBuffer(m_lastBuffer); + } } else { + qCDebug(lcWlBufferRenderer) + << "Output cache texture provider cleared" + << "cacheBuffer" << m_cacheBuffer + << "cacheLockers" << m_cacheBufferLocker.size(); m_textureProvider->setBuffer(nullptr); } } QSGNode *WBufferRenderer::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { + auto provider = wTextureProvider(); + QSGTexture *texture = provider ? provider->texture() : nullptr; + if (!texture || width() <= 0 || height() <= 0) { + delete oldNode; + return nullptr; + } + auto node = static_cast(oldNode); if (Q_UNLIKELY(!node)) { node = window()->createImageNode(); node->setOwnsTexture(false); - node->setTexture(m_textureProvider->texture()); } else { node->markDirty(QSGNode::DirtyMaterial); } - const QRectF textureGeometry = QRectF(QPointF(0, 0), node->texture()->textureSize()); + node->setTexture(texture); + const QRectF textureGeometry = QRectF(QPointF(0, 0), texture->textureSize()); node->setSourceRect(textureGeometry); const QRectF targetGeometry(QPointF(0, 0), size()); node->setRect(targetGeometry); node->setFiltering(QSGTexture::Linear); node->setMipmapFiltering(QSGTexture::None); + qCDebug(lcWlBufferRenderer) + << "Output cache texture node updated" + << "providerBufferPtr" << quintptr(provider->qwBuffer()) + << "textureSize" << texture->textureSize() + << "textureHasAlpha" << texture->hasAlphaChannel() + << "itemSize" << size() + << "sourceRect" << textureGeometry + << "targetRect" << targetGeometry; + return node; } diff --git a/waylib/src/server/qtquick/private/wbufferrenderer_p.h b/waylib/src/server/qtquick/private/wbufferrenderer_p.h index 628684c71..dd0b85a54 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer_p.h +++ b/waylib/src/server/qtquick/private/wbufferrenderer_p.h @@ -75,6 +75,7 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem const QMatrix4x4 ¤tWorldTransform() const; QW_NAMESPACE::qw_buffer *currentBuffer() const; QW_NAMESPACE::qw_buffer *lastBuffer() const; + bool currentBufferReadyForScanout() const; QRhiTexture *currentRenderTarget() const; const QW_NAMESPACE::qw_damage_ring *damageRing() const; QW_NAMESPACE::qw_damage_ring *damageRing(); @@ -99,6 +100,7 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem void render(int sourceIndex, const QMatrix4x4 &renderMatrix, const QRectF &sourceRect = {}, const QRectF &targetRect = {}, bool preserveColorContents = false); + bool releaseCurrentBufferForScanout(); void endRender(); void componentComplete() override; @@ -143,6 +145,7 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem QQuickRenderTarget renderTarget; QSGRenderTarget sgRenderTarget; QRegion dirty; + bool scanoutReady = true; } state; QPointer m_output; diff --git a/waylib/src/server/qtquick/private/wrenderbuffernode.cpp b/waylib/src/server/qtquick/private/wrenderbuffernode.cpp index 2077bb719..2a8c7b6a3 100644 --- a/waylib/src/server/qtquick/private/wrenderbuffernode.cpp +++ b/waylib/src/server/qtquick/private/wrenderbuffernode.cpp @@ -12,6 +12,7 @@ #include "private/wprivateaccessor_p.h" #include +#include #include #include @@ -350,6 +351,7 @@ struct WlrAndRhiTexture { struct wlr_buffer *buffer = nullptr; qw_texture *wlrTexture = nullptr; QRhiTexture *rhiTexture = nullptr; + WRenderHelper::NativeTextureCleanup nativeCleanup; }; class Q_DECL_HIDDEN RhiTextureManager : public DataManager @@ -364,9 +366,9 @@ class Q_DECL_HIDDEN RhiTextureManager : public DataManagerbuffer; + auto buffer = qw_buffer::from(texture->buffer); wlr_dmabuf_attributes attribs; - if (!wlr_buffer_get_dmabuf(buffer, &attribs)) { + if (!buffer->get_dmabuf(&attribs)) { qCWarning(lcWlRenderBuffer) << "Failed to get dmabuf attributes for texture" << texture << "with buffer" << buffer << ", Can't check texture without dmabuf attributes"; return false; @@ -382,14 +384,16 @@ class Q_DECL_HIDDEN RhiTextureManager : public DataManagerrhiTexture; + WRenderHelper::releaseNativeTexture(&texture->nativeCleanup); delete texture->wlrTexture; if (texture->buffer) - wlr_buffer_drop(texture->buffer); + qw_buffer::from(texture->buffer)->drop(); delete texture; } }; @@ -657,6 +661,9 @@ class Q_DECL_HIDDEN RhiNode : public WRenderBufferNode { } StateFlags changedStates() const override { + if (isVulkanRhi()) + return ViewportState | ScissorState; + if (Q_UNLIKELY(renderData && !contentNode)) return (RenderTargetState | ViewportState); @@ -675,10 +682,14 @@ class Q_DECL_HIDDEN RhiNode : public WRenderBufferNode { // // We should fix this bug in Qt in the future. RenderingFlags flags() const override { + RenderingFlags result = DepthAwareRendering; if (Q_UNLIKELY(!contentNode)) - return BoundedRectRendering | DepthAwareRendering; + result |= BoundedRectRendering; - return DepthAwareRendering; + if (isVulkanRhi()) + result |= NoExternalRendering; + + return result; } void releaseResources() override { @@ -705,6 +716,14 @@ class Q_DECL_HIDDEN RhiNode : public WRenderBufferNode { : nullptr; } + bool isVulkanRhi() const { + if (!m_item || !m_item->window()) + return false; + + auto *window = qobject_cast(m_item->window()); + return window && window->rhi() && window->rhi()->backend() == QRhi::Vulkan; + } + void prepare() override { contentNode = nullptr; diff --git a/waylib/src/server/qtquick/private/wsurfaceitem_p.h b/waylib/src/server/qtquick/private/wsurfaceitem_p.h index 038bfdd07..4e6400d3e 100644 --- a/waylib/src/server/qtquick/private/wsurfaceitem_p.h +++ b/waylib/src/server/qtquick/private/wsurfaceitem_p.h @@ -87,4 +87,3 @@ class Q_DECL_HIDDEN WSurfaceItemPrivate : public QQuickItemPrivate }; WAYLIB_SERVER_END_NAMESPACE - diff --git a/waylib/src/server/qtquick/wbufferitem.cpp b/waylib/src/server/qtquick/wbufferitem.cpp index d8a3bb530..2266e2c86 100644 --- a/waylib/src/server/qtquick/wbufferitem.cpp +++ b/waylib/src/server/qtquick/wbufferitem.cpp @@ -13,9 +13,20 @@ #include #include #include +#ifdef ENABLE_VULKAN_RENDER +#include +#endif WAYLIB_SERVER_BEGIN_NAMESPACE +#ifdef ENABLE_VULKAN_RENDER +static QRhiCommandBuffer *currentFrameCommandBuffer(QSGRenderContext *context) +{ + auto *defaultContext = qobject_cast(context); + return defaultContext ? defaultContext->currentFrameCommandBuffer() : nullptr; +} +#endif + class Q_DECL_HIDDEN WBufferItemPrivate : public QQuickItemPrivate { public: @@ -138,8 +149,13 @@ QSGNode *WBufferItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) return nullptr; } - // Refresh provider with the latest buffer. - tp->setBuffer(d->buffer.get()); + if (!tp->texture()) { + QRhiCommandBuffer *commandBuffer = nullptr; +#ifdef ENABLE_VULKAN_RENDER + commandBuffer = currentFrameCommandBuffer(d->sceneGraphRenderContext()); +#endif + tp->setBuffer(d->buffer.get(), nullptr, commandBuffer); + } if (!tp->texture() || width() <= 0 || height() <= 0) { int bufW = -1; diff --git a/waylib/src/server/qtquick/woutputhelper.cpp b/waylib/src/server/qtquick/woutputhelper.cpp index a71a58f39..ccee1ee0b 100644 --- a/waylib/src/server/qtquick/woutputhelper.cpp +++ b/waylib/src/server/qtquick/woutputhelper.cpp @@ -10,9 +10,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -20,14 +22,38 @@ #include #include +#include #ifndef QT_NO_OPENGL #include #endif #include +extern "C" { +#include +#include +#include +#ifdef ENABLE_VULKAN_RENDER +#include +#endif +} + QW_USE_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE +static bool envFlagEnabled(const char *name) +{ + const QByteArray value = qgetenv(name).trimmed().toLower(); + return !value.isEmpty() && value != "0" && value != "false" + && value != "no" && value != "off"; +} + +static bool vulkanOutputLayerCompositorDisabled() +{ + static const bool disabled = envFlagEnabled("WAYLIB_VK_DISABLE_OUTPUT_LAYER_COMPOSITOR") + || envFlagEnabled("TREELAND_VK_DISABLE_OUTPUT_LAYER_COMPOSITOR"); + return disabled; +} + class Q_DECL_HIDDEN WOutputHelperPrivate : public WObjectPrivate { public: @@ -219,6 +245,171 @@ void WOutputHelper::setLayers(const wlr_output_layer_state_array &layers) } } +bool WOutputHelper::isVulkanOutputLayerCompositorRequested() +{ + static const bool enabled = envFlagEnabled("WAYLIB_VK_OUTPUT_LAYER_COMPOSITOR") + || envFlagEnabled("TREELAND_VK_OUTPUT_LAYER_COMPOSITOR"); + return enabled; +} + +bool WOutputHelper::usesVulkanOutputLayerCompositor() const +{ +#ifdef ENABLE_VULKAN_RENDER + W_DC(WOutputHelper); + if (vulkanOutputLayerCompositorDisabled()) + return false; + if (!d->renderer() || !d->renderer()->is_vk()) + return false; + + return isVulkanOutputLayerCompositorRequested() + || WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan; +#else + return false; +#endif +} + +bool WOutputHelper::commitWithVulkanOutputLayer(qw_buffer *sourceBuffer) +{ +#ifdef ENABLE_VULKAN_RENDER + W_D(WOutputHelper); + + // Execute before-commit jobs exactly like commit(). + QList beforeJobs; + beforeJobs.swap(d->beforeCommitJobs); + for (const auto &entry : beforeJobs) { + entry.jobWithState(true, d->extraState); + } + + wlr_output_state state = d->state; + wlr_output_state_init(&d->state); + + auto finishCommit = [d, &state](bool ok) { + wlr_output_state_finish(&state); + ExtraState committedExtraState = d->extraState; + + if (Q_UNLIKELY(d->extraState)) { + d->extraState.reset(); + } + + QList afterJobs; + afterJobs.swap(d->afterCommitJobs); + for (const auto &entry : afterJobs) { + entry.jobWithState(ok, committedExtraState); + } + return ok; + }; + + if (Q_UNLIKELY(d->extraState)) { + qCDebug(lcWlVulkanCompositor) + << "Vulkan output-layer compositor: committing external output state only for" + << d->qwoutput()->handle()->name; + wlr_output_state_finish(&state); + wlr_output_state_copy(&state, d->extraState.get()); + + const bool ok = d->qwoutput()->commit_state(&state); + if (!ok) { + qCCritical(lcWlVulkanCompositor) + << "Vulkan output-layer compositor: state-only commit failed on output" + << d->qwoutput()->handle()->name; + } + return finishCommit(ok); + } + + if (Q_UNLIKELY(!usesVulkanOutputLayerCompositor())) { + qCWarning(lcWlVulkanCompositor) + << "Vulkan output-layer compositor requested for non-Vulkan renderer on output" + << d->qwoutput()->handle()->name; + return finishCommit(false); + } + + if (Q_UNLIKELY(!sourceBuffer)) { + qCWarning(lcWlVulkanCompositor) + << "Vulkan output-layer compositor: missing Qt layer buffer for output" + << d->qwoutput()->handle()->name; + return finishCommit(false); + } + + qw_texture *sourceTexture = + qw_texture::from_buffer(*d->renderer(), *sourceBuffer); + if (Q_UNLIKELY(!sourceTexture)) { + qCWarning(lcWlVulkanCompositor) + << "Vulkan output-layer compositor: failed to import Qt layer buffer as texture for output" + << d->qwoutput()->handle()->name + << "buffer size" << sourceBuffer->handle()->width << "x" << sourceBuffer->handle()->height + << "locks" << sourceBuffer->handle()->n_locks; + return finishCommit(false); + } + + wlr_buffer_pass_options passOptions = {}; + qw_render_pass *pass = qw_render_pass::from( + d->qwoutput()->begin_render_pass(&state, &passOptions)); + if (Q_UNLIKELY(!pass)) { + qCWarning(lcWlVulkanCompositor) + << "Vulkan output-layer compositor: failed to begin output render pass for" + << d->qwoutput()->handle()->name + << "layer buffer size" << sourceBuffer->handle()->width << "x" << sourceBuffer->handle()->height; + delete sourceTexture; + return finishCommit(false); + } + + int width = 0; + int height = 0; + d->qwoutput()->transformed_resolution(&width, &height); + + wlr_render_rect_options clearOptions = {}; + clearOptions.box.x = 0; + clearOptions.box.y = 0; + clearOptions.box.width = width; + clearOptions.box.height = height; + clearOptions.color.r = 0.0f; + clearOptions.color.g = 0.0f; + clearOptions.color.b = 0.0f; + clearOptions.color.a = 1.0f; + clearOptions.blend_mode = WLR_RENDER_BLEND_MODE_NONE; + pass->add_rect(&clearOptions); + + wlr_render_texture_options textureOptions = {}; + textureOptions.texture = sourceTexture->handle(); + textureOptions.dst_box.x = 0; + textureOptions.dst_box.y = 0; + textureOptions.dst_box.width = width; + textureOptions.dst_box.height = height; + textureOptions.transform = WL_OUTPUT_TRANSFORM_NORMAL; + textureOptions.filter_mode = WLR_SCALE_FILTER_BILINEAR; + textureOptions.blend_mode = WLR_RENDER_BLEND_MODE_PREMULTIPLIED; + pass->add_texture(&textureOptions); + + const bool submitted = pass->submit(); + delete sourceTexture; + if (Q_UNLIKELY(!submitted)) { + qCWarning(lcWlVulkanCompositor) + << "Vulkan output-layer compositor: render pass submit failed for output" + << d->qwoutput()->handle()->name + << "target size" << width << "x" << height; + return finishCommit(false); + } + + qCDebug(lcWlVulkanCompositor) + << "Vulkan output-layer compositor: committing output" + << d->qwoutput()->handle()->name + << "target" << width << "x" << height + << "source buffer" << sourceBuffer->handle()->width << "x" << sourceBuffer->handle()->height + << "source locks" << sourceBuffer->handle()->n_locks; + + const bool ok = d->qwoutput()->commit_state(&state); + if (!ok) { + qCCritical(lcWlVulkanCompositor) + << "Vulkan output-layer compositor: commit failed on output" + << d->qwoutput()->handle()->name + << "target" << width << "x" << height; + } + return finishCommit(ok); +#else + Q_UNUSED(sourceBuffer); + return false; +#endif +} + bool WOutputHelper::commit() { W_D(WOutputHelper); @@ -360,7 +551,7 @@ void WOutputHelper::resetState() // reset output state if (d->state.committed & WLR_OUTPUT_STATE_BUFFER) { - wlr_buffer_unlock(d->state.buffer); + qw_buffer::from(d->state.buffer)->unlock(); d->state.buffer = nullptr; } diff --git a/waylib/src/server/qtquick/woutputhelper.h b/waylib/src/server/qtquick/woutputhelper.h index 3a4d3fb25..f7aa3e812 100644 --- a/waylib/src/server/qtquick/woutputhelper.h +++ b/waylib/src/server/qtquick/woutputhelper.h @@ -10,6 +10,9 @@ #include #include #include +#include + +#include QT_BEGIN_NAMESPACE class QOpenGLContext; @@ -60,6 +63,9 @@ class WAYLIB_SERVER_EXPORT WOutputHelper : public QObject, public WObject const pixman_region32 *damage() const; void setLayers(const wlr_output_layer_state_array &layers); bool commit(); + static bool isVulkanOutputLayerCompositorRequested(); + bool usesVulkanOutputLayerCompositor() const; + bool commitWithVulkanOutputLayer(QW_NAMESPACE::qw_buffer *sourceBuffer); bool testCommit(); bool testCommit(QW_NAMESPACE::qw_buffer *buffer, const wlr_output_layer_state_array &layers); diff --git a/waylib/src/server/qtquick/woutputrenderwindow.cpp b/waylib/src/server/qtquick/woutputrenderwindow.cpp index 454167897..7494ad33d 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.cpp +++ b/waylib/src/server/qtquick/woutputrenderwindow.cpp @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include #include @@ -79,6 +81,35 @@ W_DECLARE_PRIVATE_MEMBER(QQuickAnimCtrl_m_window_tag, QQuickAnimatorController, WAYLIB_SERVER_BEGIN_NAMESPACE +#ifdef ENABLE_VULKAN_RENDER +static bool envFlagEnabledForVulkanRenderer(const char *name) +{ + const QByteArray value = qgetenv(name).trimmed().toLower(); + return value == "1" || value == "true" || value == "yes" || value == "on"; +} + +static bool vulkanSoftwareCursorRequested() +{ + static const bool enabled = envFlagEnabledForVulkanRenderer("WAYLIB_VK_FORCE_SOFTWARE_CURSOR") + || envFlagEnabledForVulkanRenderer("TREELAND_VK_FORCE_SOFTWARE_CURSOR"); + return enabled; +} + +static bool vulkanHardwareCursorRequested() +{ + static const bool enabled = envFlagEnabledForVulkanRenderer("WAYLIB_VK_ENABLE_HARDWARE_CURSOR") + || envFlagEnabledForVulkanRenderer("TREELAND_VK_ENABLE_HARDWARE_CURSOR"); + return enabled; +} + +static bool vulkanOutputLayerCompositorDisabled() +{ + static const bool disabled = envFlagEnabledForVulkanRenderer("WAYLIB_VK_DISABLE_OUTPUT_LAYER_COMPOSITOR") + || envFlagEnabledForVulkanRenderer("TREELAND_VK_DISABLE_OUTPUT_LAYER_COMPOSITOR"); + return disabled; +} +#endif + // Call it before any wlroots render to clean up the GL state in Qt. // If you don't do this, there will be tearing, flickering and other graphics problems inline static void resetGlState() @@ -193,6 +224,42 @@ class Q_DECL_HIDDEN OutputHelper : public WOutputHelper return output()->output()->handle(); } +#ifdef ENABLE_VULKAN_RENDER + inline bool isVulkanRenderer() const { + return m_output && m_output->output() && m_output->output()->renderer() + && m_output->output()->renderer()->is_vk(); + } + + inline bool shouldDeferVulkanRenderRetry() { + if (!isVulkanRenderer() || m_vulkanRenderRetryDelayFrames <= 0) + return false; + + --m_vulkanRenderRetryDelayFrames; + scheduleFrame(); + return true; + } + + inline bool noteVulkanRenderFailed() { + if (!isVulkanRenderer()) + return false; + + m_vulkanRenderFailureBackoffFrames = m_vulkanRenderFailureBackoffFrames + ? qMin(m_vulkanRenderFailureBackoffFrames * 2, 4) + : 1; + m_vulkanRenderRetryDelayFrames = m_vulkanRenderFailureBackoffFrames; + scheduleFrame(); + return true; + } + + inline void noteVulkanRenderSucceeded() { + if (!isVulkanRenderer()) + return; + + m_vulkanRenderFailureBackoffFrames = 0; + m_vulkanRenderRetryDelayFrames = 0; + } +#endif + inline WOutputRenderWindow *renderWindow() const { return static_cast(parent()); } @@ -262,6 +329,10 @@ class Q_DECL_HIDDEN OutputHelper : public WOutputHelper BufferRendererProxy *m_cursorLayerProxy = nullptr; bool m_cursorDirty = false; bool m_hardwareCursorRenderComplete = false; +#ifdef ENABLE_VULKAN_RENDER + int m_vulkanRenderFailureBackoffFrames = 0; + int m_vulkanRenderRetryDelayFrames = 0; +#endif // for compositeLayers QPointer m_output2; @@ -1043,8 +1114,12 @@ WBufferRenderer *OutputHelper::compositeLayers(const QList layers, b if (ok) { // stop primary render - if (bufferRenderer()->currentBuffer()) + if (bufferRenderer()->currentBuffer()) { +#ifdef ENABLE_VULKAN_RENDER + bufferRenderer()->releaseCurrentBufferForScanout(); +#endif bufferRenderer()->endRender(); + } render(bufferRenderer2(), 0, {}, m_output->effectiveSourceRect(), m_output->targetRect(), true); return bufferRenderer2(); @@ -1073,6 +1148,28 @@ bool OutputHelper::commit(WBufferRenderer *buffer) return WOutputHelper::commit(); } +#ifdef ENABLE_VULKAN_RENDER + const bool scanoutReady = buffer->currentBufferReadyForScanout(); + if (!scanoutReady) { + qCWarning(lcWlBufferRenderer) + << "Skipping output commit because current Vulkan render target was not released for scanout" + << qwoutput()->handle()->name + << "buffer size" << buffer->currentBuffer()->handle()->width + << "x" << buffer->currentBuffer()->handle()->height; + return false; + } + + if (usesVulkanOutputLayerCompositor()) { + qCDebug(lcWlVulkanCompositor) + << "Committing Qt Quick layer through wlroots Vulkan render pass for output" + << qwoutput()->handle()->name + << "buffer size" << buffer->currentBuffer()->handle()->width + << "x" << buffer->currentBuffer()->handle()->height; + const bool ok = commitWithVulkanOutputLayer(buffer->currentBuffer()); + return ok; + } +#endif + setBuffer(buffer->currentBuffer()); if (m_lastCommitBuffer == buffer) { @@ -1102,6 +1199,22 @@ bool OutputHelper::tryToHardwareCursor(const LayerData *layer) return true; } +#ifdef ENABLE_VULKAN_RENDER + if (isVulkanRenderer() + && WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan + && (vulkanSoftwareCursorRequested() || !vulkanHardwareCursorRequested())) { + static bool logged = false; + if (!logged) { + qCInfo(lcWlRenderer) + << "Keeping cursor in Qt composition for Vulkan RHI with wlroots Vulkan renderer" + << "forcedSoftware" << vulkanSoftwareCursorRequested() + << "hardwareOptIn" << vulkanHardwareCursorRequested(); + logged = true; + } + return false; + } +#endif + if (qwoutput()->handle()->software_cursor_locks > 0) break; @@ -1169,6 +1282,11 @@ bool OutputHelper::tryToHardwareCursor(const LayerData *layer) // needs render cursor again if (!m_cursorRenderer) { m_cursorRenderer = new WBufferRenderer(renderWindow()->contentItem()); + // The rendered cursor buffer is handed directly to the backend + // cursor plane. Publishing it as a Qt texture as well can make + // the Vulkan RHI import/sample the same small swapchain buffer + // while wlroots is using it as the hardware cursor. + m_cursorRenderer->setCacheBuffer(false); if (visualizeLayers()) m_cursorRenderer->setClearColor(Qt::cyan); m_cursorLayerProxy = new BufferRendererProxy(m_cursorRenderer); @@ -1278,6 +1396,25 @@ void WOutputRenderWindowPrivate::init() Q_ASSERT(m_renderer); Q_Q(WOutputRenderWindow); +#ifdef ENABLE_VULKAN_RENDER + const bool explicitLayerCompositor = WOutputHelper::isVulkanOutputLayerCompositorRequested(); + const bool automaticLayerCompositor = !explicitLayerCompositor + && WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan; + if (!vulkanOutputLayerCompositorDisabled() + && (explicitLayerCompositor || automaticLayerCompositor)) { + if (m_renderer->is_vk()) { + qCInfo(lcWlVulkanCompositor) + << "Vulkan output-layer compositor enabled: Qt Quick renders an intermediate dmabuf layer," + " then wlroots Vulkan render pass commits the output." + << (explicitLayerCompositor ? "mode: explicit environment request" + : "mode: automatic for Qt Quick Vulkan RHI"); + } else { + qCInfo(lcWlVulkanCompositor) + << "Vulkan output-layer compositor requested but ignored because wlroots renderer is not Vulkan."; + } + } +#endif + if (QSGRendererInterface::isApiRhiBased(graphicsApi())) initRCWithRhi(); Q_ASSERT(context); @@ -1355,20 +1492,45 @@ bool WOutputRenderWindowPrivate::initRCWithRhi() // sanity check for Vulkan #ifdef ENABLE_VULKAN_RENDER if (rhiSupport->rhiBackend() == QRhi::Vulkan) { + if (!m_renderer || !m_renderer->handle() || !m_renderer->is_vk()) { + qCWarning(lcWlRenderer) + << "Vulkan: Qt RHI requested Vulkan, but wlroots renderer is not Vulkan." + << "Set WLR_RENDERER=vulkan before initializing the Qt scene graph."; + return false; + } + vkInstance.reset(new QVulkanInstance()); - auto phdev = wlr_vk_renderer_get_physical_device(m_renderer->handle()); - auto dev = wlr_vk_renderer_get_device(m_renderer->handle()); - auto queue_family = wlr_vk_renderer_get_queue_family(m_renderer->handle()); + auto phdev = m_renderer->get_physical_device(); + auto dev = m_renderer->get_device(); + auto queue_family = m_renderer->get_queue_family(); + if (Q_UNLIKELY(!phdev || !dev)) { + qCWarning(lcWlRenderer) << "Vulkan: wlroots renderer exposed null VkPhysicalDevice/VkDevice, cannot adopt into Qt RHI"; + return false; + } #if QT_VERSION > QT_VERSION_CHECK(6, 6, 0) - auto instance = wlr_vk_renderer_get_instance(m_renderer->handle()); + auto instance = m_renderer->get_instance(); + if (Q_UNLIKELY(!instance)) { + qCWarning(lcWlRenderer) + << "Vulkan: wlroots renderer exposed null VkInstance, cannot adopt into Qt RHI"; + return false; + } vkInstance->setVkInstance(instance); #endif // vkInstance->setExtensions(fromCStyleList(vkRendererAttribs.extension_count, vkRendererAttribs.extensions)); // vkInstance->setLayers(fromCStyleList(vkRendererAttribs.layer_count, vkRendererAttribs.layers)); vkInstance->setApiVersion({1, 1, 0}); - vkInstance->create(); + if (!vkInstance->create()) { + qCWarning(lcWlRenderer) << "Vulkan: QVulkanInstance::create() failed when adopting wlroots VkInstance, errorCode=" << vkInstance->errorCode(); + return false; + } + qCInfo(lcWlRenderer) + << "Vulkan: adopting wlroots VkInstance/VkDevice into Qt RHI" + << "instance" << Qt::hex << reinterpret_cast(m_renderer->get_instance()) + << "physicalDevice" << reinterpret_cast(phdev) + << "device" << reinterpret_cast(dev) + << Qt::dec << "queueFamily" << queue_family; q->setVulkanInstance(vkInstance.data()); auto gd = QQuickGraphicsDevice::fromDeviceObjects(phdev, dev, queue_family); @@ -1376,7 +1538,7 @@ bool WOutputRenderWindowPrivate::initRCWithRhi() } else #endif if (rhiSupport->rhiBackend() == QRhi::OpenGLES2) { - Q_ASSERT(wlr_renderer_is_gles2(m_renderer->handle())); + Q_ASSERT(m_renderer->is_gles2()); auto egl = wlr_gles2_renderer_get_egl(m_renderer->handle()); auto display = wlr_egl_get_display(egl); auto context = wlr_egl_get_context(egl); @@ -1484,9 +1646,43 @@ WOutputRenderWindowPrivate::doRenderOutputs(qw_output *needsFrameOutput, const Q if (!helper->output()->depends().isEmpty()) updateDirtyNodes(); +#ifdef ENABLE_VULKAN_RENDER + if (helper->shouldDeferVulkanRenderRetry()) + continue; +#endif + +#ifdef ENABLE_VULKAN_RENDER + const bool useVulkanLayerCompositor = helper->usesVulkanOutputLayerCompositor(); + WBufferRenderer::RenderFlags renderFlags = + WBufferRenderer::RedirectOpenGLContextDefaultFrameBufferObject; + if (useVulkanLayerCompositor) + renderFlags |= WBufferRenderer::DontConfigureSwapchain; + if (useVulkanLayerCompositor) { + qCDebug(lcWlVulkanCompositor) + << "Rendering Qt Quick scene to intermediate Vulkan compositor layer for output" + << helper->qwoutput()->handle()->name + << "size" << helper->output()->output()->size() + << "format" << Qt::hex << format << Qt::dec; + } +#else + const WBufferRenderer::RenderFlags renderFlags = + WBufferRenderer::RedirectOpenGLContextDefaultFrameBufferObject; +#endif + qw_buffer *buffer = helper->beginRender(helper->bufferRenderer(), helper->output()->output()->size(), format, - WBufferRenderer::RedirectOpenGLContextDefaultFrameBufferObject); + renderFlags); Q_ASSERT(buffer == helper->bufferRenderer()->currentBuffer()); + if (!buffer) { +#ifdef ENABLE_VULKAN_RENDER + if (!helper->extraState() && helper->noteVulkanRenderFailed()) + continue; +#endif + } +#ifdef ENABLE_VULKAN_RENDER + else { + helper->noteVulkanRenderSucceeded(); + } +#endif if (buffer) { helper->render(helper->bufferRenderer(), 0, renderMatrix, helper->output()->effectiveSourceRect(), @@ -1561,6 +1757,14 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, if (QSGRendererInterface::isApiRhiBased(WRenderHelper::getGraphicsApi())) rc()->endFrame(); +#ifdef ENABLE_VULKAN_RENDER + for (const auto &commitTarget : std::as_const(needsCommit)) { + WBufferRenderer *bufferRenderer = commitTarget.second; + if (bufferRenderer->currentBuffer()) + bufferRenderer->releaseCurrentBufferForScanout(); + } +#endif + // prevent gles2-render exception in wlroots. // wlroots may have render operations after commit, so do // not move the location during the reset operation. @@ -1571,8 +1775,13 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, if (doCommit) { committedOutputs.reserve(needsCommit.size()); for (auto i : std::as_const(needsCommit)) { + bool commitAttempted = false; + bool commitSucceeded = false; + const bool hadCurrentBuffer = i.second->currentBuffer(); if (Q_UNLIKELY(!i.first->framePending())) { - if (Q_LIKELY(i.first->commit(i.second))) { + commitAttempted = true; + commitSucceeded = i.first->commit(i.second); + if (Q_LIKELY(commitSucceeded)) { // Make sure the output is still valid after commit auto output = i.first->output()->output(); if (Q_LIKELY(needsFrameOutput)) { @@ -1585,11 +1794,17 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, } } - if (i.second->currentBuffer()) { + if (hadCurrentBuffer) { i.second->endRender(); } i.first->resetState(); +#ifdef ENABLE_VULKAN_RENDER + if (hadCurrentBuffer && commitAttempted && !commitSucceeded + && i.first->noteVulkanRenderFailed()) { + i.first->update(); + } +#endif } } diff --git a/waylib/src/server/qtquick/woutputrenderwindow.h b/waylib/src/server/qtquick/woutputrenderwindow.h index 41e61dbbf..81c891769 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.h +++ b/waylib/src/server/qtquick/woutputrenderwindow.h @@ -12,6 +12,10 @@ Q_MOC_INCLUDE() +QW_BEGIN_NAMESPACE +class qw_buffer; +QW_END_NAMESPACE + WAYLIB_SERVER_BEGIN_NAMESPACE class WOutputViewport; diff --git a/waylib/src/server/qtquick/wquickcursor.cpp b/waylib/src/server/qtquick/wquickcursor.cpp index 24670e452..8f1828bad 100644 --- a/waylib/src/server/qtquick/wquickcursor.cpp +++ b/waylib/src/server/qtquick/wquickcursor.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -39,6 +40,32 @@ class Q_DECL_HIDDEN CursorTextureProvider : public WSGTextureProvider return; } +#ifdef ENABLE_VULKAN_RENDER + if (isVulkanRenderer()) { + if (isCachedImage(image) && buffer && directImageTextureValid) + return; + + QImage ownedImage = image.copy(); + ownedImage.setDevicePixelRatio(image.devicePixelRatio()); + + // WImageBufferImpl destroy following qw_buffer + auto buffer = qw_buffer::create(new WImageBufferImpl(ownedImage), + ownedImage.width(), ownedImage.height()); + this->buffer.reset(buffer); + + directImageTexture.setImage(ownedImage); + directImageTexture.setHasAlphaChannel(ownedImage.hasAlphaChannel()); + directImageTextureValid = true; + updateCachedImage(image); + + if (WSGTextureProvider::texture() || WSGTextureProvider::qwBuffer()) + WSGTextureProvider::setBuffer(nullptr); + else + Q_EMIT textureChanged(); + return; + } +#endif + // WImageBufferImpl destroy following qw_buffer auto buffer = qw_buffer::create(new WImageBufferImpl(image), image.width(), image.height()); @@ -66,8 +93,15 @@ class Q_DECL_HIDDEN CursorTextureProvider : public WSGTextureProvider } void resetBuffer() { +#ifdef ENABLE_VULKAN_RENDER + if (directImageTextureValid) + clearDirectImageTexture(); +#endif setBuffer(nullptr); buffer.reset(); +#ifdef ENABLE_VULKAN_RENDER + clearCachedImage(); +#endif } void reset() { resetBuffer(); @@ -77,6 +111,10 @@ class Q_DECL_HIDDEN CursorTextureProvider : public WSGTextureProvider QSGTexture *texture() const override { if (proxy) return proxy->texture(); +#ifdef ENABLE_VULKAN_RENDER + if (directImageTextureValid) + return const_cast(&directImageTexture); +#endif return WSGTextureProvider::texture(); } qw_texture *qwTexture() const override { @@ -87,11 +125,76 @@ class Q_DECL_HIDDEN CursorTextureProvider : public WSGTextureProvider qw_buffer *qwBuffer() const override { if (proxy) return proxy->qwBuffer(); +#ifdef ENABLE_VULKAN_RENDER + if (directImageTextureValid) + return buffer.get(); +#endif return WSGTextureProvider::qwBuffer(); } + QSGTexture *paintTexture() const { +#ifdef ENABLE_VULKAN_RENDER + if (directImageTextureValid) + return const_cast(&directImageTexture); +#endif + return WSGTextureProvider::texture(); + } + +#ifdef ENABLE_VULKAN_RENDER + bool hasDirectImageTexture() const { + return directImageTextureValid; + } + + bool hasVulkanRenderer() const { + return isVulkanRenderer(); + } +#endif + std::unique_ptr buffer; QPointer proxy; + +#ifdef ENABLE_VULKAN_RENDER +private: + bool isVulkanRenderer() const { + return window() && window()->renderer() + && window()->renderer()->is_vk(); + } + + bool isCachedImage(const QImage &image) const { + return cachedImageKey == image.cacheKey() + && cachedImageSize == image.size() + && cachedImageDevicePixelRatio == image.devicePixelRatio() + && cachedImageFormat == image.format(); + } + + void updateCachedImage(const QImage &image) { + cachedImageKey = image.cacheKey(); + cachedImageSize = image.size(); + cachedImageDevicePixelRatio = image.devicePixelRatio(); + cachedImageFormat = image.format(); + } + + void clearDirectImageTexture() { + directImageTexture.setTexture(nullptr); + directImageTexture.setTextureSize({}); + directImageTexture.setHasAlphaChannel(false); + directImageTextureValid = false; + } + + void clearCachedImage() { + cachedImageKey = 0; + cachedImageSize = {}; + cachedImageDevicePixelRatio = 0; + cachedImageFormat = QImage::Format_Invalid; + } + + qint64 cachedImageKey = 0; + QSize cachedImageSize; + qreal cachedImageDevicePixelRatio = 0; + QImage::Format cachedImageFormat = QImage::Format_Invalid; + QSGPlainTexture directImageTexture; + bool directImageTextureValid = false; +#endif }; WQuickCursorAttached::WQuickCursorAttached(QQuickItem *parent) @@ -576,13 +679,30 @@ QSGNode *WQuickCursor::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) tp->setImage(d->cursorImage->image()); } - // Ignore the tp->proxy, Don't use tp->qwBuffer() + QSGTexture *texture = nullptr; +#ifdef ENABLE_VULKAN_RENDER + if (tp->hasDirectImageTexture()) { + texture = tp->paintTexture(); + if (!texture) { + delete node; + return nullptr; + } + } else +#endif if (!tp->buffer) { delete node; return nullptr; } + else { + texture = tp->WSGTextureProvider::texture(); + } +#ifdef ENABLE_VULKAN_RENDER + if (!texture && tp->hasVulkanRenderer()) { + delete node; + return nullptr; + } +#endif - auto texture = tp->WSGTextureProvider::texture(); auto imageNode = static_cast(node); if (!imageNode) imageNode = window()->createImageNode(); diff --git a/waylib/src/server/qtquick/wrenderhelper.cpp b/waylib/src/server/qtquick/wrenderhelper.cpp index 0f0063159..732f0253d 100644 --- a/waylib/src/server/qtquick/wrenderhelper.cpp +++ b/waylib/src/server/qtquick/wrenderhelper.cpp @@ -17,8 +17,21 @@ #include #include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -35,10 +48,27 @@ extern "C" { #include #ifdef ENABLE_VULKAN_RENDER #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #endif } #include #include +#include + +#include +#include +#include +#include QW_USE_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE @@ -51,6 +81,81 @@ struct Q_DECL_HIDDEN RhiRenderEntry { Q_GLOBAL_STATIC(QVector, s_rhiRenderBuffers) +static void removeRhiRenderBufferEntry(const QRhiRenderTarget *renderTarget) +{ + if (!renderTarget) + return; + + auto it = s_rhiRenderBuffers->begin(); + while (it != s_rhiRenderBuffers->end()) { + if (it->renderTarget == renderTarget) { + it = s_rhiRenderBuffers->erase(it); + } else { + ++it; + } + } +} + +static const char *quickRenderTargetTypeName(QQuickRenderTargetPrivate::Type type) +{ + switch (type) { + case QQuickRenderTargetPrivate::Type::Null: + return "Null"; + case QQuickRenderTargetPrivate::Type::NativeTexture: + return "NativeTexture"; + case QQuickRenderTargetPrivate::Type::NativeTextureArray: + return "NativeTextureArray"; + case QQuickRenderTargetPrivate::Type::NativeRenderbuffer: + return "NativeRenderbuffer"; + case QQuickRenderTargetPrivate::Type::RhiRenderTarget: + return "RhiRenderTarget"; + case QQuickRenderTargetPrivate::Type::PaintDevice: + return "PaintDevice"; + } + + return "Unknown"; +} + +#ifdef ENABLE_VULKAN_RENDER +struct Q_DECL_HIDDEN VulkanImportedRenderTarget { + VkDevice device = VK_NULL_HANDLE; + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memories[WLR_DMABUF_MAX_PLANES] = {}; + uint32_t memoryCount = 0; + VkFormat format = VK_FORMAT_UNDEFINED; + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + bool ownerIsForeign = true; + bool scanoutReleaseReady = false; + QSize size; + uint32_t drmFormat = 0; + uint64_t drmModifier = DRM_FORMAT_MOD_INVALID; + + bool isValid() const { + return device != VK_NULL_HANDLE && image != VK_NULL_HANDLE + && format != VK_FORMAT_UNDEFINED && !size.isEmpty(); + } +}; + +struct Q_DECL_HIDDEN VulkanImportedNativeTexture { + VkDevice device = VK_NULL_HANDLE; + VkQueue queue = VK_NULL_HANDLE; + uint32_t queueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memories[WLR_DMABUF_MAX_PLANES] = {}; + uint32_t memoryCount = 0; + VkFormat format = VK_FORMAT_UNDEFINED; + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + QSize size; + uint32_t drmFormat = 0; + uint64_t drmModifier = DRM_FORMAT_MOD_INVALID; + + bool isValid() const { + return device != VK_NULL_HANDLE && image != VK_NULL_HANDLE + && format != VK_FORMAT_UNDEFINED && !size.isEmpty(); + } +}; +#endif + struct Q_DECL_HIDDEN BufferData { BufferData() { @@ -58,25 +163,52 @@ struct Q_DECL_HIDDEN BufferData { ~BufferData() { resetWindowRenderTarget(); +#ifdef ENABLE_VULKAN_RENDER + destroyEglDmabufTexture(); + destroyVulkanRenderTarget(); +#endif } qw_buffer *buffer = nullptr; +#ifdef ENABLE_VULKAN_RENDER + // EGL dmabuf import state (used when wlroots renderer is Vulkan but Qt RHI + // is GL). The dmabuf is imported as an EGLImage, then bound to a GL texture + // via glEGLImageTargetTexture2DOES. This bypasses wlroots's texture system + // entirely — dmabuf is API-agnostic, any EGL context can import it. + EGLImage eglImage = EGL_NO_IMAGE; + GLuint glTexture = 0; + EGLDisplay eglDisplay = EGL_NO_DISPLAY; + + void destroyEglDmabufTexture(); + void destroyVulkanRenderTarget(); + + VulkanImportedRenderTarget vulkanRenderTarget; +#endif // for software renderer WImageRenderTarget paintDevice; QQuickRenderTarget renderTarget; + QQuickRenderTarget preserveRenderTarget; QQuickWindowRenderTarget windowRenderTarget; + QQuickWindowRenderTarget preserveWindowRenderTarget; inline void resetWindowRenderTarget() { #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + removeRhiRenderBufferEntry(windowRenderTarget.rt.renderTarget); + removeRhiRenderBufferEntry(preserveWindowRenderTarget.rt.renderTarget); + + if (preserveWindowRenderTarget.rt.owns) + delete preserveWindowRenderTarget.rt.renderTarget; + + delete preserveWindowRenderTarget.res.renderBuffer; + delete preserveWindowRenderTarget.res.rpDesc; + + preserveWindowRenderTarget.rt = {}; + preserveWindowRenderTarget.res = {}; { - auto it = s_rhiRenderBuffers->begin(); - while (it != s_rhiRenderBuffers->end()) { - if (windowRenderTarget.rt.renderTarget == it->renderTarget) { - it = s_rhiRenderBuffers->erase(it); - break; - } - ++it; - } + delete preserveWindowRenderTarget.implicitBuffers.depthStencil; + delete preserveWindowRenderTarget.implicitBuffers.depthStencilTexture; + delete preserveWindowRenderTarget.implicitBuffers.multisampleTexture; + preserveWindowRenderTarget.implicitBuffers = {}; } if (windowRenderTarget.rt.owns) @@ -99,18 +231,27 @@ struct Q_DECL_HIDDEN BufferData { delete windowRenderTarget.sw.paintDevice; windowRenderTarget.sw = {}; + preserveWindowRenderTarget.sw = {}; #else - { - auto it = s_rhiRenderBuffers->begin(); - while (it != s_rhiRenderBuffers->end()) { - if (windowRenderTarget.renderTarget == it->renderTarget) { - it = s_rhiRenderBuffers->erase(it); - break; - } - ++it; - } + removeRhiRenderBufferEntry(windowRenderTarget.renderTarget); + removeRhiRenderBufferEntry(preserveWindowRenderTarget.renderTarget); + + if (preserveWindowRenderTarget.owns) { + delete preserveWindowRenderTarget.renderTarget; + delete preserveWindowRenderTarget.rpDesc; + delete preserveWindowRenderTarget.renderBuffer; + delete preserveWindowRenderTarget.depthStencil; + delete preserveWindowRenderTarget.paintDevice; } + preserveWindowRenderTarget.renderTarget = nullptr; + preserveWindowRenderTarget.rpDesc = nullptr; + preserveWindowRenderTarget.texture = nullptr; + preserveWindowRenderTarget.renderBuffer = nullptr; + preserveWindowRenderTarget.depthStencil = nullptr; + preserveWindowRenderTarget.paintDevice = nullptr; + preserveWindowRenderTarget.owns = false; + if (windowRenderTarget.owns) { delete windowRenderTarget.renderTarget; delete windowRenderTarget.rpDesc; @@ -128,233 +269,2723 @@ struct Q_DECL_HIDDEN BufferData { windowRenderTarget.paintDevice = nullptr; windowRenderTarget.owns = false; #endif + preserveRenderTarget = {}; } }; -// Copy from qquickrendertarget.cpp -static bool createRhiRenderTarget(const QRhiColorAttachment &colorAttachment, - const QSize &pixelSize, - int sampleCount, - QRhi *rhi, - QQuickWindowRenderTarget &dst) +#ifdef ENABLE_VULKAN_RENDER +// Resolve Vulkan loader entry points via dlsym. wlroots links libvulkan, but +// waylib does not link it directly in all builds, so keep Vulkan calls behind +// runtime-resolved function pointers. +static PFN_vkGetDeviceProcAddr resolveVkGetDeviceProcAddr() { - std::unique_ptr depthStencil(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount)); - if (!depthStencil->create()) { - qCWarning(lcWlRenderHelper, "Failed to build depth-stencil buffer for QQuickRenderTarget"); - return false; - } + static PFN_vkGetDeviceProcAddr proc = + reinterpret_cast(dlsym(RTLD_DEFAULT, "vkGetDeviceProcAddr")); + return proc; +} - QRhiTextureRenderTargetDescription rtDesc(colorAttachment); - rtDesc.setDepthStencilBuffer(depthStencil.get()); - std::unique_ptr rt(rhi->newTextureRenderTarget(rtDesc)); - std::unique_ptr rp(rt->newCompatibleRenderPassDescriptor()); - rt->setRenderPassDescriptor(rp.get()); +static PFN_vkGetInstanceProcAddr resolveVkGetInstanceProcAddr() +{ + static PFN_vkGetInstanceProcAddr proc = + reinterpret_cast(dlsym(RTLD_DEFAULT, "vkGetInstanceProcAddr")); + return proc; +} - if (!rt->create()) { - qCWarning(lcWlRenderHelper, "Failed to build texture render target for QQuickRenderTarget"); - return false; - } +static QByteArray drmFormatToName(uint32_t format) +{ + char *name = drmGetFormatName(format); + if (!name) + return QByteArray::number(format, 16); - rt->setName(QByteArrayLiteral("WaylibTextureRenderTarget")); -#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) - dst.rt.renderTarget = rt.release(); - dst.res.rpDesc = rp.release(); - dst.implicitBuffers.depthStencil = depthStencil.release(); - dst.rt.owns = true; // ownership of the native resource itself is not transferred but the QRhi objects are on us now -#else - dst.renderTarget = rt.release(); - dst.rpDesc = rp.release(); - dst.depthStencil = depthStencil.release(); - dst.owns = true; // ownership of the native resource itself is not transferred but the QRhi objects are on us now -#endif - return true; + QByteArray result(name); + free(name); + return result; } -bool createRhiRenderTarget(QRhi *rhi, const QQuickRenderTarget &source, QQuickWindowRenderTarget &dst) +static QByteArray drmModifierToName(uint64_t modifier) { - auto rtd = QQuickRenderTargetPrivate::get(&source); - - switch (rtd->type) { - 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( -#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)); - texture->setName(QByteArrayLiteral("WaylibTexture")); -#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) - if (!texture->createFrom({ rtd->u.nativeTexture.object, rtd->u.nativeTexture.layout })) -#else - if (!texture->createFrom({ rtd->u.nativeTexture.object, rtd->u.nativeTexture.layoutOrState })) -#endif - return false; - QRhiColorAttachment att(texture.get()); - if (!createRhiRenderTarget(att, rtd->pixelSize, rtd->sampleCount, rhi, dst)) - return false; -#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) - dst.res.texture = texture.release(); -#else - dst.texture = texture.release(); -#endif - return true; - } - case QQuickRenderTargetPrivate::Type::NativeRenderbuffer: { - std::unique_ptr renderbuffer(rhi->newRenderBuffer(QRhiRenderBuffer::Color, rtd->pixelSize, rtd->sampleCount)); - if (!renderbuffer->createFrom({ rtd->u.nativeRenderbufferObject })) { - qCWarning(lcWlRenderHelper, "Failed to build wrapper renderbuffer for QQuickRenderTarget"); - return false; - } - QRhiColorAttachment att(renderbuffer.get()); - if (!createRhiRenderTarget(att, rtd->pixelSize, rtd->sampleCount, rhi, dst)) - return false; - renderbuffer->setName(QByteArrayLiteral("WaylibRenderBuffer")); -#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) - dst.res.renderBuffer = renderbuffer.release(); -#else - dst.renderBuffer = renderbuffer.release(); -#endif - return true; - } + if (modifier == DRM_FORMAT_MOD_INVALID) + return QByteArrayLiteral("INVALID"); - default: - break; - } + char *name = drmGetFormatModifierName(modifier); + if (!name) + return QByteArray::number(modifier, 16); - return false; + QByteArray result(name); + free(name); + return result; } -// Copy end -class Q_DECL_HIDDEN WRenderHelperPrivate : public WObjectPrivate +static const char *vkResultName(VkResult result) { -public: - WRenderHelperPrivate(WRenderHelper *qq, qw_renderer *renderer) - : WObjectPrivate(qq) - , renderer(renderer) - {} - ~WRenderHelperPrivate() { - resetRenderBuffer(); + switch (result) { + case VK_SUCCESS: return "VK_SUCCESS"; + case VK_NOT_READY: return "VK_NOT_READY"; + case VK_TIMEOUT: return "VK_TIMEOUT"; + case VK_EVENT_SET: return "VK_EVENT_SET"; + case VK_EVENT_RESET: return "VK_EVENT_RESET"; + case VK_INCOMPLETE: return "VK_INCOMPLETE"; + case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY"; + case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY"; + case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED"; + case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST"; + case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED"; + case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT"; + case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT"; + case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT"; + case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER"; + case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS"; + case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED"; + case VK_ERROR_FRAGMENTED_POOL: return "VK_ERROR_FRAGMENTED_POOL"; +#ifdef VK_ERROR_UNKNOWN + case VK_ERROR_UNKNOWN: return "VK_ERROR_UNKNOWN"; +#endif + default: return "VK_RESULT_UNKNOWN"; } +} - void resetRenderBuffer(); - void onBufferDestroy(); - static bool ensureRhiRenderTarget(QQuickRenderControl *rc, BufferData *data); +static const char *vkImageLayoutName(VkImageLayout layout) +{ + switch (layout) { + case VK_IMAGE_LAYOUT_UNDEFINED: return "UNDEFINED"; + case VK_IMAGE_LAYOUT_GENERAL: return "GENERAL"; + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: return "COLOR_ATTACHMENT_OPTIMAL"; + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: return "DEPTH_STENCIL_ATTACHMENT_OPTIMAL"; + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: return "SHADER_READ_ONLY_OPTIMAL"; + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: return "TRANSFER_SRC_OPTIMAL"; + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: return "TRANSFER_DST_OPTIMAL"; + case VK_IMAGE_LAYOUT_PREINITIALIZED: return "PREINITIALIZED"; +#ifdef VK_IMAGE_LAYOUT_PRESENT_SRC_KHR + case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: return "PRESENT_SRC_KHR"; +#endif + default: return "OTHER"; + } +} - W_DECLARE_PUBLIC(WRenderHelper) - qw_renderer *renderer; - QList buffers; - BufferData *lastBuffer = nullptr; +template +static quint64 vulkanHandleToInteger(Handle handle) +{ + if constexpr (std::is_pointer_v) + return quint64(reinterpret_cast(handle)); + else + return quint64(handle); +} - QSize size; +struct Q_DECL_HIDDEN PendingVulkanCommandCleanup { + VkDevice device = VK_NULL_HANDLE; + VkCommandPool commandPool = VK_NULL_HANDLE; + VkFence fence = VK_NULL_HANDLE; + VkSemaphore semaphore = VK_NULL_HANDLE; }; -void WRenderHelperPrivate::resetRenderBuffer() -{ - qDeleteAll(buffers); - lastBuffer = nullptr; - buffers.clear(); -} +Q_GLOBAL_STATIC(QMutex, s_pendingVulkanCommandCleanupMutex) +Q_GLOBAL_STATIC(QVector, s_pendingVulkanCommandCleanups) -void WRenderHelperPrivate::onBufferDestroy() +static void destroyPendingVulkanCommandCleanup(PendingVulkanCommandCleanup *cleanup) { - qw_buffer *buffer = qobject_cast(q_func()->sender()); + if (!cleanup || cleanup->device == VK_NULL_HANDLE) { + if (cleanup) + *cleanup = {}; + return; + } - for (int i = 0; i < buffers.count(); ++i) { - auto data = buffers[i]; - if (data->buffer == buffer) { - if (lastBuffer == data) - lastBuffer = nullptr; - buffers.removeAt(i); - break; - } + auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); + if (!vkGetDeviceProcAddr) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI command cleanup skipped: vkGetDeviceProcAddr unavailable" + << "commandPool" << Qt::hex << vulkanHandleToInteger(cleanup->commandPool) + << "fence" << vulkanHandleToInteger(cleanup->fence) + << "semaphore" << vulkanHandleToInteger(cleanup->semaphore) << Qt::dec; + *cleanup = {}; + return; } + + auto vkDestroyFence = reinterpret_cast( + vkGetDeviceProcAddr(cleanup->device, "vkDestroyFence")); + auto vkDestroySemaphore = reinterpret_cast( + vkGetDeviceProcAddr(cleanup->device, "vkDestroySemaphore")); + auto vkDestroyCommandPool = reinterpret_cast( + vkGetDeviceProcAddr(cleanup->device, "vkDestroyCommandPool")); + + if (cleanup->fence != VK_NULL_HANDLE && vkDestroyFence) + vkDestroyFence(cleanup->device, cleanup->fence, nullptr); + if (cleanup->semaphore != VK_NULL_HANDLE && vkDestroySemaphore) + vkDestroySemaphore(cleanup->device, cleanup->semaphore, nullptr); + if (cleanup->commandPool != VK_NULL_HANDLE && vkDestroyCommandPool) + vkDestroyCommandPool(cleanup->device, cleanup->commandPool, nullptr); + + *cleanup = {}; } -bool WRenderHelperPrivate::ensureRhiRenderTarget(QQuickRenderControl *rc, BufferData *data) +static bool pendingVulkanCommandCleanupReady(PendingVulkanCommandCleanup *cleanup, bool wait) { - data->resetWindowRenderTarget(); -#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) - auto rhi = QQuickRenderControlPrivate::get(rc)->rhi; -#else - auto rhi = rc->rhi(); -#endif - auto tmp = data->renderTarget; - bool ok = createRhiRenderTarget(rhi, tmp, data->windowRenderTarget); - if (!ok) + if (!cleanup || cleanup->device == VK_NULL_HANDLE || cleanup->fence == VK_NULL_HANDLE) + return true; + + auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); + if (!vkGetDeviceProcAddr) + return true; + + auto vkGetFenceStatus = reinterpret_cast( + vkGetDeviceProcAddr(cleanup->device, "vkGetFenceStatus")); + auto vkWaitForFences = reinterpret_cast( + vkGetDeviceProcAddr(cleanup->device, "vkWaitForFences")); + + VkResult res = VK_SUCCESS; + if (wait) { + if (!vkWaitForFences) + return true; + res = vkWaitForFences(cleanup->device, 1, &cleanup->fence, VK_TRUE, UINT64_MAX); + } else { + if (!vkGetFenceStatus) + return true; + res = vkGetFenceStatus(cleanup->device, cleanup->fence); + } + + if (res == VK_SUCCESS) + return true; + if (res == VK_NOT_READY) return false; -#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) - data->renderTarget = QQuickRenderTarget::fromRhiRenderTarget(data->windowRenderTarget.rt.renderTarget); -#else - data->renderTarget = QQuickRenderTarget::fromRhiRenderTarget(data->windowRenderTarget.renderTarget); -#endif - data->renderTarget.setDevicePixelRatio(tmp.devicePixelRatio()); - data->renderTarget.setMirrorVertically(tmp.mirrorVertically()); + qCWarning(lcWlRenderHelper) + << "Vulkan RHI command cleanup fence status failed" + << vkResultName(res) << int(res) + << "commandPool" << Qt::hex << vulkanHandleToInteger(cleanup->commandPool) + << "fence" << vulkanHandleToInteger(cleanup->fence) << Qt::dec; return true; } -WRenderHelper::WRenderHelper(qw_renderer *renderer, QObject *parent) - : QObject(parent) - , WObject(*new WRenderHelperPrivate(this, renderer)) +static void flushPendingVulkanCommandCleanups(bool wait = false) { + QVector pending; + { + QMutexLocker locker(s_pendingVulkanCommandCleanupMutex()); + if (s_pendingVulkanCommandCleanups->isEmpty()) + return; + pending.swap(*s_pendingVulkanCommandCleanups); + } -} + QVector remaining; + for (auto &cleanup : pending) { + if (pendingVulkanCommandCleanupReady(&cleanup, wait)) { + destroyPendingVulkanCommandCleanup(&cleanup); + } else { + remaining.append(cleanup); + } + } -QSize WRenderHelper::size() const -{ - W_DC(WRenderHelper); - return d->size; + if (!remaining.isEmpty()) { + QMutexLocker locker(s_pendingVulkanCommandCleanupMutex()); + *s_pendingVulkanCommandCleanups += remaining; + } } -void WRenderHelper::setSize(const QSize &size) +static void queuePendingVulkanCommandCleanup(PendingVulkanCommandCleanup *cleanup) { - W_D(WRenderHelper); - if (d->size == size) + if (!cleanup || cleanup->device == VK_NULL_HANDLE) return; - d->size = size; - d->resetRenderBuffer(); - Q_EMIT sizeChanged(); -} + flushPendingVulkanCommandCleanups(false); -QSGRendererInterface::GraphicsApi WRenderHelper::getGraphicsApi(QQuickRenderControl *rc) -{ - auto d = QQuickRenderControlPrivate::get(rc); - return d->sg->rendererInterface(d->rc)->graphicsApi(); + QMutexLocker locker(s_pendingVulkanCommandCleanupMutex()); + s_pendingVulkanCommandCleanups->append(*cleanup); + *cleanup = {}; } -QSGRendererInterface::GraphicsApi WRenderHelper::getGraphicsApi() +static void destroyVulkanImportedRenderTarget(VulkanImportedRenderTarget *target) { - auto getApi = [] () { - // Only for get GraphicsApi - QQuickRenderControl rc; - return getGraphicsApi(&rc); - }; + if (!target || target->device == VK_NULL_HANDLE) { + if (target) + *target = {}; + return; + } - static auto api = getApi(); - return api; -} + flushPendingVulkanCommandCleanups(true); -class Q_DECL_HIDDEN GLTextureBuffer : public qw_buffer_interface -{ -public: - explicit GLTextureBuffer(wlr_egl *egl, QSGTexture *texture); + auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); + if (!vkGetDeviceProcAddr) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output target cleanup skipped: vkGetDeviceProcAddr unavailable" + << "image" << Qt::hex << vulkanHandleToInteger(target->image) << Qt::dec + << "memoryCount" << target->memoryCount; + *target = {}; + return; + } - QW_INTERFACE(get_dmabuf, bool, wlr_dmabuf_attributes *attribs); + auto vkDestroyImage = reinterpret_cast( + vkGetDeviceProcAddr(target->device, "vkDestroyImage")); + auto vkFreeMemory = reinterpret_cast( + vkGetDeviceProcAddr(target->device, "vkFreeMemory")); -private: - wlr_egl *m_egl; - QSGTexture *m_texture; -}; + if (target->image != VK_NULL_HANDLE && vkDestroyImage) + vkDestroyImage(target->device, target->image, nullptr); -GLTextureBuffer::GLTextureBuffer(wlr_egl *egl, QSGTexture *texture) - : m_egl(egl) - , m_texture(texture) -{ + if (vkFreeMemory) { + for (uint32_t i = 0; i < target->memoryCount && i < WLR_DMABUF_MAX_PLANES; ++i) { + if (target->memories[i] != VK_NULL_HANDLE) + vkFreeMemory(target->device, target->memories[i], nullptr); + } + } else if (target->memoryCount > 0) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output target cleanup could not free imported memory:" + << "vkFreeMemory unavailable" + << "image" << Qt::hex << vulkanHandleToInteger(target->image) << Qt::dec + << "memoryCount" << target->memoryCount; + } + + qCDebug(lcWlRenderHelper) + << "Destroyed Vulkan RHI output render target import" + << "image" << Qt::hex << vulkanHandleToInteger(target->image) << Qt::dec + << "size" << target->size + << "format" << drmFormatToName(target->drmFormat) + << "modifier" << drmModifierToName(target->drmModifier); + + *target = {}; +} + +static void destroyVulkanImportedNativeTexture(VulkanImportedNativeTexture *texture) +{ + if (!texture || texture->device == VK_NULL_HANDLE) { + if (texture) + *texture = {}; + return; + } + + flushPendingVulkanCommandCleanups(true); + + auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); + if (!vkGetDeviceProcAddr) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture cleanup skipped: vkGetDeviceProcAddr unavailable" + << "image" << Qt::hex << vulkanHandleToInteger(texture->image) << Qt::dec + << "memoryCount" << texture->memoryCount; + *texture = {}; + return; + } + + auto vkDestroyImage = reinterpret_cast( + vkGetDeviceProcAddr(texture->device, "vkDestroyImage")); + auto vkFreeMemory = reinterpret_cast( + vkGetDeviceProcAddr(texture->device, "vkFreeMemory")); + + if (texture->image != VK_NULL_HANDLE && vkDestroyImage) + vkDestroyImage(texture->device, texture->image, nullptr); + + if (vkFreeMemory) { + for (uint32_t i = 0; i < texture->memoryCount && i < WLR_DMABUF_MAX_PLANES; ++i) { + if (texture->memories[i] != VK_NULL_HANDLE) + vkFreeMemory(texture->device, texture->memories[i], nullptr); + } + } else if (texture->memoryCount > 0) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture cleanup could not free imported memory:" + << "vkFreeMemory unavailable" + << "image" << Qt::hex << vulkanHandleToInteger(texture->image) << Qt::dec + << "memoryCount" << texture->memoryCount; + } + + qCDebug(lcWlRenderHelper) + << "Destroyed Vulkan RHI client texture import" + << "image" << Qt::hex << vulkanHandleToInteger(texture->image) << Qt::dec + << "size" << texture->size + << "format" << drmFormatToName(texture->drmFormat) + << "modifier" << drmModifierToName(texture->drmModifier) + << "lastLayout" << vkImageLayoutName(texture->layout); + + *texture = {}; +} + +static bool waitDmabufImplicitFence(qw_buffer *buffer, uint32_t flags, + const char *subject, const char *phase) +{ + if (!buffer || !buffer->handle()) + return false; + + wlr_dmabuf_attributes dmabuf = {}; + if (!buffer->get_dmabuf(&dmabuf)) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "cannot wait dmabuf fence: buffer has no dmabuf"; + return false; + } + + const short events = (flags & DMA_BUF_SYNC_WRITE) ? POLLOUT : POLLIN; + for (int i = 0; i < dmabuf.n_planes; ++i) { + pollfd pfd = {}; + pfd.fd = dmabuf.fd[i]; + pfd.events = events; + + const int timeoutMs = 1000; + const int ret = poll(&pfd, 1, timeoutMs); + if (ret < 0) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "failed to wait dmabuf fence" + << "plane" << i + << "errno" << errno; + return false; + } + + if (ret == 0) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "timed out waiting dmabuf fence" + << "plane" << i + << "timeoutMs" << timeoutMs; + return false; + } + } + + qCDebug(lcWlRenderHelper) + << subject << phase + << "implicit dmabuf fence signaled" + << "buffer" << buffer + << "size" << QSize(dmabuf.width, dmabuf.height) + << "format" << drmFormatToName(dmabuf.format) + << "modifier" << drmModifierToName(dmabuf.modifier) + << "planes" << dmabuf.n_planes + << "events" << events; + + return true; +} + +static bool waitSyncFileFence(int syncFileFd, const char *subject, const char *phase) +{ + if (syncFileFd < 0) + return false; + + pollfd pfd = {}; + pfd.fd = syncFileFd; + pfd.events = POLLIN; + + const int timeoutMs = 1000; + int ret = 0; + do { + ret = poll(&pfd, 1, timeoutMs); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "failed to wait sync_file fence" + << "errno" << errno; + return false; + } + + if (ret == 0) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "timed out waiting sync_file fence" + << "timeoutMs" << timeoutMs; + return false; + } + + if (!(pfd.revents & POLLIN)) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "sync_file fence wait returned unexpected events" + << "events" << pfd.revents; + return false; + } + + return true; +} + +static bool waitSurfaceExplicitAcquireFence(wlr_surface *surface, + const char *subject, + bool *usedExplicitAcquire) +{ + if (usedExplicitAcquire) + *usedExplicitAcquire = false; + + if (!surface) + return true; + + auto *state = qw_linux_drm_syncobj_surface_v1_state::get_surface_state(surface); + if (!state || !state->has_acquire_timeline()) + return true; + + if (usedExplicitAcquire) + *usedExplicitAcquire = true; + + const uint64_t acquirePoint = state->acquire_point(); + const int syncFileFd = state->export_acquire_sync_file(); + if (syncFileFd < 0) { + qCWarning(lcWlRenderHelper) + << subject << "explicit acquire" + << "failed to export syncobj timeline point as sync_file" + << "surface" << surface + << "point" << acquirePoint; + return false; + } + + const bool ok = waitSyncFileFence(syncFileFd, subject, "explicit acquire"); + close(syncFileFd); + + if (ok) { + qCDebug(lcWlRenderHelper) + << subject << "explicit acquire fence signaled" + << "surface" << surface + << "point" << acquirePoint; + } + + return ok; +} + +static bool importSyncFileIntoDmabuf(qw_buffer *buffer, int syncFileFd, const char *phase) +{ + if (!buffer || !buffer->handle() || syncFileFd < 0) + return false; + + wlr_dmabuf_attributes dmabuf = {}; + if (!buffer->get_dmabuf(&dmabuf)) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "cannot import sync_file: output buffer has no dmabuf"; + return false; + } + + for (int i = 0; i < dmabuf.n_planes; ++i) { + struct dma_buf_import_sync_file data = {}; + data.flags = DMA_BUF_SYNC_WRITE; + data.fd = syncFileFd; + + if (ioctl(dmabuf.fd[i], DMA_BUF_IOCTL_IMPORT_SYNC_FILE, &data) != 0) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed to import sync_file into dmabuf" + << "plane" << i + << "errno" << errno; + return false; + } + } + + return true; +} + +static bool submitVulkanOutputTargetBarrier(QRhi *rhi, + VulkanImportedRenderTarget *target, + qw_buffer *buffer, + const char *phase, + VkImageLayout oldLayout, + VkImageLayout newLayout, + uint32_t srcQueueFamily, + uint32_t dstQueueFamily, + VkAccessFlags srcAccessMask, + VkAccessFlags dstAccessMask, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool exportSyncFile) +{ + if (!rhi || !target || !target->isValid()) + return false; + + flushPendingVulkanCommandCleanups(false); + + const auto *handles = static_cast(rhi->nativeHandles()); + if (!handles || !handles->dev || !handles->gfxQueue || !handles->inst) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed: QRhi Vulkan native handles unavailable"; + return false; + } + + if (handles->dev != target->device) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed: QRhi device differs from imported output target" + << "rhi" << Qt::hex << vulkanHandleToInteger(handles->dev) + << "target" << vulkanHandleToInteger(target->device) << Qt::dec; + return false; + } + + auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); + if (!vkGetDeviceProcAddr) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed: vkGetDeviceProcAddr unavailable"; + return false; + } + + VkDevice device = handles->dev; + VkQueue queue = handles->gfxQueue; + + auto vkCreateCommandPool = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCreateCommandPool")); + auto vkDestroyCommandPool = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkDestroyCommandPool")); + auto vkAllocateCommandBuffers = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkAllocateCommandBuffers")); + auto vkFreeCommandBuffers = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkFreeCommandBuffers")); + auto vkBeginCommandBuffer = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkBeginCommandBuffer")); + auto vkEndCommandBuffer = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkEndCommandBuffer")); + auto vkCmdPipelineBarrier = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCmdPipelineBarrier")); + auto vkQueueSubmit = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkQueueSubmit")); + auto vkCreateFence = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCreateFence")); + auto vkDestroyFence = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkDestroyFence")); + auto vkCreateSemaphore = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCreateSemaphore")); + auto vkDestroySemaphore = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkDestroySemaphore")); + auto vkGetSemaphoreFdKHR = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkGetSemaphoreFdKHR")); + + if (!vkCreateCommandPool || !vkDestroyCommandPool || !vkAllocateCommandBuffers + || !vkFreeCommandBuffers || !vkBeginCommandBuffer || !vkEndCommandBuffer + || !vkCmdPipelineBarrier || !vkQueueSubmit || !vkCreateFence || !vkDestroyFence + || (exportSyncFile && (!vkCreateSemaphore || !vkDestroySemaphore || !vkGetSemaphoreFdKHR))) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed: required Vulkan command/sync functions unavailable" + << "exportSyncFile" << exportSyncFile; + return false; + } + + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; + poolInfo.queueFamilyIndex = handles->gfxQueueFamilyIdx; + + VkCommandPool commandPool = VK_NULL_HANDLE; + VkResult res = vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed: vkCreateCommandPool" + << vkResultName(res) << int(res); + return false; + } + + VkCommandBufferAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer cb = VK_NULL_HANDLE; + res = vkAllocateCommandBuffers(device, &allocInfo, &cb); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed: vkAllocateCommandBuffers" + << vkResultName(res) << int(res); + vkDestroyCommandPool(device, commandPool, nullptr); + return false; + } + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + res = vkBeginCommandBuffer(cb, &beginInfo); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed: vkBeginCommandBuffer" + << vkResultName(res) << int(res); + vkFreeCommandBuffers(device, commandPool, 1, &cb); + vkDestroyCommandPool(device, commandPool, nullptr); + return false; + } + + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcQueueFamilyIndex = srcQueueFamily; + barrier.dstQueueFamilyIndex = dstQueueFamily; + barrier.image = target->image; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcAccessMask = srcAccessMask; + barrier.dstAccessMask = dstAccessMask; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + vkCmdPipelineBarrier(cb, srcStageMask, dstStageMask, + 0, 0, nullptr, 0, nullptr, 1, &barrier); + + res = vkEndCommandBuffer(cb); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed: vkEndCommandBuffer" + << vkResultName(res) << int(res); + vkFreeCommandBuffers(device, commandPool, 1, &cb); + vkDestroyCommandPool(device, commandPool, nullptr); + return false; + } + + VkSemaphore semaphore = VK_NULL_HANDLE; + if (exportSyncFile) { + VkExportSemaphoreCreateInfo exportInfo = {}; + exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + + VkSemaphoreCreateInfo semInfo = {}; + semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semInfo.pNext = &exportInfo; + + res = vkCreateSemaphore(device, &semInfo, nullptr, &semaphore); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed: vkCreateSemaphore" + << vkResultName(res) << int(res); + vkFreeCommandBuffers(device, commandPool, 1, &cb); + vkDestroyCommandPool(device, commandPool, nullptr); + return false; + } + } + + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + + VkFence fence = VK_NULL_HANDLE; + res = vkCreateFence(device, &fenceInfo, nullptr, &fence); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed: vkCreateFence" + << vkResultName(res) << int(res); + if (semaphore != VK_NULL_HANDLE) + vkDestroySemaphore(device, semaphore, nullptr); + vkFreeCommandBuffers(device, commandPool, 1, &cb); + vkDestroyCommandPool(device, commandPool, nullptr); + return false; + } + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &cb; + if (semaphore != VK_NULL_HANDLE) { + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &semaphore; + } + + res = vkQueueSubmit(queue, 1, &submitInfo, fence); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed: vkQueueSubmit" + << vkResultName(res) << int(res); + if (semaphore != VK_NULL_HANDLE) + vkDestroySemaphore(device, semaphore, nullptr); + vkDestroyFence(device, fence, nullptr); + vkFreeCommandBuffers(device, commandPool, 1, &cb); + vkDestroyCommandPool(device, commandPool, nullptr); + return false; + } + + bool ok = true; + if (semaphore != VK_NULL_HANDLE) { + VkSemaphoreGetFdInfoKHR getFdInfo = {}; + getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR; + getFdInfo.semaphore = semaphore; + getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + + int syncFileFd = -1; + res = vkGetSemaphoreFdKHR(device, &getFdInfo, &syncFileFd); + if (res != VK_SUCCESS || syncFileFd < 0) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "failed: vkGetSemaphoreFdKHR" + << vkResultName(res) << int(res) + << "fd" << syncFileFd; + ok = false; + } else { + ok = importSyncFileIntoDmabuf(buffer, syncFileFd, phase); + close(syncFileFd); + } + } + + PendingVulkanCommandCleanup cleanup; + cleanup.device = device; + cleanup.commandPool = commandPool; + cleanup.fence = fence; + cleanup.semaphore = semaphore; + queuePendingVulkanCommandCleanup(&cleanup); + + if (ok) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI output" << phase + << "barrier submitted" + << "image" << Qt::hex << vulkanHandleToInteger(target->image) << Qt::dec + << "oldLayout" << vkImageLayoutName(oldLayout) + << "newLayout" << vkImageLayoutName(newLayout) + << "srcQueue" << srcQueueFamily + << "dstQueue" << dstQueueFamily + << "syncFile" << exportSyncFile; + } + + return ok; +} + +// Resolve EGL function pointers for dmabuf import. These are EGL extensions +// (EGL_KHR_image_base, EGL_EXT_image_dma_buf_import, GL_OES_EGL_image) and +// must be resolved at runtime via eglGetProcAddress. +static PFNEGLCREATEIMAGEKHRPROC resolveEglCreateImageKHR() +{ + static auto proc = reinterpret_cast(eglGetProcAddress("eglCreateImageKHR")); + return proc; +} +static PFNEGLDESTROYIMAGEKHRPROC resolveEglDestroyImageKHR() +{ + static auto proc = reinterpret_cast(eglGetProcAddress("eglDestroyImageKHR")); + return proc; +} +static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC resolveGlEGLImageTargetTexture2DOES() +{ + static auto proc = reinterpret_cast(eglGetProcAddress("glEGLImageTargetTexture2DOES")); + return proc; +} + +static void clearGlErrors() +{ + while (glGetError() != GL_NO_ERROR) { + } +} + +static bool textureUploadSucceeded() +{ + return glGetError() == GL_NO_ERROR; +} + +static bool drmFormatLikelyHasAlpha(uint32_t format) +{ + switch (format) { + case DRM_FORMAT_RGB332: + case DRM_FORMAT_BGR233: + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_BGRX4444: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_BGRX1010102: +#ifdef DRM_FORMAT_XRGB16161616 + case DRM_FORMAT_XRGB16161616: +#endif +#ifdef DRM_FORMAT_XBGR16161616 + case DRM_FORMAT_XBGR16161616: +#endif +#ifdef DRM_FORMAT_XRGB16161616F + case DRM_FORMAT_XRGB16161616F: +#endif +#ifdef DRM_FORMAT_XBGR16161616F + case DRM_FORMAT_XBGR16161616F: +#endif + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_XYUV8888: + case DRM_FORMAT_XVUY8888: + return false; + default: + return true; + } +} + +static bool surfaceOpaqueRegionCoversBuffer(wlr_surface *surface, qw_buffer *buffer) +{ + if (!surface || !buffer || !buffer->handle()) + return false; + + if (!pixman_region32_not_empty(&surface->opaque_region)) + return false; + + const int width = surface->current.width > 0 + ? surface->current.width + : buffer->handle()->width; + const int height = surface->current.height > 0 + ? surface->current.height + : buffer->handle()->height; + if (width <= 0 || height <= 0) + return false; + + const pixman_box32_t *box = pixman_region32_extents(&surface->opaque_region); + return box + && box->x1 <= 0 + && box->y1 <= 0 + && box->x2 >= width + && box->y2 >= height; +} + +Q_GLOBAL_STATIC(QMutex, s_pendingNativeTextureCleanupMutex) +Q_GLOBAL_STATIC(QVector, s_pendingNativeTextureCleanups) + +static bool releaseNativeTextureNow(WRenderHelper::NativeTextureCleanup *cleanup) +{ + if (!cleanup || cleanup->type == WRenderHelper::NativeTextureCleanup::Type::None) + return true; + + if (cleanup->type == WRenderHelper::NativeTextureCleanup::Type::OpenGLTexture) { + if (!cleanup->texture && !cleanup->eglImage) { + *cleanup = {}; + return true; + } + + if (!QOpenGLContext::currentContext()) + return false; + + if (cleanup->texture) { + GLuint texture = GLuint(cleanup->texture); + glDeleteTextures(1, &texture); + } + + if (cleanup->eglImage && cleanup->eglDisplay) { + if (auto destroyImage = resolveEglDestroyImageKHR()) + destroyImage(reinterpret_cast(cleanup->eglDisplay), + reinterpret_cast(cleanup->eglImage)); + } + } else if (cleanup->type == WRenderHelper::NativeTextureCleanup::Type::VulkanTexture) { + auto imported = static_cast(cleanup->nativeData); + if (imported) { + destroyVulkanImportedNativeTexture(imported); + delete imported; + } + } else if (cleanup->type == WRenderHelper::NativeTextureCleanup::Type::VulkanRenderTarget) { + auto imported = static_cast(cleanup->nativeData); + if (imported) { + destroyVulkanImportedRenderTarget(imported); + delete imported; + } + } + + *cleanup = {}; + return true; +} + +static void queueNativeTextureCleanup(WRenderHelper::NativeTextureCleanup *cleanup) +{ + if (!cleanup || cleanup->type == WRenderHelper::NativeTextureCleanup::Type::None) + return; + + QMutexLocker locker(s_pendingNativeTextureCleanupMutex()); + s_pendingNativeTextureCleanups->append(*cleanup); + *cleanup = {}; +} + +static void flushPendingNativeTextureCleanups() +{ + if (!QOpenGLContext::currentContext()) + return; + + QVector pending; + { + QMutexLocker locker(s_pendingNativeTextureCleanupMutex()); + if (s_pendingNativeTextureCleanups->isEmpty()) + return; + pending.swap(*s_pendingNativeTextureCleanups); + } + + QVector remaining; + for (auto &cleanup : pending) { + if (!releaseNativeTextureNow(&cleanup)) + remaining.append(cleanup); + } + + if (!remaining.isEmpty()) { + QMutexLocker locker(s_pendingNativeTextureCleanupMutex()); + *s_pendingNativeTextureCleanups += remaining; + } +} + +void BufferData::destroyEglDmabufTexture() +{ + WRenderHelper::NativeTextureCleanup cleanup { + glTexture || eglImage != EGL_NO_IMAGE + ? WRenderHelper::NativeTextureCleanup::Type::OpenGLTexture + : WRenderHelper::NativeTextureCleanup::Type::None, + glTexture, + eglImage != EGL_NO_IMAGE ? reinterpret_cast(eglImage) : nullptr, + eglDisplay != EGL_NO_DISPLAY ? reinterpret_cast(eglDisplay) : nullptr, + }; + WRenderHelper::releaseNativeTexture(&cleanup); + glTexture = 0; + eglImage = EGL_NO_IMAGE; + eglDisplay = EGL_NO_DISPLAY; +} + +void BufferData::destroyVulkanRenderTarget() +{ + destroyVulkanImportedRenderTarget(&vulkanRenderTarget); +} + +static VkFormat vkFormatFromDrmFormat(uint32_t drmFormat) +{ + switch (drmFormat) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + return VK_FORMAT_B8G8R8A8_UNORM; + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return VK_FORMAT_R8G8B8A8_UNORM; + case DRM_FORMAT_RGB565: + return VK_FORMAT_R5G6B5_UNORM_PACK16; + case DRM_FORMAT_BGR565: + return VK_FORMAT_B5G6R5_UNORM_PACK16; + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_ARGB2101010: + return VK_FORMAT_A2R10G10B10_UNORM_PACK32; + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_ABGR2101010: + return VK_FORMAT_A2B10G10R10_UNORM_PACK32; +#ifdef DRM_FORMAT_XBGR16161616 + case DRM_FORMAT_XBGR16161616: +#endif +#ifdef DRM_FORMAT_ABGR16161616 + case DRM_FORMAT_ABGR16161616: +#endif +#if defined(DRM_FORMAT_XBGR16161616) || defined(DRM_FORMAT_ABGR16161616) + return VK_FORMAT_R16G16B16A16_UNORM; +#endif +#ifdef DRM_FORMAT_XBGR16161616F + case DRM_FORMAT_XBGR16161616F: +#endif +#ifdef DRM_FORMAT_ABGR16161616F + case DRM_FORMAT_ABGR16161616F: +#endif +#if defined(DRM_FORMAT_XBGR16161616F) || defined(DRM_FORMAT_ABGR16161616F) + return VK_FORMAT_R16G16B16A16_SFLOAT; +#endif + default: + return VK_FORMAT_UNDEFINED; + } +} + +static VkImageAspectFlagBits vulkanMemoryPlaneAspect(uint32_t plane) +{ + switch (plane) { + 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: return VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT; + } +} + +static bool dmabufUsesDisjointMemory(const wlr_dmabuf_attributes *attribs) +{ + if (!attribs || attribs->n_planes <= 1) + return false; + + struct stat firstStat = {}; + if (fstat(attribs->fd[0], &firstStat) != 0) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import: fstat failed for dmabuf plane 0" + << "errno" << errno; + return true; + } + + for (int i = 1; i < attribs->n_planes; ++i) { + struct stat planeStat = {}; + if (fstat(attribs->fd[i], &planeStat) != 0) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import: fstat failed for dmabuf plane" + << i << "errno" << errno; + return true; + } + + if (firstStat.st_ino != planeStat.st_ino) + return true; + } + + return false; +} + +static int findVulkanMemoryType(PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties, + VkPhysicalDevice physicalDevice, + VkMemoryPropertyFlags flags, + uint32_t requiredBits) +{ + if (!vkGetPhysicalDeviceMemoryProperties || physicalDevice == VK_NULL_HANDLE) + return -1; + + VkPhysicalDeviceMemoryProperties props = {}; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &props); + + for (uint32_t i = 0; i < props.memoryTypeCount; ++i) { + const bool typeMatches = requiredBits & (1u << i); + const bool flagsMatch = (props.memoryTypes[i].propertyFlags & flags) == flags; + if (typeMatches && flagsMatch) + return int(i); + } + + return -1; +} + +struct Q_DECL_HIDDEN VulkanDmabufTextureImportSupport { + VkFormat sampledViewFormat = VK_FORMAT_UNDEFINED; + + bool usesMutableSrgbView() const { + return sampledViewFormat != VK_FORMAT_UNDEFINED; + } +}; + +static VkResult queryVulkanDmabufTextureImageFormatProperties( + PFN_vkGetPhysicalDeviceImageFormatProperties2 vkGetPhysicalDeviceImageFormatProperties2, + VkPhysicalDevice physicalDevice, + VkFormat vkFormat, + VkFormat srgbViewFormat, + const wlr_dmabuf_attributes *attribs, + bool disjoint, + VkImageFormatProperties *imageFormatProperties, + VkExternalMemoryFeatureFlags *externalMemoryFeatures) +{ + if (!vkGetPhysicalDeviceImageFormatProperties2 || physicalDevice == VK_NULL_HANDLE + || !attribs || !imageFormatProperties || !externalMemoryFeatures) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VkFormat viewFormats[2] = { vkFormat, srgbViewFormat }; + VkImageFormatListCreateInfoKHR formatListInfo = {}; + formatListInfo.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR; + formatListInfo.viewFormatCount = srgbViewFormat != VK_FORMAT_UNDEFINED ? 2u : 1u; + formatListInfo.pViewFormats = viewFormats; + + VkPhysicalDeviceImageDrmFormatModifierInfoEXT modifierInfo = {}; + modifierInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT; + modifierInfo.drmFormatModifier = attribs->modifier; + modifierInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + if (srgbViewFormat != VK_FORMAT_UNDEFINED) + modifierInfo.pNext = &formatListInfo; + + VkPhysicalDeviceExternalImageFormatInfo externalInfo = {}; + externalInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO; + externalInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; + externalInfo.pNext = &modifierInfo; + + VkPhysicalDeviceImageFormatInfo2 formatInfo = {}; + formatInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2; + formatInfo.type = VK_IMAGE_TYPE_2D; + formatInfo.format = vkFormat; + formatInfo.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + formatInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + formatInfo.flags = disjoint ? VK_IMAGE_CREATE_DISJOINT_BIT : 0; + if (srgbViewFormat != VK_FORMAT_UNDEFINED) + formatInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + formatInfo.pNext = &externalInfo; + + VkExternalImageFormatProperties externalProps = {}; + externalProps.sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES; + VkImageFormatProperties2 imageProps = {}; + imageProps.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2; + imageProps.pNext = &externalProps; + + const VkResult res = + vkGetPhysicalDeviceImageFormatProperties2(physicalDevice, &formatInfo, &imageProps); + if (res == VK_SUCCESS) { + *imageFormatProperties = imageProps.imageFormatProperties; + *externalMemoryFeatures = externalProps.externalMemoryProperties.externalMemoryFeatures; + } + return res; +} + +static bool vulkanImagePropertiesFitDmabuf(const VkImageFormatProperties &imageFormatProperties, + const wlr_dmabuf_attributes *attribs) +{ + return attribs + && uint32_t(attribs->width) <= imageFormatProperties.maxExtent.width + && uint32_t(attribs->height) <= imageFormatProperties.maxExtent.height; +} + +static VkFormatFeatureFlags vulkanRequiredFeaturesForImageUsage(VkImageUsageFlags usage) +{ + VkFormatFeatureFlags features = 0; + if (usage & VK_IMAGE_USAGE_SAMPLED_BIT) + features |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; + if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) + features |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT; + if (usage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) + features |= VK_FORMAT_FEATURE_TRANSFER_SRC_BIT; + if (usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) + features |= VK_FORMAT_FEATURE_TRANSFER_DST_BIT; + if (usage & VK_IMAGE_USAGE_STORAGE_BIT) + features |= VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT; + return features; +} + +static VkImageUsageFlags vulkanImageUsageForRhiTextureFlags(QRhiTexture::Flags flags) +{ + VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + if (flags.testFlag(QRhiTexture::RenderTarget)) + usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + if (flags.testFlag(QRhiTexture::UsedAsTransferSource) + || flags.testFlag(QRhiTexture::UsedWithGenerateMips)) { + usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + } + if (flags.testFlag(QRhiTexture::UsedWithLoadStore)) + usage |= VK_IMAGE_USAGE_STORAGE_BIT; + return usage; +} + +static bool validateVulkanDmabufRenderImportSupport( + VkInstance instance, + VkPhysicalDevice physicalDevice, + VkFormat vkFormat, + const wlr_dmabuf_attributes *attribs, + bool disjoint, + VkImageUsageFlags imageUsage) +{ + if (!attribs) + return false; + + if (attribs->modifier == DRM_FORMAT_MOD_INVALID) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: dmabuf modifier is INVALID" + << "format" << drmFormatToName(attribs->format) + << "size" << QSize(attribs->width, attribs->height); + return false; + } + + auto vkGetInstanceProcAddr = resolveVkGetInstanceProcAddr(); + if (!vkGetInstanceProcAddr || instance == VK_NULL_HANDLE) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: vkGetInstanceProcAddr or VkInstance unavailable"; + return false; + } + + auto vkGetPhysicalDeviceImageFormatProperties2 = + reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceImageFormatProperties2")); + auto vkGetPhysicalDeviceFormatProperties2 = + reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFormatProperties2")); + + if (!vkGetPhysicalDeviceImageFormatProperties2) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: vkGetPhysicalDeviceImageFormatProperties2 unavailable"; + return false; + } + + if (vkGetPhysicalDeviceFormatProperties2) { + VkDrmFormatModifierPropertiesListEXT modifierList = {}; + modifierList.sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT; + + VkFormatProperties2 formatProps = {}; + formatProps.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + formatProps.pNext = &modifierList; + vkGetPhysicalDeviceFormatProperties2(physicalDevice, vkFormat, &formatProps); + + QVector modifiers( + int(modifierList.drmFormatModifierCount)); + if (!modifiers.isEmpty()) { + modifierList.pDrmFormatModifierProperties = modifiers.data(); + vkGetPhysicalDeviceFormatProperties2(physicalDevice, vkFormat, &formatProps); + + const auto found = std::find_if(modifiers.cbegin(), modifiers.cend(), + [attribs](const VkDrmFormatModifierPropertiesEXT &props) { + return props.drmFormatModifier == attribs->modifier; + }); + + if (found == modifiers.cend()) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: modifier not exposed for VkFormat" + << vkFormat + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + return false; + } + + if (int(found->drmFormatModifierPlaneCount) != attribs->n_planes) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: modifier plane count mismatch" + << "expected" << found->drmFormatModifierPlaneCount + << "dmabuf" << attribs->n_planes + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + return false; + } + + const VkFormatFeatureFlags requiredFeatures = + vulkanRequiredFeaturesForImageUsage(imageUsage); + if ((found->drmFormatModifierTilingFeatures & requiredFeatures) + != requiredFeatures) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: modifier lacks required image usage support" + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier) + << "usage" << Qt::hex << imageUsage + << "requiredFeatures" << requiredFeatures + << "features" << Qt::hex << found->drmFormatModifierTilingFeatures << Qt::dec; + return false; + } + + if (disjoint + && !(found->drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_DISJOINT_BIT)) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: disjoint dmabuf lacks DISJOINT support" + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier) + << "features" << Qt::hex << found->drmFormatModifierTilingFeatures << Qt::dec; + return false; + } + } + } + + VkPhysicalDeviceImageDrmFormatModifierInfoEXT modifierInfo = {}; + modifierInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT; + modifierInfo.drmFormatModifier = attribs->modifier; + modifierInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VkPhysicalDeviceExternalImageFormatInfo externalInfo = {}; + externalInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO; + externalInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; + externalInfo.pNext = &modifierInfo; + + VkPhysicalDeviceImageFormatInfo2 formatInfo = {}; + formatInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2; + formatInfo.type = VK_IMAGE_TYPE_2D; + formatInfo.format = vkFormat; + formatInfo.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + formatInfo.usage = imageUsage; + formatInfo.flags = disjoint ? VK_IMAGE_CREATE_DISJOINT_BIT : 0; + formatInfo.pNext = &externalInfo; + + VkExternalImageFormatProperties externalProps = {}; + externalProps.sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES; + VkImageFormatProperties2 imageProps = {}; + imageProps.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2; + imageProps.pNext = &externalProps; + + VkResult res = vkGetPhysicalDeviceImageFormatProperties2(physicalDevice, &formatInfo, &imageProps); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: image format properties query failed" + << vkResultName(res) << int(res) + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier) + << "vkFormat" << vkFormat + << "size" << QSize(attribs->width, attribs->height); + return false; + } + + if (!(externalProps.externalMemoryProperties.externalMemoryFeatures + & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT)) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: external memory is not importable" + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier) + << "vkFormat" << vkFormat; + return false; + } + + if (uint32_t(attribs->width) > imageProps.imageFormatProperties.maxExtent.width + || uint32_t(attribs->height) > imageProps.imageFormatProperties.maxExtent.height) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: dmabuf exceeds max image extent" + << "size" << QSize(attribs->width, attribs->height) + << "max" << QSize(imageProps.imageFormatProperties.maxExtent.width, + imageProps.imageFormatProperties.maxExtent.height) + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + return false; + } + + return true; +} + +static bool importDmabufAsVulkanRenderTarget( + VkInstance instance, + VkPhysicalDevice physicalDevice, + VkDevice device, + const wlr_dmabuf_attributes *attribs, + VkImageUsageFlags imageUsage, + bool ownerIsForeign, + VulkanImportedRenderTarget *out) +{ + if (!attribs || !out || instance == VK_NULL_HANDLE + || physicalDevice == VK_NULL_HANDLE || device == VK_NULL_HANDLE) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: missing Vulkan handles or dmabuf"; + return false; + } + + if (attribs->n_planes <= 0 || attribs->n_planes > WLR_DMABUF_MAX_PLANES) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: invalid dmabuf plane count" + << attribs->n_planes; + return false; + } + + for (int i = 0; i < attribs->n_planes; ++i) { + if (attribs->fd[i] < 0) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: invalid dmabuf fd on plane" + << i; + return false; + } + } + + const VkFormat vkFormat = vkFormatFromDrmFormat(attribs->format); + if (vkFormat == VK_FORMAT_UNDEFINED) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: unsupported DRM format" + << drmFormatToName(attribs->format) + << Qt::hex << attribs->format << Qt::dec; + return false; + } + + const bool disjoint = dmabufUsesDisjointMemory(attribs); + if (!validateVulkanDmabufRenderImportSupport(instance, physicalDevice, vkFormat, + attribs, disjoint, imageUsage)) { + return false; + } + + auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); + auto vkGetInstanceProcAddr = resolveVkGetInstanceProcAddr(); + if (!vkGetDeviceProcAddr || !vkGetInstanceProcAddr) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: Vulkan loader entry points unavailable"; + return false; + } + + auto vkCreateImage = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCreateImage")); + auto vkDestroyImage = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkDestroyImage")); + auto vkGetMemoryFdPropertiesKHR = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkGetMemoryFdPropertiesKHR")); + auto vkGetImageMemoryRequirements2 = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkGetImageMemoryRequirements2")); + auto vkAllocateMemory = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkAllocateMemory")); + auto vkFreeMemory = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkFreeMemory")); + auto vkBindImageMemory2 = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkBindImageMemory2")); + auto vkGetPhysicalDeviceMemoryProperties = + reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceMemoryProperties")); + + if (!vkCreateImage || !vkDestroyImage || !vkGetMemoryFdPropertiesKHR + || !vkGetImageMemoryRequirements2 || !vkAllocateMemory || !vkFreeMemory + || !vkBindImageMemory2 || !vkGetPhysicalDeviceMemoryProperties) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: required Vulkan import functions unavailable"; + return false; + } + + VkExternalMemoryImageCreateInfo externalImageInfo = {}; + externalImageInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO; + externalImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; + + VkSubresourceLayout planeLayouts[WLR_DMABUF_MAX_PLANES] = {}; + for (int i = 0; i < attribs->n_planes; ++i) { + planeLayouts[i].offset = attribs->offset[i]; + planeLayouts[i].rowPitch = attribs->stride[i]; + } + + VkImageDrmFormatModifierExplicitCreateInfoEXT modifierInfo = {}; + modifierInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT; + modifierInfo.drmFormatModifier = attribs->modifier; + modifierInfo.drmFormatModifierPlaneCount = uint32_t(attribs->n_planes); + modifierInfo.pPlaneLayouts = planeLayouts; + externalImageInfo.pNext = &modifierInfo; + + VkImageCreateInfo imageInfo = {}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.pNext = &externalImageInfo; + imageInfo.flags = disjoint ? VK_IMAGE_CREATE_DISJOINT_BIT : 0; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.format = vkFormat; + imageInfo.extent = VkExtent3D { uint32_t(attribs->width), uint32_t(attribs->height), 1 }; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + imageInfo.usage = imageUsage; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VulkanImportedRenderTarget imported; + imported.device = device; + imported.format = vkFormat; + imported.layout = VK_IMAGE_LAYOUT_UNDEFINED; + imported.ownerIsForeign = ownerIsForeign; + imported.scanoutReleaseReady = false; + imported.size = QSize(attribs->width, attribs->height); + imported.drmFormat = attribs->format; + imported.drmModifier = attribs->modifier; + + VkResult res = vkCreateImage(device, &imageInfo, nullptr, &imported.image); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import failed: vkCreateImage" + << vkResultName(res) << int(res) + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier) + << "vkFormat" << vkFormat + << "size" << imported.size + << "planes" << attribs->n_planes + << "disjoint" << disjoint; + return false; + } + + const uint32_t memoryCount = disjoint ? uint32_t(attribs->n_planes) : 1u; + VkBindImageMemoryInfo bindInfos[WLR_DMABUF_MAX_PLANES] = {}; + VkBindImagePlaneMemoryInfo planeInfos[WLR_DMABUF_MAX_PLANES] = {}; + + for (uint32_t i = 0; i < memoryCount; ++i) { + VkMemoryFdPropertiesKHR fdProps = {}; + fdProps.sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR; + res = vkGetMemoryFdPropertiesKHR(device, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + attribs->fd[i], + &fdProps); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import failed: vkGetMemoryFdPropertiesKHR" + << vkResultName(res) << int(res) + << "plane" << i + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + destroyVulkanImportedRenderTarget(&imported); + return false; + } + + VkImagePlaneMemoryRequirementsInfo planeReqInfo = {}; + VkImageMemoryRequirementsInfo2 memoryReqInfo = {}; + memoryReqInfo.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2; + memoryReqInfo.image = imported.image; + + if (disjoint) { + planeReqInfo.sType = VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO; + planeReqInfo.planeAspect = vulkanMemoryPlaneAspect(i); + memoryReqInfo.pNext = &planeReqInfo; + } + + VkMemoryRequirements2 memoryReq = {}; + memoryReq.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2; + vkGetImageMemoryRequirements2(device, &memoryReqInfo, &memoryReq); + + const int memoryType = findVulkanMemoryType( + vkGetPhysicalDeviceMemoryProperties, + physicalDevice, + 0, + memoryReq.memoryRequirements.memoryTypeBits & fdProps.memoryTypeBits); + if (memoryType < 0) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import failed: no compatible memory type" + << "plane" << i + << "requirements" << Qt::hex << memoryReq.memoryRequirements.memoryTypeBits + << "fd" << fdProps.memoryTypeBits << Qt::dec + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + destroyVulkanImportedRenderTarget(&imported); + return false; + } + + const int dupFd = fcntl(attribs->fd[i], F_DUPFD_CLOEXEC, 0); + if (dupFd < 0) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import failed: failed to duplicate dmabuf fd" + << "plane" << i + << "errno" << errno + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + destroyVulkanImportedRenderTarget(&imported); + return false; + } + + VkImportMemoryFdInfoKHR importFdInfo = {}; + importFdInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR; + importFdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; + importFdInfo.fd = dupFd; + + VkMemoryDedicatedAllocateInfo dedicatedInfo = {}; + dedicatedInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO; + dedicatedInfo.image = imported.image; + importFdInfo.pNext = &dedicatedInfo; + + VkMemoryAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.pNext = &importFdInfo; + allocInfo.allocationSize = memoryReq.memoryRequirements.size; + allocInfo.memoryTypeIndex = uint32_t(memoryType); + + res = vkAllocateMemory(device, &allocInfo, nullptr, &imported.memories[i]); + if (res != VK_SUCCESS) { + close(dupFd); + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import failed: vkAllocateMemory" + << vkResultName(res) << int(res) + << "plane" << i + << "allocationSize" << qulonglong(allocInfo.allocationSize) + << "memoryType" << memoryType + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + destroyVulkanImportedRenderTarget(&imported); + return false; + } + ++imported.memoryCount; + + bindInfos[i].sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; + bindInfos[i].image = imported.image; + bindInfos[i].memory = imported.memories[i]; + bindInfos[i].memoryOffset = 0; + + if (disjoint) { + planeInfos[i].sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO; + planeInfos[i].planeAspect = vulkanMemoryPlaneAspect(i); + bindInfos[i].pNext = &planeInfos[i]; + } + } + + res = vkBindImageMemory2(device, memoryCount, bindInfos); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import failed: vkBindImageMemory2" + << vkResultName(res) << int(res) + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier) + << "planes" << attribs->n_planes + << "memoryCount" << memoryCount + << "disjoint" << disjoint; + destroyVulkanImportedRenderTarget(&imported); + return false; + } + + qCInfo(lcWlRenderHelper) + << "Vulkan RHI output import succeeded" + << "image" << Qt::hex << vulkanHandleToInteger(imported.image) << Qt::dec + << "size" << imported.size + << "format" << drmFormatToName(imported.drmFormat) + << "modifier" << drmModifierToName(imported.drmModifier) + << "vkFormat" << imported.format + << "planes" << attribs->n_planes + << "memoryCount" << imported.memoryCount + << "disjoint" << disjoint; + + *out = imported; + return true; +} + +static bool validateVulkanDmabufTextureImportSupport( + VkInstance instance, + VkPhysicalDevice physicalDevice, + VkFormat vkFormat, + const wlr_dmabuf_attributes *attribs, + bool disjoint, + VulkanDmabufTextureImportSupport *support) +{ + if (!attribs) + return false; + if (support) + *support = {}; + + if (attribs->modifier == DRM_FORMAT_MOD_INVALID) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: dmabuf modifier is INVALID" + << "format" << drmFormatToName(attribs->format) + << "size" << QSize(attribs->width, attribs->height); + return false; + } + + auto vkGetInstanceProcAddr = resolveVkGetInstanceProcAddr(); + if (!vkGetInstanceProcAddr || instance == VK_NULL_HANDLE) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: vkGetInstanceProcAddr or VkInstance unavailable"; + return false; + } + + auto vkGetPhysicalDeviceImageFormatProperties2 = + reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceImageFormatProperties2")); + auto vkGetPhysicalDeviceFormatProperties2 = + reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFormatProperties2")); + + if (!vkGetPhysicalDeviceImageFormatProperties2) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: vkGetPhysicalDeviceImageFormatProperties2 unavailable"; + return false; + } + + if (vkGetPhysicalDeviceFormatProperties2) { + VkDrmFormatModifierPropertiesListEXT modifierList = {}; + modifierList.sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT; + + VkFormatProperties2 formatProps = {}; + formatProps.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + formatProps.pNext = &modifierList; + vkGetPhysicalDeviceFormatProperties2(physicalDevice, vkFormat, &formatProps); + + QVector modifiers( + int(modifierList.drmFormatModifierCount)); + if (!modifiers.isEmpty()) { + modifierList.pDrmFormatModifierProperties = modifiers.data(); + vkGetPhysicalDeviceFormatProperties2(physicalDevice, vkFormat, &formatProps); + + const auto found = std::find_if(modifiers.cbegin(), modifiers.cend(), + [attribs](const VkDrmFormatModifierPropertiesEXT &props) { + return props.drmFormatModifier == attribs->modifier; + }); + + if (found == modifiers.cend()) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: modifier not exposed for VkFormat" + << vkFormat + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + return false; + } + + if (int(found->drmFormatModifierPlaneCount) != attribs->n_planes) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: modifier plane count mismatch" + << "expected" << found->drmFormatModifierPlaneCount + << "dmabuf" << attribs->n_planes + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + return false; + } + + constexpr VkFormatFeatureFlags requiredFeatures = + VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT + | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; + if ((found->drmFormatModifierTilingFeatures & requiredFeatures) != requiredFeatures) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: modifier lacks sampled texture support" + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier) + << "features" << Qt::hex << found->drmFormatModifierTilingFeatures << Qt::dec; + return false; + } + + if (disjoint + && !(found->drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_DISJOINT_BIT)) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: disjoint dmabuf lacks DISJOINT support" + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier) + << "features" << Qt::hex << found->drmFormatModifierTilingFeatures << Qt::dec; + return false; + } + } + } + + VkImageFormatProperties imageFormatProperties = {}; + VkExternalMemoryFeatureFlags externalMemoryFeatures = 0; + VkResult res = VK_SUCCESS; + + if (support) + support->sampledViewFormat = VK_FORMAT_UNDEFINED; + + res = queryVulkanDmabufTextureImageFormatProperties( + vkGetPhysicalDeviceImageFormatProperties2, + physicalDevice, + vkFormat, + VK_FORMAT_UNDEFINED, + attribs, + disjoint, + &imageFormatProperties, + &externalMemoryFeatures); + + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: image format properties query failed" + << vkResultName(res) << int(res) + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier) + << "vkFormat" << vkFormat + << "size" << QSize(attribs->width, attribs->height); + return false; + } + + if (!(externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT)) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: external memory is not importable" + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier) + << "vkFormat" << vkFormat; + return false; + } + + if (!vulkanImagePropertiesFitDmabuf(imageFormatProperties, attribs)) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: dmabuf exceeds max image extent" + << "size" << QSize(attribs->width, attribs->height) + << "max" << QSize(imageFormatProperties.maxExtent.width, + imageFormatProperties.maxExtent.height) + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + return false; + } + + return true; +} + +static bool importDmabufAsVulkanNativeTexture( + VkInstance instance, + VkPhysicalDevice physicalDevice, + VkDevice device, + const wlr_dmabuf_attributes *attribs, + VulkanImportedNativeTexture *out) +{ + if (!attribs || !out || instance == VK_NULL_HANDLE + || physicalDevice == VK_NULL_HANDLE || device == VK_NULL_HANDLE) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: missing Vulkan handles or dmabuf"; + return false; + } + + if (attribs->n_planes <= 0 || attribs->n_planes > WLR_DMABUF_MAX_PLANES) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: invalid dmabuf plane count" + << attribs->n_planes; + return false; + } + + for (int i = 0; i < attribs->n_planes; ++i) { + if (attribs->fd[i] < 0) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: invalid dmabuf fd on plane" + << i; + return false; + } + } + + const VkFormat vkFormat = vkFormatFromDrmFormat(attribs->format); + if (vkFormat == VK_FORMAT_UNDEFINED) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: unsupported DRM format" + << drmFormatToName(attribs->format) + << Qt::hex << attribs->format << Qt::dec; + return false; + } + + const bool disjoint = dmabufUsesDisjointMemory(attribs); + VulkanDmabufTextureImportSupport importSupport; + if (!validateVulkanDmabufTextureImportSupport(instance, physicalDevice, vkFormat, + attribs, disjoint, &importSupport)) { + return false; + } + const bool useMutableSrgbView = importSupport.usesMutableSrgbView(); + + auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); + auto vkGetInstanceProcAddr = resolveVkGetInstanceProcAddr(); + if (!vkGetDeviceProcAddr || !vkGetInstanceProcAddr) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: Vulkan loader entry points unavailable"; + return false; + } + + auto vkCreateImage = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCreateImage")); + auto vkDestroyImage = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkDestroyImage")); + auto vkGetMemoryFdPropertiesKHR = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkGetMemoryFdPropertiesKHR")); + auto vkGetImageMemoryRequirements2 = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkGetImageMemoryRequirements2")); + auto vkAllocateMemory = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkAllocateMemory")); + auto vkFreeMemory = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkFreeMemory")); + auto vkBindImageMemory2 = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkBindImageMemory2")); + auto vkGetPhysicalDeviceMemoryProperties = + reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceMemoryProperties")); + + if (!vkCreateImage || !vkDestroyImage || !vkGetMemoryFdPropertiesKHR + || !vkGetImageMemoryRequirements2 || !vkAllocateMemory || !vkFreeMemory + || !vkBindImageMemory2 || !vkGetPhysicalDeviceMemoryProperties) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import rejected: required Vulkan import functions unavailable"; + return false; + } + + VkExternalMemoryImageCreateInfo externalImageInfo = {}; + externalImageInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO; + externalImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; + + VkSubresourceLayout planeLayouts[WLR_DMABUF_MAX_PLANES] = {}; + for (int i = 0; i < attribs->n_planes; ++i) { + planeLayouts[i].offset = attribs->offset[i]; + planeLayouts[i].rowPitch = attribs->stride[i]; + } + + VkImageDrmFormatModifierExplicitCreateInfoEXT modifierInfo = {}; + modifierInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT; + modifierInfo.drmFormatModifier = attribs->modifier; + modifierInfo.drmFormatModifierPlaneCount = uint32_t(attribs->n_planes); + modifierInfo.pPlaneLayouts = planeLayouts; + VkFormat viewFormats[2] = { vkFormat, importSupport.sampledViewFormat }; + VkImageFormatListCreateInfoKHR formatListInfo = {}; + if (useMutableSrgbView) { + formatListInfo.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR; + formatListInfo.viewFormatCount = 2; + formatListInfo.pViewFormats = viewFormats; + modifierInfo.pNext = &formatListInfo; + } + externalImageInfo.pNext = &modifierInfo; + + VkImageCreateInfo imageInfo = {}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.pNext = &externalImageInfo; + imageInfo.flags = disjoint ? VK_IMAGE_CREATE_DISJOINT_BIT : 0; + if (useMutableSrgbView) + imageInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.format = vkFormat; + imageInfo.extent = VkExtent3D { uint32_t(attribs->width), uint32_t(attribs->height), 1 }; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VulkanImportedNativeTexture imported; + imported.device = device; + imported.format = useMutableSrgbView ? importSupport.sampledViewFormat : vkFormat; + imported.layout = VK_IMAGE_LAYOUT_GENERAL; + imported.size = QSize(attribs->width, attribs->height); + imported.drmFormat = attribs->format; + imported.drmModifier = attribs->modifier; + + VkResult res = vkCreateImage(device, &imageInfo, nullptr, &imported.image); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import failed: vkCreateImage" + << vkResultName(res) << int(res) + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier) + << "imageVkFormat" << vkFormat + << "viewVkFormat" << imported.format + << "size" << imported.size + << "planes" << attribs->n_planes + << "disjoint" << disjoint + << "mutableSrgbView" << useMutableSrgbView; + return false; + } + + const uint32_t memoryCount = disjoint ? uint32_t(attribs->n_planes) : 1u; + VkBindImageMemoryInfo bindInfos[WLR_DMABUF_MAX_PLANES] = {}; + VkBindImagePlaneMemoryInfo planeInfos[WLR_DMABUF_MAX_PLANES] = {}; + + for (uint32_t i = 0; i < memoryCount; ++i) { + VkMemoryFdPropertiesKHR fdProps = {}; + fdProps.sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR; + res = vkGetMemoryFdPropertiesKHR(device, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + attribs->fd[i], + &fdProps); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import failed: vkGetMemoryFdPropertiesKHR" + << vkResultName(res) << int(res) + << "plane" << i + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + destroyVulkanImportedNativeTexture(&imported); + return false; + } + + VkImagePlaneMemoryRequirementsInfo planeReqInfo = {}; + VkImageMemoryRequirementsInfo2 memoryReqInfo = {}; + memoryReqInfo.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2; + memoryReqInfo.image = imported.image; + + if (disjoint) { + planeReqInfo.sType = VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO; + planeReqInfo.planeAspect = vulkanMemoryPlaneAspect(i); + memoryReqInfo.pNext = &planeReqInfo; + } + + VkMemoryRequirements2 memoryReq = {}; + memoryReq.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2; + vkGetImageMemoryRequirements2(device, &memoryReqInfo, &memoryReq); + + const int memoryType = findVulkanMemoryType( + vkGetPhysicalDeviceMemoryProperties, + physicalDevice, + 0, + memoryReq.memoryRequirements.memoryTypeBits & fdProps.memoryTypeBits); + if (memoryType < 0) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import failed: no compatible memory type" + << "plane" << i + << "requirements" << Qt::hex << memoryReq.memoryRequirements.memoryTypeBits + << "fd" << fdProps.memoryTypeBits << Qt::dec + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + destroyVulkanImportedNativeTexture(&imported); + return false; + } + + const int dupFd = fcntl(attribs->fd[i], F_DUPFD_CLOEXEC, 0); + if (dupFd < 0) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import failed: failed to duplicate dmabuf fd" + << "plane" << i + << "errno" << errno + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + destroyVulkanImportedNativeTexture(&imported); + return false; + } + + VkImportMemoryFdInfoKHR importFdInfo = {}; + importFdInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR; + importFdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; + importFdInfo.fd = dupFd; + + VkMemoryDedicatedAllocateInfo dedicatedInfo = {}; + dedicatedInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO; + dedicatedInfo.image = imported.image; + importFdInfo.pNext = &dedicatedInfo; + + VkMemoryAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.pNext = &importFdInfo; + allocInfo.allocationSize = memoryReq.memoryRequirements.size; + allocInfo.memoryTypeIndex = uint32_t(memoryType); + + res = vkAllocateMemory(device, &allocInfo, nullptr, &imported.memories[i]); + if (res != VK_SUCCESS) { + close(dupFd); + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import failed: vkAllocateMemory" + << vkResultName(res) << int(res) + << "plane" << i + << "allocationSize" << qulonglong(allocInfo.allocationSize) + << "memoryType" << memoryType + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier); + destroyVulkanImportedNativeTexture(&imported); + return false; + } + ++imported.memoryCount; + + bindInfos[i].sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO; + bindInfos[i].image = imported.image; + bindInfos[i].memory = imported.memories[i]; + bindInfos[i].memoryOffset = 0; + + if (disjoint) { + planeInfos[i].sType = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO; + planeInfos[i].planeAspect = vulkanMemoryPlaneAspect(i); + bindInfos[i].pNext = &planeInfos[i]; + } + } + + res = vkBindImageMemory2(device, memoryCount, bindInfos); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture import failed: vkBindImageMemory2" + << vkResultName(res) << int(res) + << "format" << drmFormatToName(attribs->format) + << "modifier" << drmModifierToName(attribs->modifier) + << "planes" << attribs->n_planes + << "memoryCount" << memoryCount + << "disjoint" << disjoint; + destroyVulkanImportedNativeTexture(&imported); + return false; + } + + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture import succeeded" + << "image" << Qt::hex << vulkanHandleToInteger(imported.image) << Qt::dec + << "size" << imported.size + << "format" << drmFormatToName(imported.drmFormat) + << "modifier" << drmModifierToName(imported.drmModifier) + << "imageVkFormat" << vkFormat + << "viewVkFormat" << imported.format + << "planes" << attribs->n_planes + << "memoryCount" << imported.memoryCount + << "disjoint" << disjoint + << "mutableSrgbView" << useMutableSrgbView; + + *out = imported; + return true; +} + +static bool acquireVulkanNativeTextureForSampling(QRhi *rhi, + VulkanImportedNativeTexture *texture) +{ + if (!rhi || !texture || !texture->isValid()) + return false; + + flushPendingVulkanCommandCleanups(false); + + const auto *handles = static_cast(rhi->nativeHandles()); + if (!handles || !handles->dev || !handles->gfxQueue || !handles->inst) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture acquire failed: QRhi Vulkan native handles unavailable"; + return false; + } + + if (handles->dev != texture->device) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture acquire failed: QRhi device differs from imported texture" + << "rhi" << Qt::hex << vulkanHandleToInteger(handles->dev) + << "texture" << vulkanHandleToInteger(texture->device) << Qt::dec; + return false; + } + + auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); + if (!vkGetDeviceProcAddr) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture acquire failed: vkGetDeviceProcAddr unavailable"; + return false; + } + + VkDevice device = handles->dev; + VkQueue queue = handles->gfxQueue; + texture->queue = queue; + texture->queueFamilyIndex = handles->gfxQueueFamilyIdx; + + auto vkCreateCommandPool = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCreateCommandPool")); + auto vkDestroyCommandPool = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkDestroyCommandPool")); + auto vkAllocateCommandBuffers = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkAllocateCommandBuffers")); + auto vkFreeCommandBuffers = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkFreeCommandBuffers")); + auto vkBeginCommandBuffer = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkBeginCommandBuffer")); + auto vkEndCommandBuffer = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkEndCommandBuffer")); + auto vkCmdPipelineBarrier = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCmdPipelineBarrier")); + auto vkQueueSubmit = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkQueueSubmit")); + auto vkCreateFence = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCreateFence")); + auto vkDestroyFence = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkDestroyFence")); + + if (!vkCreateCommandPool || !vkDestroyCommandPool || !vkAllocateCommandBuffers + || !vkFreeCommandBuffers || !vkBeginCommandBuffer || !vkEndCommandBuffer + || !vkCmdPipelineBarrier || !vkQueueSubmit || !vkCreateFence || !vkDestroyFence) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture acquire failed: required Vulkan command functions unavailable"; + return false; + } + + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; + poolInfo.queueFamilyIndex = handles->gfxQueueFamilyIdx; + + VkCommandPool commandPool = VK_NULL_HANDLE; + VkResult res = vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture acquire failed: vkCreateCommandPool" + << vkResultName(res) << int(res); + return false; + } + + VkCommandBufferAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer cb = VK_NULL_HANDLE; + res = vkAllocateCommandBuffers(device, &allocInfo, &cb); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture acquire failed: vkAllocateCommandBuffers" + << vkResultName(res) << int(res); + vkDestroyCommandPool(device, commandPool, nullptr); + return false; + } + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + res = vkBeginCommandBuffer(cb, &beginInfo); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture acquire failed: vkBeginCommandBuffer" + << vkResultName(res) << int(res); + vkFreeCommandBuffers(device, commandPool, 1, &cb); + vkDestroyCommandPool(device, commandPool, nullptr); + return false; + } + + const VkImageLayout oldLayout = texture->layout; + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT; + barrier.dstQueueFamilyIndex = handles->gfxQueueFamilyIdx; + barrier.image = texture->image; + barrier.oldLayout = oldLayout; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + vkCmdPipelineBarrier(cb, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &barrier); + + res = vkEndCommandBuffer(cb); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture acquire failed: vkEndCommandBuffer" + << vkResultName(res) << int(res); + vkFreeCommandBuffers(device, commandPool, 1, &cb); + vkDestroyCommandPool(device, commandPool, nullptr); + return false; + } + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &cb; + + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + + VkFence fence = VK_NULL_HANDLE; + res = vkCreateFence(device, &fenceInfo, nullptr, &fence); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture acquire failed: vkCreateFence" + << vkResultName(res) << int(res); + vkFreeCommandBuffers(device, commandPool, 1, &cb); + vkDestroyCommandPool(device, commandPool, nullptr); + return false; + } + + res = vkQueueSubmit(queue, 1, &submitInfo, fence); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture acquire failed: vkQueueSubmit" + << vkResultName(res) << int(res); + vkDestroyFence(device, fence, nullptr); + vkFreeCommandBuffers(device, commandPool, 1, &cb); + vkDestroyCommandPool(device, commandPool, nullptr); + return false; + } + + PendingVulkanCommandCleanup cleanup; + cleanup.device = device; + cleanup.commandPool = commandPool; + cleanup.fence = fence; + queuePendingVulkanCommandCleanup(&cleanup); + + texture->layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture acquired for Qt sampling" + << "image" << Qt::hex << vulkanHandleToInteger(texture->image) << Qt::dec + << "oldLayout" << vkImageLayoutName(oldLayout) + << "newLayout" << vkImageLayoutName(texture->layout) + << "format" << drmFormatToName(texture->drmFormat) + << "modifier" << drmModifierToName(texture->drmModifier) + << "size" << texture->size + << "srcQueue" << VK_QUEUE_FAMILY_FOREIGN_EXT + << "dstQueue" << handles->gfxQueueFamilyIdx; + + return true; +} + +// Import a dmabuf as a GL texture via EGL, so Qt RHI (GL) can render into it +// even when the wlroots renderer is Vulkan. This mirrors wlroots' +// wlr_egl_create_image_from_dmabuf (render/egl.c) but uses the Qt RHI GL +// context's EGL display instead of wlroots' gles2 EGL. dmabuf is API-agnostic: +// any EGL context can import it regardless of which renderer created it. +// On success, sets *outImage and *outTex; caller owns both (destroy via +// eglDestroyImageKHR / glDeleteTextures). +static bool eglImportDmabufToGLTexture(EGLDisplay display, + const wlr_dmabuf_attributes *attribs, + EGLImage *outImage, GLuint *outTex) +{ + flushPendingNativeTextureCleanups(); + + auto eglCreateImageKHR = resolveEglCreateImageKHR(); + auto glEGLImageTargetTexture2DOES = resolveGlEGLImageTargetTexture2DOES(); + if (!eglCreateImageKHR || !glEGLImageTargetTexture2DOES) { + qCWarning(lcWlRenderHelper) << "EGL dmabuf import: eglCreateImageKHR or glEGLImageTargetTexture2DOES not available"; + return false; + } + + // Build EGL attribute list, mirroring wlroots egl.c:733-830. + EGLint eglAttribs[50]; + unsigned int atti = 0; + eglAttribs[atti++] = EGL_WIDTH; + eglAttribs[atti++] = attribs->width; + eglAttribs[atti++] = EGL_HEIGHT; + eglAttribs[atti++] = attribs->height; + eglAttribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; + eglAttribs[atti++] = attribs->format; + + static const struct { + EGLint fd, offset, pitch, mod_lo, mod_hi; + } plane_attrs[WLR_DMABUF_MAX_PLANES] = { + { EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT, + EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT }, + { EGL_DMA_BUF_PLANE1_FD_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT, + EGL_DMA_BUF_PLANE1_PITCH_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT }, + { EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, + EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT }, + { EGL_DMA_BUF_PLANE3_FD_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT, + EGL_DMA_BUF_PLANE3_PITCH_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT }, + }; + + for (int i = 0; i < attribs->n_planes; i++) { + eglAttribs[atti++] = plane_attrs[i].fd; + eglAttribs[atti++] = attribs->fd[i]; + eglAttribs[atti++] = plane_attrs[i].offset; + eglAttribs[atti++] = attribs->offset[i]; + eglAttribs[atti++] = plane_attrs[i].pitch; + eglAttribs[atti++] = attribs->stride[i]; + if (attribs->modifier != DRM_FORMAT_MOD_INVALID) { + eglAttribs[atti++] = plane_attrs[i].mod_lo; + eglAttribs[atti++] = EGLint(attribs->modifier & 0xFFFFFFFF); + eglAttribs[atti++] = plane_attrs[i].mod_hi; + eglAttribs[atti++] = EGLint(attribs->modifier >> 32); + } + } + eglAttribs[atti++] = EGL_IMAGE_PRESERVED_KHR; + eglAttribs[atti++] = EGL_TRUE; + eglAttribs[atti++] = EGL_NONE; + + EGLImage image = eglCreateImageKHR(display, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, NULL, eglAttribs); + if (image == EGL_NO_IMAGE) { + qCWarning(lcWlRenderHelper) << "EGL dmabuf import: eglCreateImageKHR failed, EGL error=" << eglGetError(); + return false; + } + + GLuint tex = 0; + clearGlErrors(); + glGenTextures(1, &tex); + if (!tex) { + if (auto destroyImage = resolveEglDestroyImageKHR()) + destroyImage(display, image); + return false; + } + + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); + glBindTexture(GL_TEXTURE_2D, 0); + if (!textureUploadSucceeded()) { + glDeleteTextures(1, &tex); + if (auto destroyImage = resolveEglDestroyImageKHR()) + destroyImage(display, image); + return false; + } + + *outImage = image; + *outTex = tex; + return true; +} +#endif + +void WRenderHelper::releaseNativeTexture(NativeTextureCleanup *cleanup) +{ + if (!cleanup || cleanup->type == NativeTextureCleanup::Type::None) + return; + +#ifdef ENABLE_VULKAN_RENDER + if (cleanup->type == NativeTextureCleanup::Type::VulkanTexture) { + releaseNativeTextureNow(cleanup); + return; + } + + if (cleanup->type == NativeTextureCleanup::Type::OpenGLTexture) { + if (QOpenGLContext::currentContext()) + flushPendingNativeTextureCleanups(); + if (!releaseNativeTextureNow(cleanup)) + queueNativeTextureCleanup(cleanup); + return; + } +#endif + + *cleanup = {}; +} + +// Copy from qquickrendertarget.cpp +static bool createRhiRenderTarget(const QRhiColorAttachment &colorAttachment, + const QSize &pixelSize, + int sampleCount, + QRhi *rhi, + QQuickWindowRenderTarget &dst, + QRhiTextureRenderTarget::Flags flags = {}) +{ + std::unique_ptr depthStencil(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount)); + if (!depthStencil->create()) { + qCWarning(lcWlRenderHelper, "Failed to build depth-stencil buffer for QQuickRenderTarget"); + return false; + } + + QRhiTextureRenderTargetDescription rtDesc(colorAttachment); + rtDesc.setDepthStencilBuffer(depthStencil.get()); + std::unique_ptr rt(rhi->newTextureRenderTarget(rtDesc)); + rt->setFlags(flags); + std::unique_ptr rp(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rp.get()); + + if (!rt->create()) { + qCWarning(lcWlRenderHelper, "Failed to build texture render target for QQuickRenderTarget"); + return false; + } + + rt->setName(QByteArrayLiteral("WaylibTextureRenderTarget")); +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + dst.rt.renderTarget = rt.release(); + dst.res.rpDesc = rp.release(); + dst.implicitBuffers.depthStencil = depthStencil.release(); + dst.rt.owns = true; // ownership of the native resource itself is not transferred but the QRhi objects are on us now +#else + dst.renderTarget = rt.release(); + dst.rpDesc = rp.release(); + dst.depthStencil = depthStencil.release(); + dst.owns = true; // ownership of the native resource itself is not transferred but the QRhi objects are on us now +#endif + return true; +} + +bool createRhiRenderTarget(QRhi *rhi, const QQuickRenderTarget &source, QQuickWindowRenderTarget &dst) +{ + auto rtd = QQuickRenderTargetPrivate::get(&source); + + switch (rtd->type) { + 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( +#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)); + texture->setName(QByteArrayLiteral("WaylibTexture")); + if (Q_UNLIKELY(lcWlRenderHelper().isDebugEnabled())) { + qCDebug(lcWlRenderHelper) + << "Resolving native texture render target into QRhiTexture" + << "nativeObject" << Qt::hex << rtd->u.nativeTexture.object << Qt::dec +#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) + << "layoutOrState" << rtd->u.nativeTexture.layout +#else + << "layoutOrState" << rtd->u.nativeTexture.layoutOrState +#endif + << "pixelSize" << rtd->pixelSize + << "sampleCount" << rtd->sampleCount + << "rhiFormat" << format + << "formatFlags" << int(flags); + } +#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) + if (!texture->createFrom({ rtd->u.nativeTexture.object, rtd->u.nativeTexture.layout })) +#else + if (!texture->createFrom({ rtd->u.nativeTexture.object, rtd->u.nativeTexture.layoutOrState })) +#endif + { + qCWarning(lcWlRenderHelper) << "Failed to wrap native texture (VkImage/GL texture) into QRhiTexture for render target"; + return false; + } + if (Q_UNLIKELY(lcWlRenderHelper().isDebugEnabled())) { + qCDebug(lcWlRenderHelper) + << "Resolved native texture render target into QRhiTexture" + << "texturePtr" << quintptr(texture.get()) + << "pixelSize" << texture->pixelSize() + << "sampleCount" << rtd->sampleCount; + } + QRhiColorAttachment att(texture.get()); + if (!createRhiRenderTarget(att, rtd->pixelSize, rtd->sampleCount, rhi, dst)) + return false; +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + dst.res.texture = texture.release(); +#else + dst.texture = texture.release(); +#endif + return true; + } + case QQuickRenderTargetPrivate::Type::NativeRenderbuffer: { + std::unique_ptr renderbuffer(rhi->newRenderBuffer(QRhiRenderBuffer::Color, rtd->pixelSize, rtd->sampleCount)); + if (!renderbuffer->createFrom({ rtd->u.nativeRenderbufferObject })) { + qCWarning(lcWlRenderHelper, "Failed to build wrapper renderbuffer for QQuickRenderTarget"); + return false; + } + QRhiColorAttachment att(renderbuffer.get()); + if (!createRhiRenderTarget(att, rtd->pixelSize, rtd->sampleCount, rhi, dst)) + return false; + renderbuffer->setName(QByteArrayLiteral("WaylibRenderBuffer")); +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + dst.res.renderBuffer = renderbuffer.release(); +#else + dst.renderBuffer = renderbuffer.release(); +#endif + return true; + } + + default: + break; + } + + return false; +} +// Copy end + +class Q_DECL_HIDDEN WRenderHelperPrivate : public WObjectPrivate +{ +public: + WRenderHelperPrivate(WRenderHelper *qq, qw_renderer *renderer) + : WObjectPrivate(qq) + , renderer(renderer) + {} + ~WRenderHelperPrivate() { + resetRenderBuffer(); + } + + void resetRenderBuffer(); + void onBufferDestroy(); + static bool ensureRhiRenderTarget(QQuickRenderControl *rc, BufferData *data); + + W_DECLARE_PUBLIC(WRenderHelper) + qw_renderer *renderer; + QList buffers; + BufferData *lastBuffer = nullptr; + + QSize size; +}; + +void WRenderHelperPrivate::resetRenderBuffer() +{ + qDeleteAll(buffers); + lastBuffer = nullptr; + buffers.clear(); +} + +void WRenderHelperPrivate::onBufferDestroy() +{ + qw_buffer *buffer = qobject_cast(q_func()->sender()); + + for (int i = 0; i < buffers.count(); ++i) { + auto data = buffers[i]; + if (data->buffer == buffer) { + if (lastBuffer == data) + lastBuffer = nullptr; + buffers.removeAt(i); +#ifdef ENABLE_VULKAN_RENDER + if (renderer && renderer->is_vk()) + delete data; +#endif + break; + } + } +} + +bool WRenderHelperPrivate::ensureRhiRenderTarget(QQuickRenderControl *rc, BufferData *data) +{ + data->resetWindowRenderTarget(); +#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) + auto rhi = QQuickRenderControlPrivate::get(rc)->rhi; +#else + auto rhi = rc->rhi(); +#endif + auto tmp = data->renderTarget; + bool ok = createRhiRenderTarget(rhi, tmp, data->windowRenderTarget); + if (!ok) + return false; +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + data->renderTarget = QQuickRenderTarget::fromRhiRenderTarget(data->windowRenderTarget.rt.renderTarget); +#else + data->renderTarget = QQuickRenderTarget::fromRhiRenderTarget(data->windowRenderTarget.renderTarget); +#endif + data->renderTarget.setDevicePixelRatio(tmp.devicePixelRatio()); + data->renderTarget.setMirrorVertically(tmp.mirrorVertically()); + +#ifdef ENABLE_VULKAN_RENDER + if (data->vulkanRenderTarget.isValid() && rhi && rhi->backend() == QRhi::Vulkan) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + QRhiTexture *sharedTexture = data->windowRenderTarget.res.texture; + QRhiRenderTarget *clearRenderTarget = data->windowRenderTarget.rt.renderTarget; +#else + QRhiTexture *sharedTexture = data->windowRenderTarget.texture; + QRhiRenderTarget *clearRenderTarget = data->windowRenderTarget.renderTarget; +#endif + if (!sharedTexture || !clearRenderTarget) { + qCWarning(lcWlRenderHelper) + << "Failed to build Vulkan output preserve render target:" + << "clear target or shared texture is missing" + << "bufferPtr" << quintptr(data->buffer) + << "image" << Qt::hex + << vulkanHandleToInteger(data->vulkanRenderTarget.image) + << Qt::dec + << "size" << data->vulkanRenderTarget.size; + return false; + } + + QRhiColorAttachment preserveAttachment(sharedTexture); + if (!createRhiRenderTarget(preserveAttachment, + data->vulkanRenderTarget.size, + 1, + rhi, + data->preserveWindowRenderTarget, + QRhiTextureRenderTarget::PreserveColorContents)) { + qCWarning(lcWlRenderHelper) + << "Failed to build Vulkan output preserve render target" + << "bufferPtr" << quintptr(data->buffer) + << "image" << Qt::hex + << vulkanHandleToInteger(data->vulkanRenderTarget.image) + << Qt::dec + << "size" << data->vulkanRenderTarget.size; + return false; + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + data->preserveRenderTarget = + QQuickRenderTarget::fromRhiRenderTarget(data->preserveWindowRenderTarget.rt.renderTarget); +#else + data->preserveRenderTarget = + QQuickRenderTarget::fromRhiRenderTarget(data->preserveWindowRenderTarget.renderTarget); +#endif + data->preserveRenderTarget.setDevicePixelRatio(tmp.devicePixelRatio()); + data->preserveRenderTarget.setMirrorVertically(tmp.mirrorVertically()); + + if (Q_UNLIKELY(lcWlRenderHelper().isDebugEnabled())) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + auto *preserveRt = data->preserveWindowRenderTarget.rt.renderTarget; +#else + auto *preserveRt = data->preserveWindowRenderTarget.renderTarget; +#endif + qCDebug(lcWlRenderHelper) + << "Created Vulkan output clear/load render target pair" + << "bufferPtr" << quintptr(data->buffer) + << "clearRenderTargetPtr" << quintptr(clearRenderTarget) + << "loadRenderTargetPtr" << quintptr(preserveRt) + << "sharedTexturePtr" << quintptr(sharedTexture) + << "sharedTextureSize" << sharedTexture->pixelSize() + << "image" << Qt::hex + << vulkanHandleToInteger(data->vulkanRenderTarget.image) + << Qt::dec + << "format" << drmFormatToName(data->vulkanRenderTarget.drmFormat) + << "modifier" << drmModifierToName(data->vulkanRenderTarget.drmModifier); + } + } +#endif + + if (Q_UNLIKELY(lcWlRenderHelper().isDebugEnabled())) { + const auto *sourceRt = QQuickRenderTargetPrivate::get(&tmp); + const auto *resolvedRt = QQuickRenderTargetPrivate::get(&data->renderTarget); +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + auto *rhiRenderTarget = data->windowRenderTarget.rt.renderTarget; + auto *rhiTexture = data->windowRenderTarget.res.texture; + auto *preserveRhiRenderTarget = data->preserveWindowRenderTarget.rt.renderTarget; +#else + auto *rhiRenderTarget = data->windowRenderTarget.renderTarget; + auto *rhiTexture = data->windowRenderTarget.texture; + auto *preserveRhiRenderTarget = data->preserveWindowRenderTarget.renderTarget; +#endif + qCDebug(lcWlRenderHelper) + << "Resolved QQuickRenderTarget for Qt RHI rendering" + << "bufferPtr" << quintptr(data->buffer) + << "sourceType" << quickRenderTargetTypeName(sourceRt->type) + << "resolvedType" << quickRenderTargetTypeName(resolvedRt->type) + << "pixelSize" << sourceRt->pixelSize + << "resolvedPixelSize" << resolvedRt->pixelSize + << "sampleCount" << sourceRt->sampleCount + << "mirrorVertically" << data->renderTarget.mirrorVertically() + << "devicePixelRatio" << data->renderTarget.devicePixelRatio() + << "rhiRenderTargetPtr" << quintptr(rhiRenderTarget) + << "preserveRhiRenderTargetPtr" << quintptr(preserveRhiRenderTarget) + << "rhiTexturePtr" << quintptr(rhiTexture) + << "rhiTextureSize" << (rhiTexture ? rhiTexture->pixelSize() : QSize()); + } + + return true; +} + +WRenderHelper::WRenderHelper(qw_renderer *renderer, QObject *parent) + : QObject(parent) + , WObject(*new WRenderHelperPrivate(this, renderer)) +{ + +} + +QSize WRenderHelper::size() const +{ + W_DC(WRenderHelper); + return d->size; +} + +void WRenderHelper::setSize(const QSize &size) +{ + W_D(WRenderHelper); + if (d->size == size) + return; + d->size = size; + d->resetRenderBuffer(); + + Q_EMIT sizeChanged(); +} + +QSGRendererInterface::GraphicsApi WRenderHelper::getGraphicsApi(QQuickRenderControl *rc) +{ + auto d = QQuickRenderControlPrivate::get(rc); + return d->sg->rendererInterface(d->rc)->graphicsApi(); +} + +QSGRendererInterface::GraphicsApi WRenderHelper::getGraphicsApi() +{ + auto getApi = [] () { + // Only for get GraphicsApi + QQuickRenderControl rc; + return getGraphicsApi(&rc); + }; + + static auto api = getApi(); + return api; +} + +class Q_DECL_HIDDEN GLTextureBuffer : public qw_buffer_interface +{ +public: + explicit GLTextureBuffer(wlr_egl *egl, QSGTexture *texture); + + QW_INTERFACE(get_dmabuf, bool, wlr_dmabuf_attributes *attribs); + +private: + wlr_egl *m_egl; + QSGTexture *m_texture; +}; + +GLTextureBuffer::GLTextureBuffer(wlr_egl *egl, QSGTexture *texture) + : m_egl(egl) + , m_texture(texture) +{ } @@ -494,16 +3125,16 @@ qw_buffer *WRenderHelper::toBuffer(qw_renderer *renderer, QSGTexture *texture, Q switch (api) { case QSGRendererInterface::OpenGL: { - Q_ASSERT(wlr_renderer_is_gles2(renderer->handle())); + Q_ASSERT(renderer->is_gles2()); auto egl = wlr_gles2_renderer_get_egl(renderer->handle()); return qw_buffer::create(new GLTextureBuffer(egl, texture), size.width(), size.height()); } #ifdef ENABLE_VULKAN_RENDER case QSGRendererInterface::Vulkan: { - Q_ASSERT(wlr_renderer_is_vk(renderer->handle())); - auto instance = wlr_vk_renderer_get_instance(renderer->handle()); - auto device = wlr_vk_renderer_get_device(renderer->handle()); + Q_ASSERT(renderer->is_vk()); + auto instance = renderer->get_instance(); + auto device = renderer->get_device(); return qw_buffer::create(new VkTextureBuffer(instance, device, texture), size.width(), size.height()); } @@ -540,72 +3171,757 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q W_D(WRenderHelper); Q_ASSERT(buffer); - if (d->size.isEmpty()) - return {}; + if (d->size.isEmpty()) + return {}; + + for (int i = 0; i < d->buffers.count(); ++i) { + auto data = d->buffers[i]; + if (data->buffer == buffer) { + d->lastBuffer = data; +#ifdef ENABLE_VULKAN_RENDER + if (data->vulkanRenderTarget.isValid()) { + if (Q_UNLIKELY(lcWlRenderHelper().isDebugEnabled())) { + const auto *rtData = QQuickRenderTargetPrivate::get(&data->renderTarget); +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + auto *rhiRenderTarget = data->windowRenderTarget.rt.renderTarget; + auto *preserveRhiRenderTarget = data->preserveWindowRenderTarget.rt.renderTarget; + auto *rhiTexture = data->windowRenderTarget.res.texture; +#else + auto *rhiRenderTarget = data->windowRenderTarget.renderTarget; + auto *preserveRhiRenderTarget = data->preserveWindowRenderTarget.renderTarget; + auto *rhiTexture = data->windowRenderTarget.texture; +#endif + qCDebug(lcWlRenderHelper) + << "Reusing Qt render target for Vulkan output buffer" + << "bufferPtr" << quintptr(buffer) + << "renderTargetType" << quickRenderTargetTypeName(rtData->type) + << "renderTargetPixelSize" << rtData->pixelSize + << "rhiRenderTargetPtr" << quintptr(rhiRenderTarget) + << "preserveRhiRenderTargetPtr" << quintptr(preserveRhiRenderTarget) + << "rhiTexturePtr" << quintptr(rhiTexture) + << "rhiTextureSize" << (rhiTexture ? rhiTexture->pixelSize() : QSize()) + << "image" << Qt::hex + << vulkanHandleToInteger(data->vulkanRenderTarget.image) + << Qt::dec + << "layout" << vkImageLayoutName(data->vulkanRenderTarget.layout) + << "ownerIsForeign" << data->vulkanRenderTarget.ownerIsForeign + << "scanoutReleaseReady" << data->vulkanRenderTarget.scanoutReleaseReady + << "format" << drmFormatToName(data->vulkanRenderTarget.drmFormat) + << "modifier" << drmModifierToName(data->vulkanRenderTarget.drmModifier); + } + qCDebug(lcWlRenderHelper) + << "Reusing Vulkan RHI output render target import" + << "image" << Qt::hex + << vulkanHandleToInteger(data->vulkanRenderTarget.image) + << Qt::dec + << "size" << data->vulkanRenderTarget.size + << "format" << drmFormatToName(data->vulkanRenderTarget.drmFormat) + << "modifier" << drmModifierToName(data->vulkanRenderTarget.drmModifier); + } +#endif + return data->renderTarget; + } + } + + std::unique_ptr bufferData(new BufferData); + bufferData->buffer = buffer; + + QQuickRenderTarget rt; + + if (d->renderer->is_pixman()) { + auto texture = qw_texture::from_buffer(*d->renderer, *buffer); + pixman_image_t *image = texture->get_image(); + void *data = pixman_image_get_data(image); + if (bufferData->paintDevice.constBits() != data) + bufferData->paintDevice = WTools::fromPixmanImage(image, data); + Q_ASSERT(!bufferData->paintDevice.isNull()); + rt = QQuickRenderTarget::fromPaintDevice(&bufferData->paintDevice); + delete texture; + } +#ifdef ENABLE_VULKAN_RENDER + else if (d->renderer->is_vk()) { +#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) + auto rhi = QQuickRenderControlPrivate::get(rc)->rhi; +#else + auto rhi = rc->rhi(); +#endif + if (rhi && rhi->backend() == QRhi::Vulkan) { + const auto *handles = static_cast(rhi->nativeHandles()); + if (!handles || !handles->inst || handles->inst->vkInstance() == VK_NULL_HANDLE + || handles->physDev == VK_NULL_HANDLE || handles->dev == VK_NULL_HANDLE) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: QRhi Vulkan native handles unavailable"; + return {}; + } + + if (!d->renderer || !d->renderer->handle() || !d->renderer->is_vk()) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: wlroots renderer is not Vulkan"; + return {}; + } + + const VkDevice wlrDevice = d->renderer->get_device(); + if (wlrDevice == VK_NULL_HANDLE || wlrDevice != handles->dev) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: Qt RHI and wlroots use different VkDevice" + << "qt" << Qt::hex << vulkanHandleToInteger(handles->dev) + << "wlroots" << vulkanHandleToInteger(wlrDevice) << Qt::dec; + return {}; + } + + wlr_dmabuf_attributes dmabuf = {}; + if (!buffer->get_dmabuf(&dmabuf)) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: output buffer has no dmabuf" + << buffer << "size" << d->size; + return {}; + } + + if (dmabuf.width != d->size.width() || dmabuf.height != d->size.height()) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import rejected: dmabuf size differs from render target size" + << "dmabuf" << QSize(dmabuf.width, dmabuf.height) + << "target" << d->size + << "format" << drmFormatToName(dmabuf.format) + << "modifier" << drmModifierToName(dmabuf.modifier); + return {}; + } + + constexpr VkImageUsageFlags outputRenderTargetUsage = + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT + | VK_IMAGE_USAGE_SAMPLED_BIT + | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + if (!importDmabufAsVulkanRenderTarget(handles->inst->vkInstance(), + handles->physDev, + handles->dev, + &dmabuf, + outputRenderTargetUsage, + true, + &bufferData->vulkanRenderTarget)) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import failed: cannot create render target for output buffer" + << buffer + << "size" << d->size + << "format" << drmFormatToName(dmabuf.format) + << "modifier" << drmModifierToName(dmabuf.modifier) + << "planes" << dmabuf.n_planes + << "usage" << Qt::hex << outputRenderTargetUsage << Qt::dec; + return {}; + } + + rt = QQuickRenderTarget::fromVulkanImage(bufferData->vulkanRenderTarget.image, + bufferData->vulkanRenderTarget.layout, + bufferData->vulkanRenderTarget.format, + bufferData->vulkanRenderTarget.size, + 1); + if (rt.isNull()) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output import failed: QQuickRenderTarget::fromVulkanImage returned null" + << "image" << Qt::hex + << vulkanHandleToInteger(bufferData->vulkanRenderTarget.image) + << Qt::dec + << "size" << bufferData->vulkanRenderTarget.size + << "format" << bufferData->vulkanRenderTarget.format; + return {}; + } + if (Q_UNLIKELY(lcWlRenderHelper().isDebugEnabled())) { + const auto *rtData = QQuickRenderTargetPrivate::get(&rt); + qCDebug(lcWlRenderHelper) + << "Created QQuickRenderTarget from imported Vulkan output image" + << "bufferPtr" << quintptr(buffer) + << "renderTargetType" << quickRenderTargetTypeName(rtData->type) + << "image" << Qt::hex + << vulkanHandleToInteger(bufferData->vulkanRenderTarget.image) + << Qt::dec + << "layout" << vkImageLayoutName(bufferData->vulkanRenderTarget.layout) + << "pixelSize" << rtData->pixelSize + << "sampleCount" << rtData->sampleCount + << "vkFormat" << bufferData->vulkanRenderTarget.format + << "format" << drmFormatToName(bufferData->vulkanRenderTarget.drmFormat) + << "modifier" << drmModifierToName(bufferData->vulkanRenderTarget.drmModifier) + << "usage" << Qt::hex << outputRenderTargetUsage << Qt::dec + << "ownerIsForeign" << bufferData->vulkanRenderTarget.ownerIsForeign; + } + } else { + // Vulkan wlroots renderer with GL Qt RHI: import the output buffer's + // dmabuf as a GL texture via EGL (EGL_EXT_image_dma_buf_import), so Qt + // RHI (GL) can render into it. This remains as an explicit fallback + // when Qt Quick is forced to OpenGL while wlroots uses Vulkan. + EGLDisplay eglDisplay = eglGetCurrentDisplay(); + if (eglDisplay == EGL_NO_DISPLAY) { + qCWarning(lcWlRenderHelper) << "Vulkan+GL: no current EGL display (GL context not current?)"; + return {}; + } + + wlr_dmabuf_attributes dmabuf; + if (!buffer->get_dmabuf(&dmabuf)) { + qCWarning(lcWlRenderHelper) << "Vulkan+GL: output buffer has no dmabuf"; + return {}; + } + + EGLImage eglImage = EGL_NO_IMAGE; + GLuint glTex = 0; + if (!eglImportDmabufToGLTexture(eglDisplay, &dmabuf, &eglImage, &glTex)) { + qCWarning(lcWlRenderHelper) << "Vulkan+GL: EGL dmabuf import failed for output buffer"; + return {}; + } + + bufferData->eglImage = eglImage; + bufferData->glTexture = glTex; + bufferData->eglDisplay = eglDisplay; + + rt = QQuickRenderTarget::fromOpenGLTexture(glTex, d->size); + rt.setMirrorVertically(true); + } + } +#endif + else if (d->renderer->is_gles2()) { + auto texture = qw_texture::from_buffer(*d->renderer, *buffer); + wlr_gles2_texture_attribs attribs; + texture->get_attribs(&attribs); + + rt = QQuickRenderTarget::fromOpenGLTexture(attribs.tex, d->size); + 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())) + bufferData->renderTarget = {}; + } + + if (bufferData->renderTarget.isNull()) + return {}; + + if (auto texture = bufferData->windowRenderTarget.res.texture) { + s_rhiRenderBuffers->append({ bufferData->windowRenderTarget.rt.renderTarget, + texture, bufferData->buffer }); + if (bufferData->preserveWindowRenderTarget.rt.renderTarget) { + s_rhiRenderBuffers->append({ bufferData->preserveWindowRenderTarget.rt.renderTarget, + texture, bufferData->buffer }); + } + } + } + + connect(buffer, SIGNAL(before_destroy()), + this, SLOT(onBufferDestroy()), Qt::UniqueConnection); + + d->buffers.append(bufferData.release()); + d->lastBuffer = d->buffers.last(); + + if (Q_UNLIKELY(lcWlRenderHelper().isDebugEnabled())) { + const auto *rtData = QQuickRenderTargetPrivate::get(&d->lastBuffer->renderTarget); +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + auto *rhiRenderTarget = d->lastBuffer->windowRenderTarget.rt.renderTarget; + auto *preserveRhiRenderTarget = d->lastBuffer->preserveWindowRenderTarget.rt.renderTarget; + auto *rhiTexture = d->lastBuffer->windowRenderTarget.res.texture; +#else + auto *rhiRenderTarget = d->lastBuffer->windowRenderTarget.renderTarget; + auto *preserveRhiRenderTarget = d->lastBuffer->preserveWindowRenderTarget.renderTarget; + auto *rhiTexture = d->lastBuffer->windowRenderTarget.texture; +#endif + qCDebug(lcWlRenderHelper) + << "Acquired render target for output buffer" + << "bufferPtr" << quintptr(buffer) + << "renderTargetType" << quickRenderTargetTypeName(rtData->type) + << "renderTargetPixelSize" << rtData->pixelSize + << "mirrorVertically" << d->lastBuffer->renderTarget.mirrorVertically() + << "devicePixelRatio" << d->lastBuffer->renderTarget.devicePixelRatio() + << "rhiRenderTargetPtr" << quintptr(rhiRenderTarget) + << "preserveRhiRenderTargetPtr" << quintptr(preserveRhiRenderTarget) + << "rhiTexturePtr" << quintptr(rhiTexture) + << "rhiTextureSize" << (rhiTexture ? rhiTexture->pixelSize() : QSize()) +#ifdef ENABLE_VULKAN_RENDER + << "hasVulkanImport" << d->lastBuffer->vulkanRenderTarget.isValid() + << "vulkanImage" << Qt::hex + << vulkanHandleToInteger(d->lastBuffer->vulkanRenderTarget.image) + << Qt::dec + << "vulkanLayout" << vkImageLayoutName(d->lastBuffer->vulkanRenderTarget.layout) + << "ownerIsForeign" << d->lastBuffer->vulkanRenderTarget.ownerIsForeign + << "scanoutReleaseReady" << d->lastBuffer->vulkanRenderTarget.scanoutReleaseReady +#endif + ; + } + + return d->buffers.last()->renderTarget; +} + +QQuickRenderTarget WRenderHelper::renderTargetForBuffer(qw_buffer *buffer, + bool preserveColorContents) const +{ + W_DC(WRenderHelper); + if (!buffer) + return {}; + + for (auto bufferData : std::as_const(d->buffers)) { + if (bufferData->buffer != buffer) + continue; + +#ifdef ENABLE_VULKAN_RENDER + if (preserveColorContents && bufferData->vulkanRenderTarget.isValid()) { + if (!bufferData->preserveRenderTarget.isNull()) + return bufferData->preserveRenderTarget; + + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output preserve render target unavailable" + << "bufferPtr" << quintptr(buffer) + << "image" << Qt::hex + << vulkanHandleToInteger(bufferData->vulkanRenderTarget.image) + << Qt::dec + << "size" << bufferData->vulkanRenderTarget.size; + return {}; + } +#else + Q_UNUSED(preserveColorContents); +#endif + return bufferData->renderTarget; + } + + return {}; +} + +bool WRenderHelper::prepareVulkanRenderTargetForQt(QRhi *rhi, QRhiTexture *texture, + qw_buffer *buffer) +{ +#ifdef ENABLE_VULKAN_RENDER + W_D(WRenderHelper); + if (!rhi || !texture || !buffer || !d->renderer || !d->renderer->handle() + || !d->renderer->is_vk() || rhi->backend() != QRhi::Vulkan) { + return true; + } + + BufferData *data = nullptr; + for (auto bufferData : std::as_const(d->buffers)) { + if (bufferData->buffer == buffer) { + data = bufferData; + break; + } + } + + if (!data || !data->vulkanRenderTarget.isValid()) + return true; + + VulkanImportedRenderTarget &target = data->vulkanRenderTarget; + const auto nativeTexture = texture->nativeTexture(); + const VkImage textureImage = reinterpret_cast(static_cast(nativeTexture.object)); + if (textureImage != target.image) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output acquire rejected: QRhi texture image differs from imported output image" + << "texture" << Qt::hex << vulkanHandleToInteger(textureImage) + << "target" << vulkanHandleToInteger(target.image) << Qt::dec + << "size" << target.size; + return false; + } + + const bool needsOwnershipAcquire = target.ownerIsForeign; + const bool needsLayoutTransition = target.layout != VK_IMAGE_LAYOUT_GENERAL; + if (!needsOwnershipAcquire && !needsLayoutTransition) { + texture->setNativeLayout(VK_IMAGE_LAYOUT_GENERAL); + if (Q_UNLIKELY(lcWlRenderHelper().isDebugEnabled())) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI output acquire kept Qt ownership" + << "bufferPtr" << quintptr(buffer) + << "texturePtr" << quintptr(texture) + << "image" << Qt::hex << vulkanHandleToInteger(target.image) << Qt::dec + << "layout" << vkImageLayoutName(target.layout) + << "ownerIsForeign" << target.ownerIsForeign + << "scanoutReleaseReady" << target.scanoutReleaseReady + << "nativeLayout" << vkImageLayoutName(static_cast(nativeTexture.layout)) + << "size" << target.size + << "format" << drmFormatToName(target.drmFormat) + << "modifier" << drmModifierToName(target.drmModifier); + } + return true; + } + + if (needsOwnershipAcquire + && !waitDmabufImplicitFence(buffer, DMA_BUF_SYNC_WRITE, + "Vulkan RHI output", "acquire")) { + return false; + } + + const auto *handles = static_cast(rhi->nativeHandles()); + if (!handles) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output acquire failed: QRhi Vulkan native handles unavailable"; + return false; + } + + const uint32_t queueFamily = static_cast(handles->gfxQueueFamilyIdx); + const uint32_t srcQueue = needsOwnershipAcquire ? VK_QUEUE_FAMILY_FOREIGN_EXT : VK_QUEUE_FAMILY_IGNORED; + const uint32_t dstQueue = needsOwnershipAcquire ? queueFamily : VK_QUEUE_FAMILY_IGNORED; + const VkImageLayout oldLayout = target.layout; + + if (Q_UNLIKELY(lcWlRenderHelper().isDebugEnabled())) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI output acquire transition requested" + << "bufferPtr" << quintptr(buffer) + << "texturePtr" << quintptr(texture) + << "image" << Qt::hex << vulkanHandleToInteger(target.image) << Qt::dec + << "oldLayout" << vkImageLayoutName(oldLayout) + << "newLayout" << vkImageLayoutName(VK_IMAGE_LAYOUT_GENERAL) + << "needsOwnershipAcquire" << needsOwnershipAcquire + << "needsLayoutTransition" << needsLayoutTransition + << "srcQueue" << srcQueue + << "dstQueue" << dstQueue + << "nativeLayout" << vkImageLayoutName(static_cast(nativeTexture.layout)) + << "size" << target.size + << "format" << drmFormatToName(target.drmFormat) + << "modifier" << drmModifierToName(target.drmModifier); + } + + if (!submitVulkanOutputTargetBarrier(rhi, + &target, + buffer, + "acquire", + oldLayout, + VK_IMAGE_LAYOUT_GENERAL, + srcQueue, + dstQueue, + 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, + false)) { + return false; + } + + target.layout = VK_IMAGE_LAYOUT_GENERAL; + target.ownerIsForeign = false; + target.scanoutReleaseReady = false; + texture->setNativeLayout(VK_IMAGE_LAYOUT_GENERAL); + + qCDebug(lcWlRenderHelper) + << "Vulkan RHI output acquired for Qt rendering" + << "image" << Qt::hex << vulkanHandleToInteger(target.image) << Qt::dec + << "oldLayout" << vkImageLayoutName(oldLayout) + << "newLayout" << vkImageLayoutName(target.layout) + << "format" << drmFormatToName(target.drmFormat) + << "modifier" << drmModifierToName(target.drmModifier) + << "size" << target.size; + + return true; +#else + Q_UNUSED(rhi); + Q_UNUSED(texture); + Q_UNUSED(buffer); + return true; +#endif +} + +bool WRenderHelper::releaseVulkanRenderTargetToScanout(QRhi *rhi, QRhiTexture *texture, + qw_buffer *buffer) +{ +#ifdef ENABLE_VULKAN_RENDER + W_D(WRenderHelper); + if (!rhi || !texture || !buffer || !d->renderer || !d->renderer->handle() + || !d->renderer->is_vk() || rhi->backend() != QRhi::Vulkan) { + return true; + } + + BufferData *data = nullptr; + for (auto bufferData : std::as_const(d->buffers)) { + if (bufferData->buffer == buffer) { + data = bufferData; + break; + } + } + + if (!data || !data->vulkanRenderTarget.isValid()) + return true; + + VulkanImportedRenderTarget &target = data->vulkanRenderTarget; + const auto nativeTexture = texture->nativeTexture(); + const VkImage textureImage = reinterpret_cast(static_cast(nativeTexture.object)); + if (textureImage != target.image) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output release rejected: QRhi texture image differs from imported output image" + << "texture" << Qt::hex << vulkanHandleToInteger(textureImage) + << "target" << vulkanHandleToInteger(target.image) << Qt::dec + << "size" << target.size; + return false; + } - for (int i = 0; i < d->buffers.count(); ++i) { - auto data = d->buffers[i]; - if (data->buffer == buffer) { - d->lastBuffer = data; - return data->renderTarget; - } + if (target.ownerIsForeign) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI output release skipped: target already owned by foreign queue" + << "image" << Qt::hex << vulkanHandleToInteger(target.image) << Qt::dec + << "layout" << vkImageLayoutName(target.layout); + return target.scanoutReleaseReady; } - std::unique_ptr bufferData(new BufferData); - bufferData->buffer = buffer; - auto texture = qw_texture::from_buffer(*d->renderer, *buffer); + const auto *handles = static_cast(rhi->nativeHandles()); + if (!handles) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output release failed: QRhi Vulkan native handles unavailable"; + return false; + } - QQuickRenderTarget rt; + const uint32_t queueFamily = static_cast(handles->gfxQueueFamilyIdx); + const VkImageLayout oldLayout = static_cast(nativeTexture.layout); + + if (Q_UNLIKELY(lcWlRenderHelper().isDebugEnabled())) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI output release transition requested" + << "bufferPtr" << quintptr(buffer) + << "texturePtr" << quintptr(texture) + << "image" << Qt::hex << vulkanHandleToInteger(target.image) << Qt::dec + << "oldLayout" << vkImageLayoutName(oldLayout) + << "trackedLayout" << vkImageLayoutName(target.layout) + << "newLayout" << vkImageLayoutName(VK_IMAGE_LAYOUT_GENERAL) + << "queueFamily" << queueFamily + << "ownerIsForeign" << target.ownerIsForeign + << "scanoutReleaseReady" << target.scanoutReleaseReady + << "size" << target.size + << "format" << drmFormatToName(target.drmFormat) + << "modifier" << drmModifierToName(target.drmModifier); + } - if (wlr_renderer_is_pixman(d->renderer->handle())) { - pixman_image_t *image = wlr_pixman_texture_get_image(texture->handle()); - void *data = pixman_image_get_data(image); - if (bufferData->paintDevice.constBits() != data) - bufferData->paintDevice = WTools::fromPixmanImage(image, data); - Q_ASSERT(!bufferData->paintDevice.isNull()); - rt = QQuickRenderTarget::fromPaintDevice(&bufferData->paintDevice); + if (!submitVulkanOutputTargetBarrier(rhi, + &target, + buffer, + "release", + oldLayout, + VK_IMAGE_LAYOUT_GENERAL, + queueFamily, + VK_QUEUE_FAMILY_FOREIGN_EXT, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT + | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + 0, + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + true)) { + target.scanoutReleaseReady = false; + return false; } + + target.layout = VK_IMAGE_LAYOUT_GENERAL; + target.ownerIsForeign = true; + target.scanoutReleaseReady = true; + texture->setNativeLayout(VK_IMAGE_LAYOUT_GENERAL); + + qCDebug(lcWlRenderHelper) + << "Vulkan RHI output released for scanout" + << "image" << Qt::hex << vulkanHandleToInteger(target.image) << Qt::dec + << "oldLayout" << vkImageLayoutName(oldLayout) + << "newLayout" << vkImageLayoutName(target.layout) + << "format" << drmFormatToName(target.drmFormat) + << "modifier" << drmModifierToName(target.drmModifier) + << "size" << target.size; + + return true; +#else + Q_UNUSED(rhi); + Q_UNUSED(texture); + Q_UNUSED(buffer); + return true; +#endif +} + +void WRenderHelper::transitionVkImageToGeneral(QRhi *rhi, QRhiTexture *texture, + qw_buffer *buffer) +{ #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); + if (!rhi || !texture || !buffer) + return; + + // Obtain the wlroots-adopted VkDevice/VkQueue from Qt RHI. QRhiVulkanNativeHandles + // (qrhi_platform.h) exposes dev, gfxQueue, gfxQueueFamilyIdx and inst, all of + // which belong to the wlroots-adopted Vulkan device. + const auto *handles = static_cast(rhi->nativeHandles()); + if (!handles || !handles->dev || !handles->gfxQueue || !handles->inst) { + qCWarning(lcWlRenderHelper) << "Vulkan: QRhi native handles unavailable, cannot transition render image layout"; + return; } -#endif - else if (wlr_renderer_is_gles2(d->renderer->handle())) { - wlr_gles2_texture_attribs attribs; - wlr_gles2_texture_get_attribs(texture->handle(), &attribs); - rt = QQuickRenderTarget::fromOpenGLTexture(attribs.tex, d->size); - rt.setMirrorVertically(true); + VkDevice device = handles->dev; + VkQueue queue = handles->gfxQueue; + VkImage image = reinterpret_cast(static_cast(texture->nativeTexture().object)); + + // Device-level functions must be resolved via vkGetDeviceProcAddr, which + // returns device-relative entry points (dispatch table or layer chain), + // per the Vulkan loader rules (trampoline.c vkGetDeviceProcAddr). All + // functions used below are device-level commands. + auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); + if (!vkGetDeviceProcAddr) { + qCWarning(lcWlRenderHelper) << "Vulkan: vkGetDeviceProcAddr unavailable, cannot transition render image layout"; + return; + } + PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkAllocateCommandBuffers")); + PFN_vkBeginCommandBuffer vkBeginCommandBuffer = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkBeginCommandBuffer")); + PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdPipelineBarrier")); + PFN_vkEndCommandBuffer vkEndCommandBuffer = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkEndCommandBuffer")); + PFN_vkQueueSubmit vkQueueSubmit = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkQueueSubmit")); + PFN_vkQueueWaitIdle vkQueueWaitIdle = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkQueueWaitIdle")); + PFN_vkFreeCommandBuffers vkFreeCommandBuffers = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkFreeCommandBuffers")); + PFN_vkCreateCommandPool vkCreateCommandPool = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkCreateCommandPool")); + PFN_vkDestroyCommandPool vkDestroyCommandPool = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkDestroyCommandPool")); + // For implicit sync: create a binary semaphore exportable as a sync_file + // (mirrors wlroots pass.c:485-494). vkGetSemaphoreFdKHR is provided by + // VK_KHR_external_semaphore_fd, which wlroots enables on the device + // (vulkan.c:506-635); RADV on amdgpu supports it. + PFN_vkCreateSemaphore vkCreateSemaphore = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkCreateSemaphore")); + PFN_vkDestroySemaphore vkDestroySemaphore = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkDestroySemaphore")); + PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetSemaphoreFdKHR")); + + if (!vkAllocateCommandBuffers || !vkBeginCommandBuffer || !vkCmdPipelineBarrier || + !vkEndCommandBuffer || !vkQueueSubmit || !vkQueueWaitIdle || + !vkFreeCommandBuffers || !vkCreateCommandPool || !vkDestroyCommandPool || + !vkCreateSemaphore || !vkDestroySemaphore || !vkGetSemaphoreFdKHR) { + qCWarning(lcWlRenderHelper) << "Vulkan: required command/sync functions unavailable for layout transition"; + return; } - delete texture; - bufferData->renderTarget = rt; + // Create a transient command pool for the graphics queue family. + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; + poolInfo.queueFamilyIndex = handles->gfxQueueFamilyIdx; + VkCommandPool commandPool = VK_NULL_HANDLE; + VkResult res = vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) << "Vulkan: vkCreateCommandPool failed for layout transition, error=" << res; + return; + } - if (QSGRendererInterface::isApiRhiBased(getGraphicsApi(rc))) { - if (!rt.isNull()) { - // Force convert to Rhi render target - if (!d->ensureRhiRenderTarget(rc, bufferData.get())) - bufferData->renderTarget = {}; - } + VkCommandBufferAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + VkCommandBuffer cb = VK_NULL_HANDLE; + res = vkAllocateCommandBuffers(device, &allocInfo, &cb); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) << "Vulkan: vkAllocateCommandBuffers failed for layout transition, error=" << res; + vkDestroyCommandPool(device, commandPool, nullptr); + return; + } - if (bufferData->renderTarget.isNull()) - return {}; + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(cb, &beginInfo); + + // NOTE: No layout barrier is recorded here. The previous approach + // (transition COLOR_ATTACHMENT_OPTIMAL -> GENERAL + setNativeLayout) caused + // InvalidImageLayout validation errors (13.txt) because Qt RHI does not + // update usageState.layout after its render pass (finalLayout stays + // COLOR_ATTACHMENT_OPTIMAL but the tracked value goes stale), and + // preserveColorContents mode expects COLOR_ATTACHMENT_OPTIMAL as + // initialLayout. KMS scanout reads the dmabuf's physical memory directly + // and does not care about the Vulkan image layout, so leaving the image in + // COLOR_ATTACHMENT_OPTIMAL is acceptable. This command buffer is empty and + // serves only as a submit载体 to signal the semaphore for sync_file export. + + vkEndCommandBuffer(cb); + + // Create a binary semaphore exportable as a sync_file (VK_KHR_external_ + // semaphore_fd). Signalled by the submit below, then exported to a + // sync_file fd and imported into the dmabuf so KMS implicit sync waits + // for the Vulkan render. Mirrors wlroots pass.c:485-494 (creation) and + // renderer.c:994-1026 (export + dmabuf_import_sync_file). + VkExportSemaphoreCreateInfo exportInfo = {}; + exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + VkSemaphoreCreateInfo semInfo = {}; + semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semInfo.pNext = &exportInfo; + VkSemaphore semaphore = VK_NULL_HANDLE; + res = vkCreateSemaphore(device, &semInfo, nullptr, &semaphore); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) << "Vulkan: vkCreateSemaphore failed for sync, error=" << res; + vkFreeCommandBuffers(device, commandPool, 1, &cb); + vkDestroyCommandPool(device, commandPool, nullptr); + return; + } - if (auto texture = bufferData->windowRenderTarget.res.texture) { - s_rhiRenderBuffers->append({ bufferData->windowRenderTarget.rt.renderTarget, - texture, bufferData->buffer }); + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &cb; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &semaphore; + res = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) << "Vulkan: vkQueueSubmit failed for layout transition, error=" << res; + } else { + // Wait for the submitted command buffer (layout transition) to complete + // before exporting the sync_file and destroying the semaphore/command + // buffer. Unlike wlroots (which uses timeline semaphores with + // vkQueueSubmit2KHR and relies on vkGetSemaphoreFdKHR's implicit wait), + // our binary semaphore + vkQueueSubmit path requires an explicit + // vkQueueWaitIdle — the validation layer (12.txt) confirmed that + // vkDestroySemaphore/vkFreeCommandBuffers were called while the + // semaphore/command buffer was still pending. + vkQueueWaitIdle(queue); + + // Export the signalled semaphore as a sync_file fd. After vkQueueWaitIdle + // the semaphore is signalled and the GPU is idle, so this returns an + // already-signalled sync_file fd and resets the semaphore. + VkSemaphoreGetFdInfoKHR getFdInfo = {}; + getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR; + getFdInfo.semaphore = semaphore; + getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + int syncFileFd = -1; + VkResult fdRes = vkGetSemaphoreFdKHR(device, &getFdInfo, &syncFileFd); + if (fdRes != VK_SUCCESS || syncFileFd < 0) { + qCWarning(lcWlRenderHelper) << "Vulkan: vkGetSemaphoreFdKHR failed, error=" << fdRes + << "- dmabuf implicit sync not signalled (KMS may reject commit)"; + } else { + // The GPU has completed (vkQueueWaitIdle above). Signal the dmabuf's + // implicit sync fence on every plane so KMS scanout waits for the + // Vulkan render. Mirrors wlroots renderer.c:1014-1026 + // (dmabuf_import_sync_file with DMA_BUF_SYNC_WRITE). + // DMA_BUF_IOCTL_IMPORT_SYNC_FILE is a kernel UAPI (linux/dma-buf.h, + // available since Linux 5.20). + // + // NOTE: qw_buffer::get_dmabuf() returns a *reference* to the + // buffer's dmabuf attributes (gbm's buffer_get_dmabuf does a + // shallow struct copy, no fd dup). wlr_dmabuf_attributes_finish() + // must NOT be called — it would close the buffer's own fds. + wlr_dmabuf_attributes dmabuf; + if (buffer->get_dmabuf(&dmabuf)) { + for (int i = 0; i < dmabuf.n_planes; ++i) { + struct dma_buf_import_sync_file data = {}; + data.flags = DMA_BUF_SYNC_WRITE; + data.fd = syncFileFd; + if (ioctl(dmabuf.fd[i], DMA_BUF_IOCTL_IMPORT_SYNC_FILE, &data) != 0) { + qCWarning(lcWlRenderHelper) << "Vulkan: DMA_BUF_IOCTL_IMPORT_SYNC_FILE failed on plane" << i + << "errno=" << errno << "- KMS implicit sync may not wait"; + break; + } + } + } else { + qCWarning(lcWlRenderHelper) << "Vulkan: output buffer has no dmabuf, cannot signal implicit sync"; + } + close(syncFileFd); } } - 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; + vkDestroySemaphore(device, semaphore, nullptr); + vkFreeCommandBuffers(device, commandPool, 1, &cb); + vkDestroyCommandPool(device, commandPool, nullptr); +#else + Q_UNUSED(rhi); + Q_UNUSED(texture); + Q_UNUSED(buffer); +#endif } std::pair WRenderHelper::lastRenderTarget() const @@ -635,21 +3951,35 @@ qw_renderer *WRenderHelper::createRenderer(qw_backend *backend) qw_renderer *WRenderHelper::createRenderer(qw_backend *backend, QSGRendererInterface::GraphicsApi api) { qw_renderer *renderer = nullptr; + // The wlroots renderer type is determined by WLR_RENDERER. treeland pairs + // the Vulkan wlroots renderer with Qt RHI Vulkan; the OpenGL branch below + // keeps the historical bridge available for non-treeland callers. + const auto wlrRenderer = qgetenv("WLR_RENDERER"); switch (api) { case QSGRendererInterface::OpenGL: +#ifdef ENABLE_VULKAN_RENDER + if (wlrRenderer == "vulkan") { + renderer = createRendererWithType("vulkan", backend); + Q_ASSERT(!renderer || renderer->is_vk()); + break; + } +#endif renderer = createRendererWithType("gles2", backend); - Q_ASSERT(!renderer || wlr_renderer_is_gles2(renderer->handle())); + Q_ASSERT(!renderer || renderer->is_gles2()); break; #ifdef ENABLE_VULKAN_RENDER case QSGRendererInterface::Vulkan: { renderer = createRendererWithType("vulkan", backend); - Q_ASSERT(!renderer || wlr_renderer_is_vk(renderer->handle())); + if (renderer && !renderer->is_vk()) { + qCWarning(lcWlRenderHelper) << "Vulkan: wlr_renderer was created but is not a Vulkan renderer, rendering will likely fail"; + } + Q_ASSERT(!renderer || renderer->is_vk()); break; } #endif case QSGRendererInterface::Software: renderer = createRendererWithType("pixman", backend); - Q_ASSERT(!renderer || wlr_renderer_is_pixman(renderer->handle())); + Q_ASSERT(!renderer || renderer->is_pixman()); break; default: qFatal("Not supported graphics api: %s", qPrintable(QQuickWindow::sceneGraphBackend())); @@ -707,7 +4037,11 @@ void WRenderHelper::setupRendererBackend(qw_backend *testBackend) QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); } else if (wlrRenderer == "vulkan") { #ifdef ENABLE_VULKAN_RENDER + qunsetenv("QSG_RHI_BACKEND"); QQuickWindow::setGraphicsApi(QSGRendererInterface::Vulkan); + qCInfo(lcWlRenderHelper) + << "Vulkan wlroots renderer requested: forcing treeland Qt Quick RHI to Vulkan" + << "and clearing QSG_RHI_BACKEND for child processes"; #else qFatal("Vulkan support is not enabled"); #endif @@ -764,53 +4098,805 @@ QSGRendererInterface::GraphicsApi WRenderHelper::probe(qw_backend *testBackend, } } - acceptApi = api; - break; + acceptApi = api; + break; + } + + return acceptApi; +} + +static void updateGLTexture(QRhi *rhi, qw_texture *handle, QSGPlainTexture *texture) { + wlr_gles2_texture_attribs attribs; + handle->get_attribs(&attribs); + QSize size(handle->handle()->width, handle->handle()->height); + +#define GL_TEXTURE_EXTERNAL_OES 0x8D65 + QQuickWindowPrivate::TextureFromNativeTextureFlags flags = attribs.target == GL_TEXTURE_EXTERNAL_OES + ? QQuickWindowPrivate::NativeTextureIsExternalOES + : QQuickWindowPrivate::TextureFromNativeTextureFlags {}; + texture->setOwnsTexture(false); + texture->setTextureFromNativeTexture(rhi, attribs.tex, 0, 0, size, {}, flags); + + texture->setHasAlphaChannel(attribs.has_alpha); + texture->setTextureSize(size); +} + +static inline quint64 vkimage_cast(void *image) { + return reinterpret_cast(image); +} + +[[maybe_unused]] static inline quint64 vkimage_cast(quint64 image) { + return image; +} + +#ifdef ENABLE_VULKAN_RENDER +static void updateVKTexture(QRhi *rhi, qw_texture *handle, QSGPlainTexture *texture) { + wlr_vk_image_attribs attribs; + handle->get_image_attribs(&attribs); + QSize size(handle->handle()->width, handle->handle()->height); + + texture->setOwnsTexture(false); + texture->setTextureFromNativeTexture(rhi, + vkimage_cast(attribs.image), + attribs.layout, attribs.format, size, + {}, {}); + texture->setHasAlphaChannel(handle->has_alpha()); + texture->setTextureSize(size); +} +#endif + +#ifdef ENABLE_VULKAN_RENDER +// (updateEglDmabufTexture removed - logic inlined into makeTexture) + +static bool bufferExportsDmabuf(qw_buffer *buffer) +{ + if (!buffer || !buffer->handle()) + return false; + + wlr_dmabuf_attributes dmabuf = {}; + return buffer->get_dmabuf(&dmabuf); +} + +static bool envFlagExplicitlyEnabled(const char *name) +{ + const QByteArray value = qgetenv(name).trimmed().toLower(); + return value == "1" || value == "true" || value == "yes" || value == "on"; +} + +static bool envFlagExplicitlyDisabled(const char *name) +{ + const QByteArray value = qgetenv(name).trimmed().toLower(); + return value == "0" || value == "false" || value == "no" || value == "off"; +} + +static bool vulkanNonDmabufDiagnosticsEnabled() +{ + static const bool enabled = envFlagExplicitlyEnabled("WAYLIB_VK_NON_DMABUF_DIAGNOSTICS") + || envFlagExplicitlyEnabled("TREELAND_VK_NON_DMABUF_DIAGNOSTICS"); + return enabled; +} + +static bool vulkanNonDmabufForceOpaqueEnabled() +{ + static const bool enabled = envFlagExplicitlyEnabled("WAYLIB_VK_NON_DMABUF_FORCE_OPAQUE") + || envFlagExplicitlyEnabled("TREELAND_VK_NON_DMABUF_FORCE_OPAQUE"); + return enabled; +} + +static bool vulkanNonDmabufForceReadbackEnabled() +{ + static const bool enabled = envFlagExplicitlyEnabled("WAYLIB_VK_NON_DMABUF_FORCE_READBACK") + || envFlagExplicitlyEnabled("TREELAND_VK_NON_DMABUF_FORCE_READBACK"); + return enabled; +} + +static bool vulkanNonDmabufPartialUploadEnabled() +{ + static const bool enabled = envFlagExplicitlyEnabled("WAYLIB_VK_NON_DMABUF_PARTIAL_UPLOAD") + || envFlagExplicitlyEnabled("TREELAND_VK_NON_DMABUF_PARTIAL_UPLOAD"); + return enabled; +} + +static bool vulkanUnsafeReadbackEnabled() +{ + static const bool enabled = envFlagExplicitlyEnabled("WAYLIB_VK_ALLOW_UNSAFE_READBACK") + || envFlagExplicitlyEnabled("TREELAND_VK_ALLOW_UNSAFE_READBACK"); + return enabled; +} + +struct Q_DECL_HIDDEN VulkanTextureImageSample { + int samples = 0; + int minAlpha = 0; + int maxAlpha = 0; + int nonZeroAlpha = 0; + int opaqueAlpha = 0; + int nonZeroColor = 0; + int maxColor = 0; + QRgb topLeft = 0; + QRgb center = 0; + QRgb bottomRight = 0; +}; + +static VulkanTextureImageSample sampleVulkanTextureImage(const QImage &image) +{ + VulkanTextureImageSample sample; + if (image.isNull()) + return sample; + + sample.minAlpha = 255; + sample.topLeft = image.pixel(0, 0); + sample.center = image.pixel(image.width() / 2, image.height() / 2); + sample.bottomRight = image.pixel(image.width() - 1, image.height() - 1); + + const int xStep = std::max(1, image.width() / 64); + const int yStep = std::max(1, image.height() / 64); + for (int y = 0; y < image.height(); y += yStep) { + for (int x = 0; x < image.width(); x += xStep) { + const QRgb pixel = image.pixel(x, y); + const int alpha = qAlpha(pixel); + const int maxChannel = std::max({ qRed(pixel), qGreen(pixel), qBlue(pixel) }); + sample.minAlpha = std::min(sample.minAlpha, alpha); + sample.maxAlpha = std::max(sample.maxAlpha, alpha); + sample.maxColor = std::max(sample.maxColor, maxChannel); + ++sample.samples; + if (alpha > 0) + ++sample.nonZeroAlpha; + if (alpha == 255) + ++sample.opaqueAlpha; + if (maxChannel > 0) + ++sample.nonZeroColor; + } + } + + if (sample.samples == 0) + sample.minAlpha = 0; + + return sample; +} + +static void logVulkanTextureImageDiagnostics(const char *source, const QImage &image, + uint32_t drmFormat, qsizetype stride, + bool sourceHasAlpha, bool forcedOpaque) +{ + if (!vulkanNonDmabufDiagnosticsEnabled()) + return; + + const auto sample = sampleVulkanTextureImage(image); + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture image diagnostics" + << "source" << source + << "size" << image.size() + << "imageFormat" << image.format() + << "drmFormat" << drmFormatToName(drmFormat) + << "stride" << stride + << "sourceAlpha" << sourceHasAlpha + << "forcedOpaque" << forcedOpaque + << "samples" << sample.samples + << "alphaRange" << sample.minAlpha << "-" << sample.maxAlpha + << "nonZeroAlpha" << sample.nonZeroAlpha + << "opaqueAlpha" << sample.opaqueAlpha + << "nonZeroColor" << sample.nonZeroColor + << "maxColor" << sample.maxColor + << "topLeft" << Qt::hex << quint32(sample.topLeft) + << "center" << quint32(sample.center) + << "bottomRight" << quint32(sample.bottomRight) << Qt::dec; +} + +static bool vulkanNonDmabufAlphaLooksInvalidOpaque(uint32_t drmFormat, + bool sourceHasAlpha, + const VulkanTextureImageSample &sample) +{ + return sourceHasAlpha + && drmFormatLikelyHasAlpha(drmFormat) + && sample.samples > 0 + && sample.maxAlpha == 0 + && sample.nonZeroColor > 0; +} + +static QImage forceOpaqueVulkanTextureImage(QImage image) +{ + if (image.isNull() || image.format() == QImage::Format_RGB32) + return image; + + image = image.convertToFormat(QImage::Format_RGB32); + image.setDevicePixelRatio(1.0); + return image; +} + +static qint64 rectArea(const QRect &rect) +{ + return qint64(rect.width()) * rect.height(); +} + +struct Q_DECL_HIDDEN VulkanBufferUploadRegions { + QVarLengthArray rects; + QRect bounds; + int inputRectCount = 0; + bool usedFullBuffer = false; + bool usedUnion = false; + const char *reason = "unknown"; + + qint64 uploadArea() const { + qint64 area = 0; + for (const auto &rect : rects) + area += rectArea(rect); + return area; + } +}; + +static VulkanBufferUploadRegions vulkanUploadRegionsFromSurfaceDamage(wlr_surface *surface, + const QSize &bufferSize, + bool forceFullBuffer) +{ + VulkanBufferUploadRegions result; + const QRect bufferRect(QPoint(0, 0), bufferSize); + if (bufferRect.isEmpty()) { + result.reason = "invalid-buffer-size"; + return result; + } + + auto useFullBuffer = [&result, bufferRect](const char *reason) { + result.rects.clear(); + result.rects.append(bufferRect); + result.bounds = bufferRect; + result.inputRectCount = 1; + result.usedFullBuffer = true; + result.reason = reason; + }; + + if (forceFullBuffer) { + useFullBuffer("new-or-resized-upload-texture"); + return result; + } + + if (!surface) { + useFullBuffer("missing-surface-damage"); + return result; + } + + const auto *bufferDamage = qw_surface::from(surface)->buffer_damage(); + if (!bufferDamage || !pixman_region32_not_empty(bufferDamage)) { + useFullBuffer("empty-surface-buffer-damage"); + return result; + } + + int rectCount = 0; + const pixman_box32_t *boxes = pixman_region32_rectangles(bufferDamage, &rectCount); + result.inputRectCount = rectCount; + if (!boxes || rectCount <= 0) { + useFullBuffer("unreadable-surface-buffer-damage"); + return result; + } + + QRect bounds; + for (int i = 0; i < rectCount; ++i) { + QRect rect(QPoint(boxes[i].x1, boxes[i].y1), + QPoint(boxes[i].x2 - 1, boxes[i].y2 - 1)); + rect = rect.intersected(bufferRect); + if (rect.isEmpty()) + continue; + + result.rects.append(rect); + bounds = bounds.isNull() ? rect : bounds.united(rect); + } + + if (result.rects.isEmpty()) { + result.reason = "surface-buffer-damage-outside-buffer"; + return result; + } + + result.bounds = bounds; + result.reason = "surface-buffer-damage"; + + static constexpr int maxIndividualUploadRects = 16; + if (result.rects.size() > maxIndividualUploadRects) { + result.rects.clear(); + result.rects.append(bounds); + result.usedUnion = true; + result.reason = "surface-buffer-damage-union"; + } + + return result; +} + +static QRhiTexture::Format rhiTextureFormatForVulkanUpload(QRhi *rhi, + QImage::Format imageFormat, + bool *usesBgra) +{ + if (usesBgra) + *usesBgra = false; + + switch (imageFormat) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32_Premultiplied: + if (rhi && rhi->isTextureFormatSupported(QRhiTexture::BGRA8)) { + if (usesBgra) + *usesBgra = true; + return QRhiTexture::BGRA8; + } + return QRhiTexture::UnknownFormat; + case QImage::Format_RGBX8888: + case QImage::Format_RGBA8888: + case QImage::Format_RGBA8888_Premultiplied: + return QRhiTexture::RGBA8; + default: + return QRhiTexture::UnknownFormat; + } +} + +static bool updateVulkanTextureFromBufferDataWithRhiUpload(QRhi *rhi, + QRhiCommandBuffer *commandBuffer, + QSGPlainTexture *texture, + const QImage &wrapped, + const QSize &size, + uint32_t drmFormat, + qsizetype stride, + bool sourceHasAlpha, + bool forcedOpaque, + wlr_surface *surface, + qw_buffer *buffer, + qw_texture *handle, + const VulkanTextureImageSample &sample) +{ + if (!vulkanNonDmabufPartialUploadEnabled() || !rhi || !commandBuffer + || !texture || wrapped.isNull()) { + return false; + } + + bool usesBgra = false; + const QRhiTexture::Format textureFormat = + rhiTextureFormatForVulkanUpload(rhi, wrapped.format(), &usesBgra); + if (textureFormat == QRhiTexture::UnknownFormat) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI non-dmabuf partial upload unavailable:" + << "unsupported image format" + << "buffer" << buffer + << "texture" << handle + << "size" << size + << "imageFormat" << wrapped.format() + << "drmFormat" << drmFormatToName(drmFormat); + return false; + } + + QRhiTexture *rhiTexture = texture->rhiTexture(); + QRhiTexture::Flags textureFlags; + const bool wantsMipmaps = texture->mipmapFiltering() != QSGTexture::None; + if (wantsMipmaps) + textureFlags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips; + const bool canReuseTexture = + texture->ownsTexture() + && rhiTexture + && rhiTexture->pixelSize() == size + && rhiTexture->format() == textureFormat + && rhiTexture->flags() == textureFlags; + const bool rebuildTexture = !canReuseTexture; + + if (rebuildTexture) { + auto *newTexture = rhi->newTexture(textureFormat, size, 1, textureFlags); + if (!newTexture || !newTexture->create()) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI non-dmabuf upload texture creation failed" + << "buffer" << buffer + << "texture" << handle + << "size" << size + << "format" << drmFormatToName(drmFormat) + << "rhiFormat" << textureFormat; + delete newTexture; + return false; + } + + if (texture->rhiTexture() && !texture->ownsTexture()) + texture->setTexture(nullptr); + + texture->setOwnsTexture(true); + texture->setTexture(newTexture); + rhiTexture = newTexture; + } + + const VulkanBufferUploadRegions regions = + vulkanUploadRegionsFromSurfaceDamage(surface, size, rebuildTexture); + if (regions.rects.isEmpty()) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI non-dmabuf upload skipped because damage is empty after clipping" + << "buffer" << buffer + << "texture" << handle + << "surface" << surface + << "size" << size + << "damageRects" << regions.inputRectCount + << "reason" << regions.reason + << "hasExistingTexture" << bool(rhiTexture); + texture->setHasAlphaChannel(sourceHasAlpha && !forcedOpaque); + texture->setTextureSize(size); + return bool(rhiTexture); + } + + QVarLengthArray entries; + entries.reserve(regions.rects.size()); + qint64 copiedBytes = 0; + for (const auto &rect : regions.rects) { + QImage image = wrapped.copy(rect); + if (forcedOpaque) + image = forceOpaqueVulkanTextureImage(std::move(image)); + + if (image.isNull()) + continue; + + copiedBytes += image.sizeInBytes(); + QRhiTextureSubresourceUploadDescription subres(image); + subres.setDestinationTopLeft(rect.topLeft()); + entries.append(QRhiTextureUploadEntry(0, 0, subres)); + } + + if (entries.isEmpty()) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI non-dmabuf upload unavailable: all dirty rect copies were empty" + << "buffer" << buffer + << "texture" << handle + << "size" << size + << "dirtyRects" << regions.rects.size(); + return false; + } + + auto *resourceUpdates = rhi->nextResourceUpdateBatch(); + QRhiTextureUploadDescription uploadDescription; + uploadDescription.setEntries(entries.begin(), entries.end()); + resourceUpdates->uploadTexture(rhiTexture, uploadDescription); + if (wantsMipmaps) + resourceUpdates->generateMips(rhiTexture); + commandBuffer->resourceUpdate(resourceUpdates); + + texture->setOwnsTexture(true); + texture->setTexture(rhiTexture); + texture->setHasAlphaChannel(sourceHasAlpha && !forcedOpaque); + texture->setTextureSize(size); + + const qint64 bufferArea = rectArea(QRect(QPoint(0, 0), size)); + const qint64 uploadArea = regions.uploadArea(); + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture uploaded from buffer data" + << "mode" << (rebuildTexture ? "rhi-full-upload" : "rhi-damage-upload") + << "buffer" << buffer + << "texture" << handle + << "surface" << surface + << "size" << size + << "format" << drmFormatToName(drmFormat) + << "imageFormat" << wrapped.format() + << "rhiFormat" << textureFormat + << "usesBgra" << usesBgra + << "stride" << stride + << "alpha" << texture->hasAlphaChannel() + << "forcedOpaque" << forcedOpaque + << "createdTexture" << rebuildTexture + << "damageRects" << regions.inputRectCount + << "uploadRects" << regions.rects.size() + << "usedFullBuffer" << regions.usedFullBuffer + << "usedUnion" << regions.usedUnion + << "uploadReason" << regions.reason + << "uploadBounds" << regions.bounds + << "uploadArea" << uploadArea + << "bufferArea" << bufferArea + << "uploadPermille" << (bufferArea > 0 ? uploadArea * 1000 / bufferArea : 0) + << "copiedBytes" << copiedBytes + << "sampleAlphaRange" << sample.minAlpha << "-" << sample.maxAlpha + << "sampleNonZeroColor" << sample.nonZeroColor + << "bufferExportsDmabuf" << false; + return true; +} + +static bool updateVulkanTextureFromImage(QSGPlainTexture *texture, QImage image, + bool hasAlpha) +{ + if (image.isNull()) + return false; + + if (texture->rhiTexture() && !texture->ownsTexture()) + texture->setTexture(nullptr); + + const QSize size = image.size(); + texture->setOwnsTexture(true); + texture->setImage(std::move(image)); + texture->setHasAlphaChannel(hasAlpha); + texture->setTextureSize(size); + return true; +} + +static bool updateVulkanTextureFromBufferData(qw_buffer *buffer, qw_texture *handle, + QSGPlainTexture *texture, + wlr_surface *surface, + QRhi *rhi, + QRhiCommandBuffer *commandBuffer) +{ + if (!buffer || !buffer->handle() || !handle || !handle->handle() || !texture) + return false; + + QElapsedTimer elapsed; + elapsed.start(); + + void *data = nullptr; + uint32_t drmFormat = DRM_FORMAT_INVALID; + size_t stride = 0; + if (!buffer->begin_data_ptr_access(WLR_BUFFER_DATA_PTR_ACCESS_READ, + &data, &drmFormat, &stride)) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture buffer-data upload skipped:" + << "data pointer access unavailable" + << "buffer" << buffer + << "texture" << handle + << "size" << QSize(buffer->handle()->width, buffer->handle()->height) + << "surface" << surface + << "commandBuffer" << commandBuffer + << "bufferExportsDmabuf" << bufferExportsDmabuf(buffer); + return false; + } + + const QSize size(buffer->handle()->width, buffer->handle()->height); + const QImage::Format imageFormat = WTools::toImageFormat(drmFormat); + bool updated = false; + bool forcedOpaque = false; + const bool sourceHasAlpha = handle->has_alpha(); + const bool forceSurfaceOpaque = surfaceOpaqueRegionCoversBuffer(surface, buffer); + if (!size.isEmpty() && data && stride > 0 && imageFormat != QImage::Format_Invalid) { + const QImage wrapped(static_cast(data), + size.width(), size.height(), qsizetype(stride), imageFormat); + const auto sample = sampleVulkanTextureImage(wrapped); + logVulkanTextureImageDiagnostics("buffer-data", wrapped, drmFormat, + qsizetype(stride), sourceHasAlpha, false); + const bool alphaLooksInvalidOpaque = + vulkanNonDmabufAlphaLooksInvalidOpaque(drmFormat, sourceHasAlpha, sample); + forcedOpaque = forceSurfaceOpaque + || vulkanNonDmabufForceOpaqueEnabled() + || alphaLooksInvalidOpaque; + if (alphaLooksInvalidOpaque) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture treating invalid zero-alpha buffer data as opaque" + << "buffer" << buffer + << "texture" << handle + << "size" << size + << "format" << drmFormatToName(drmFormat) + << "stride" << stride + << "alphaRange" << sample.minAlpha << "-" << sample.maxAlpha + << "nonZeroColor" << sample.nonZeroColor + << "maxColor" << sample.maxColor + << "surfaceOpaqueFull" << forceSurfaceOpaque; + } + + updated = updateVulkanTextureFromBufferDataWithRhiUpload(rhi, commandBuffer, + texture, wrapped, size, + drmFormat, qsizetype(stride), + sourceHasAlpha, forcedOpaque, + surface, buffer, handle, + sample); + if (!updated) { + QImage image = wrapped.copy(); + if (forcedOpaque) { + image = forceOpaqueVulkanTextureImage(std::move(image)); + logVulkanTextureImageDiagnostics("buffer-data-forced-opaque", image, + drmFormat, qsizetype(image.bytesPerLine()), + sourceHasAlpha, true); + } + + updated = updateVulkanTextureFromImage(texture, std::move(image), + sourceHasAlpha && !forcedOpaque); + if (updated) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture fell back to full QSGPlainTexture image upload" + << "buffer" << buffer + << "texture" << handle + << "surface" << surface + << "size" << size + << "format" << drmFormatToName(drmFormat) + << "imageFormat" << imageFormat + << "stride" << stride + << "hasCommandBuffer" << bool(commandBuffer) + << "partialUploadEnabled" << vulkanNonDmabufPartialUploadEnabled(); + } + } + } + + buffer->end_data_ptr_access(); + + if (updated) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture uploaded from buffer data" + << "mode" << "buffer-data-final" + << "buffer" << buffer + << "texture" << handle + << "surface" << surface + << "size" << size + << "format" << drmFormatToName(drmFormat) + << "stride" << stride + << "alpha" << texture->hasAlphaChannel() + << "forcedOpaque" << forcedOpaque + << "surfaceOpaqueFull" << forceSurfaceOpaque + << "bufferExportsDmabuf" << false + << "elapsedUs" << elapsed.nsecsElapsed() / 1000; + } else { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture buffer-data upload unavailable" + << "buffer" << buffer + << "texture" << handle + << "surface" << surface + << "size" << size + << "format" << drmFormatToName(drmFormat) + << "stride" << stride + << "imageFormat" << imageFormat + << "elapsedUs" << elapsed.nsecsElapsed() / 1000; } - return acceptApi; + return updated; } -static void updateGLTexture(QRhi *rhi, qw_texture *handle, QSGPlainTexture *texture) { - wlr_gles2_texture_attribs attribs; - wlr_gles2_texture_get_attribs(handle->handle(), &attribs); - QSize size(handle->handle()->width, handle->handle()->height); +static bool readVulkanTextureToImage(qw_texture *handle, QSGPlainTexture *texture, + uint32_t drmFormat, bool forceSurfaceOpaque) +{ + if (!handle || !handle->handle() || !texture) + return false; -#define GL_TEXTURE_EXTERNAL_OES 0x8D65 - QQuickWindowPrivate::TextureFromNativeTextureFlags flags = attribs.target == GL_TEXTURE_EXTERNAL_OES - ? QQuickWindowPrivate::NativeTextureIsExternalOES - : QQuickWindowPrivate::TextureFromNativeTextureFlags {}; - texture->setTextureFromNativeTexture(rhi, attribs.tex, 0, 0, size, {}, flags); + const QSize size(handle->handle()->width, handle->handle()->height); + const QImage::Format imageFormat = WTools::toImageFormat(drmFormat); + if (size.isEmpty() || imageFormat == QImage::Format_Invalid) + return false; - texture->setHasAlphaChannel(attribs.has_alpha); - texture->setTextureSize(size); -} + QImage image(size, imageFormat); + if (image.isNull()) + return false; -static inline quint64 vkimage_cast(void *image) { - return reinterpret_cast(image); + wlr_texture_read_pixels_options options = {}; + options.data = image.bits(); + options.format = drmFormat; + options.stride = uint32_t(image.bytesPerLine()); + if (!handle->read_pixels(&options)) + return false; + + const bool sourceHasAlpha = handle->has_alpha(); + const auto sample = sampleVulkanTextureImage(image); + logVulkanTextureImageDiagnostics("readback", image, drmFormat, + qsizetype(image.bytesPerLine()), + sourceHasAlpha, false); + + const bool alphaLooksInvalidOpaque = + vulkanNonDmabufAlphaLooksInvalidOpaque(drmFormat, sourceHasAlpha, sample); + const bool forcedOpaque = forceSurfaceOpaque + || vulkanNonDmabufForceOpaqueEnabled() + || alphaLooksInvalidOpaque; + if (alphaLooksInvalidOpaque) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture treating invalid zero-alpha readback as opaque" + << "texture" << handle + << "size" << size + << "format" << drmFormatToName(drmFormat) + << "alphaRange" << sample.minAlpha << "-" << sample.maxAlpha + << "nonZeroColor" << sample.nonZeroColor + << "maxColor" << sample.maxColor + << "surfaceOpaqueFull" << forceSurfaceOpaque; + } + if (forcedOpaque) { + image = forceOpaqueVulkanTextureImage(std::move(image)); + logVulkanTextureImageDiagnostics("readback-forced-opaque", image, + drmFormat, qsizetype(image.bytesPerLine()), + sourceHasAlpha, true); + return updateVulkanTextureFromImage(texture, std::move(image), false); + } + + return updateVulkanTextureFromImage(texture, + std::move(image), + sourceHasAlpha); } -[[maybe_unused]] static inline quint64 vkimage_cast(quint64 image) { - return image; +static bool updateVulkanTextureFromReadback(qw_buffer *buffer, qw_texture *handle, + QSGPlainTexture *texture, + wlr_surface *surface) +{ + if (!handle || !handle->handle() || !texture) + return false; + + if (handle->is_vk() && !vulkanUnsafeReadbackEnabled()) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture readback skipped:" + << "wlroots Vulkan texture read_pixels has unsafe layout ownership for live client textures" + << "buffer" << buffer + << "texture" << handle + << "surface" << surface + << "size" << QSize(handle->handle()->width, handle->handle()->height) + << "bufferExportsDmabuf" << bufferExportsDmabuf(buffer) + << "enableOverride" << "WAYLIB_VK_ALLOW_UNSAFE_READBACK=1"; + return false; + } + + if (bufferExportsDmabuf(buffer) + && !waitDmabufImplicitFence(buffer, DMA_BUF_SYNC_READ, + "Vulkan RHI client texture readback", + "implicit acquire")) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture readback rejected:" + << "implicit producer fence wait failed" + << "buffer" << buffer + << "texture" << handle + << "size" << QSize(handle->handle()->width, handle->handle()->height); + return false; + } + + const uint32_t preferredFormat = handle->preferred_read_format(); + const bool forceSurfaceOpaque = surfaceOpaqueRegionCoversBuffer(surface, buffer); + const uint32_t fallbackFormats[] = { + preferredFormat, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + }; + + const size_t fallbackFormatCount = sizeof(fallbackFormats) / sizeof(fallbackFormats[0]); + for (size_t i = 0; i < fallbackFormatCount; ++i) { + const uint32_t drmFormat = fallbackFormats[i]; + if (drmFormat == DRM_FORMAT_INVALID) + continue; + + bool alreadyTried = false; + for (size_t j = 0; j < i; ++j) { + if (fallbackFormats[j] == drmFormat) { + alreadyTried = true; + break; + } + } + if (alreadyTried) + continue; + + if (!readVulkanTextureToImage(handle, texture, drmFormat, forceSurfaceOpaque)) + continue; + + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture uploaded from wlroots readback" + << "buffer" << buffer + << "texture" << handle + << "size" << QSize(handle->handle()->width, handle->handle()->height) + << "format" << drmFormatToName(drmFormat) + << "alpha" << texture->hasAlphaChannel() + << "surfaceOpaqueFull" << forceSurfaceOpaque; + return true; + } + + qCWarning(lcWlRenderHelper) + << "Vulkan RHI client texture readback fallback failed" + << "buffer" << buffer + << "texture" << handle + << "size" << QSize(handle->handle()->width, handle->handle()->height) + << "preferredFormat" << drmFormatToName(preferredFormat); + return false; } -#ifdef ENABLE_VULKAN_RENDER -static void updateVKTexture(QRhi *rhi, qw_texture *handle, QSGPlainTexture *texture) { - wlr_vk_image_attribs attribs; - wlr_vk_texture_get_image_attribs(handle->handle(), &attribs); - QSize size(handle->handle()->width, handle->handle()->height); +static bool updateVulkanTextureFromNonDmabufBuffer(qw_buffer *buffer, qw_texture *handle, + QSGPlainTexture *texture, + wlr_surface *surface, + QRhi *rhi, + QRhiCommandBuffer *commandBuffer) +{ + if (!buffer || bufferExportsDmabuf(buffer)) + return false; - texture->setTextureFromNativeTexture(rhi, - vkimage_cast(attribs.image), - attribs.layout, attribs.format, size, - {}, {}); - texture->setHasAlphaChannel(wlr_vk_texture_has_alpha(handle->handle())); - texture->setTextureSize(size); + if (!vulkanNonDmabufForceReadbackEnabled() + && updateVulkanTextureFromBufferData(buffer, handle, texture, surface, + rhi, commandBuffer)) { + return true; + } + + if (vulkanNonDmabufForceReadbackEnabled()) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI non-dmabuf client texture buffer-data upload skipped by diagnostics env" + << "buffer" << buffer + << "texture" << handle + << "surface" << surface + << "size" << QSize(handle->handle()->width, handle->handle()->height) + << "unsafeReadbackEnabled" << vulkanUnsafeReadbackEnabled(); + } + + return updateVulkanTextureFromReadback(buffer, handle, texture, surface); } #endif static void updateImage(QRhi *, qw_texture *handle, QSGPlainTexture *texture) { - auto image = wlr_pixman_texture_get_image(handle->handle()); + auto image = handle->get_image(); + if (texture->rhiTexture() && !texture->ownsTexture()) + texture->setTexture(nullptr); + texture->setOwnsTexture(true); texture->setImage(WTools::fromPixmanImage(image)); } @@ -820,32 +4906,584 @@ static UpdateTextureFunction getUpdateTextFunction(qw_texture *handle) { const auto api = WRenderHelper::getGraphicsApi(); if (api == QSGRendererInterface::OpenGL) { - Q_ASSERT(wlr_texture_is_gles2(handle->handle())); - return updateGLTexture; + if (handle->is_gles2()) { + return updateGLTexture; + } +#ifdef ENABLE_VULKAN_RENDER + // Vulkan wlroots renderer with GL Qt RHI: handled separately in + // makeTexture() via updateEglDmabufTexture (needs buffer access). +#endif + return nullptr; } #ifdef ENABLE_VULKAN_RENDER else if (api == QSGRendererInterface::Vulkan) { - Q_ASSERT(wlr_texture_is_vk(handle->handle())); + Q_ASSERT(handle->is_vk()); return updateVKTexture; } #endif else if (api == QSGRendererInterface::Software) { - Q_ASSERT(wlr_texture_is_pixman(handle->handle())); + Q_ASSERT(handle->is_pixman()); return updateImage; } return nullptr; } -bool WRenderHelper::makeTexture(QRhi *rhi, qw_texture *handle, QSGPlainTexture *texture) +bool WRenderHelper::makeTexture(QRhi *rhi, qw_texture *handle, + QSGPlainTexture *texture, qw_buffer *buffer, + NativeTextureCleanup *nativeCleanup, + bool allowBufferDirectImport, + wlr_surface *surface, + QRhiCommandBuffer *commandBuffer) { +#ifdef ENABLE_VULKAN_RENDER + // Vulkan renderer + GL RHI: import the buffer's dmabuf as a GL texture + // via EGL. This is the client-surface counterpart of acquireRenderTarget. + // If EGL import fails (EGL_BAD_ALLOC on some modifiers), return false + // gracefully — the client window won't display but the system stays + // stable (no commit failure, no crash). + if (WRenderHelper::getGraphicsApi() == QSGRendererInterface::OpenGL + && handle->is_vk()) { + QSize size(handle->handle()->width, handle->handle()->height); + + // Try EGL dmabuf import first (for client surfaces with dmabuf buffers). + if (allowBufferDirectImport && buffer) { + wlr_dmabuf_attributes dmabuf; + if (buffer->get_dmabuf(&dmabuf)) { + EGLDisplay eglDisplay = eglGetCurrentDisplay(); + if (eglDisplay != EGL_NO_DISPLAY) { + EGLImage eglImage = EGL_NO_IMAGE; + GLuint glTex = 0; + if (eglImportDmabufToGLTexture(eglDisplay, &dmabuf, &eglImage, &glTex)) { + // NOTE: do NOT call wlr_dmabuf_attributes_finish() — + // qw_buffer::get_dmabuf returns a shallow reference + // (no fd dup). finish would close the buffer's own fds. + texture->setOwnsTexture(false); + texture->setTextureFromNativeTexture(rhi, glTex, 0, 0, size, {}, + QQuickWindowPrivate::TextureFromNativeTextureFlags{}); + texture->setHasAlphaChannel(handle->has_alpha()); + texture->setTextureSize(size); + if (nativeCleanup) { + *nativeCleanup = { + NativeTextureCleanup::Type::OpenGLTexture, + glTex, + reinterpret_cast(eglImage), + reinterpret_cast(eglDisplay), + }; + } + return texture->rhiTexture() != nullptr; + } + } + } + } + + // Fallback for shm/pixels textures (e.g. cursor QImage, no dmabuf): + // read pixels via wlr_texture_read_pixels and upload to a GL texture + // via glTexImage2D. This mirrors how QSGPlainTexture handles QImage + // textures in the software/GL path. + uint32_t fmt = DRM_FORMAT_ARGB8888; + // Use wlr_texture_preferred_read_format to get the optimal format. + uint32_t pref = handle->preferred_read_format(); + if (pref != 0) + fmt = pref; + + int bpp = 4; // ARGB8888 = 4 bytes per pixel + if (fmt == DRM_FORMAT_ARGB8888 || fmt == DRM_FORMAT_XRGB8888) { + bpp = 4; + } else { + // Unsupported read format for GL upload fallback + return false; + } + + const int stride = size.width() * bpp; + QByteArray pixels(size.height() * stride, 0); + + struct wlr_texture_read_pixels_options options = {}; + options.data = pixels.data(); + options.format = fmt; + options.stride = stride; + if (!handle->read_pixels(&options)) + return false; + + GLuint glTex = 0; + clearGlErrors(); + glGenTextures(1, &glTex); + if (!glTex) + return false; + + glBindTexture(GL_TEXTURE_2D, glTex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + // DRM_FORMAT_ARGB8888 maps to GL_BGRA + GL_UNSIGNED_BYTE on little-endian. + // QImage::Format_ARGB32 uses the same memory layout. + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size.width(), size.height(), 0, + GL_BGRA, GL_UNSIGNED_BYTE, pixels.constData()); + glBindTexture(GL_TEXTURE_2D, 0); + if (!textureUploadSucceeded()) { + glDeleteTextures(1, &glTex); + return false; + } + + texture->setOwnsTexture(false); + texture->setTextureFromNativeTexture(rhi, glTex, 0, 0, size, {}, + QQuickWindowPrivate::TextureFromNativeTextureFlags{}); + texture->setHasAlphaChannel(handle->has_alpha()); + texture->setTextureSize(size); + if (nativeCleanup) { + *nativeCleanup = { + NativeTextureCleanup::Type::OpenGLTexture, + glTex, + nullptr, + nullptr, + }; + } + return texture->rhiTexture() != nullptr; + } +#endif auto updateTexture = getUpdateTextFunction(handle); +#ifdef ENABLE_VULKAN_RENDER + const bool isVulkanRhiVkTexture = WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan + && handle->is_vk(); + const bool dmabufBackedBuffer = isVulkanRhiVkTexture && buffer && bufferExportsDmabuf(buffer); + if (allowBufferDirectImport && isVulkanRhiVkTexture && dmabufBackedBuffer && nativeCleanup) { + if (WRenderHelper::makeVulkanTextureFromBuffer(rhi, buffer, texture, nativeCleanup, + surface)) + return true; + + qCDebug(lcWlRenderHelper) + << "Vulkan RHI dmabuf texture import unavailable," + " trying synchronized fallback paths" + << "buffer" << buffer + << "texture" << handle + << "size" << handle->handle()->width << "x" << handle->handle()->height + << "allowBufferDirectImport" << allowBufferDirectImport; + } + + if (isVulkanRhiVkTexture && buffer && !dmabufBackedBuffer) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture using non-dmabuf buffer upload path" + << "buffer" << buffer + << "texture" << handle + << "size" << handle->handle()->width << "x" << handle->handle()->height + << "allowBufferDirectImport" << allowBufferDirectImport; + } + + if (isVulkanRhiVkTexture && buffer + && updateVulkanTextureFromNonDmabufBuffer(buffer, handle, texture, surface, + rhi, commandBuffer)) { + return true; + } + + if (isVulkanRhiVkTexture && buffer && dmabufBackedBuffer + && updateVulkanTextureFromReadback(buffer, handle, texture, surface)) { + return true; + } + + if (isVulkanRhiVkTexture && buffer) { + if (dmabufBackedBuffer) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI skipped unsafe wlroots native VkImage wrapper for dmabuf-backed texture" + << "buffer" << buffer + << "texture" << handle + << "size" << handle->handle()->width << "x" << handle->handle()->height + << "allowBufferDirectImport" << allowBufferDirectImport; + return false; + } + + qCDebug(lcWlRenderHelper) + << "Vulkan RHI texture using wlroots native VkImage wrapper" + << "buffer" << buffer + << "texture" << handle + << "size" << handle->handle()->width << "x" << handle->handle()->height + << "bufferExportsDmabuf" << dmabufBackedBuffer + << "allowBufferDirectImport" << allowBufferDirectImport; + } +#endif if (Q_UNLIKELY(!updateTexture)) return false; updateTexture(rhi, handle, texture); return true; } +bool WRenderHelper::makeOpenGLTextureFromBuffer(QRhi *rhi, qw_buffer *buffer, + QSGPlainTexture *texture, + NativeTextureCleanup *nativeCleanup) +{ +#ifdef ENABLE_VULKAN_RENDER + if (!rhi || !buffer || !texture || !nativeCleanup) + return false; + + if (rhi->backend() != QRhi::OpenGLES2) + return false; + + auto *handle = buffer->handle(); + if (!handle || handle->width <= 0 || handle->height <= 0) + return false; + + wlr_dmabuf_attributes dmabuf; + if (!buffer->get_dmabuf(&dmabuf)) { + qCDebug(lcWlRenderHelper) + << "Vulkan+GL direct texture import skipped: buffer has no dmabuf" + << buffer << "size" << handle->width << "x" << handle->height; + return false; + } + + EGLDisplay eglDisplay = eglGetCurrentDisplay(); + if (eglDisplay == EGL_NO_DISPLAY) { + qCWarning(lcWlRenderHelper) + << "Vulkan+GL direct texture import failed: no current EGL display" + << buffer << "size" << handle->width << "x" << handle->height; + return false; + } + + EGLImage eglImage = EGL_NO_IMAGE; + GLuint glTex = 0; + if (!eglImportDmabufToGLTexture(eglDisplay, &dmabuf, &eglImage, &glTex)) { + qCWarning(lcWlRenderHelper) + << "Vulkan+GL direct texture import failed" + << buffer + << "size" << handle->width << "x" << handle->height + << "format" << Qt::hex << dmabuf.format << Qt::dec + << "modifier" << Qt::hex << dmabuf.modifier << Qt::dec + << "planes" << dmabuf.n_planes; + return false; + } + + const QSize size(handle->width, handle->height); + NativeTextureCleanup cleanup { + NativeTextureCleanup::Type::OpenGLTexture, + glTex, + reinterpret_cast(eglImage), + reinterpret_cast(eglDisplay), + }; + + texture->setOwnsTexture(false); + texture->setTextureFromNativeTexture(rhi, glTex, 0, 0, size, {}, + QQuickWindowPrivate::TextureFromNativeTextureFlags{}); + texture->setHasAlphaChannel(drmFormatLikelyHasAlpha(dmabuf.format)); + texture->setTextureSize(size); + + if (!texture->rhiTexture()) { + qCWarning(lcWlRenderHelper) + << "Vulkan+GL direct texture import failed: QRhiTexture wrapper creation failed" + << buffer << "size" << size; + releaseNativeTexture(&cleanup); + return false; + } + + *nativeCleanup = cleanup; + qCDebug(lcWlRenderHelper) + << "Vulkan+GL direct texture import succeeded" + << buffer + << "size" << size + << "format" << Qt::hex << dmabuf.format << Qt::dec + << "modifier" << Qt::hex << dmabuf.modifier << Qt::dec + << "planes" << dmabuf.n_planes + << "alpha" << texture->hasAlphaChannel(); + return true; +#else + Q_UNUSED(rhi); + Q_UNUSED(buffer); + Q_UNUSED(texture); + Q_UNUSED(nativeCleanup); + return false; +#endif +} + +void WRenderHelper::releaseImportedVulkanTexture(ImportedVulkanTexture *importedTexture) +{ + if (!importedTexture) + return; + + delete importedTexture->texture; + importedTexture->texture = nullptr; + releaseNativeTexture(&importedTexture->nativeCleanup); + *importedTexture = {}; +} + +bool WRenderHelper::importVulkanTextureFromBuffer(QRhi *rhi, qw_buffer *buffer, + wlr_surface *surface, + ImportedVulkanTexture *importedTexture) +{ +#ifdef ENABLE_VULKAN_RENDER + if (!rhi || !buffer || !importedTexture) + return false; + + releaseImportedVulkanTexture(importedTexture); + + if (rhi->backend() != QRhi::Vulkan) + return false; + + auto *handle = buffer->handle(); + if (!handle || handle->width <= 0 || handle->height <= 0) + return false; + + const auto *handles = static_cast(rhi->nativeHandles()); + if (!handles || !handles->inst || handles->inst->vkInstance() == VK_NULL_HANDLE + || handles->physDev == VK_NULL_HANDLE || handles->dev == VK_NULL_HANDLE) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI dmabuf texture import rejected: QRhi Vulkan native handles unavailable" + << buffer << "size" << handle->width << "x" << handle->height; + return false; + } + + wlr_dmabuf_attributes dmabuf; + if (!buffer->get_dmabuf(&dmabuf)) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI dmabuf texture import skipped: buffer has no dmabuf" + << buffer << "size" << handle->width << "x" << handle->height; + return false; + } + + bool usedExplicitAcquire = false; + if (!waitSurfaceExplicitAcquireFence(surface, + "Vulkan RHI dmabuf texture", + &usedExplicitAcquire)) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI dmabuf texture import rejected: explicit acquire wait failed" + << buffer + << "size" << QSize(dmabuf.width, dmabuf.height) + << "format" << drmFormatToName(dmabuf.format) + << "modifier" << drmModifierToName(dmabuf.modifier) + << "planes" << dmabuf.n_planes; + return false; + } + + if (!usedExplicitAcquire + && !waitDmabufImplicitFence(buffer, DMA_BUF_SYNC_READ, + "Vulkan RHI dmabuf texture", "implicit acquire")) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI dmabuf texture import rejected: implicit producer fence wait failed" + << buffer + << "size" << QSize(dmabuf.width, dmabuf.height) + << "format" << drmFormatToName(dmabuf.format) + << "modifier" << drmModifierToName(dmabuf.modifier) + << "planes" << dmabuf.n_planes; + return false; + } + + auto imported = std::make_unique(); + if (!importDmabufAsVulkanNativeTexture(handles->inst->vkInstance(), + handles->physDev, + handles->dev, + &dmabuf, + imported.get())) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI dmabuf texture import failed: cannot create sampled VkImage" + << buffer + << "size" << QSize(dmabuf.width, dmabuf.height) + << "format" << drmFormatToName(dmabuf.format) + << "modifier" << drmModifierToName(dmabuf.modifier) + << "planes" << dmabuf.n_planes; + return false; + } + + if (!acquireVulkanNativeTextureForSampling(rhi, imported.get())) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI dmabuf texture import failed: cannot acquire image for Qt sampling" + << buffer + << "image" << Qt::hex << vulkanHandleToInteger(imported->image) << Qt::dec + << "size" << imported->size + << "format" << drmFormatToName(imported->drmFormat) + << "modifier" << drmModifierToName(imported->drmModifier); + destroyVulkanImportedNativeTexture(imported.get()); + return false; + } + + QRhiTexture::Flags textureFlags; + QRhiTexture::Format textureFormat = QRhiTexture::RGBA8; + QRhiTexture::Flags formatFlags; + const auto rhiFormat = + QSGRhiSupport::instance()->toRhiTextureFormat(imported->format, &formatFlags); + if (rhiFormat != QRhiTexture::UnknownFormat) { + textureFormat = rhiFormat; + textureFlags |= formatFlags; + } + + std::unique_ptr texture(rhi->newTexture(textureFormat, + imported->size, + 1, + textureFlags)); + if (!texture) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI dmabuf texture import failed: QRhiTexture allocation failed" + << buffer + << "image" << Qt::hex << vulkanHandleToInteger(imported->image) << Qt::dec + << "size" << imported->size + << "format" << drmFormatToName(imported->drmFormat) + << "modifier" << drmModifierToName(imported->drmModifier) + << "viewVkFormat" << imported->format; + destroyVulkanImportedNativeTexture(imported.get()); + return false; + } + + if (!texture->createFrom({vulkanHandleToInteger(imported->image), imported->layout})) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI dmabuf texture import failed: QRhiTexture::createFrom rejected native image" + << buffer + << "image" << Qt::hex << vulkanHandleToInteger(imported->image) << Qt::dec + << "size" << imported->size + << "format" << drmFormatToName(imported->drmFormat) + << "modifier" << drmModifierToName(imported->drmModifier) + << "viewVkFormat" << imported->format + << "layout" << vkImageLayoutName(imported->layout) + << "rhiFormat" << textureFormat + << "flags" << textureFlags; + texture.reset(); + destroyVulkanImportedNativeTexture(imported.get()); + return false; + } + + VulkanImportedNativeTexture *ownedImport = imported.release(); + importedTexture->texture = texture.release(); + importedTexture->nativeCleanup = { + NativeTextureCleanup::Type::VulkanTexture, + vulkanHandleToInteger(ownedImport->image), + nullptr, + nullptr, + ownedImport, + }; + importedTexture->size = ownedImport->size; + importedTexture->drmFormat = ownedImport->drmFormat; + importedTexture->drmModifier = ownedImport->drmModifier; + importedTexture->hasAlpha = drmFormatLikelyHasAlpha(dmabuf.format) + && !surfaceOpaqueRegionCoversBuffer(surface, buffer); + + qCDebug(lcWlRenderHelper) + << "Vulkan RHI dmabuf QRhiTexture import ready" + << buffer + << "image" << Qt::hex << vulkanHandleToInteger(ownedImport->image) << Qt::dec + << "size" << ownedImport->size + << "format" << drmFormatToName(ownedImport->drmFormat) + << "modifier" << drmModifierToName(ownedImport->drmModifier) + << "viewVkFormat" << ownedImport->format + << "layout" << vkImageLayoutName(ownedImport->layout) + << "planes" << dmabuf.n_planes + << "usedExplicitAcquire" << usedExplicitAcquire + << "usedImplicitAcquire" << !usedExplicitAcquire + << "rhiFormat" << textureFormat + << "alpha" << importedTexture->hasAlpha; + return true; +#else + Q_UNUSED(rhi); + Q_UNUSED(buffer); + Q_UNUSED(surface); + Q_UNUSED(importedTexture); + return false; +#endif +} + +bool WRenderHelper::acquireImportedVulkanTextureFromBuffer(QRhi *rhi, + qw_buffer *buffer, + wlr_surface *surface, + NativeTextureCleanup *nativeCleanup) +{ +#ifdef ENABLE_VULKAN_RENDER + if (!rhi || !buffer || !nativeCleanup + || nativeCleanup->type != NativeTextureCleanup::Type::VulkanTexture + || !nativeCleanup->nativeData) { + return false; + } + + wlr_dmabuf_attributes dmabuf = {}; + if (!buffer->get_dmabuf(&dmabuf)) + return false; + + bool usedExplicitAcquire = false; + if (!waitSurfaceExplicitAcquireFence(surface, + "Vulkan RHI cached dmabuf texture", + &usedExplicitAcquire)) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI cached dmabuf texture acquire rejected: explicit acquire wait failed" + << buffer + << "size" << QSize(dmabuf.width, dmabuf.height) + << "format" << drmFormatToName(dmabuf.format) + << "modifier" << drmModifierToName(dmabuf.modifier) + << "planes" << dmabuf.n_planes; + return false; + } + + if (!usedExplicitAcquire + && !waitDmabufImplicitFence(buffer, DMA_BUF_SYNC_READ, + "Vulkan RHI cached dmabuf texture", "implicit acquire")) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI cached dmabuf texture acquire rejected: implicit producer fence wait failed" + << buffer + << "size" << QSize(dmabuf.width, dmabuf.height) + << "format" << drmFormatToName(dmabuf.format) + << "modifier" << drmModifierToName(dmabuf.modifier) + << "planes" << dmabuf.n_planes; + return false; + } + + auto *imported = static_cast(nativeCleanup->nativeData); + if (!imported || !imported->isValid()) + return false; + + imported->layout = VK_IMAGE_LAYOUT_GENERAL; + if (!acquireVulkanNativeTextureForSampling(rhi, imported)) + return false; + + qCDebug(lcWlRenderHelper) + << "Vulkan RHI cached dmabuf texture reacquired for Qt sampling" + << buffer + << "image" << Qt::hex << vulkanHandleToInteger(imported->image) << Qt::dec + << "size" << imported->size + << "format" << drmFormatToName(imported->drmFormat) + << "modifier" << drmModifierToName(imported->drmModifier) + << "usedExplicitAcquire" << usedExplicitAcquire + << "usedImplicitAcquire" << !usedExplicitAcquire; + return true; +#else + Q_UNUSED(rhi); + Q_UNUSED(buffer); + Q_UNUSED(surface); + Q_UNUSED(nativeCleanup); + return false; +#endif +} + +bool WRenderHelper::makeVulkanTextureFromBuffer(QRhi *rhi, qw_buffer *buffer, + QSGPlainTexture *texture, + NativeTextureCleanup *nativeCleanup, + wlr_surface *surface) +{ +#ifdef ENABLE_VULKAN_RENDER + if (!rhi || !buffer || !texture || !nativeCleanup) + return false; + + ImportedVulkanTexture imported; + if (!importVulkanTextureFromBuffer(rhi, buffer, surface, &imported)) + return false; + + texture->setOwnsTexture(false); + texture->setTexture(imported.texture); + texture->setHasAlphaChannel(imported.hasAlpha); + texture->setTextureSize(imported.size); + *nativeCleanup = imported.nativeCleanup; + imported.texture = nullptr; + imported.nativeCleanup = {}; + + qCDebug(lcWlRenderHelper) + << "Vulkan RHI dmabuf texture import ready" + << buffer + << "rhiTexture" << texture->rhiTexture() + << "size" << texture->textureSize() + << "format" << drmFormatToName(imported.drmFormat) + << "modifier" << drmModifierToName(imported.drmModifier) + << "alpha" << texture->hasAlphaChannel(); + return true; +#else + Q_UNUSED(rhi); + Q_UNUSED(buffer); + Q_UNUSED(texture); + Q_UNUSED(nativeCleanup); + return false; +#endif +} + WRenderHelper::TextureEntry WRenderHelper::newTexture(qw_allocator *allocator, qw_renderer *renderer, uint32_t drmFormat, uint64_t drmModifier, @@ -865,49 +5503,153 @@ WRenderHelper::newTexture(qw_allocator *allocator, qw_renderer *renderer, qCCritical(lcWlRenderHelper) << "Failed to create qw_buffer from allocator"; return {}; } + auto *qbuffer = qw_buffer::from(buffer); + + const auto qformat = static_cast(rhiFormat); + const auto qflags = QRhiTexture::Flags(rhiFlags); + std::unique_ptr rhiTexture(rhi->newTexture(qformat, size, 1, qflags)); + NativeTextureCleanup nativeCleanup; + +#ifdef ENABLE_VULKAN_RENDER + const bool importVulkanRenderTarget = + rhi + && rhi->backend() == QRhi::Vulkan + && renderer + && renderer->handle() + && renderer->is_vk(); + if (importVulkanRenderTarget) { + const auto *handles = static_cast(rhi->nativeHandles()); + if (!handles || !handles->inst || handles->inst->vkInstance() == VK_NULL_HANDLE + || handles->physDev == VK_NULL_HANDLE || handles->dev == VK_NULL_HANDLE) { + qCCritical(lcWlRenderHelper) + << "Vulkan RHI newTexture: QRhi Vulkan native handles unavailable"; + qbuffer->drop(); + return {}; + } + + wlr_dmabuf_attributes dmabuf = {}; + if (!qbuffer->get_dmabuf(&dmabuf)) { + qCCritical(lcWlRenderHelper) + << "Vulkan RHI newTexture: buffer has no dmabuf"; + qbuffer->drop(); + return {}; + } + + const VkImageUsageFlags imageUsage = vulkanImageUsageForRhiTextureFlags(qflags); + auto imported = std::make_unique(); + if (!importDmabufAsVulkanRenderTarget(handles->inst->vkInstance(), + handles->physDev, + handles->dev, + &dmabuf, + imageUsage, + false, + imported.get())) { + qCCritical(lcWlRenderHelper) + << "Vulkan RHI newTexture: failed to import dmabuf as renderable QRhiTexture" + << "size" << size + << "format" << drmFormatToName(dmabuf.format) + << "modifier" << drmModifierToName(dmabuf.modifier) + << "usage" << Qt::hex << imageUsage << Qt::dec; + qbuffer->drop(); + return {}; + } + + if (!rhiTexture->createFrom({vulkanHandleToInteger(imported->image), + imported->layout})) { + qCCritical(lcWlRenderHelper) + << "Vulkan RHI newTexture: createFrom imported render target failed" + << "image" << Qt::hex << vulkanHandleToInteger(imported->image) + << "layout" << imported->layout << Qt::dec + << "size" << imported->size + << "format" << drmFormatToName(imported->drmFormat) + << "modifier" << drmModifierToName(imported->drmModifier); + destroyVulkanImportedRenderTarget(imported.get()); + qbuffer->drop(); + return {}; + } + + nativeCleanup = { + NativeTextureCleanup::Type::VulkanRenderTarget, + vulkanHandleToInteger(imported->image), + nullptr, + nullptr, + imported.release(), + }; + + rhiTexture->setName("WaylibTexture"); + return {buffer, nullptr, rhiTexture.release(), nativeCleanup}; + } +#endif - std::unique_ptr texture(qw_texture::from_buffer(*renderer, buffer)); + std::unique_ptr texture(qw_texture::from_buffer(*renderer, *qbuffer)); if (!texture) { qCCritical(lcWlRenderHelper) << "Failed to create qw_texture from buffer"; - wlr_buffer_drop(buffer); + qbuffer->drop(); return {}; } - const auto qformat = static_cast(rhiFormat); - const auto qflags = QRhiTexture::Flags(rhiFlags); - std::unique_ptr rhiTexture(rhi->newTexture(qformat, size, 1, qflags)); - - if (wlr_texture_is_gles2(*texture.get())) { + if (texture->is_gles2()) { if (rhi->backend() != QRhi::OpenGLES2) { qFatal("The current QRhi backend doesn't support creating texture from GLES2 texture"); } wlr_gles2_texture_attribs attribs; - wlr_gles2_texture_get_attribs(*texture.get(), &attribs); + texture->get_attribs(&attribs); if (!rhiTexture->createFrom({attribs.tex, 0})) { qCCritical(lcWlRenderHelper, "Failed to create QRhiTexture from GLES2 texture"); - wlr_buffer_drop(buffer); + qbuffer->drop(); return {}; } } #ifdef ENABLE_VULKAN_RENDER - else if (wlr_texture_is_vk(*texture.get())) { - if (rhi->backend() != QRhi::Vulkan) { + else if (texture->is_vk()) { + // Historical bridge for callers that still combine a Vulkan wlroots + // renderer with an OpenGL QRhi. treeland itself forces Vulkan QRhi when + // WLR_RENDERER=vulkan, so normal render-buffer-node allocations are + // imported above as renderable Vulkan images before creating qw_texture. + if (rhi->backend() == QRhi::Vulkan) { + wlr_vk_image_attribs vkAttribs; + texture->get_image_attribs(&vkAttribs); + + if (!rhiTexture->createFrom({vkimage_cast(vkAttribs.image), vkAttribs.layout})) { + qCCritical(lcWlRenderHelper, "Failed to create QRhiTexture from Vulkan image"); + qbuffer->drop(); + return {}; + } + } else if (rhi->backend() == QRhi::OpenGLES2) { + wlr_dmabuf_attributes dmabuf; + if (!qbuffer->get_dmabuf(&dmabuf)) { + qCCritical(lcWlRenderHelper, "Vulkan+GL newTexture: buffer has no dmabuf"); + qbuffer->drop(); + return {}; + } + EGLDisplay eglDisplay = eglGetCurrentDisplay(); + EGLImage eglImage = EGL_NO_IMAGE; + GLuint glTex = 0; + if (!eglImportDmabufToGLTexture(eglDisplay, &dmabuf, &eglImage, &glTex)) { + qCCritical(lcWlRenderHelper, "Vulkan+GL newTexture: EGL dmabuf import failed"); + qbuffer->drop(); + return {}; + } + nativeCleanup = { + NativeTextureCleanup::Type::OpenGLTexture, + glTex, + reinterpret_cast(eglImage), + reinterpret_cast(eglDisplay), + }; + if (!rhiTexture->createFrom({glTex, 0})) { + qCCritical(lcWlRenderHelper, "Vulkan+GL newTexture: createFrom GL texture failed"); + releaseNativeTexture(&nativeCleanup); + qbuffer->drop(); + return {}; + } + } else { qFatal("The current QRhi backend doesn't support creating texture from Vulkan image"); } - - wlr_vk_image_attribs attribs; - wlr_vk_texture_get_image_attribs(*texture.get(), &attribs); - - if (!rhiTexture->createFrom({vkimage_cast(attribs.image), attribs.layout})) { - qCCritical(lcWlRenderHelper, "Failed to create QRhiTexture from Vulkan image"); - wlr_buffer_drop(buffer); - return {}; - } } #endif - else if (wlr_texture_is_pixman(*texture.get())) { + else if (texture->is_pixman()) { qFatal("Creating QRhiTexture from Pixman image is not supported"); } else { qFatal("Unknown texture type"); @@ -915,7 +5657,7 @@ WRenderHelper::newTexture(qw_allocator *allocator, qw_renderer *renderer, rhiTexture->setName("WaylibTexture"); - return {buffer, texture.release(), rhiTexture.release()}; + return {buffer, texture.release(), rhiTexture.release(), nativeCleanup}; } WRenderHelper::TextureEntry diff --git a/waylib/src/server/qtquick/wrenderhelper.h b/waylib/src/server/qtquick/wrenderhelper.h index 789edb5ca..0b3fe49ee 100644 --- a/waylib/src/server/qtquick/wrenderhelper.h +++ b/waylib/src/server/qtquick/wrenderhelper.h @@ -10,11 +10,15 @@ #include #include +#include + QT_BEGIN_NAMESPACE class QQuickRenderControl; +class QRhiTexture; class QSGTexture; class QSGPlainTexture; class QRhi; +class QRhiCommandBuffer; QT_END_NAMESPACE QW_BEGIN_NAMESPACE @@ -26,6 +30,7 @@ class qw_texture; QW_END_NAMESPACE struct wlr_buffer; +struct wlr_surface; WAYLIB_SERVER_BEGIN_NAMESPACE @@ -48,19 +53,82 @@ 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 renderTargetForBuffer(QW_NAMESPACE::qw_buffer *buffer, + bool preserveColorContents) const; std::pair lastRenderTarget() const; + bool prepareVulkanRenderTargetForQt(QRhi *rhi, QRhiTexture *texture, + QW_NAMESPACE::qw_buffer *buffer); + bool releaseVulkanRenderTargetToScanout(QRhi *rhi, QRhiTexture *texture, + QW_NAMESPACE::qw_buffer *buffer); 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); static void setupRendererBackend(QW_NAMESPACE::qw_backend *testBackend = nullptr); static QSGRendererInterface::GraphicsApi probe(QW_NAMESPACE::qw_backend *testBackend, const QList &apiList); - static bool makeTexture(QRhi *rhi, QW_NAMESPACE::qw_texture *handle, QSGPlainTexture *texture); + struct NativeTextureCleanup { + enum class Type { + None, + OpenGLTexture, + VulkanTexture, + VulkanRenderTarget, + }; + + Type type = Type::None; + quint64 texture = 0; + void *eglImage = nullptr; + void *eglDisplay = nullptr; + void *nativeData = nullptr; + }; + + static void releaseNativeTexture(NativeTextureCleanup *cleanup); + + struct ImportedVulkanTexture { + QRhiTexture *texture = nullptr; + NativeTextureCleanup nativeCleanup; + QSize size; + uint32_t drmFormat = 0; + uint64_t drmModifier = 0; + bool hasAlpha = false; + + bool isValid() const { return texture && !size.isEmpty(); } + }; + + static bool importVulkanTextureFromBuffer(QRhi *rhi, QW_NAMESPACE::qw_buffer *buffer, + wlr_surface *surface, + ImportedVulkanTexture *importedTexture); + static bool acquireImportedVulkanTextureFromBuffer(QRhi *rhi, + QW_NAMESPACE::qw_buffer *buffer, + wlr_surface *surface, + NativeTextureCleanup *nativeCleanup); + static void releaseImportedVulkanTexture(ImportedVulkanTexture *importedTexture); + + static bool makeTexture(QRhi *rhi, QW_NAMESPACE::qw_texture *handle, + QSGPlainTexture *texture, QW_NAMESPACE::qw_buffer *buffer = nullptr, + NativeTextureCleanup *nativeCleanup = nullptr, + bool allowBufferDirectImport = true, + wlr_surface *surface = nullptr, + QRhiCommandBuffer *commandBuffer = nullptr); + static bool makeOpenGLTextureFromBuffer(QRhi *rhi, QW_NAMESPACE::qw_buffer *buffer, + QSGPlainTexture *texture, + NativeTextureCleanup *nativeCleanup); + static bool makeVulkanTextureFromBuffer(QRhi *rhi, QW_NAMESPACE::qw_buffer *buffer, + QSGPlainTexture *texture, + NativeTextureCleanup *nativeCleanup, + wlr_surface *surface = nullptr); + + // Legacy explicit-sync experiment kept for compatibility with older local + // tests. The active Qt RHI Vulkan output path uses + // prepareVulkanRenderTargetForQt() and releaseVulkanRenderTargetToScanout() + // so layout and external queue-family ownership stay tracked per buffer. + static void transitionVkImageToGeneral(QRhi *rhi, QRhiTexture *texture, + QW_NAMESPACE::qw_buffer *buffer); struct TextureEntry { wlr_buffer *buffer; QW_NAMESPACE::qw_texture *texture; QRhiTexture *rhiTexture; + NativeTextureCleanup nativeCleanup; }; static TextureEntry newTexture(QW_NAMESPACE::qw_allocator *allocator, QW_NAMESPACE::qw_renderer *renderer, diff --git a/waylib/src/server/qtquick/wsgtextureprovider.cpp b/waylib/src/server/qtquick/wsgtextureprovider.cpp index 362c84160..d7cb8a8c0 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.cpp +++ b/waylib/src/server/qtquick/wsgtextureprovider.cpp @@ -11,11 +11,67 @@ #include #include +#include +#include #include #include +#include + +#ifdef ENABLE_VULKAN_RENDER +extern "C" { +#include +} +#endif + WAYLIB_SERVER_BEGIN_NAMESPACE +static quintptr pointerAddress(const void *ptr) +{ + return reinterpret_cast(ptr); +} + +static QSize bufferSize(qw_buffer *buffer) +{ + return buffer && buffer->handle() + ? QSize(buffer->handle()->width, buffer->handle()->height) + : QSize(); +} + +static int bufferLocks(qw_buffer *buffer) +{ + return buffer && buffer->handle() ? buffer->handle()->n_locks : -1; +} + +#ifdef ENABLE_VULKAN_RENDER +static constexpr int s_deferredVulkanTextureReleaseFrames = 3; +static constexpr int s_vulkanDmabufTextureCacheCapacity = 8; + +static bool envFlagExplicitlyDisabled(const char *name) +{ + const QByteArray value = qgetenv(name).trimmed().toLower(); + return value == "0" || value == "false" || value == "no" || value == "off"; +} + +static bool bufferHasDmabuf(qw_buffer *buffer) +{ + if (!buffer || !buffer->handle()) + return false; + + wlr_dmabuf_attributes dmabuf = {}; + return buffer->get_dmabuf(&dmabuf); +} + +static bool directDmabufImportEnabled() +{ + static const bool enabled = !envFlagExplicitlyDisabled("WAYLIB_VK_DIRECT_DMABUF") + && !envFlagExplicitlyDisabled("TREELAND_VK_DIRECT_DMABUF") + && !envFlagExplicitlyDisabled("WAYLIB_VK_DIRECT_CLIENT_DMABUF") + && !envFlagExplicitlyDisabled("TREELAND_VK_DIRECT_CLIENT_DMABUF"); + return enabled; +} +#endif + class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate { public: @@ -32,44 +88,421 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate ~WSGTextureProviderPrivate() { cleanTexture(); +#ifdef ENABLE_VULKAN_RENDER + processDeferredVulkanTextureReleases(true); +#endif } - void cleanTexture() { - if (rhiTexture) { - Q_ASSERT(window); - class TextureCleanupJob : public QRunnable - { - public: - TextureCleanupJob(QRhiTexture *texture) - : texture(texture) { } - void run() override { - texture->deleteLater(); + bool isVulkanRenderer() const { +#ifdef ENABLE_VULKAN_RENDER + return window && window->renderer() + && window->renderer()->is_vk(); +#else + return false; +#endif + } + +#ifdef ENABLE_VULKAN_RENDER + struct DeferredVulkanTextureRelease { + WRenderHelper::NativeTextureCleanup nativeCleanup; + int framesLeft = 0; + }; + + struct VulkanDmabufTextureCacheEntry { + QPointer buffer; + QRhiTexture *rhiTexture = nullptr; + WRenderHelper::NativeTextureCleanup nativeCleanup; + QSize size; + uint32_t drmFormat = 0; + uint64_t drmModifier = 0; + bool hasAlpha = false; + quint64 lastUsed = 0; + }; + + void ensureDeferredVulkanTextureReleaseConnection() + { + if (deferredVulkanTextureReleaseConnection || !window) + return; + + W_Q(WSGTextureProvider); + deferredVulkanTextureReleaseConnection = + QObject::connect(window, &WOutputRenderWindow::renderEnd, + q, [this] { + processDeferredVulkanTextureReleases(false); + }); + } + + void processDeferredVulkanTextureReleases(bool force) + { + if (deferredVulkanTextureReleases.isEmpty()) + return; + + if (force && window && window->rhi()) + window->rhi()->finish(); + + for (int i = deferredVulkanTextureReleases.size() - 1; i >= 0; --i) { + auto &release = deferredVulkanTextureReleases[i]; + if (!force && --release.framesLeft > 0) + continue; + + auto cleanup = release.nativeCleanup; + deferredVulkanTextureReleases.removeAt(i); + WRenderHelper::releaseNativeTexture(&cleanup); + } + } + + void deferVulkanNativeTextureRelease(WRenderHelper::NativeTextureCleanup *cleanup, + QRhiTexture *rhiTexture) + { + if (!cleanup || cleanup->type != WRenderHelper::NativeTextureCleanup::Type::VulkanTexture) + return; + + if (rhiTexture) + rhiTexture->deleteLater(); + + deferredVulkanTextureReleases.append({ + *cleanup, + s_deferredVulkanTextureReleaseFrames, + }); + qCDebug(lcWlQtQuickTexture) + << "Vulkan renderer deferred old client texture native release" + << "rhiTexture" << rhiTexture + << "frames" << s_deferredVulkanTextureReleaseFrames + << "pending" << deferredVulkanTextureReleases.size(); + + *cleanup = {}; + ensureDeferredVulkanTextureReleaseConnection(); + } + + int findVulkanDmabufTextureCacheEntry(qw_buffer *newBuffer) const + { + if (!newBuffer) + return -1; + + for (int i = 0; i < vulkanDmabufTextureCache.size(); ++i) { + if (vulkanDmabufTextureCache[i].buffer == newBuffer) + return i; + } + + return -1; + } + + void releaseVulkanDmabufTextureCacheEntry(int index) + { + if (index < 0 || index >= vulkanDmabufTextureCache.size()) + return; + + auto entry = vulkanDmabufTextureCache.takeAt(index); + if (entry.rhiTexture == rhiTexture) { + qtTexture.setTexture(nullptr); + rhiTexture = nullptr; + nativeCleanup = {}; + if (buffer == entry.buffer) + buffer = nullptr; + } + + qCDebug(lcWlQtQuickTexture) + << "Vulkan renderer dmabuf texture cache evicted" + << "buffer" << pointerAddress(entry.buffer.data()) + << "rhiTexture" << entry.rhiTexture + << "size" << entry.size + << "format" << Qt::hex << entry.drmFormat << Qt::dec + << "modifier" << Qt::hex << entry.drmModifier << Qt::dec + << "remaining" << vulkanDmabufTextureCache.size(); + + releaseDetachedTexture(nullptr, false, entry.rhiTexture, entry.nativeCleanup); + } + + void trimVulkanDmabufTextureCache() + { + for (int i = vulkanDmabufTextureCache.size() - 1; i >= 0; --i) { + const auto &entry = vulkanDmabufTextureCache[i]; + if (!entry.buffer && entry.rhiTexture != rhiTexture) + releaseVulkanDmabufTextureCacheEntry(i); + } + + while (vulkanDmabufTextureCache.size() > s_vulkanDmabufTextureCacheCapacity) { + int evictIndex = -1; + quint64 oldestUse = std::numeric_limits::max(); + for (int i = 0; i < vulkanDmabufTextureCache.size(); ++i) { + const auto &entry = vulkanDmabufTextureCache[i]; + if (entry.rhiTexture == rhiTexture) + continue; + + if (entry.lastUsed < oldestUse) { + oldestUse = entry.lastUsed; + evictIndex = i; } - QRhiTexture *texture; - }; + } + + if (evictIndex < 0) + break; + + releaseVulkanDmabufTextureCacheEntry(evictIndex); + } + } + + void clearVulkanDmabufTextureCache() + { + while (!vulkanDmabufTextureCache.isEmpty()) + releaseVulkanDmabufTextureCacheEntry(vulkanDmabufTextureCache.size() - 1); + } + + void activateVulkanDmabufTextureCacheEntry(int index, qw_buffer *newBuffer, + const char *backendName, bool cacheHit) + { + auto &entry = vulkanDmabufTextureCache[index]; + entry.lastUsed = ++vulkanDmabufTextureUseSerial; + + auto oldTexture = texture; + const bool oldOwnsTexture = ownsTexture; + auto oldRhiTexture = rhiTexture; + auto oldNativeCleanup = nativeCleanup; + + texture = nullptr; + ownsTexture = false; + buffer = newBuffer; + qtTexture.setOwnsTexture(false); + qtTexture.setTexture(entry.rhiTexture); + qtTexture.setHasAlphaChannel(entry.hasAlpha); + qtTexture.setTextureSize(entry.size); + rhiTexture = entry.rhiTexture; + nativeCleanup = {}; + + qCDebug(lcWlQtQuickTexture) + << "Vulkan renderer dmabuf texture import path" + << "qtBackend" << backendName + << "cacheHit" << cacheHit + << "buffer" << pointerAddress(newBuffer) + << "rhiTexture" << rhiTexture + << "size" << entry.size + << "format" << Qt::hex << entry.drmFormat << Qt::dec + << "modifier" << Qt::hex << entry.drmModifier << Qt::dec + << "locks" << bufferLocks(newBuffer) + << "alpha" << qtTexture.hasAlphaChannel() + << "cacheSize" << vulkanDmabufTextureCache.size(); + + releaseDetachedTexture(oldTexture, oldOwnsTexture, oldRhiTexture, oldNativeCleanup); + } +#endif - // Delay clean the qt rhi textures. - window->scheduleRenderJob(new TextureCleanupJob(rhiTexture), - QQuickWindow::AfterSynchronizingStage); + void releaseDetachedTexture(qw_texture *texture, bool ownsTexture, + QRhiTexture *rhiTexture, + WRenderHelper::NativeTextureCleanup nativeCleanup) + { +#ifdef ENABLE_VULKAN_RENDER + if (nativeCleanup.type == WRenderHelper::NativeTextureCleanup::Type::VulkanTexture) { + deferVulkanNativeTextureRelease(&nativeCleanup, rhiTexture); + } else +#endif + { + if (nativeCleanup.type != WRenderHelper::NativeTextureCleanup::Type::None) + delete rhiTexture; + WRenderHelper::releaseNativeTexture(&nativeCleanup); + } + + if (ownsTexture && texture) + delete texture; + } + + void cleanTexture() { + const bool releaseImportedWrapper = + nativeCleanup.type != WRenderHelper::NativeTextureCleanup::Type::None; + QRhiTexture *importedWrapper = releaseImportedWrapper ? rhiTexture : nullptr; + + // Native imports are non-owned by QSGPlainTexture and are released here + // together with their import objects. QImage fallbacks switch the + // QSGPlainTexture back to owned mode, so setTexture(nullptr) deletes + // those Qt-created textures normally. + if (rhiTexture) { + qtTexture.setTexture(nullptr); rhiTexture = nullptr; } + releaseDetachedTexture(nullptr, false, importedWrapper, nativeCleanup); + nativeCleanup = {}; if (ownsTexture && texture) delete texture; texture = nullptr; + ownsTexture = false; + buffer = nullptr; +#ifdef ENABLE_VULKAN_RENDER + clearVulkanDmabufTextureCache(); +#endif + } + + bool replaceTexture(qw_texture *newTexture, bool newOwnsTexture, qw_buffer *newBuffer, + bool allowBufferDirectImport = true, wlr_surface *surface = nullptr, + QRhiCommandBuffer *commandBuffer = nullptr) { + Q_ASSERT(newTexture); + + WRenderHelper::NativeTextureCleanup newCleanup; + if (!WRenderHelper::makeTexture(window->rhi(), newTexture, &qtTexture, + newBuffer, &newCleanup, allowBufferDirectImport, + surface, commandBuffer)) { + WRenderHelper::releaseNativeTexture(&newCleanup); +#ifdef ENABLE_VULKAN_RENDER + if (newBuffer && !allowBufferDirectImport && window && window->rhi() + && window->rhi()->backend() == QRhi::Vulkan) { + const bool hasExistingTexture = hasTexture(); + qCDebug(lcWlQtQuickTexture) + << "Vulkan renderer kept existing texture after unsafe native image wrapper was refused" + << "texture" << pointerAddress(newTexture) + << "buffer" << pointerAddress(newBuffer) + << "size" << newTexture->handle()->width << "x" << newTexture->handle()->height + << "keptExistingTexture" << hasExistingTexture; + if (newOwnsTexture && newTexture != texture) + delete newTexture; + return hasExistingTexture; + } +#endif + qCWarning(lcWlQtQuickTexture) + << "Failed to make texture" + << "texture" << pointerAddress(newTexture) + << "size" << newTexture->handle()->width << "x" << newTexture->handle()->height; + if (newOwnsTexture) + delete newTexture; + return false; + } + + auto oldTexture = texture; + const bool oldOwnsTexture = ownsTexture; + auto oldRhiTexture = rhiTexture; + auto oldNativeCleanup = nativeCleanup; + + texture = newTexture; + ownsTexture = newOwnsTexture; + buffer = newBuffer; + rhiTexture = qtTexture.rhiTexture(); + nativeCleanup = newCleanup; + + releaseDetachedTexture(oldTexture == newTexture ? nullptr : oldTexture, + oldTexture == newTexture ? false : oldOwnsTexture, + oldRhiTexture, + oldNativeCleanup); + return true; + } + + bool replaceBufferWithDirectDmabufTexture(qw_buffer *newBuffer, wlr_surface *surface) + { +#ifdef ENABLE_VULKAN_RENDER + if (!newBuffer || !window || !window->rhi()) + return false; + + WRenderHelper::NativeTextureCleanup newCleanup; + const auto backend = window->rhi()->backend(); + bool imported = false; + const char *backendName = "unknown RHI"; + if (backend == QRhi::OpenGLES2) { + backendName = "OpenGL RHI"; + imported = WRenderHelper::makeOpenGLTextureFromBuffer(window->rhi(), newBuffer, + &qtTexture, &newCleanup); + } else if (backend == QRhi::Vulkan) { + backendName = "Vulkan RHI"; + trimVulkanDmabufTextureCache(); + + int cacheIndex = findVulkanDmabufTextureCacheEntry(newBuffer); + if (cacheIndex >= 0) { + auto &entry = vulkanDmabufTextureCache[cacheIndex]; + if (WRenderHelper::acquireImportedVulkanTextureFromBuffer(window->rhi(), + newBuffer, + surface, + &entry.nativeCleanup)) { + activateVulkanDmabufTextureCacheEntry(cacheIndex, newBuffer, + backendName, true); + return true; + } + + qCDebug(lcWlQtQuickTexture) + << "Vulkan renderer dmabuf texture cache entry could not be reacquired," + " evicting and reimporting" + << "buffer" << pointerAddress(newBuffer) + << "rhiTexture" << entry.rhiTexture + << "size" << entry.size; + releaseVulkanDmabufTextureCacheEntry(cacheIndex); + } + + WRenderHelper::ImportedVulkanTexture importedTexture; + imported = WRenderHelper::importVulkanTextureFromBuffer(window->rhi(), newBuffer, + surface, &importedTexture); + if (imported) { + VulkanDmabufTextureCacheEntry entry; + entry.buffer = newBuffer; + entry.rhiTexture = importedTexture.texture; + entry.nativeCleanup = importedTexture.nativeCleanup; + entry.size = importedTexture.size; + entry.drmFormat = importedTexture.drmFormat; + entry.drmModifier = importedTexture.drmModifier; + entry.hasAlpha = importedTexture.hasAlpha; + importedTexture.texture = nullptr; + importedTexture.nativeCleanup = {}; + + vulkanDmabufTextureCache.append(entry); + cacheIndex = vulkanDmabufTextureCache.size() - 1; + activateVulkanDmabufTextureCacheEntry(cacheIndex, newBuffer, + backendName, false); + trimVulkanDmabufTextureCache(); + return true; + } + } + + if (!imported) { + return false; + } + + auto oldTexture = texture; + const bool oldOwnsTexture = ownsTexture; + auto oldRhiTexture = rhiTexture; + auto oldNativeCleanup = nativeCleanup; + + texture = nullptr; + ownsTexture = false; + buffer = newBuffer; + rhiTexture = qtTexture.rhiTexture(); + nativeCleanup = newCleanup; + + qCDebug(lcWlQtQuickTexture) + << "Vulkan renderer dmabuf texture import path" + << "qtBackend" << backendName + << "buffer" << pointerAddress(newBuffer) + << "size" << bufferSize(newBuffer) + << "locks" << bufferLocks(newBuffer) + << "alpha" << qtTexture.hasAlphaChannel(); + + releaseDetachedTexture(oldTexture, oldOwnsTexture, oldRhiTexture, oldNativeCleanup); + return true; +#else + Q_UNUSED(newBuffer); + return false; +#endif + } + + bool hasTexture() const + { + return texture || rhiTexture; } - void updateRhiTexture() { + void updateRhiTexture(QRhiCommandBuffer *commandBuffer = nullptr) { Q_ASSERT(texture); - bool ok = WRenderHelper::makeTexture(window->rhi(), texture, &qtTexture); + // NOTE: We cannot cache by wlr_texture* alone: wlroots may reuse the + // same texture object (via wlr_client_buffer_apply_damage) while updating + // its contents. Callers should re-run makeTexture for real buffer updates, + // but reuse the provider texture for pure scene graph animation frames. + WRenderHelper::NativeTextureCleanup newCleanup; + bool ok = WRenderHelper::makeTexture(window->rhi(), texture, &qtTexture, + buffer, &newCleanup, true, nullptr, + commandBuffer); if (Q_UNLIKELY(!ok)) { - qCWarning(lcWlQtQuickTexture) << "Failed to make texture:" << texture - << ", width height:" << texture->handle()->width - << texture->handle()->height; + WRenderHelper::releaseNativeTexture(&newCleanup); + qCWarning(lcWlQtQuickTexture) + << "Failed to make texture" + << "texture" << pointerAddress(texture) + << "size" << texture->handle()->width << "x" << texture->handle()->height; return; } rhiTexture = qtTexture.rhiTexture(); + nativeCleanup = newCleanup; } W_DECLARE_PUBLIC(WSGTextureProvider) @@ -84,7 +517,15 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate // qt resources QSGPlainTexture qtTexture; QRhiTexture *rhiTexture = nullptr; + WRenderHelper::NativeTextureCleanup nativeCleanup; bool smooth = true; + bool directBufferImportAllowed = false; +#ifdef ENABLE_VULKAN_RENDER + QVector deferredVulkanTextureReleases; + QVector vulkanDmabufTextureCache; + quint64 vulkanDmabufTextureUseSerial = 0; + QMetaObject::Connection deferredVulkanTextureReleaseConnection; +#endif }; WSGTextureProvider::WSGTextureProvider(WOutputRenderWindow *window) @@ -99,17 +540,152 @@ WOutputRenderWindow *WSGTextureProvider::window() const return d->window; } -void WSGTextureProvider::setBuffer(qw_buffer *buffer) +bool WSGTextureProvider::prefersDirectBufferImport(WOutputRenderWindow *window) +{ +#ifdef ENABLE_VULKAN_RENDER + if (!directDmabufImportEnabled() + || !window + || !window->renderer() + || !window->renderer()->is_vk()) { + return false; + } + + const auto api = WRenderHelper::getGraphicsApi(); + if (api == QSGRendererInterface::OpenGL) + return true; + + // Keep client surfaces inside the Qt Quick scene graph. The Vulkan path + // first tries a conservative dmabuf -> QRhiTexture import and falls back + // to synchronized upload/readback when import is not safe. + if (api == QSGRendererInterface::Vulkan) + return true; + + return false; +#else + Q_UNUSED(window); +#endif + return false; +} + +bool WSGTextureProvider::directBufferImportAllowed() const +{ + W_DC(WSGTextureProvider); + return d->directBufferImportAllowed; +} + +void WSGTextureProvider::setDirectBufferImportAllowed(bool allowed) +{ + W_D(WSGTextureProvider); + d->directBufferImportAllowed = allowed; +} + +bool WSGTextureProvider::setBuffer(qw_buffer *buffer) { - if (buffer == qwBuffer()) { + return setBuffer(buffer, nullptr); +} + +bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface) +{ + return setBuffer(buffer, surface, nullptr); +} + +bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface, + QRhiCommandBuffer *commandBuffer) +{ + W_D(WSGTextureProvider); + + const bool sameBuffer = buffer == d->buffer; +#ifdef ENABLE_VULKAN_RENDER + const bool allowDirectBufferImport = + d->directBufferImportAllowed && prefersDirectBufferImport(d->window); + const bool needsSameBufferUpload = + buffer + && sameBuffer + && !allowDirectBufferImport + && d->isVulkanRenderer() + && WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan; +#else + const bool allowDirectBufferImport = false; + const bool needsSameBufferUpload = false; +#endif + + if (sameBuffer && !allowDirectBufferImport && !needsSameBufferUpload) { // The buffer object is not changed, but maybe the buffer's content is changed. // So should emit textureChanged() signal too. if (buffer) Q_EMIT textureChanged(); - return; + return true; + } + + if (d->isVulkanRenderer()) { +#ifdef ENABLE_VULKAN_RENDER + const bool bufferHasNativeDmabuf = bufferHasDmabuf(buffer); +#else + const bool bufferHasNativeDmabuf = false; +#endif + qCDebug(lcWlQtQuickTexture) + << "Vulkan texture provider setBuffer" + << "buffer" << pointerAddress(buffer) + << "sameBuffer" << sameBuffer + << "allowDirectBufferImport" << allowDirectBufferImport + << "directBufferImportAllowed" << d->directBufferImportAllowed + << "bufferHasDmabuf" << bufferHasNativeDmabuf + << "hasExistingTexture" << d->hasTexture() + << "currentBuffer" << pointerAddress(d->buffer) + << "commandBuffer" << pointerAddress(commandBuffer) + << "size" << bufferSize(buffer); + + if (!buffer) { + d->cleanTexture(); + Q_EMIT textureChanged(); + return true; + } + + const bool directImportEligible = allowDirectBufferImport && bufferHasNativeDmabuf; + bool triedDirectBufferImport = false; + if (directImportEligible) { + triedDirectBufferImport = true; + if (d->replaceBufferWithDirectDmabufTexture(buffer, surface)) { + Q_EMIT textureChanged(); + return true; + } + + qCDebug(lcWlQtQuickTexture) + << "Vulkan renderer dmabuf texture import path unavailable," + " falling back to synchronized wlroots texture import" + << "buffer" << pointerAddress(buffer) + << "sameBuffer" << sameBuffer + << "size" << bufferSize(buffer) + << "locks" << bufferLocks(buffer); + } + + bool ownsTexture = false; + qw_texture *texture = nullptr; + if (auto clientBuffer = qw_client_buffer::get(*buffer)) { + texture = qw_texture::from(clientBuffer->handle()->texture); + } else { + texture = qw_texture::from_buffer(*d->window->renderer(), *buffer); + ownsTexture = true; + } + + if (Q_UNLIKELY(!texture)) { + qCWarning(lcWlQtQuickTexture) + << "Failed to update texture from buffer" + << "buffer" << pointerAddress(buffer) + << "size" << bufferSize(buffer) + << "locks" << bufferLocks(buffer); + return false; + } + + if (d->replaceTexture(texture, ownsTexture, buffer, + directImportEligible && !triedDirectBufferImport, + surface, commandBuffer)) { + Q_EMIT textureChanged(); + return true; + } + return false; } - W_D(WSGTextureProvider); d->cleanTexture(); d->buffer = buffer; @@ -127,29 +703,96 @@ void WSGTextureProvider::setBuffer(qw_buffer *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; + qCWarning(lcWlQtQuickTexture) + << "Failed to update texture from buffer" + << "buffer" << pointerAddress(buffer) + << "size" << bufferSize(buffer) + << "locks" << bufferLocks(buffer); } else { d->updateRhiTexture(); } } Q_EMIT textureChanged(); + return true; +} + +bool WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer) +{ + return setTexture(texture, srcBuffer, nullptr); +} + +bool WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer, + wlr_surface *surface) +{ + return setTexture(texture, srcBuffer, surface, nullptr); } -void WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer) +bool WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer, + wlr_surface *surface, + QRhiCommandBuffer *commandBuffer) { W_D(WSGTextureProvider); + if (d->isVulkanRenderer()) { +#ifdef ENABLE_VULKAN_RENDER + const bool sourceBufferHasDmabuf = bufferHasDmabuf(srcBuffer); +#else + const bool sourceBufferHasDmabuf = false; +#endif + qCDebug(lcWlQtQuickTexture) + << "Vulkan texture provider setTexture" + << "texture" << pointerAddress(texture) + << "sourceBuffer" << pointerAddress(srcBuffer) + << "directBufferImportAllowed" << d->directBufferImportAllowed + << "sourceBufferHasDmabuf" << sourceBufferHasDmabuf + << "hasExistingTexture" << d->hasTexture() + << "currentBuffer" << pointerAddress(d->buffer) + << "commandBuffer" << pointerAddress(commandBuffer) + << "sourceSize" << bufferSize(srcBuffer); + + if (!texture) { + d->cleanTexture(); + Q_EMIT textureChanged(); + return true; + } + + const bool allowDirectBufferImport = + d->directBufferImportAllowed && prefersDirectBufferImport(d->window); + const bool directImportEligible = + allowDirectBufferImport && sourceBufferHasDmabuf; + bool triedDirectBufferImport = false; + if (srcBuffer && directImportEligible) { + triedDirectBufferImport = true; + if (d->replaceBufferWithDirectDmabufTexture(srcBuffer, surface)) { + Q_EMIT textureChanged(); + return true; + } + + qCDebug(lcWlQtQuickTexture) + << "Vulkan renderer dmabuf texture import path unavailable for provided wlroots texture," + " falling back to wlroots texture wrapper" + << "buffer" << pointerAddress(srcBuffer) + << "texture" << pointerAddress(texture); + } + + if (d->replaceTexture(texture, false, srcBuffer, + directImportEligible && !triedDirectBufferImport, + surface, commandBuffer)) { + Q_EMIT textureChanged(); + return true; + } + return false; + } + d->cleanTexture(); d->texture = texture; d->buffer = srcBuffer; d->ownsTexture = false; if (texture) - d->updateRhiTexture(); + d->updateRhiTexture(commandBuffer); Q_EMIT textureChanged(); + return true; } void WSGTextureProvider::invalidate() @@ -164,7 +807,7 @@ void WSGTextureProvider::invalidate() QSGTexture *WSGTextureProvider::texture() const { W_DC(WSGTextureProvider); - return d->texture ? const_cast(&d->qtTexture) : nullptr; + return d->hasTexture() ? const_cast(&d->qtTexture) : nullptr; } qw_texture *WSGTextureProvider::qwTexture() const diff --git a/waylib/src/server/qtquick/wsgtextureprovider.h b/waylib/src/server/qtquick/wsgtextureprovider.h index 8e6e6b3ad..7527e68c6 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.h +++ b/waylib/src/server/qtquick/wsgtextureprovider.h @@ -7,10 +7,15 @@ #include +QT_BEGIN_NAMESPACE +class QRhiCommandBuffer; +QT_END_NAMESPACE + QW_BEGIN_NAMESPACE class qw_texture; class qw_buffer; QW_END_NAMESPACE +struct wlr_surface; WAYLIB_SERVER_BEGIN_NAMESPACE class WOutputRenderWindow; @@ -26,9 +31,20 @@ class WAYLIB_SERVER_EXPORT WSGTextureProvider : public QSGTextureProvider, publi explicit WSGTextureProvider(WOutputRenderWindow *window); WOutputRenderWindow *window() const; - - void setBuffer(QW_NAMESPACE::qw_buffer *buffer); - void setTexture(QW_NAMESPACE::qw_texture *texture, QW_NAMESPACE::qw_buffer *srcBuffer); + static bool prefersDirectBufferImport(WOutputRenderWindow *window); + + bool directBufferImportAllowed() const; + void setDirectBufferImportAllowed(bool allowed); + + bool setBuffer(QW_NAMESPACE::qw_buffer *buffer); + bool setBuffer(QW_NAMESPACE::qw_buffer *buffer, wlr_surface *surface); + bool setBuffer(QW_NAMESPACE::qw_buffer *buffer, wlr_surface *surface, + QRhiCommandBuffer *commandBuffer); + bool setTexture(QW_NAMESPACE::qw_texture *texture, QW_NAMESPACE::qw_buffer *srcBuffer); + bool setTexture(QW_NAMESPACE::qw_texture *texture, QW_NAMESPACE::qw_buffer *srcBuffer, + wlr_surface *surface); + bool setTexture(QW_NAMESPACE::qw_texture *texture, QW_NAMESPACE::qw_buffer *srcBuffer, + wlr_surface *surface, QRhiCommandBuffer *commandBuffer); void invalidate(); QSGTexture *texture() const override; diff --git a/waylib/src/server/qtquick/wsurfaceitem.cpp b/waylib/src/server/qtquick/wsurfaceitem.cpp index 4654ce21b..6affdf04c 100644 --- a/waylib/src/server/qtquick/wsurfaceitem.cpp +++ b/waylib/src/server/qtquick/wsurfaceitem.cpp @@ -14,24 +14,44 @@ #include "wayliblogging.h" #include +#ifdef ENABLE_VULKAN_RENDER +#include +#endif #include #include #include #include +#ifdef ENABLE_VULKAN_RENDER +#include +#endif #include #include #include +#include #include #include #include #include #include +#include +#include + QW_USE_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE +#ifdef ENABLE_VULKAN_RENDER +static void registerSurfaceBufferExplicitRelease(WSurface *surface, qw_buffer *buffer); + +static QRhiCommandBuffer *currentFrameCommandBuffer(QSGRenderContext *context) +{ + auto *defaultContext = qobject_cast(context); + return defaultContext ? defaultContext->currentFrameCommandBuffer() : nullptr; +} +#endif + class Q_DECL_HIDDEN SubsurfaceContainer : public QQuickItem { Q_OBJECT @@ -222,6 +242,14 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate } surface = nullptr; } + textureDirty = true; +#ifdef ENABLE_VULKAN_RENDER + textureRetryBackoffFrames = 0; + textureRetryDelayFrames = 0; + releaseDeferredDirectBuffers(); + directBuffer.reset(); + pendingDirectBuffer.reset(); +#endif if (frameDoneConnection) QObject::disconnect(frameDoneConnection); @@ -230,6 +258,10 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate if (dontCacheLastBuffer) { buffer.reset(); +#ifdef ENABLE_VULKAN_RENDER + releaseDeferredDirectBuffers(); + directBuffer.reset(); +#endif cleanTextureProvider(); q->update(); } @@ -251,15 +283,26 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate const bool bufferChanged = committedState & WLR_SURFACE_STATE_BUFFER; if (bufferChanged) { + textureDirty = true; // Get the new buffer pointer from surface auto newBuffer = surface->buffer(); +#ifdef ENABLE_VULKAN_RENDER + auto newDirectBuffer = surface->committedBuffer(); + registerSurfaceBufferExplicitRelease(surface.data(), newDirectBuffer); +#endif if (!live) { // Non-live mode: defer to pendingBuffer pendingBuffer.reset(newBuffer); +#ifdef ENABLE_VULKAN_RENDER + pendingDirectBuffer.reset(newDirectBuffer); +#endif } else { // Live mode: update buffer immediately buffer.reset(newBuffer); +#ifdef ENABLE_VULKAN_RENDER + directBuffer.reset(newDirectBuffer); +#endif q->update(); } } @@ -287,6 +330,9 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate // wayland protocol job should not run in rendering thread, so set context qobject to contentItem frameDoneConnection = QObject::connect(rw, &WOutputRenderWindow::renderEnd, q, [this, q] (const QList> committedOutputs) { +#ifdef ENABLE_VULKAN_RENDER + releaseDeferredDirectBuffers(); +#endif lastRendered = rendered; if (Q_LIKELY((rendered || q->isVisible()) && live) && surface && @@ -331,10 +377,83 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate q->setImplicitSize(s.width(), s.height()); } +#ifdef ENABLE_VULKAN_RENDER + inline bool isVulkanRenderer() const { + auto rw = qobject_cast(window); + return rw && rw->renderer() && rw->renderer()->is_vk(); + } + + inline bool shouldDeferTextureRetry(bool hasCachedTexture) { + if (!hasCachedTexture || !isVulkanRenderer() || textureRetryDelayFrames <= 0) + return false; + + --textureRetryDelayFrames; + return true; + } + + inline void noteTextureUpdateResult(bool ok) { + if (!isVulkanRenderer()) + return; + + if (ok) { + textureRetryBackoffFrames = 0; + textureRetryDelayFrames = 0; + return; + } + + textureRetryBackoffFrames = textureRetryBackoffFrames + ? qMin(textureRetryBackoffFrames * 2, 4) + : 1; + textureRetryDelayFrames = textureRetryBackoffFrames; + } + + inline void deferDirectBufferReleaseAfterRender() const { + if (!directBuffer) + return; + + if (Q_UNLIKELY(lcWlSurface().isDebugEnabled())) { + qCDebug(lcWlSurface) + << "Deferring direct client buffer release until render end" + << "surface" << surface + << "buffer" << directBuffer.get() + << "deferredCount" << deferredDirectBufferReleases.size() + 1; + } + deferredDirectBufferReleases.emplace_back(std::move(directBuffer)); + } + + inline void releaseDeferredDirectBuffers() const { + if (deferredDirectBufferReleases.empty()) + return; + + if (Q_UNLIKELY(lcWlSurface().isDebugEnabled())) { + qCDebug(lcWlSurface) + << "Releasing deferred direct client buffers after render" + << "surface" << surface + << "count" << deferredDirectBufferReleases.size(); + } + deferredDirectBufferReleases.clear(); + } +#endif + inline void swapBufferIfNeeded() { - if (pendingBuffer) + if (pendingBuffer) { buffer = std::move(pendingBuffer); +#ifdef ENABLE_VULKAN_RENDER + directBuffer = std::move(pendingDirectBuffer); + pendingDirectBuffer.reset(); +#endif + textureDirty = true; + } + } + +#ifdef ENABLE_VULKAN_RENDER + inline qw_buffer *textureBufferForPreferredPath(WOutputRenderWindow *renderWindow) const { + if (directBuffer && WSGTextureProvider::prefersDirectBufferImport(renderWindow)) + return directBuffer.get(); + + return buffer.get(); } +#endif inline void setDevicePixelRatio(qreal dpr) { if (dpr == devicePixelRatio) @@ -369,9 +488,17 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate mutable QMetaObject::Connection updateTextureConnection; bool dontCacheLastBuffer = false; bool live = true; + mutable bool textureDirty = true; bool ignoreBufferOffset = false; bool lastRendered = false; QAtomicInteger rendered = false; +#ifdef ENABLE_VULKAN_RENDER + mutable BufferRef directBuffer; + mutable std::vector deferredDirectBufferReleases; + BufferRef pendingDirectBuffer; + int textureRetryBackoffFrames = 0; + int textureRetryDelayFrames = 0; +#endif }; @@ -459,15 +586,43 @@ WSGTextureProvider *WSurfaceItemContent::wTextureProvider() const if (!d->textureProvider) { d->textureProvider = new WSGTextureProvider(w); + d->textureProvider->setDirectBufferImportAllowed(true); d->textureProvider->setSmooth(smooth()); connect(this, &WSurfaceItemContent::smoothChanged, d->textureProvider, &WSGTextureProvider::setSmooth); if (d->surface) { - if (auto texture = d->surface->handle()->get_texture()) { - d->textureProvider->setTexture(qw_texture::from(texture), d->buffer.get()); - } else { - d->textureProvider->setBuffer(d->buffer.get()); + bool textureUpdated = false; + wlr_surface *surfaceHandle = d->surface->handle() + ? d->surface->handle()->handle() + : nullptr; +#ifdef ENABLE_VULKAN_RENDER + if (WSGTextureProvider::prefersDirectBufferImport(w)) { + auto *preferredBuffer = d->textureBufferForPreferredPath(w); + textureUpdated = d->textureProvider->setBuffer(preferredBuffer, surfaceHandle); + qCDebug(lcWlSurface) + << "Initialized surface texture provider through preferred direct client dmabuf path" + << "surface" << d->surface + << "buffer" << preferredBuffer + << "legacyBuffer" << d->buffer.get() + << "directBuffer" << d->directBuffer.get() + << "updated" << textureUpdated; + } +#endif + if (!textureUpdated) { + if (auto texture = d->surface->handle()->get_texture()) { + textureUpdated = d->textureProvider->setTexture(qw_texture::from(texture), + d->buffer.get(), + surfaceHandle); + } else { + textureUpdated = d->textureProvider->setBuffer(d->buffer.get(), surfaceHandle); + } + } + if (textureUpdated && d->textureProvider->texture()) { + d->textureDirty = false; +#ifdef ENABLE_VULKAN_RENDER + d->deferDirectBufferReleaseAfterRender(); +#endif } } } @@ -508,6 +663,11 @@ void WSurfaceItemContent::setLive(bool live) d->live = live; if (live) { d->swapBufferIfNeeded(); + d->textureDirty = true; +#ifdef ENABLE_VULKAN_RENDER + d->textureRetryBackoffFrames = 0; + d->textureRetryDelayFrames = 0; +#endif update(); } Q_EMIT liveChanged(); @@ -566,11 +726,13 @@ qreal WSurfaceItemContent::alphaModifier() const class Q_DECL_HIDDEN WSGRenderFootprintNode: public QSGRenderNode { public: - WSGRenderFootprintNode(WSurfaceItemContent *owner) + WSGRenderFootprintNode(WSurfaceItemContent *owner, bool opaque = true, bool ownedByParent = true) : QSGRenderNode() , m_owner(owner) + , m_opaque(opaque) { - setFlag(QSGNode::OwnedByParent); // parent is fixed, auto release + if (ownedByParent) + setFlag(QSGNode::OwnedByParent); // parent is fixed, auto release } ~WSGRenderFootprintNode() {} @@ -581,38 +743,154 @@ class Q_DECL_HIDDEN WSGRenderFootprintNode: public QSGRenderNode m_owner->d_func()->rendered = true; } + QRectF rect() const override + { + return m_rect; + } + + void setRect(const QRectF &rect) + { + if (m_rect == rect) + return; + + m_rect = rect; + markDirty(QSGNode::DirtyGeometry); + } + RenderingFlags flags() const override { - return NoExternalRendering | BoundedRectRendering | DepthAwareRendering | OpaqueRendering; + RenderingFlags result = NoExternalRendering | BoundedRectRendering | DepthAwareRendering; + if (m_opaque) + result |= OpaqueRendering; + return result; } QPointer m_owner; + QRectF m_rect; + bool m_opaque = true; }; +#ifdef ENABLE_VULKAN_RENDER +static void registerSurfaceBufferExplicitRelease(WSurface *surface, qw_buffer *buffer) +{ + if (!surface || !surface->handle() || !surface->handle()->handle() + || !buffer || !buffer->handle()) { + return; + } + + auto *syncState = + qw_linux_drm_syncobj_surface_v1_state::get_surface_state(surface->handle()->handle()); + if (!syncState || !syncState->has_release_timeline()) + return; + + if (buffer->lock_count() == 0) { + qCWarning(lcWlVulkanCompositor) + << "Vulkan surface explicit release skipped: buffer has no locks" + << "surface" << surface + << "buffer" << buffer; + return; + } + + if (!syncState->signal_release_with_buffer(buffer)) { + qCWarning(lcWlVulkanCompositor) + << "Vulkan surface explicit release registration failed" + << "surface" << surface + << "buffer" << buffer + << "releasePoint" << syncState->release_point(); + return; + } + + qCDebug(lcWlVulkanCompositor) + << "Vulkan surface explicit release registered" + << "surface" << surface + << "buffer" << buffer + << "releasePoint" << syncState->release_point(); +} +#endif + QSGNode *WSurfaceItemContent::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { W_D(WSurfaceItemContent); + const QRectF targetGeometry(d->ignoreBufferOffset ? QPointF() : d->bufferOffset, size()); + auto tp = wTextureProvider(); - if (d->live || !tp->texture()) { - auto texture = d->surface ? d->surface->handle()->get_texture() : nullptr; + QRhiCommandBuffer *commandBuffer = nullptr; +#ifdef ENABLE_VULKAN_RENDER + commandBuffer = currentFrameCommandBuffer(d->sceneGraphRenderContext()); +#endif + wlr_surface *surfaceHandle = d->surface && d->surface->handle() + ? d->surface->handle()->handle() + : nullptr; + const bool hasCachedTexture = tp->texture(); + if (((d->live && d->textureDirty) || !hasCachedTexture) +#ifdef ENABLE_VULKAN_RENDER + && !d->shouldDeferTextureRetry(hasCachedTexture) +#endif + ) { + bool textureUpdated = false; +#ifdef ENABLE_VULKAN_RENDER + if (WSGTextureProvider::prefersDirectBufferImport(tp->window())) { + auto *preferredBuffer = d->textureBufferForPreferredPath(tp->window()); + textureUpdated = tp->setBuffer(preferredBuffer, surfaceHandle, commandBuffer); + qCDebug(lcWlSurface) + << "Updated surface texture through preferred direct client dmabuf path" + << "surface" << d->surface + << "buffer" << preferredBuffer + << "legacyBuffer" << d->buffer.get() + << "directBuffer" << d->directBuffer.get() + << "updated" << textureUpdated + << "commandBuffer" << quintptr(commandBuffer); + } +#endif + auto texture = !textureUpdated && d->surface ? d->surface->handle()->get_texture() : nullptr; if (texture) { - tp->setTexture(qw_texture::from(texture), d->buffer.get()); - } else { - tp->setBuffer(d->buffer.get()); + textureUpdated = tp->setTexture(qw_texture::from(texture), d->buffer.get(), + surfaceHandle, commandBuffer); + } else if (!textureUpdated) { + textureUpdated = tp->setBuffer(d->buffer.get(), surfaceHandle, commandBuffer); + } +#ifdef ENABLE_VULKAN_RENDER + d->noteTextureUpdateResult(textureUpdated); +#endif + if (textureUpdated) { + d->textureDirty = false; +#ifdef ENABLE_VULKAN_RENDER + d->deferDirectBufferReleaseAfterRender(); +#endif } } if (!tp->texture() || width() <= 0 || height() <= 0) { +#ifdef ENABLE_VULKAN_RENDER + if (Q_UNLIKELY(lcWlSurface().isDebugEnabled())) { + qCDebug(lcWlSurface) + << "Surface texture node skipped" + << "surfacePtr" << quintptr(d->surface.data()) + << "bufferPtr" << quintptr(d->buffer.get()) + << "directBufferPtr" << quintptr(d->directBuffer.get()) + << "hasTexture" << bool(tp->texture()) + << "itemSize" << size() + << "targetRect" << targetGeometry + << "sourceRect" << d->bufferSourceBox + << "sourceRectValid" << d->bufferSourceBox.isValid() + << "live" << d->live + << "textureDirty" << d->textureDirty + << "visible" << isVisible() + << "opacity" << opacity(); + } +#endif delete oldNode; return nullptr; } - auto node = static_cast(oldNode); + auto node = dynamic_cast(oldNode); if (Q_UNLIKELY(!node)) { + delete oldNode; node = window()->createImageNode(); node->setOwnsTexture(false); QSGNode *fpnode = new WSGRenderFootprintNode(this); + static_cast(fpnode)->setRect(targetGeometry); node->appendChildNode(fpnode); } @@ -620,10 +898,33 @@ QSGNode *WSurfaceItemContent::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeD node->setTexture(texture); const QRectF textureGeometry = d->bufferSourceBox; node->setSourceRect(textureGeometry); - const QRectF targetGeometry(d->ignoreBufferOffset ? QPointF() : d->bufferOffset, size()); node->setRect(targetGeometry); node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest); +#ifdef ENABLE_VULKAN_RENDER + if (Q_UNLIKELY(lcWlSurface().isDebugEnabled())) { + qCDebug(lcWlSurface) + << "Surface texture node updated" + << "surfacePtr" << quintptr(d->surface.data()) + << "bufferPtr" << quintptr(d->buffer.get()) + << "directBufferPtr" << quintptr(d->directBuffer.get()) + << "texturePtr" << quintptr(texture) + << "textureSize" << texture->textureSize() + << "textureHasAlpha" << texture->hasAlphaChannel() + << "itemSize" << size() + << "targetRect" << targetGeometry + << "targetRectValid" << targetGeometry.isValid() + << "sourceRect" << textureGeometry + << "sourceRectValid" << textureGeometry.isValid() + << "devicePixelRatio" << d->devicePixelRatio + << "live" << d->live + << "textureDirty" << d->textureDirty + << "smooth" << smooth() + << "visible" << isVisible() + << "opacity" << opacity(); + } +#endif + return node; } diff --git a/waylib/src/server/utils/wbufferdumper.cpp b/waylib/src/server/utils/wbufferdumper.cpp index c9a9b2cf6..b87dd5376 100644 --- a/waylib/src/server/utils/wbufferdumper.cpp +++ b/waylib/src/server/utils/wbufferdumper.cpp @@ -5,6 +5,8 @@ #include "wtools.h" #include "wayliblogging.h" +#include + #include extern "C" { @@ -12,6 +14,7 @@ extern "C" { #include } +QW_USE_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE WBufferDumper::DumpResult WBufferDumper::dumpBufferToImage(wlr_buffer *buffer, @@ -23,21 +26,21 @@ WBufferDumper::DumpResult WBufferDumper::dumpBufferToImage(wlr_buffer *buffer, return DumpResult::InvalidBuffer; } - wlr_texture *texture = wlr_texture_from_buffer(renderer, buffer); + qw_texture *texture = qw_texture::from_buffer(renderer, buffer); if (!texture) { qCWarning(lcWlBufferDumper) << "Failed to create texture from buffer"; return DumpResult::TextureCreationFailed; } - uint32_t format = wlr_texture_preferred_read_format(texture); + uint32_t format = texture->preferred_read_format(); QImage::Format qImageFormat = WTools::toImageFormat(format); if (qImageFormat == QImage::Format_Invalid) { - wlr_texture_destroy(texture); + delete texture; return DumpResult::UnsupportedFormat; } - outputImage = QImage(texture->width, texture->height, qImageFormat); + outputImage = QImage(texture->handle()->width, texture->handle()->height, qImageFormat); uint32_t stride = outputImage.bytesPerLine(); wlr_texture_read_pixels_options options = {}; @@ -45,13 +48,13 @@ WBufferDumper::DumpResult WBufferDumper::dumpBufferToImage(wlr_buffer *buffer, options.format = format; options.stride = stride; - if (!wlr_texture_read_pixels(texture, &options)) { + if (!texture->read_pixels(&options)) { qCWarning(lcWlBufferDumper) << "Failed to read pixels from texture"; - wlr_texture_destroy(texture); + delete texture; return DumpResult::TextureReadFailed; } - wlr_texture_destroy(texture); + delete texture; return DumpResult::Success; } diff --git a/waylib/src/server/utils/wextimagecapturesourcev1impl.cpp b/waylib/src/server/utils/wextimagecapturesourcev1impl.cpp index 901d2af25..c5a31fe0b 100644 --- a/waylib/src/server/utils/wextimagecapturesourcev1impl.cpp +++ b/waylib/src/server/utils/wextimagecapturesourcev1impl.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -51,14 +52,15 @@ struct ConstraintBuilder { uint32_t format = DRM_FORMAT_ARGB8888; // fallback if (renderer && swapchain) { - struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain->handle()); + struct wlr_buffer *buffer = swapchain->acquire(); if (buffer) { - struct wlr_texture *texture = wlr_texture_from_buffer(renderer->handle(), buffer); - wlr_buffer_unlock(buffer); + auto *qbuffer = qw_buffer::from(buffer); + qw_texture *texture = qw_texture::from_buffer(*renderer, *qbuffer); + qbuffer->unlock(); if (texture) { - uint32_t shm_format = wlr_texture_preferred_read_format(texture); - wlr_texture_destroy(texture); + uint32_t shm_format = texture->preferred_read_format(); + delete texture; if (shm_format != DRM_FORMAT_INVALID) { format = shm_format; } @@ -83,7 +85,7 @@ struct ConstraintBuilder { if (!renderer || !swapchain) return; - int drm_fd = wlr_renderer_get_drm_fd(renderer->handle()); + int drm_fd = renderer->get_drm_fd(); if (swapchain->handle()->allocator && (swapchain->handle()->allocator->buffer_caps & WLR_BUFFER_CAP_DMABUF) && drm_fd >= 0) { diff --git a/waylib/src/server/wayliblogging.cpp b/waylib/src/server/wayliblogging.cpp index 383778255..1ed0078db 100644 --- a/waylib/src/server/wayliblogging.cpp +++ b/waylib/src/server/wayliblogging.cpp @@ -73,3 +73,4 @@ Q_LOGGING_CATEGORY(lcWlPlatform, "waylib.platform", QtWarningMsg) // QP Q_LOGGING_CATEGORY(lcWlRenderHelper, "waylib.render.helper", QtWarningMsg) // Render target creation and buffer import Q_LOGGING_CATEGORY(lcWlRenderBuffer, "waylib.render.buffer", QtWarningMsg) // Render buffer node DMA-BUF Q_LOGGING_CATEGORY(lcWlBufferRenderer, "waylib.render.bufferrenderer", QtWarningMsg) // Buffer renderer texture provider +Q_LOGGING_CATEGORY(lcWlVulkanCompositor, "waylib.vulkan.compositor", QtInfoMsg) // Vulkan output-layer composition path diff --git a/waylib/src/server/wayliblogging.h b/waylib/src/server/wayliblogging.h index 38763942a..44d99ef0f 100644 --- a/waylib/src/server/wayliblogging.h +++ b/waylib/src/server/wayliblogging.h @@ -68,5 +68,6 @@ Q_DECLARE_LOGGING_CATEGORY(lcWlPlatform) Q_DECLARE_LOGGING_CATEGORY(lcWlRenderHelper) Q_DECLARE_LOGGING_CATEGORY(lcWlRenderBuffer) Q_DECLARE_LOGGING_CATEGORY(lcWlBufferRenderer) +Q_DECLARE_LOGGING_CATEGORY(lcWlVulkanCompositor) #endif // WAYLIB_LOGGING_H From 5f861e7ff5578704d624056ea9c6a3e638b46409 Mon Sep 17 00:00:00 2001 From: LFRon Date: Thu, 2 Jul 2026 22:18:43 +0800 Subject: [PATCH 2/3] perf(waylib): optimize Qt Quick Vulkan direct output rendering Use the Vulkan QRhi direct-primary path when both wlroots and Qt Quick are running on Vulkan, so Qt renders directly into wlroots output buffers instead of defaulting to the intermediate output-layer compositor. Keep the wlroots Vulkan output-layer compositor available for explicit requests and temporary fallback after direct-primary failures. Import output dmabufs as Vulkan QQuickRenderTarget objects with modifier validation, explicit layout/queue-family transitions, and scanout release sync. Reuse Vulkan interop command pools, command buffers, fences, and exportable semaphores across frames to avoid per-frame command object allocation overhead. Improve client dmabuf texture handling for the Qt Vulkan RHI path by tracking surface content serials, reusing cached QRhi texture imports when the buffer content is unchanged, and reacquiring cached imports when the same buffer receives new content. This keeps the existing GLES2 path unchanged while making the Vulkan path match the GLES2 direct-output model more closely. --- waylib/src/server/qtquick/woutputhelper.cpp | 16 +- .../server/qtquick/woutputrenderwindow.cpp | 122 ++- waylib/src/server/qtquick/wrenderhelper.cpp | 840 +++++++++--------- .../src/server/qtquick/wsgtextureprovider.cpp | 77 +- .../src/server/qtquick/wsgtextureprovider.h | 5 + waylib/src/server/qtquick/wsurfaceitem.cpp | 34 +- 6 files changed, 638 insertions(+), 456 deletions(-) diff --git a/waylib/src/server/qtquick/woutputhelper.cpp b/waylib/src/server/qtquick/woutputhelper.cpp index ccee1ee0b..ef0ae8619 100644 --- a/waylib/src/server/qtquick/woutputhelper.cpp +++ b/waylib/src/server/qtquick/woutputhelper.cpp @@ -261,8 +261,7 @@ bool WOutputHelper::usesVulkanOutputLayerCompositor() const if (!d->renderer() || !d->renderer()->is_vk()) return false; - return isVulkanOutputLayerCompositorRequested() - || WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan; + return isVulkanOutputLayerCompositorRequested(); #else return false; #endif @@ -315,10 +314,17 @@ bool WOutputHelper::commitWithVulkanOutputLayer(qw_buffer *sourceBuffer) return finishCommit(ok); } - if (Q_UNLIKELY(!usesVulkanOutputLayerCompositor())) { + if (Q_UNLIKELY(vulkanOutputLayerCompositorDisabled() + || !d->renderer() + || !d->renderer()->is_vk() + || WRenderHelper::getGraphicsApi() != QSGRendererInterface::Vulkan)) { qCWarning(lcWlVulkanCompositor) - << "Vulkan output-layer compositor requested for non-Vulkan renderer on output" - << d->qwoutput()->handle()->name; + << "Vulkan output-layer compositor requested but unavailable on output" + << d->qwoutput()->handle()->name + << "disabled" << vulkanOutputLayerCompositorDisabled() + << "hasRenderer" << bool(d->renderer()) + << "rendererIsVulkan" << (d->renderer() ? d->renderer()->is_vk() : false) + << "graphicsApi" << WRenderHelper::getGraphicsApi(); return finishCommit(false); } diff --git a/waylib/src/server/qtquick/woutputrenderwindow.cpp b/waylib/src/server/qtquick/woutputrenderwindow.cpp index 7494ad33d..0c7a3b8ff 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.cpp +++ b/waylib/src/server/qtquick/woutputrenderwindow.cpp @@ -218,6 +218,11 @@ class Q_DECL_HIDDEN OutputHelper : public WOutputHelper inline void init() { // TODO: pre update scale after WOutputHelper::setScale output()->output()->safeConnect(&WOutput::scaleChanged, this, &OutputHelper::updateSceneDPR); +#ifdef ENABLE_VULKAN_RENDER + output()->output()->safeConnect(&WOutput::modeChanged, this, [this] { + resetVulkanOutputLayerFallback("output mode changed"); + }); +#endif } inline qw_output *qwoutput() const { @@ -230,6 +235,56 @@ class Q_DECL_HIDDEN OutputHelper : public WOutputHelper && m_output->output()->renderer()->is_vk(); } + inline bool isVulkanRhiOutput() const { + return isVulkanRenderer() + && WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan; + } + + inline bool shouldUseVulkanOutputLayerCompositor() { + if (!isVulkanRhiOutput()) + return false; + if (vulkanOutputLayerCompositorDisabled()) + return false; + if (WOutputHelper::isVulkanOutputLayerCompositorRequested()) + return true; + + if (!m_vulkanOutputLayerFallback) + return false; + + if (m_vulkanOutputLayerFallbackRetryFrames > 0) + --m_vulkanOutputLayerFallbackRetryFrames; + if (m_vulkanOutputLayerFallbackRetryFrames <= 0) { + m_vulkanOutputLayerFallback = false; + qCInfo(lcWlVulkanCompositor) + << "Retrying Vulkan direct-primary output path for" + << qwoutput()->handle()->name + << "after temporary output-layer fallback"; + return false; + } + + return true; + } + + inline void setCurrentVulkanRenderUsesOutputLayer(bool usesOutputLayer) { + m_vulkanCurrentRenderUsesOutputLayer = usesOutputLayer; + } + + inline bool currentVulkanRenderUsesOutputLayer() const { + return m_vulkanCurrentRenderUsesOutputLayer; + } + + inline void resetVulkanOutputLayerFallback(const char *reason) { + if (!m_vulkanOutputLayerFallback && m_vulkanOutputLayerFallbackRetryFrames == 0) + return; + + qCDebug(lcWlVulkanCompositor) + << "Resetting Vulkan output-layer fallback for" + << qwoutput()->handle()->name + << "reason" << reason; + m_vulkanOutputLayerFallback = false; + m_vulkanOutputLayerFallbackRetryFrames = 0; + } + inline bool shouldDeferVulkanRenderRetry() { if (!isVulkanRenderer() || m_vulkanRenderRetryDelayFrames <= 0) return false; @@ -239,10 +294,28 @@ class Q_DECL_HIDDEN OutputHelper : public WOutputHelper return true; } - inline bool noteVulkanRenderFailed() { + inline bool noteVulkanRenderFailed(bool usedOutputLayer) { if (!isVulkanRenderer()) return false; + if (isVulkanRhiOutput() + && !usedOutputLayer + && !vulkanOutputLayerCompositorDisabled() + && !WOutputHelper::isVulkanOutputLayerCompositorRequested()) { + static constexpr int retryFrames = 120; + if (!m_vulkanOutputLayerFallback) { + qCWarning(lcWlVulkanCompositor) + << "Vulkan direct-primary output path failed for" + << qwoutput()->handle()->name + << "- falling back to wlroots Vulkan output-layer compositor" + << "retryFrames" << retryFrames; + } + m_vulkanOutputLayerFallback = true; + m_vulkanOutputLayerFallbackRetryFrames = retryFrames; + scheduleFrame(); + return true; + } + m_vulkanRenderFailureBackoffFrames = m_vulkanRenderFailureBackoffFrames ? qMin(m_vulkanRenderFailureBackoffFrames * 2, 4) : 1; @@ -251,12 +324,16 @@ class Q_DECL_HIDDEN OutputHelper : public WOutputHelper return true; } - inline void noteVulkanRenderSucceeded() { + inline void noteVulkanRenderSucceeded(bool usedOutputLayer) { if (!isVulkanRenderer()) return; m_vulkanRenderFailureBackoffFrames = 0; m_vulkanRenderRetryDelayFrames = 0; + if (!usedOutputLayer) { + m_vulkanOutputLayerFallback = false; + m_vulkanOutputLayerFallbackRetryFrames = 0; + } } #endif @@ -332,6 +409,9 @@ class Q_DECL_HIDDEN OutputHelper : public WOutputHelper #ifdef ENABLE_VULKAN_RENDER int m_vulkanRenderFailureBackoffFrames = 0; int m_vulkanRenderRetryDelayFrames = 0; + bool m_vulkanOutputLayerFallback = false; + int m_vulkanOutputLayerFallbackRetryFrames = 0; + bool m_vulkanCurrentRenderUsesOutputLayer = false; #endif // for compositeLayers @@ -1159,7 +1239,7 @@ bool OutputHelper::commit(WBufferRenderer *buffer) return false; } - if (usesVulkanOutputLayerCompositor()) { + if (currentVulkanRenderUsesOutputLayer()) { qCDebug(lcWlVulkanCompositor) << "Committing Qt Quick layer through wlroots Vulkan render pass for output" << qwoutput()->handle()->name @@ -1398,20 +1478,22 @@ void WOutputRenderWindowPrivate::init() #ifdef ENABLE_VULKAN_RENDER const bool explicitLayerCompositor = WOutputHelper::isVulkanOutputLayerCompositorRequested(); - const bool automaticLayerCompositor = !explicitLayerCompositor - && WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan; - if (!vulkanOutputLayerCompositorDisabled() - && (explicitLayerCompositor || automaticLayerCompositor)) { + const bool vulkanRhiOutput = WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan + && m_renderer->is_vk(); + if (!vulkanOutputLayerCompositorDisabled() && explicitLayerCompositor) { if (m_renderer->is_vk()) { qCInfo(lcWlVulkanCompositor) << "Vulkan output-layer compositor enabled: Qt Quick renders an intermediate dmabuf layer," " then wlroots Vulkan render pass commits the output." - << (explicitLayerCompositor ? "mode: explicit environment request" - : "mode: automatic for Qt Quick Vulkan RHI"); + << "mode: explicit environment request"; } else { qCInfo(lcWlVulkanCompositor) << "Vulkan output-layer compositor requested but ignored because wlroots renderer is not Vulkan."; } + } else if (vulkanRhiOutput) { + qCInfo(lcWlVulkanCompositor) + << "Vulkan direct-primary output path enabled: Qt Quick renders directly into the wlroots primary buffer." + << "output-layerFallback" << !vulkanOutputLayerCompositorDisabled(); } #endif @@ -1652,7 +1734,8 @@ WOutputRenderWindowPrivate::doRenderOutputs(qw_output *needsFrameOutput, const Q #endif #ifdef ENABLE_VULKAN_RENDER - const bool useVulkanLayerCompositor = helper->usesVulkanOutputLayerCompositor(); + const bool useVulkanLayerCompositor = helper->shouldUseVulkanOutputLayerCompositor(); + helper->setCurrentVulkanRenderUsesOutputLayer(useVulkanLayerCompositor); WBufferRenderer::RenderFlags renderFlags = WBufferRenderer::RedirectOpenGLContextDefaultFrameBufferObject; if (useVulkanLayerCompositor) @@ -1674,15 +1757,10 @@ WOutputRenderWindowPrivate::doRenderOutputs(qw_output *needsFrameOutput, const Q Q_ASSERT(buffer == helper->bufferRenderer()->currentBuffer()); if (!buffer) { #ifdef ENABLE_VULKAN_RENDER - if (!helper->extraState() && helper->noteVulkanRenderFailed()) + if (!helper->extraState() && helper->noteVulkanRenderFailed(useVulkanLayerCompositor)) continue; #endif } -#ifdef ENABLE_VULKAN_RENDER - else { - helper->noteVulkanRenderSucceeded(); - } -#endif if (buffer) { helper->render(helper->bufferRenderer(), 0, renderMatrix, helper->output()->effectiveSourceRect(), @@ -1778,6 +1856,9 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, bool commitAttempted = false; bool commitSucceeded = false; const bool hadCurrentBuffer = i.second->currentBuffer(); +#ifdef ENABLE_VULKAN_RENDER + const bool usedVulkanOutputLayer = i.first->currentVulkanRenderUsesOutputLayer(); +#endif if (Q_UNLIKELY(!i.first->framePending())) { commitAttempted = true; commitSucceeded = i.first->commit(i.second); @@ -1800,9 +1881,12 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, i.first->resetState(); #ifdef ENABLE_VULKAN_RENDER - if (hadCurrentBuffer && commitAttempted && !commitSucceeded - && i.first->noteVulkanRenderFailed()) { - i.first->update(); + if (hadCurrentBuffer && commitAttempted) { + if (commitSucceeded) { + i.first->noteVulkanRenderSucceeded(usedVulkanOutputLayer); + } else if (i.first->noteVulkanRenderFailed(usedVulkanOutputLayer)) { + i.first->update(); + } } #endif } diff --git a/waylib/src/server/qtquick/wrenderhelper.cpp b/waylib/src/server/qtquick/wrenderhelper.cpp index 732f0253d..3f443ed70 100644 --- a/waylib/src/server/qtquick/wrenderhelper.cpp +++ b/waylib/src/server/qtquick/wrenderhelper.cpp @@ -371,125 +371,345 @@ static quint64 vulkanHandleToInteger(Handle handle) return quint64(handle); } -struct Q_DECL_HIDDEN PendingVulkanCommandCleanup { - VkDevice device = VK_NULL_HANDLE; - VkCommandPool commandPool = VK_NULL_HANDLE; +struct Q_DECL_HIDDEN VulkanInteropCommandFunctions { + PFN_vkCreateCommandPool vkCreateCommandPool = nullptr; + PFN_vkDestroyCommandPool vkDestroyCommandPool = nullptr; + PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers = nullptr; + PFN_vkBeginCommandBuffer vkBeginCommandBuffer = nullptr; + PFN_vkEndCommandBuffer vkEndCommandBuffer = nullptr; + PFN_vkResetCommandBuffer vkResetCommandBuffer = nullptr; + PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier = nullptr; + PFN_vkQueueSubmit vkQueueSubmit = nullptr; + PFN_vkCreateFence vkCreateFence = nullptr; + PFN_vkDestroyFence vkDestroyFence = nullptr; + PFN_vkGetFenceStatus vkGetFenceStatus = nullptr; + PFN_vkWaitForFences vkWaitForFences = nullptr; + PFN_vkResetFences vkResetFences = nullptr; + PFN_vkCreateSemaphore vkCreateSemaphore = nullptr; + PFN_vkDestroySemaphore vkDestroySemaphore = nullptr; + PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = nullptr; + + bool resolve(VkDevice device, bool needSyncFileExport, + const char *subject, const char *phase) + { + auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); + if (!vkGetDeviceProcAddr) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "failed: vkGetDeviceProcAddr unavailable"; + return false; + } + + vkCreateCommandPool = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCreateCommandPool")); + vkDestroyCommandPool = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkDestroyCommandPool")); + vkAllocateCommandBuffers = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkAllocateCommandBuffers")); + vkBeginCommandBuffer = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkBeginCommandBuffer")); + vkEndCommandBuffer = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkEndCommandBuffer")); + vkResetCommandBuffer = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkResetCommandBuffer")); + vkCmdPipelineBarrier = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCmdPipelineBarrier")); + vkQueueSubmit = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkQueueSubmit")); + vkCreateFence = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCreateFence")); + vkDestroyFence = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkDestroyFence")); + vkGetFenceStatus = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkGetFenceStatus")); + vkWaitForFences = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkWaitForFences")); + vkResetFences = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkResetFences")); + vkCreateSemaphore = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCreateSemaphore")); + vkDestroySemaphore = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkDestroySemaphore")); + vkGetSemaphoreFdKHR = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkGetSemaphoreFdKHR")); + + if (!vkCreateCommandPool || !vkDestroyCommandPool || !vkAllocateCommandBuffers + || !vkBeginCommandBuffer || !vkEndCommandBuffer || !vkResetCommandBuffer + || !vkCmdPipelineBarrier || !vkQueueSubmit || !vkCreateFence || !vkDestroyFence + || !vkGetFenceStatus || !vkWaitForFences || !vkResetFences + || (needSyncFileExport && (!vkCreateSemaphore || !vkDestroySemaphore + || !vkGetSemaphoreFdKHR))) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "failed: required Vulkan command/sync functions unavailable" + << "exportSyncFile" << needSyncFileExport; + return false; + } + + return true; + } +}; + +struct Q_DECL_HIDDEN VulkanInteropCommandSlot { + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; VkFence fence = VK_NULL_HANDLE; VkSemaphore semaphore = VK_NULL_HANDLE; + bool busy = false; }; -Q_GLOBAL_STATIC(QMutex, s_pendingVulkanCommandCleanupMutex) -Q_GLOBAL_STATIC(QVector, s_pendingVulkanCommandCleanups) +struct Q_DECL_HIDDEN VulkanInteropCommandContext { + VkDevice device = VK_NULL_HANDLE; + VkQueue queue = VK_NULL_HANDLE; + uint32_t queueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + VkCommandPool commandPool = VK_NULL_HANDLE; + VulkanInteropCommandFunctions api; + QVector slots; -static void destroyPendingVulkanCommandCleanup(PendingVulkanCommandCleanup *cleanup) -{ - if (!cleanup || cleanup->device == VK_NULL_HANDLE) { - if (cleanup) - *cleanup = {}; - return; + bool matches(VkDevice dev, VkQueue q, uint32_t family) const + { + return device == dev && queue == q && queueFamilyIndex == family; } - auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); - if (!vkGetDeviceProcAddr) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI command cleanup skipped: vkGetDeviceProcAddr unavailable" - << "commandPool" << Qt::hex << vulkanHandleToInteger(cleanup->commandPool) - << "fence" << vulkanHandleToInteger(cleanup->fence) - << "semaphore" << vulkanHandleToInteger(cleanup->semaphore) << Qt::dec; - *cleanup = {}; - return; + bool init(VkDevice dev, VkQueue q, uint32_t family, + bool needSyncFileExport, const char *subject, const char *phase) + { + device = dev; + queue = q; + queueFamilyIndex = family; + if (!api.resolve(device, needSyncFileExport, subject, phase)) + return false; + + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT + | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndex; + + VkResult res = api.vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "failed: vkCreateCommandPool" + << vkResultName(res) << int(res); + commandPool = VK_NULL_HANDLE; + return false; + } + + return true; } - auto vkDestroyFence = reinterpret_cast( - vkGetDeviceProcAddr(cleanup->device, "vkDestroyFence")); - auto vkDestroySemaphore = reinterpret_cast( - vkGetDeviceProcAddr(cleanup->device, "vkDestroySemaphore")); - auto vkDestroyCommandPool = reinterpret_cast( - vkGetDeviceProcAddr(cleanup->device, "vkDestroyCommandPool")); + bool ensureSyncFileExportFunctions(const char *subject, const char *phase) + { + if (api.vkCreateSemaphore && api.vkDestroySemaphore && api.vkGetSemaphoreFdKHR) + return true; + return api.resolve(device, true, subject, phase); + } + + bool slotReady(VulkanInteropCommandSlot *slot, bool wait, + const char *subject, const char *phase) + { + if (!slot || !slot->busy || slot->fence == VK_NULL_HANDLE) + return true; - if (cleanup->fence != VK_NULL_HANDLE && vkDestroyFence) - vkDestroyFence(cleanup->device, cleanup->fence, nullptr); - if (cleanup->semaphore != VK_NULL_HANDLE && vkDestroySemaphore) - vkDestroySemaphore(cleanup->device, cleanup->semaphore, nullptr); - if (cleanup->commandPool != VK_NULL_HANDLE && vkDestroyCommandPool) - vkDestroyCommandPool(cleanup->device, cleanup->commandPool, nullptr); + VkResult res = VK_SUCCESS; + if (wait) { + res = api.vkWaitForFences(device, 1, &slot->fence, VK_TRUE, UINT64_MAX); + } else { + res = api.vkGetFenceStatus(device, slot->fence); + } - *cleanup = {}; -} + if (res == VK_NOT_READY) + return false; + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "command fence status failed" + << vkResultName(res) << int(res) + << "fence" << Qt::hex << vulkanHandleToInteger(slot->fence) << Qt::dec; + } -static bool pendingVulkanCommandCleanupReady(PendingVulkanCommandCleanup *cleanup, bool wait) -{ - if (!cleanup || cleanup->device == VK_NULL_HANDLE || cleanup->fence == VK_NULL_HANDLE) + slot->busy = false; return true; + } - auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); - if (!vkGetDeviceProcAddr) - return true; + void retireCompletedSlots(bool wait) + { + for (auto &slot : slots) + slotReady(&slot, wait, "Vulkan RHI interop", "retire"); + } - auto vkGetFenceStatus = reinterpret_cast( - vkGetDeviceProcAddr(cleanup->device, "vkGetFenceStatus")); - auto vkWaitForFences = reinterpret_cast( - vkGetDeviceProcAddr(cleanup->device, "vkWaitForFences")); + void destroy(bool wait) + { + retireCompletedSlots(wait); - VkResult res = VK_SUCCESS; - if (wait) { - if (!vkWaitForFences) - return true; - res = vkWaitForFences(cleanup->device, 1, &cleanup->fence, VK_TRUE, UINT64_MAX); - } else { - if (!vkGetFenceStatus) - return true; - res = vkGetFenceStatus(cleanup->device, cleanup->fence); - } + for (auto &slot : slots) { + if (slot.semaphore != VK_NULL_HANDLE && api.vkDestroySemaphore) + api.vkDestroySemaphore(device, slot.semaphore, nullptr); + if (slot.fence != VK_NULL_HANDLE && api.vkDestroyFence) + api.vkDestroyFence(device, slot.fence, nullptr); + } + slots.clear(); - if (res == VK_SUCCESS) - return true; - if (res == VK_NOT_READY) - return false; + if (commandPool != VK_NULL_HANDLE && api.vkDestroyCommandPool) + api.vkDestroyCommandPool(device, commandPool, nullptr); - qCWarning(lcWlRenderHelper) - << "Vulkan RHI command cleanup fence status failed" - << vkResultName(res) << int(res) - << "commandPool" << Qt::hex << vulkanHandleToInteger(cleanup->commandPool) - << "fence" << vulkanHandleToInteger(cleanup->fence) << Qt::dec; - return true; -} + commandPool = VK_NULL_HANDLE; + device = VK_NULL_HANDLE; + queue = VK_NULL_HANDLE; + queueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + } -static void flushPendingVulkanCommandCleanups(bool wait = false) -{ - QVector pending; + VulkanInteropCommandSlot *acquireSlot(bool exportSyncFile, + const char *subject, const char *phase) { - QMutexLocker locker(s_pendingVulkanCommandCleanupMutex()); - if (s_pendingVulkanCommandCleanups->isEmpty()) - return; - pending.swap(*s_pendingVulkanCommandCleanups); + if (exportSyncFile && !ensureSyncFileExportFunctions(subject, phase)) + return nullptr; + + retireCompletedSlots(false); + + for (auto &slot : slots) { + if (!slot.busy) + return ensureSlotObjects(&slot, exportSyncFile, subject, phase) ? &slot : nullptr; + } + + static constexpr qsizetype maxCachedSlots = 8; + if (slots.size() < maxCachedSlots) { + slots.append(VulkanInteropCommandSlot{}); + auto &slot = slots.last(); + return ensureSlotObjects(&slot, exportSyncFile, subject, phase) ? &slot : nullptr; + } + + VulkanInteropCommandSlot *slot = &slots.first(); + if (!slotReady(slot, true, subject, phase)) + return nullptr; + return ensureSlotObjects(slot, exportSyncFile, subject, phase) ? slot : nullptr; } - QVector remaining; - for (auto &cleanup : pending) { - if (pendingVulkanCommandCleanupReady(&cleanup, wait)) { - destroyPendingVulkanCommandCleanup(&cleanup); - } else { - remaining.append(cleanup); + bool ensureSlotObjects(VulkanInteropCommandSlot *slot, bool exportSyncFile, + const char *subject, const char *phase) + { + if (!slot) + return false; + + VkResult res = VK_SUCCESS; + if (slot->commandBuffer == VK_NULL_HANDLE) { + VkCommandBufferAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + res = api.vkAllocateCommandBuffers(device, &allocInfo, &slot->commandBuffer); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "failed: vkAllocateCommandBuffers" + << vkResultName(res) << int(res); + return false; + } + } + + if (slot->fence == VK_NULL_HANDLE) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + + res = api.vkCreateFence(device, &fenceInfo, nullptr, &slot->fence); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "failed: vkCreateFence" + << vkResultName(res) << int(res); + return false; + } + } + + res = api.vkResetFences(device, 1, &slot->fence); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "failed: vkResetFences" + << vkResultName(res) << int(res); + return false; + } + + res = api.vkResetCommandBuffer(slot->commandBuffer, 0); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "failed: vkResetCommandBuffer" + << vkResultName(res) << int(res); + return false; + } + + if (exportSyncFile && slot->semaphore == VK_NULL_HANDLE) { + VkExportSemaphoreCreateInfo exportInfo = {}; + exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + + VkSemaphoreCreateInfo semInfo = {}; + semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semInfo.pNext = &exportInfo; + + res = api.vkCreateSemaphore(device, &semInfo, nullptr, &slot->semaphore); + if (res != VK_SUCCESS) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "failed: vkCreateSemaphore" + << vkResultName(res) << int(res); + return false; + } } + + return true; } +}; - if (!remaining.isEmpty()) { - QMutexLocker locker(s_pendingVulkanCommandCleanupMutex()); - *s_pendingVulkanCommandCleanups += remaining; +Q_GLOBAL_STATIC(QMutex, s_vulkanInteropCommandMutex) +Q_GLOBAL_STATIC(QVector, s_vulkanInteropCommandContexts) + +static VulkanInteropCommandContext *vulkanInteropCommandContextFor(VkDevice device, + VkQueue queue, + uint32_t queueFamilyIndex, + bool needSyncFileExport, + const char *subject, + const char *phase) +{ + for (auto *context : std::as_const(*s_vulkanInteropCommandContexts)) { + if (context->matches(device, queue, queueFamilyIndex)) { + if (needSyncFileExport && !context->ensureSyncFileExportFunctions(subject, phase)) + return nullptr; + return context; + } } + + auto context = std::make_unique(); + if (!context->init(device, queue, queueFamilyIndex, needSyncFileExport, subject, phase)) + return nullptr; + + auto result = context.release(); + s_vulkanInteropCommandContexts->append(result); + return result; } -static void queuePendingVulkanCommandCleanup(PendingVulkanCommandCleanup *cleanup) +static void flushPendingVulkanCommandCleanups(bool wait = false) { - if (!cleanup || cleanup->device == VK_NULL_HANDLE) - return; + QMutexLocker locker(s_vulkanInteropCommandMutex()); + for (auto *context : std::as_const(*s_vulkanInteropCommandContexts)) + context->retireCompletedSlots(wait); +} - flushPendingVulkanCommandCleanups(false); +static void destroyVulkanInteropCommandContexts() +{ + QVector contexts; + { + QMutexLocker locker(s_vulkanInteropCommandMutex()); + contexts.swap(*s_vulkanInteropCommandContexts); + } - QMutexLocker locker(s_pendingVulkanCommandCleanupMutex()); - s_pendingVulkanCommandCleanups->append(*cleanup); - *cleanup = {}; + for (auto *context : std::as_const(contexts)) { + context->destroy(true); + delete context; + } } static void destroyVulkanImportedRenderTarget(VulkanImportedRenderTarget *target) @@ -501,6 +721,7 @@ static void destroyVulkanImportedRenderTarget(VulkanImportedRenderTarget *target } flushPendingVulkanCommandCleanups(true); + destroyVulkanInteropCommandContexts(); auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); if (!vkGetDeviceProcAddr) { @@ -552,6 +773,7 @@ static void destroyVulkanImportedNativeTexture(VulkanImportedNativeTexture *text } flushPendingVulkanCommandCleanups(true); + destroyVulkanInteropCommandContexts(); auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); if (!vkGetDeviceProcAddr) { @@ -763,134 +985,67 @@ static bool importSyncFileIntoDmabuf(qw_buffer *buffer, int syncFileFd, const ch return true; } -static bool submitVulkanOutputTargetBarrier(QRhi *rhi, - VulkanImportedRenderTarget *target, - qw_buffer *buffer, - const char *phase, - VkImageLayout oldLayout, - VkImageLayout newLayout, - uint32_t srcQueueFamily, - uint32_t dstQueueFamily, - VkAccessFlags srcAccessMask, - VkAccessFlags dstAccessMask, - VkPipelineStageFlags srcStageMask, - VkPipelineStageFlags dstStageMask, - bool exportSyncFile) +static bool submitVulkanImageBarrier(QRhi *rhi, + VkDevice expectedDevice, + VkImage image, + qw_buffer *buffer, + const char *subject, + const char *phase, + VkImageLayout oldLayout, + VkImageLayout newLayout, + uint32_t srcQueueFamily, + uint32_t dstQueueFamily, + VkAccessFlags srcAccessMask, + VkAccessFlags dstAccessMask, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool exportSyncFile) { - if (!rhi || !target || !target->isValid()) + if (!rhi || expectedDevice == VK_NULL_HANDLE || image == VK_NULL_HANDLE) return false; - flushPendingVulkanCommandCleanups(false); - const auto *handles = static_cast(rhi->nativeHandles()); if (!handles || !handles->dev || !handles->gfxQueue || !handles->inst) { qCWarning(lcWlRenderHelper) - << "Vulkan RHI output" << phase + << subject << phase << "failed: QRhi Vulkan native handles unavailable"; return false; } - if (handles->dev != target->device) { + if (handles->dev != expectedDevice) { qCWarning(lcWlRenderHelper) - << "Vulkan RHI output" << phase - << "failed: QRhi device differs from imported output target" + << subject << phase + << "failed: QRhi device differs from imported Vulkan image" << "rhi" << Qt::hex << vulkanHandleToInteger(handles->dev) - << "target" << vulkanHandleToInteger(target->device) << Qt::dec; - return false; - } - - auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); - if (!vkGetDeviceProcAddr) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI output" << phase - << "failed: vkGetDeviceProcAddr unavailable"; + << "expected" << vulkanHandleToInteger(expectedDevice) << Qt::dec; return false; } - VkDevice device = handles->dev; - VkQueue queue = handles->gfxQueue; - - auto vkCreateCommandPool = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkCreateCommandPool")); - auto vkDestroyCommandPool = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkDestroyCommandPool")); - auto vkAllocateCommandBuffers = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkAllocateCommandBuffers")); - auto vkFreeCommandBuffers = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkFreeCommandBuffers")); - auto vkBeginCommandBuffer = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkBeginCommandBuffer")); - auto vkEndCommandBuffer = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkEndCommandBuffer")); - auto vkCmdPipelineBarrier = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkCmdPipelineBarrier")); - auto vkQueueSubmit = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkQueueSubmit")); - auto vkCreateFence = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkCreateFence")); - auto vkDestroyFence = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkDestroyFence")); - auto vkCreateSemaphore = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkCreateSemaphore")); - auto vkDestroySemaphore = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkDestroySemaphore")); - auto vkGetSemaphoreFdKHR = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkGetSemaphoreFdKHR")); - - if (!vkCreateCommandPool || !vkDestroyCommandPool || !vkAllocateCommandBuffers - || !vkFreeCommandBuffers || !vkBeginCommandBuffer || !vkEndCommandBuffer - || !vkCmdPipelineBarrier || !vkQueueSubmit || !vkCreateFence || !vkDestroyFence - || (exportSyncFile && (!vkCreateSemaphore || !vkDestroySemaphore || !vkGetSemaphoreFdKHR))) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI output" << phase - << "failed: required Vulkan command/sync functions unavailable" - << "exportSyncFile" << exportSyncFile; - return false; - } - - VkCommandPoolCreateInfo poolInfo = {}; - poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; - poolInfo.queueFamilyIndex = handles->gfxQueueFamilyIdx; - - VkCommandPool commandPool = VK_NULL_HANDLE; - VkResult res = vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool); - if (res != VK_SUCCESS) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI output" << phase - << "failed: vkCreateCommandPool" - << vkResultName(res) << int(res); + const uint32_t queueFamily = static_cast(handles->gfxQueueFamilyIdx); + QMutexLocker locker(s_vulkanInteropCommandMutex()); + auto *context = vulkanInteropCommandContextFor(handles->dev, + handles->gfxQueue, + queueFamily, + exportSyncFile, + subject, + phase); + if (!context) return false; - } - - VkCommandBufferAllocateInfo allocInfo = {}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.commandPool = commandPool; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandBufferCount = 1; - VkCommandBuffer cb = VK_NULL_HANDLE; - res = vkAllocateCommandBuffers(device, &allocInfo, &cb); - if (res != VK_SUCCESS) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI output" << phase - << "failed: vkAllocateCommandBuffers" - << vkResultName(res) << int(res); - vkDestroyCommandPool(device, commandPool, nullptr); + auto *slot = context->acquireSlot(exportSyncFile, subject, phase); + if (!slot) return false; - } VkCommandBufferBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - res = vkBeginCommandBuffer(cb, &beginInfo); + + VkResult res = context->api.vkBeginCommandBuffer(slot->commandBuffer, &beginInfo); if (res != VK_SUCCESS) { qCWarning(lcWlRenderHelper) - << "Vulkan RHI output" << phase + << subject << phase << "failed: vkBeginCommandBuffer" << vkResultName(res) << int(res); - vkFreeCommandBuffers(device, commandPool, 1, &cb); - vkDestroyCommandPool(device, commandPool, nullptr); return false; } @@ -898,7 +1053,7 @@ static bool submitVulkanOutputTargetBarrier(QRhi *rhi, barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.srcQueueFamilyIndex = srcQueueFamily; barrier.dstQueueFamilyIndex = dstQueueFamily; - barrier.image = target->image; + barrier.image = image; barrier.oldLayout = oldLayout; barrier.newLayout = newLayout; barrier.srcAccessMask = srcAccessMask; @@ -907,110 +1062,96 @@ static bool submitVulkanOutputTargetBarrier(QRhi *rhi, barrier.subresourceRange.layerCount = 1; barrier.subresourceRange.levelCount = 1; - vkCmdPipelineBarrier(cb, srcStageMask, dstStageMask, - 0, 0, nullptr, 0, nullptr, 1, &barrier); + context->api.vkCmdPipelineBarrier(slot->commandBuffer, + srcStageMask, + dstStageMask, + 0, 0, nullptr, 0, nullptr, 1, &barrier); - res = vkEndCommandBuffer(cb); + res = context->api.vkEndCommandBuffer(slot->commandBuffer); if (res != VK_SUCCESS) { qCWarning(lcWlRenderHelper) - << "Vulkan RHI output" << phase + << subject << phase << "failed: vkEndCommandBuffer" << vkResultName(res) << int(res); - vkFreeCommandBuffers(device, commandPool, 1, &cb); - vkDestroyCommandPool(device, commandPool, nullptr); - return false; - } - - VkSemaphore semaphore = VK_NULL_HANDLE; - if (exportSyncFile) { - VkExportSemaphoreCreateInfo exportInfo = {}; - exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; - exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; - - VkSemaphoreCreateInfo semInfo = {}; - semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - semInfo.pNext = &exportInfo; - - res = vkCreateSemaphore(device, &semInfo, nullptr, &semaphore); - if (res != VK_SUCCESS) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI output" << phase - << "failed: vkCreateSemaphore" - << vkResultName(res) << int(res); - vkFreeCommandBuffers(device, commandPool, 1, &cb); - vkDestroyCommandPool(device, commandPool, nullptr); - return false; - } - } - - VkFenceCreateInfo fenceInfo = {}; - fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - - VkFence fence = VK_NULL_HANDLE; - res = vkCreateFence(device, &fenceInfo, nullptr, &fence); - if (res != VK_SUCCESS) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI output" << phase - << "failed: vkCreateFence" - << vkResultName(res) << int(res); - if (semaphore != VK_NULL_HANDLE) - vkDestroySemaphore(device, semaphore, nullptr); - vkFreeCommandBuffers(device, commandPool, 1, &cb); - vkDestroyCommandPool(device, commandPool, nullptr); return false; } VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &cb; - if (semaphore != VK_NULL_HANDLE) { + submitInfo.pCommandBuffers = &slot->commandBuffer; + if (exportSyncFile) { submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = &semaphore; + submitInfo.pSignalSemaphores = &slot->semaphore; } - res = vkQueueSubmit(queue, 1, &submitInfo, fence); + res = context->api.vkQueueSubmit(handles->gfxQueue, 1, &submitInfo, slot->fence); if (res != VK_SUCCESS) { qCWarning(lcWlRenderHelper) - << "Vulkan RHI output" << phase + << subject << phase << "failed: vkQueueSubmit" << vkResultName(res) << int(res); - if (semaphore != VK_NULL_HANDLE) - vkDestroySemaphore(device, semaphore, nullptr); - vkDestroyFence(device, fence, nullptr); - vkFreeCommandBuffers(device, commandPool, 1, &cb); - vkDestroyCommandPool(device, commandPool, nullptr); return false; } - bool ok = true; - if (semaphore != VK_NULL_HANDLE) { - VkSemaphoreGetFdInfoKHR getFdInfo = {}; - getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR; - getFdInfo.semaphore = semaphore; - getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + slot->busy = true; - int syncFileFd = -1; - res = vkGetSemaphoreFdKHR(device, &getFdInfo, &syncFileFd); - if (res != VK_SUCCESS || syncFileFd < 0) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI output" << phase - << "failed: vkGetSemaphoreFdKHR" - << vkResultName(res) << int(res) - << "fd" << syncFileFd; - ok = false; - } else { - ok = importSyncFileIntoDmabuf(buffer, syncFileFd, phase); - close(syncFileFd); - } + if (!exportSyncFile) + return true; + + VkSemaphoreGetFdInfoKHR getFdInfo = {}; + getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR; + getFdInfo.semaphore = slot->semaphore; + getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + + int syncFileFd = -1; + res = context->api.vkGetSemaphoreFdKHR(handles->dev, &getFdInfo, &syncFileFd); + if (res != VK_SUCCESS || syncFileFd < 0) { + qCWarning(lcWlRenderHelper) + << subject << phase + << "failed: vkGetSemaphoreFdKHR" + << vkResultName(res) << int(res) + << "fd" << syncFileFd; + return false; } - PendingVulkanCommandCleanup cleanup; - cleanup.device = device; - cleanup.commandPool = commandPool; - cleanup.fence = fence; - cleanup.semaphore = semaphore; - queuePendingVulkanCommandCleanup(&cleanup); + const bool ok = importSyncFileIntoDmabuf(buffer, syncFileFd, phase); + close(syncFileFd); + return ok; +} + +static bool submitVulkanOutputTargetBarrier(QRhi *rhi, + VulkanImportedRenderTarget *target, + qw_buffer *buffer, + const char *phase, + VkImageLayout oldLayout, + VkImageLayout newLayout, + uint32_t srcQueueFamily, + uint32_t dstQueueFamily, + VkAccessFlags srcAccessMask, + VkAccessFlags dstAccessMask, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + bool exportSyncFile) +{ + if (!rhi || !target || !target->isValid()) + return false; + + const bool ok = submitVulkanImageBarrier(rhi, + target->device, + target->image, + buffer, + "Vulkan RHI output", + phase, + oldLayout, + newLayout, + srcQueueFamily, + dstQueueFamily, + srcAccessMask, + dstAccessMask, + srcStageMask, + dstStageMask, + exportSyncFile); if (ok) { qCDebug(lcWlRenderHelper) @@ -2331,8 +2472,6 @@ static bool acquireVulkanNativeTextureForSampling(QRhi *rhi, if (!rhi || !texture || !texture->isValid()) return false; - flushPendingVulkanCommandCleanups(false); - const auto *handles = static_cast(rhi->nativeHandles()); if (!handles || !handles->dev || !handles->gfxQueue || !handles->inst) { qCWarning(lcWlRenderHelper) @@ -2348,155 +2487,30 @@ static bool acquireVulkanNativeTextureForSampling(QRhi *rhi, return false; } - auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddr(); - if (!vkGetDeviceProcAddr) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI client texture acquire failed: vkGetDeviceProcAddr unavailable"; - return false; - } - VkDevice device = handles->dev; VkQueue queue = handles->gfxQueue; texture->queue = queue; texture->queueFamilyIndex = handles->gfxQueueFamilyIdx; - auto vkCreateCommandPool = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkCreateCommandPool")); - auto vkDestroyCommandPool = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkDestroyCommandPool")); - auto vkAllocateCommandBuffers = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkAllocateCommandBuffers")); - auto vkFreeCommandBuffers = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkFreeCommandBuffers")); - auto vkBeginCommandBuffer = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkBeginCommandBuffer")); - auto vkEndCommandBuffer = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkEndCommandBuffer")); - auto vkCmdPipelineBarrier = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkCmdPipelineBarrier")); - auto vkQueueSubmit = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkQueueSubmit")); - auto vkCreateFence = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkCreateFence")); - auto vkDestroyFence = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkDestroyFence")); - - if (!vkCreateCommandPool || !vkDestroyCommandPool || !vkAllocateCommandBuffers - || !vkFreeCommandBuffers || !vkBeginCommandBuffer || !vkEndCommandBuffer - || !vkCmdPipelineBarrier || !vkQueueSubmit || !vkCreateFence || !vkDestroyFence) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI client texture acquire failed: required Vulkan command functions unavailable"; - return false; - } - - VkCommandPoolCreateInfo poolInfo = {}; - poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; - poolInfo.queueFamilyIndex = handles->gfxQueueFamilyIdx; - - VkCommandPool commandPool = VK_NULL_HANDLE; - VkResult res = vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool); - if (res != VK_SUCCESS) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI client texture acquire failed: vkCreateCommandPool" - << vkResultName(res) << int(res); - return false; - } - - VkCommandBufferAllocateInfo allocInfo = {}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.commandPool = commandPool; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandBufferCount = 1; - - VkCommandBuffer cb = VK_NULL_HANDLE; - res = vkAllocateCommandBuffers(device, &allocInfo, &cb); - if (res != VK_SUCCESS) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI client texture acquire failed: vkAllocateCommandBuffers" - << vkResultName(res) << int(res); - vkDestroyCommandPool(device, commandPool, nullptr); - return false; - } - - VkCommandBufferBeginInfo beginInfo = {}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - res = vkBeginCommandBuffer(cb, &beginInfo); - if (res != VK_SUCCESS) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI client texture acquire failed: vkBeginCommandBuffer" - << vkResultName(res) << int(res); - vkFreeCommandBuffers(device, commandPool, 1, &cb); - vkDestroyCommandPool(device, commandPool, nullptr); - return false; - } - const VkImageLayout oldLayout = texture->layout; - VkImageMemoryBarrier barrier = {}; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT; - barrier.dstQueueFamilyIndex = handles->gfxQueueFamilyIdx; - barrier.image = texture->image; - barrier.oldLayout = oldLayout; - barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.layerCount = 1; - barrier.subresourceRange.levelCount = 1; - - vkCmdPipelineBarrier(cb, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, 0, nullptr, 0, nullptr, 1, &barrier); - - res = vkEndCommandBuffer(cb); - if (res != VK_SUCCESS) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI client texture acquire failed: vkEndCommandBuffer" - << vkResultName(res) << int(res); - vkFreeCommandBuffers(device, commandPool, 1, &cb); - vkDestroyCommandPool(device, commandPool, nullptr); - return false; - } - - VkSubmitInfo submitInfo = {}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &cb; - - VkFenceCreateInfo fenceInfo = {}; - fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - - VkFence fence = VK_NULL_HANDLE; - res = vkCreateFence(device, &fenceInfo, nullptr, &fence); - if (res != VK_SUCCESS) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI client texture acquire failed: vkCreateFence" - << vkResultName(res) << int(res); - vkFreeCommandBuffers(device, commandPool, 1, &cb); - vkDestroyCommandPool(device, commandPool, nullptr); + if (!submitVulkanImageBarrier(rhi, + device, + texture->image, + nullptr, + "Vulkan RHI client texture", + "acquire", + oldLayout, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_QUEUE_FAMILY_FOREIGN_EXT, + handles->gfxQueueFamilyIdx, + 0, + VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + false)) { return false; } - res = vkQueueSubmit(queue, 1, &submitInfo, fence); - if (res != VK_SUCCESS) { - qCWarning(lcWlRenderHelper) - << "Vulkan RHI client texture acquire failed: vkQueueSubmit" - << vkResultName(res) << int(res); - vkDestroyFence(device, fence, nullptr); - vkFreeCommandBuffers(device, commandPool, 1, &cb); - vkDestroyCommandPool(device, commandPool, nullptr); - return false; - } - - PendingVulkanCommandCleanup cleanup; - cleanup.device = device; - cleanup.commandPool = commandPool; - cleanup.fence = fence; - queuePendingVulkanCommandCleanup(&cleanup); - texture->layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; qCDebug(lcWlRenderHelper) << "Vulkan RHI client texture acquired for Qt sampling" diff --git a/waylib/src/server/qtquick/wsgtextureprovider.cpp b/waylib/src/server/qtquick/wsgtextureprovider.cpp index d7cb8a8c0..12ff7f406 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.cpp +++ b/waylib/src/server/qtquick/wsgtextureprovider.cpp @@ -116,6 +116,7 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate uint32_t drmFormat = 0; uint64_t drmModifier = 0; bool hasAlpha = false; + quint32 contentSerial = 0; quint64 lastUsed = 0; }; @@ -199,6 +200,7 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate nativeCleanup = {}; if (buffer == entry.buffer) buffer = nullptr; + bufferContentSerial = 0; } qCDebug(lcWlQtQuickTexture) @@ -249,10 +251,14 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate } void activateVulkanDmabufTextureCacheEntry(int index, qw_buffer *newBuffer, - const char *backendName, bool cacheHit) + quint32 newBufferContentSerial, + const char *backendName, bool cacheHit, + bool reacquired) { auto &entry = vulkanDmabufTextureCache[index]; entry.lastUsed = ++vulkanDmabufTextureUseSerial; + if (newBufferContentSerial != 0) + entry.contentSerial = newBufferContentSerial; auto oldTexture = texture; const bool oldOwnsTexture = ownsTexture; @@ -262,6 +268,7 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate texture = nullptr; ownsTexture = false; buffer = newBuffer; + bufferContentSerial = newBufferContentSerial; qtTexture.setOwnsTexture(false); qtTexture.setTexture(entry.rhiTexture); qtTexture.setHasAlphaChannel(entry.hasAlpha); @@ -273,7 +280,9 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate << "Vulkan renderer dmabuf texture import path" << "qtBackend" << backendName << "cacheHit" << cacheHit + << "reacquired" << reacquired << "buffer" << pointerAddress(newBuffer) + << "contentSerial" << newBufferContentSerial << "rhiTexture" << rhiTexture << "size" << entry.size << "format" << Qt::hex << entry.drmFormat << Qt::dec @@ -326,6 +335,7 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate texture = nullptr; ownsTexture = false; buffer = nullptr; + bufferContentSerial = 0; #ifdef ENABLE_VULKAN_RENDER clearVulkanDmabufTextureCache(); #endif @@ -333,7 +343,8 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate bool replaceTexture(qw_texture *newTexture, bool newOwnsTexture, qw_buffer *newBuffer, bool allowBufferDirectImport = true, wlr_surface *surface = nullptr, - QRhiCommandBuffer *commandBuffer = nullptr) { + QRhiCommandBuffer *commandBuffer = nullptr, + quint32 newBufferContentSerial = 0) { Q_ASSERT(newTexture); WRenderHelper::NativeTextureCleanup newCleanup; @@ -373,6 +384,7 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate texture = newTexture; ownsTexture = newOwnsTexture; buffer = newBuffer; + bufferContentSerial = newBufferContentSerial; rhiTexture = qtTexture.rhiTexture(); nativeCleanup = newCleanup; @@ -383,7 +395,8 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate return true; } - bool replaceBufferWithDirectDmabufTexture(qw_buffer *newBuffer, wlr_surface *surface) + bool replaceBufferWithDirectDmabufTexture(qw_buffer *newBuffer, wlr_surface *surface, + quint32 newBufferContentSerial) { #ifdef ENABLE_VULKAN_RENDER if (!newBuffer || !window || !window->rhi()) @@ -404,12 +417,22 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate int cacheIndex = findVulkanDmabufTextureCacheEntry(newBuffer); if (cacheIndex >= 0) { auto &entry = vulkanDmabufTextureCache[cacheIndex]; + if (newBufferContentSerial != 0 + && entry.contentSerial == newBufferContentSerial + && entry.rhiTexture) { + activateVulkanDmabufTextureCacheEntry(cacheIndex, newBuffer, + newBufferContentSerial, + backendName, true, false); + return true; + } + if (WRenderHelper::acquireImportedVulkanTextureFromBuffer(window->rhi(), newBuffer, surface, &entry.nativeCleanup)) { activateVulkanDmabufTextureCacheEntry(cacheIndex, newBuffer, - backendName, true); + newBufferContentSerial, + backendName, true, true); return true; } @@ -434,13 +457,15 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate entry.drmFormat = importedTexture.drmFormat; entry.drmModifier = importedTexture.drmModifier; entry.hasAlpha = importedTexture.hasAlpha; + entry.contentSerial = newBufferContentSerial; importedTexture.texture = nullptr; importedTexture.nativeCleanup = {}; vulkanDmabufTextureCache.append(entry); cacheIndex = vulkanDmabufTextureCache.size() - 1; activateVulkanDmabufTextureCacheEntry(cacheIndex, newBuffer, - backendName, false); + newBufferContentSerial, + backendName, false, true); trimVulkanDmabufTextureCache(); return true; } @@ -458,6 +483,7 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate texture = nullptr; ownsTexture = false; buffer = newBuffer; + bufferContentSerial = newBufferContentSerial; rhiTexture = qtTexture.rhiTexture(); nativeCleanup = newCleanup; @@ -520,6 +546,7 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate WRenderHelper::NativeTextureCleanup nativeCleanup; bool smooth = true; bool directBufferImportAllowed = false; + quint32 bufferContentSerial = 0; #ifdef ENABLE_VULKAN_RENDER QVector deferredVulkanTextureReleases; QVector vulkanDmabufTextureCache; @@ -581,16 +608,23 @@ void WSGTextureProvider::setDirectBufferImportAllowed(bool allowed) bool WSGTextureProvider::setBuffer(qw_buffer *buffer) { - return setBuffer(buffer, nullptr); + return setBuffer(buffer, nullptr, nullptr, 0); } bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface) { - return setBuffer(buffer, surface, nullptr); + return setBuffer(buffer, surface, nullptr, 0); } bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface, QRhiCommandBuffer *commandBuffer) +{ + return setBuffer(buffer, surface, commandBuffer, 0); +} + +bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface, + QRhiCommandBuffer *commandBuffer, + quint32 bufferContentSerial) { W_D(WSGTextureProvider); @@ -612,6 +646,7 @@ bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface, if (sameBuffer && !allowDirectBufferImport && !needsSameBufferUpload) { // The buffer object is not changed, but maybe the buffer's content is changed. // So should emit textureChanged() signal too. + d->bufferContentSerial = bufferContentSerial; if (buffer) Q_EMIT textureChanged(); return true; @@ -632,6 +667,8 @@ bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface, << "bufferHasDmabuf" << bufferHasNativeDmabuf << "hasExistingTexture" << d->hasTexture() << "currentBuffer" << pointerAddress(d->buffer) + << "bufferContentSerial" << bufferContentSerial + << "currentBufferContentSerial" << d->bufferContentSerial << "commandBuffer" << pointerAddress(commandBuffer) << "size" << bufferSize(buffer); @@ -645,7 +682,8 @@ bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface, bool triedDirectBufferImport = false; if (directImportEligible) { triedDirectBufferImport = true; - if (d->replaceBufferWithDirectDmabufTexture(buffer, surface)) { + if (d->replaceBufferWithDirectDmabufTexture(buffer, surface, + bufferContentSerial)) { Q_EMIT textureChanged(); return true; } @@ -679,7 +717,7 @@ bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface, if (d->replaceTexture(texture, ownsTexture, buffer, directImportEligible && !triedDirectBufferImport, - surface, commandBuffer)) { + surface, commandBuffer, bufferContentSerial)) { Q_EMIT textureChanged(); return true; } @@ -688,6 +726,7 @@ bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface, d->cleanTexture(); d->buffer = buffer; + d->bufferContentSerial = bufferContentSerial; if (buffer) { Q_ASSERT(d->window); @@ -719,18 +758,26 @@ bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface, bool WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer) { - return setTexture(texture, srcBuffer, nullptr); + return setTexture(texture, srcBuffer, nullptr, nullptr, 0); } bool WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer, wlr_surface *surface) { - return setTexture(texture, srcBuffer, surface, nullptr); + return setTexture(texture, srcBuffer, surface, nullptr, 0); } bool WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer, wlr_surface *surface, QRhiCommandBuffer *commandBuffer) +{ + return setTexture(texture, srcBuffer, surface, commandBuffer, 0); +} + +bool WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer, + wlr_surface *surface, + QRhiCommandBuffer *commandBuffer, + quint32 bufferContentSerial) { W_D(WSGTextureProvider); if (d->isVulkanRenderer()) { @@ -747,6 +794,8 @@ bool WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer, << "sourceBufferHasDmabuf" << sourceBufferHasDmabuf << "hasExistingTexture" << d->hasTexture() << "currentBuffer" << pointerAddress(d->buffer) + << "bufferContentSerial" << bufferContentSerial + << "currentBufferContentSerial" << d->bufferContentSerial << "commandBuffer" << pointerAddress(commandBuffer) << "sourceSize" << bufferSize(srcBuffer); @@ -763,7 +812,8 @@ bool WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer, bool triedDirectBufferImport = false; if (srcBuffer && directImportEligible) { triedDirectBufferImport = true; - if (d->replaceBufferWithDirectDmabufTexture(srcBuffer, surface)) { + if (d->replaceBufferWithDirectDmabufTexture(srcBuffer, surface, + bufferContentSerial)) { Q_EMIT textureChanged(); return true; } @@ -777,7 +827,7 @@ bool WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer, if (d->replaceTexture(texture, false, srcBuffer, directImportEligible && !triedDirectBufferImport, - surface, commandBuffer)) { + surface, commandBuffer, bufferContentSerial)) { Q_EMIT textureChanged(); return true; } @@ -787,6 +837,7 @@ bool WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer, d->cleanTexture(); d->texture = texture; d->buffer = srcBuffer; + d->bufferContentSerial = bufferContentSerial; d->ownsTexture = false; if (texture) d->updateRhiTexture(commandBuffer); diff --git a/waylib/src/server/qtquick/wsgtextureprovider.h b/waylib/src/server/qtquick/wsgtextureprovider.h index 7527e68c6..8a4ed0458 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.h +++ b/waylib/src/server/qtquick/wsgtextureprovider.h @@ -40,11 +40,16 @@ class WAYLIB_SERVER_EXPORT WSGTextureProvider : public QSGTextureProvider, publi bool setBuffer(QW_NAMESPACE::qw_buffer *buffer, wlr_surface *surface); bool setBuffer(QW_NAMESPACE::qw_buffer *buffer, wlr_surface *surface, QRhiCommandBuffer *commandBuffer); + bool setBuffer(QW_NAMESPACE::qw_buffer *buffer, wlr_surface *surface, + QRhiCommandBuffer *commandBuffer, quint32 bufferContentSerial); bool setTexture(QW_NAMESPACE::qw_texture *texture, QW_NAMESPACE::qw_buffer *srcBuffer); bool setTexture(QW_NAMESPACE::qw_texture *texture, QW_NAMESPACE::qw_buffer *srcBuffer, wlr_surface *surface); bool setTexture(QW_NAMESPACE::qw_texture *texture, QW_NAMESPACE::qw_buffer *srcBuffer, wlr_surface *surface, QRhiCommandBuffer *commandBuffer); + bool setTexture(QW_NAMESPACE::qw_texture *texture, QW_NAMESPACE::qw_buffer *srcBuffer, + wlr_surface *surface, QRhiCommandBuffer *commandBuffer, + quint32 bufferContentSerial); void invalidate(); QSGTexture *texture() const override; diff --git a/waylib/src/server/qtquick/wsurfaceitem.cpp b/waylib/src/server/qtquick/wsurfaceitem.cpp index 6affdf04c..42015a869 100644 --- a/waylib/src/server/qtquick/wsurfaceitem.cpp +++ b/waylib/src/server/qtquick/wsurfaceitem.cpp @@ -244,6 +244,8 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate } textureDirty = true; #ifdef ENABLE_VULKAN_RENDER + bufferContentSerial = 0; + pendingBufferContentSerial = 0; textureRetryBackoffFrames = 0; textureRetryDelayFrames = 0; releaseDeferredDirectBuffers(); @@ -286,6 +288,9 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate textureDirty = true; // Get the new buffer pointer from surface auto newBuffer = surface->buffer(); + const quint32 newBufferContentSerial = surface->handle() + ? surface->handle()->current_seq() + : 0; #ifdef ENABLE_VULKAN_RENDER auto newDirectBuffer = surface->committedBuffer(); registerSurfaceBufferExplicitRelease(surface.data(), newDirectBuffer); @@ -294,12 +299,14 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate if (!live) { // Non-live mode: defer to pendingBuffer pendingBuffer.reset(newBuffer); + pendingBufferContentSerial = newBufferContentSerial; #ifdef ENABLE_VULKAN_RENDER pendingDirectBuffer.reset(newDirectBuffer); #endif } else { // Live mode: update buffer immediately buffer.reset(newBuffer); + bufferContentSerial = newBufferContentSerial; #ifdef ENABLE_VULKAN_RENDER directBuffer.reset(newDirectBuffer); #endif @@ -438,6 +445,8 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate inline void swapBufferIfNeeded() { if (pendingBuffer) { buffer = std::move(pendingBuffer); + bufferContentSerial = pendingBufferContentSerial; + pendingBufferContentSerial = 0; #ifdef ENABLE_VULKAN_RENDER directBuffer = std::move(pendingDirectBuffer); pendingDirectBuffer.reset(); @@ -485,6 +494,8 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate mutable WSGTextureProvider *textureProvider = nullptr; BufferRef buffer; BufferRef pendingBuffer; + quint32 bufferContentSerial = 0; + quint32 pendingBufferContentSerial = 0; mutable QMetaObject::Connection updateTextureConnection; bool dontCacheLastBuffer = false; bool live = true; @@ -599,11 +610,14 @@ WSGTextureProvider *WSurfaceItemContent::wTextureProvider() const #ifdef ENABLE_VULKAN_RENDER if (WSGTextureProvider::prefersDirectBufferImport(w)) { auto *preferredBuffer = d->textureBufferForPreferredPath(w); - textureUpdated = d->textureProvider->setBuffer(preferredBuffer, surfaceHandle); + textureUpdated = d->textureProvider->setBuffer(preferredBuffer, surfaceHandle, + nullptr, + d->bufferContentSerial); qCDebug(lcWlSurface) << "Initialized surface texture provider through preferred direct client dmabuf path" << "surface" << d->surface << "buffer" << preferredBuffer + << "contentSerial" << d->bufferContentSerial << "legacyBuffer" << d->buffer.get() << "directBuffer" << d->directBuffer.get() << "updated" << textureUpdated; @@ -613,9 +627,13 @@ WSGTextureProvider *WSurfaceItemContent::wTextureProvider() const if (auto texture = d->surface->handle()->get_texture()) { textureUpdated = d->textureProvider->setTexture(qw_texture::from(texture), d->buffer.get(), - surfaceHandle); + surfaceHandle, + nullptr, + d->bufferContentSerial); } else { - textureUpdated = d->textureProvider->setBuffer(d->buffer.get(), surfaceHandle); + textureUpdated = d->textureProvider->setBuffer(d->buffer.get(), surfaceHandle, + nullptr, + d->bufferContentSerial); } } if (textureUpdated && d->textureProvider->texture()) { @@ -832,11 +850,13 @@ QSGNode *WSurfaceItemContent::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeD #ifdef ENABLE_VULKAN_RENDER if (WSGTextureProvider::prefersDirectBufferImport(tp->window())) { auto *preferredBuffer = d->textureBufferForPreferredPath(tp->window()); - textureUpdated = tp->setBuffer(preferredBuffer, surfaceHandle, commandBuffer); + textureUpdated = tp->setBuffer(preferredBuffer, surfaceHandle, commandBuffer, + d->bufferContentSerial); qCDebug(lcWlSurface) << "Updated surface texture through preferred direct client dmabuf path" << "surface" << d->surface << "buffer" << preferredBuffer + << "contentSerial" << d->bufferContentSerial << "legacyBuffer" << d->buffer.get() << "directBuffer" << d->directBuffer.get() << "updated" << textureUpdated @@ -846,9 +866,11 @@ QSGNode *WSurfaceItemContent::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeD auto texture = !textureUpdated && d->surface ? d->surface->handle()->get_texture() : nullptr; if (texture) { textureUpdated = tp->setTexture(qw_texture::from(texture), d->buffer.get(), - surfaceHandle, commandBuffer); + surfaceHandle, commandBuffer, + d->bufferContentSerial); } else if (!textureUpdated) { - textureUpdated = tp->setBuffer(d->buffer.get(), surfaceHandle, commandBuffer); + textureUpdated = tp->setBuffer(d->buffer.get(), surfaceHandle, commandBuffer, + d->bufferContentSerial); } #ifdef ENABLE_VULKAN_RENDER d->noteTextureUpdateResult(textureUpdated); From 4c88d97760a257fa8ac05b8e1e621b151551d160 Mon Sep 17 00:00:00 2001 From: LFRon Date: Fri, 3 Jul 2026 22:55:52 +0800 Subject: [PATCH 3/3] fix(waylib): stabilize Vulkan client buffer texture lifetime Share Vulkan dmabuf imports across Qt Quick texture providers with a budgeted QRhiTexture cache, ref-counted active use, and deferred native cleanup after render end. This avoids repeatedly importing the same live client buffers and prevents stale Vulkan image memory from accumulating. Upload non-dmabuf client buffers directly into Qt RHI Vulkan textures so DTK/DDE clients avoid the unsafe wlroots texture fallback path. Use Qt-native Vulkan render-buffer textures for QML intermediate targets and bound the released render-buffer cache to keep temporary scene graph targets from growing VRAM unbounded. --- .../qtquick/private/wrenderbuffernode.cpp | 190 +++- waylib/src/server/qtquick/wrenderhelper.cpp | 103 +- waylib/src/server/qtquick/wrenderhelper.h | 5 + .../src/server/qtquick/wsgtextureprovider.cpp | 909 ++++++++++++++---- 4 files changed, 1019 insertions(+), 188 deletions(-) diff --git a/waylib/src/server/qtquick/private/wrenderbuffernode.cpp b/waylib/src/server/qtquick/private/wrenderbuffernode.cpp index 2a8c7b6a3..bb9edb3a3 100644 --- a/waylib/src/server/qtquick/private/wrenderbuffernode.cpp +++ b/waylib/src/server/qtquick/private/wrenderbuffernode.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include +#include #include "wrenderhelper.h" #include "wayliblogging.h" @@ -289,12 +290,21 @@ class Q_DECL_HIDDEN DataManager : public DataManagerBase tmp.swap(manager->dataList); manager->dataList.reserve(tmp.size()); + qsizetype retainedCost = 0; for (const auto &data : std::as_const(tmp)) { - if (data->released > 2) { + if (data->released <= 0) + retainedCost += manager->get()->dataCost(data->data); + } + + for (const auto &data : std::as_const(tmp)) { + const qsizetype dataCost = manager->get()->dataCost(data->data); + if (manager->get()->shouldDestroyData(*data, retainedCost, dataCost)) { // Collect items to destroy instead of destroying immediately itemsToDestroy.append(data->data); } else { manager->dataList << data; + if (data->released > 0) + retainedCost += dataCost; if (data->released > 0) ++data->released; @@ -333,6 +343,14 @@ class Q_DECL_HIDDEN DataManager : public DataManagerBase } } + qsizetype dataCost(DataType *) const { + return 0; + } + + bool shouldDestroyData(const Data &data, qsizetype, qsizetype) const { + return data.released > 2; + } + private: friend Derive; @@ -352,6 +370,9 @@ struct WlrAndRhiTexture { qw_texture *wlrTexture = nullptr; QRhiTexture *rhiTexture = nullptr; WRenderHelper::NativeTextureCleanup nativeCleanup; + QRhiTexture::Format rhiFormat = QRhiTexture::UnknownFormat; + uint32_t drmFormat = 0; + uint64_t drmModifier = 0; }; class Q_DECL_HIDDEN RhiTextureManager : public DataManager @@ -365,30 +386,109 @@ class Q_DECL_HIDDEN RhiTextureManager : public DataManagerfindChildren(Qt::FindDirectChildrenOnly).size() == 1); } - static bool check(WlrAndRhiTexture *texture, QRhiTexture::Format, uint32_t drmFormat, uint64_t drmModifier, const QSize &size) { - auto buffer = qw_buffer::from(texture->buffer); - wlr_dmabuf_attributes attribs; - if (!buffer->get_dmabuf(&attribs)) { - qCWarning(lcWlRenderBuffer) << "Failed to get dmabuf attributes for texture" << texture << "with buffer" << buffer - << ", Can't check texture without dmabuf attributes"; + static bool check(WlrAndRhiTexture *texture, QRhiTexture::Format format, uint32_t drmFormat, uint64_t drmModifier, const QSize &size) { + if (!texture || !texture->rhiTexture) return false; + + if (texture->buffer) { + auto buffer = qw_buffer::from(texture->buffer); + wlr_dmabuf_attributes attribs; + if (!buffer->get_dmabuf(&attribs)) { + qCWarning(lcWlRenderBuffer) << "Failed to get dmabuf attributes for texture" << texture << "with buffer" << buffer + << ", Can't check texture without dmabuf attributes"; + return false; + } + if (attribs.format != drmFormat || attribs.modifier != drmModifier) + return false; } - return attribs.format == drmFormat && attribs.modifier == drmModifier && texture->rhiTexture->pixelSize() == size; + + return texture->rhiFormat == format + && texture->drmFormat == drmFormat + && texture->drmModifier == drmModifier + && texture->rhiTexture->pixelSize() == size; } WlrAndRhiTexture *create(QRhiTexture::Format format, uint32_t drmFormat, uint64_t drmModifier, const QSize &size) { + auto rhi = owner()->rhi(); + if (!rhi) { + qCWarning(lcWlRenderBuffer) + << "Failed to create render-buffer texture: window has no QRhi" + << "size" << size + << "format" << format; + return nullptr; + } + + if (rhi->backend() == QRhi::Vulkan) { + std::unique_ptr rhiTexture(rhi->newTexture(format, size, 1, QRhiTexture::RenderTarget)); + if (!rhiTexture) { + qCWarning(lcWlRenderBuffer) + << "Failed to allocate Qt-native Vulkan render-buffer texture object" + << "size" << size + << "format" << format; + return nullptr; + } + rhiTexture->setName(QByteArrayLiteral("WaylibRenderBufferTexture")); + if (!rhiTexture->create()) { + qCWarning(lcWlRenderBuffer) + << "Failed to create Qt-native Vulkan render-buffer texture" + << "size" << size + << "format" << format + << "drmFormat" << Qt::hex << drmFormat + << "modifier" << drmModifier << Qt::dec; + return nullptr; + } + + auto result = new WlrAndRhiTexture; + result->rhiTexture = rhiTexture.release(); + result->rhiFormat = format; + result->drmFormat = drmFormat; + result->drmModifier = drmModifier; + + qCDebug(lcWlRenderBuffer) + << "Created Qt-native Vulkan render-buffer texture" + << "texture" << result->rhiTexture + << "size" << size + << "bytes" << dataCost(result) + << "format" << format + << "drmFormat" << Qt::hex << drmFormat + << "modifier" << drmModifier << Qt::dec; + return result; + } + auto ow = qobject_cast(owner()); + if (!ow || !ow->allocator() || !ow->renderer()) { + qCWarning(lcWlRenderBuffer) + << "Failed to create wlroots render-buffer texture: output window renderer state is incomplete" + << "window" << owner() + << "size" << size + << "format" << format; + return nullptr; + } + auto texture = WRenderHelper::newTexture(ow->allocator(), ow->renderer(), - drmFormat, drmModifier, owner()->rhi(), + drmFormat, drmModifier, rhi, size, format, QRhiTexture::RenderTarget); if (!texture.rhiTexture) return nullptr; return new WlrAndRhiTexture{texture.buffer, texture.texture, - texture.rhiTexture, texture.nativeCleanup}; + texture.rhiTexture, texture.nativeCleanup, + format, drmFormat, drmModifier}; } static void destroy(WlrAndRhiTexture *texture) { + if (!texture) + return; + + qCDebug(lcWlRenderBuffer) + << "Destroying render-buffer texture" + << "texture" << texture->rhiTexture + << "buffer" << texture->buffer + << "size" << (texture->rhiTexture ? texture->rhiTexture->pixelSize() : QSize()) + << "bytes" << dataCost(texture) + << "format" << texture->rhiFormat + << "drmFormat" << Qt::hex << texture->drmFormat + << "modifier" << texture->drmModifier << Qt::dec; delete texture->rhiTexture; WRenderHelper::releaseNativeTexture(&texture->nativeCleanup); delete texture->wlrTexture; @@ -396,6 +496,76 @@ class Q_DECL_HIDDEN RhiTextureManager : public DataManagerbuffer)->drop(); delete texture; } + + static qsizetype dataCost(WlrAndRhiTexture *texture) { + if (!texture || !texture->rhiTexture) + return 0; + + return textureCost(texture->rhiTexture); + } + + bool shouldDestroyData(const Data &data, qsizetype retainedCost, qsizetype dataCost) const { + if (data.released > 2) + return true; + + const qsizetype budget = cacheBudget(); + if (data.released > 0 && budget >= 0 && retainedCost + dataCost > budget) { + qCDebug(lcWlRenderBuffer) + << "Evicting released render-buffer texture over cache budget" + << "texture" << (data.data ? data.data->rhiTexture : nullptr) + << "released" << data.released + << "retainedBytes" << retainedCost + << "entryBytes" << dataCost + << "budgetBytes" << budget; + return true; + } + + return false; + } + + static qsizetype cacheBudget() { + static const qsizetype budget = []() -> qsizetype { + const QByteArray value = qgetenv("WAYLIB_RENDER_BUFFER_CACHE_MB").trimmed(); + if (!value.isEmpty()) { + bool ok = false; + const qlonglong mib = value.toLongLong(&ok); + if (ok && mib >= 0) + return qsizetype(mib) * 1024 * 1024; + } + + return qsizetype(256) * 1024 * 1024; + }(); + return budget; + } + + static qsizetype textureCost(const QRhiTexture *texture) { + if (!texture) + return 0; + + const QSize size = texture->pixelSize(); + if (!size.isValid() || size.isEmpty()) + return 0; + + return qsizetype(size.width()) * qsizetype(size.height()) * bytesPerPixel(texture->format()); + } + + static qsizetype bytesPerPixel(QRhiTexture::Format format) { + switch (format) { + case QRhiTexture::R8: + case QRhiTexture::RED_OR_ALPHA8: + return 1; + case QRhiTexture::RG8: + case QRhiTexture::R16: + case QRhiTexture::R16F: + return 2; + case QRhiTexture::RGBA16F: + return 8; + case QRhiTexture::RGBA32F: + return 16; + default: + return 4; + } + } }; class Q_DECL_HIDDEN RhiManager : public DataManager diff --git a/waylib/src/server/qtquick/wrenderhelper.cpp b/waylib/src/server/qtquick/wrenderhelper.cpp index 3f443ed70..ccfa80e64 100644 --- a/waylib/src/server/qtquick/wrenderhelper.cpp +++ b/waylib/src/server/qtquick/wrenderhelper.cpp @@ -2631,7 +2631,8 @@ void WRenderHelper::releaseNativeTexture(NativeTextureCleanup *cleanup) return; #ifdef ENABLE_VULKAN_RENDER - if (cleanup->type == NativeTextureCleanup::Type::VulkanTexture) { + if (cleanup->type == NativeTextureCleanup::Type::VulkanTexture + || cleanup->type == NativeTextureCleanup::Type::VulkanRenderTarget) { releaseNativeTextureNow(cleanup); return; } @@ -4177,12 +4178,6 @@ static bool envFlagExplicitlyEnabled(const char *name) return value == "1" || value == "true" || value == "yes" || value == "on"; } -static bool envFlagExplicitlyDisabled(const char *name) -{ - const QByteArray value = qgetenv(name).trimmed().toLower(); - return value == "0" || value == "false" || value == "no" || value == "off"; -} - static bool vulkanNonDmabufDiagnosticsEnabled() { static const bool enabled = envFlagExplicitlyEnabled("WAYLIB_VK_NON_DMABUF_DIAGNOSTICS") @@ -4211,6 +4206,30 @@ static bool vulkanNonDmabufPartialUploadEnabled() return enabled; } +static qsizetype vulkanNonDmabufFullUploadMaxBytes() +{ + static const qsizetype maxBytes = [] { + const char *names[] = { + "WAYLIB_VK_NON_DMABUF_FULL_UPLOAD_MAX_MIB", + "TREELAND_VK_NON_DMABUF_FULL_UPLOAD_MAX_MIB", + }; + + for (const char *name : names) { + const QByteArray value = qgetenv(name).trimmed(); + if (value.isEmpty()) + continue; + + bool ok = false; + const qlonglong mib = value.toLongLong(&ok); + if (ok && mib >= 0) + return qsizetype(mib) * 1024 * 1024; + } + + return qsizetype(2) * 1024 * 1024; + }(); + return maxBytes; +} + static bool vulkanUnsafeReadbackEnabled() { static const bool enabled = envFlagExplicitlyEnabled("WAYLIB_VK_ALLOW_UNSAFE_READBACK") @@ -4340,7 +4359,8 @@ struct Q_DECL_HIDDEN VulkanBufferUploadRegions { static VulkanBufferUploadRegions vulkanUploadRegionsFromSurfaceDamage(wlr_surface *surface, const QSize &bufferSize, - bool forceFullBuffer) + bool forceFullBuffer, + qsizetype bufferBytes) { VulkanBufferUploadRegions result; const QRect bufferRect(QPoint(0, 0), bufferSize); @@ -4363,6 +4383,12 @@ static VulkanBufferUploadRegions vulkanUploadRegionsFromSurfaceDamage(wlr_surfac return result; } + const qsizetype fullUploadMaxBytes = vulkanNonDmabufFullUploadMaxBytes(); + if (fullUploadMaxBytes > 0 && bufferBytes > 0 && bufferBytes <= fullUploadMaxBytes) { + useFullBuffer("small-non-dmabuf-full-upload"); + return result; + } + if (!surface) { useFullBuffer("missing-surface-damage"); return result; @@ -4508,7 +4534,8 @@ static bool updateVulkanTextureFromBufferDataWithRhiUpload(QRhi *rhi, } const VulkanBufferUploadRegions regions = - vulkanUploadRegionsFromSurfaceDamage(surface, size, rebuildTexture); + vulkanUploadRegionsFromSurfaceDamage(surface, size, rebuildTexture, + wrapped.sizeInBytes()); if (regions.rects.isEmpty()) { qCDebug(lcWlRenderHelper) << "Vulkan RHI non-dmabuf upload skipped because damage is empty after clipping" @@ -4620,7 +4647,10 @@ static bool updateVulkanTextureFromBufferData(qw_buffer *buffer, qw_texture *han QRhi *rhi, QRhiCommandBuffer *commandBuffer) { - if (!buffer || !buffer->handle() || !handle || !handle->handle() || !texture) + if (!buffer || !buffer->handle() || !texture) + return false; + + if (handle && !handle->handle()) return false; QElapsedTimer elapsed; @@ -4647,7 +4677,8 @@ static bool updateVulkanTextureFromBufferData(qw_buffer *buffer, qw_texture *han const QImage::Format imageFormat = WTools::toImageFormat(drmFormat); bool updated = false; bool forcedOpaque = false; - const bool sourceHasAlpha = handle->has_alpha(); + const bool sourceHasAlpha = handle ? handle->has_alpha() + : drmFormatLikelyHasAlpha(drmFormat); const bool forceSurfaceOpaque = surfaceOpaqueRegionCoversBuffer(surface, buffer); if (!size.isEmpty() && data && stride > 0 && imageFormat != QImage::Format_Invalid) { const QImage wrapped(static_cast(data), @@ -4892,6 +4923,9 @@ static bool updateVulkanTextureFromNonDmabufBuffer(qw_buffer *buffer, qw_texture return true; } + if (!handle || !handle->handle()) + return false; + if (vulkanNonDmabufForceReadbackEnabled()) { qCDebug(lcWlRenderHelper) << "Vulkan RHI non-dmabuf client texture buffer-data upload skipped by diagnostics env" @@ -4906,6 +4940,53 @@ static bool updateVulkanTextureFromNonDmabufBuffer(qw_buffer *buffer, qw_texture } #endif +bool WRenderHelper::makeVulkanTextureFromNonDmabufBuffer(QRhi *rhi, + qw_buffer *buffer, + QSGPlainTexture *texture, + wlr_surface *surface, + QRhiCommandBuffer *commandBuffer) +{ +#ifdef ENABLE_VULKAN_RENDER + if (!rhi || rhi->backend() != QRhi::Vulkan || !buffer || !buffer->handle() + || !texture || bufferExportsDmabuf(buffer)) { + return false; + } + + if (vulkanNonDmabufForceReadbackEnabled()) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI direct non-dmabuf buffer upload skipped by diagnostics env" + << "buffer" << buffer + << "surface" << surface + << "size" << QSize(buffer->handle()->width, buffer->handle()->height) + << "commandBuffer" << commandBuffer + << "unsafeReadbackEnabled" << vulkanUnsafeReadbackEnabled(); + return false; + } + + const bool updated = updateVulkanTextureFromBufferData(buffer, nullptr, texture, + surface, rhi, + commandBuffer); + if (updated) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture uploaded directly from non-dmabuf buffer" + << "buffer" << buffer + << "surface" << surface + << "size" << QSize(buffer->handle()->width, buffer->handle()->height) + << "alpha" << texture->hasAlphaChannel() + << "commandBuffer" << commandBuffer + << "bypassedWlrootsTexture" << true; + } + return updated; +#else + Q_UNUSED(rhi); + Q_UNUSED(buffer); + Q_UNUSED(texture); + Q_UNUSED(surface); + Q_UNUSED(commandBuffer); + return false; +#endif +} + static void updateImage(QRhi *, qw_texture *handle, QSGPlainTexture *texture) { auto image = handle->get_image(); if (texture->rhiTexture() && !texture->ownsTexture()) diff --git a/waylib/src/server/qtquick/wrenderhelper.h b/waylib/src/server/qtquick/wrenderhelper.h index 0b3fe49ee..7315ba7ec 100644 --- a/waylib/src/server/qtquick/wrenderhelper.h +++ b/waylib/src/server/qtquick/wrenderhelper.h @@ -116,6 +116,11 @@ class WAYLIB_SERVER_EXPORT WRenderHelper : public QObject, public WObject QSGPlainTexture *texture, NativeTextureCleanup *nativeCleanup, wlr_surface *surface = nullptr); + static bool makeVulkanTextureFromNonDmabufBuffer(QRhi *rhi, + QW_NAMESPACE::qw_buffer *buffer, + QSGPlainTexture *texture, + wlr_surface *surface, + QRhiCommandBuffer *commandBuffer); // Legacy explicit-sync experiment kept for compatibility with older local // tests. The active Qt RHI Vulkan output path uses diff --git a/waylib/src/server/qtquick/wsgtextureprovider.cpp b/waylib/src/server/qtquick/wsgtextureprovider.cpp index 12ff7f406..0aa256e38 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.cpp +++ b/waylib/src/server/qtquick/wsgtextureprovider.cpp @@ -12,6 +12,8 @@ #include #include +#include +#include #include #include #include @@ -22,6 +24,7 @@ extern "C" { #include } +#include #endif WAYLIB_SERVER_BEGIN_NAMESPACE @@ -45,7 +48,6 @@ static int bufferLocks(qw_buffer *buffer) #ifdef ENABLE_VULKAN_RENDER static constexpr int s_deferredVulkanTextureReleaseFrames = 3; -static constexpr int s_vulkanDmabufTextureCacheCapacity = 8; static bool envFlagExplicitlyDisabled(const char *name) { @@ -62,6 +64,82 @@ static bool bufferHasDmabuf(qw_buffer *buffer) return buffer->get_dmabuf(&dmabuf); } +static qsizetype envMiBValue(const char *waylibName, const char *treelandName, + qsizetype defaultMiB) +{ + const char *names[] = { waylibName, treelandName }; + for (const char *name : names) { + const QByteArray value = qgetenv(name).trimmed(); + if (value.isEmpty()) + continue; + + bool ok = false; + const qlonglong mib = value.toLongLong(&ok); + if (ok && mib > 0) + return qsizetype(mib) * 1024 * 1024; + } + + return defaultMiB * 1024 * 1024; +} + +static int envIntValue(const char *waylibName, const char *treelandName, + int defaultValue) +{ + const char *names[] = { waylibName, treelandName }; + for (const char *name : names) { + const QByteArray value = qgetenv(name).trimmed(); + if (value.isEmpty()) + continue; + + bool ok = false; + const int parsed = value.toInt(&ok); + if (ok && parsed > 0) + return parsed; + } + + return defaultValue; +} + +static qsizetype drmFormatEstimatedBytesPerPixel(uint32_t format) +{ + switch (format) { + case DRM_FORMAT_R8: + return 1; + case DRM_FORMAT_GR88: + case DRM_FORMAT_RG88: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + return 2; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_ABGR2101010: + return 4; + default: + // Most desktop client buffers are 32-bit ARGB/XRGB. Prefer a + // conservative estimate so the cache trims earlier for uncommon formats. + return 4; + } +} + +static qsizetype estimatedDmabufCost(const wlr_dmabuf_attributes &dmabuf) +{ + if (dmabuf.width <= 0 || dmabuf.height <= 0) + return 0; + + return qsizetype(dmabuf.width) + * qsizetype(dmabuf.height) + * drmFormatEstimatedBytesPerPixel(dmabuf.format); +} + static bool directDmabufImportEnabled() { static const bool enabled = !envFlagExplicitlyDisabled("WAYLIB_VK_DIRECT_DMABUF") @@ -70,6 +148,504 @@ static bool directDmabufImportEnabled() && !envFlagExplicitlyDisabled("TREELAND_VK_DIRECT_CLIENT_DMABUF"); return enabled; } + +struct VulkanDmabufTextureCacheStats { + qsizetype cachedBytes = 0; + qsizetype budgetBytes = 0; + int entries = 0; + int maxEntries = 0; + int activeRefs = 0; + int deferredReleases = 0; +}; + +struct VulkanSharedDmabufTexture { + QRhi *rhi = nullptr; + QPointer window; + QPointer buffer; + QRhiTexture *rhiTexture = nullptr; + WRenderHelper::NativeTextureCleanup nativeCleanup; + QSize size; + uint32_t drmFormat = 0; + uint64_t drmModifier = 0; + bool hasAlpha = false; + quint32 lastContentSerial = 0; + bool lastAcquireCacheHit = false; + bool lastAcquireReacquired = false; + qsizetype costBytes = 0; + int refCount = 0; + quint64 lastUsed = 0; +}; + +class Q_DECL_HIDDEN VulkanDmabufTextureCache +{ +public: + ~VulkanDmabufTextureCache() + { + QVector cleanups; + { + QMutexLocker locker(&m_mutex); + for (auto *entry : std::as_const(m_entries)) { + if (entry->rhiTexture) + entry->rhiTexture->deleteLater(); + if (entry->nativeCleanup.type != WRenderHelper::NativeTextureCleanup::Type::None) + cleanups.append(entry->nativeCleanup); + delete entry; + } + m_entries.clear(); + for (const auto &release : std::as_const(m_deferredReleases)) { + if (release.nativeCleanup.type != WRenderHelper::NativeTextureCleanup::Type::None) + cleanups.append(release.nativeCleanup); + } + m_deferredReleases.clear(); + m_cachedBytes = 0; + } + + for (auto cleanup : cleanups) + WRenderHelper::releaseNativeTexture(&cleanup); + } + + VulkanSharedDmabufTexture *acquire(QRhi *rhi, WOutputRenderWindow *window, + qw_buffer *buffer, wlr_surface *surface, + quint32 contentSerial, + bool allowOverBudgetImport) + { + if (!rhi || !window || !buffer) + return nullptr; + + wlr_dmabuf_attributes dmabuf = {}; + if (!buffer->get_dmabuf(&dmabuf)) + return nullptr; + + ensureRenderEndConnection(window); + + if (auto *entry = acquireExisting(rhi, buffer, dmabuf, surface, contentSerial)) + return entry; + + const qsizetype costBytes = estimatedDmabufCost(dmabuf); + if (!prepareForImport(costBytes, allowOverBudgetImport, buffer, dmabuf)) + return nullptr; + + WRenderHelper::ImportedVulkanTexture importedTexture; + if (!WRenderHelper::importVulkanTextureFromBuffer(rhi, buffer, surface, + &importedTexture)) { + return nullptr; + } + + auto *entry = new VulkanSharedDmabufTexture; + entry->rhi = rhi; + entry->window = window; + entry->buffer = buffer; + entry->rhiTexture = importedTexture.texture; + entry->nativeCleanup = importedTexture.nativeCleanup; + entry->size = importedTexture.size; + entry->drmFormat = importedTexture.drmFormat; + entry->drmModifier = importedTexture.drmModifier; + entry->hasAlpha = importedTexture.hasAlpha; + entry->lastContentSerial = contentSerial; + entry->lastAcquireCacheHit = false; + entry->lastAcquireReacquired = true; + entry->costBytes = costBytes; + entry->refCount = 1; + importedTexture.texture = nullptr; + importedTexture.nativeCleanup = {}; + + VulkanDmabufTextureCacheStats stats; + { + QMutexLocker locker(&m_mutex); + pruneInvalidLocked(); + + if (auto *existing = findEntryLocked(rhi, buffer, dmabuf)) { + ++existing->refCount; + existing->lastUsed = ++m_useSerial; + stats = statsLocked(); + locker.unlock(); + WRenderHelper::releaseImportedVulkanTexture(&importedTexture); + deleteImportedEntry(entry); + if (acquireImportedEntry(existing, rhi, buffer, surface, contentSerial)) + return existing; + release(existing); + return nullptr; + } + + entry->lastUsed = ++m_useSerial; + m_entries.append(entry); + m_cachedBytes += entry->costBytes; + pruneOverBudgetLocked(0); + stats = statsLocked(); + } + + qCDebug(lcWlQtQuickTexture) + << "Vulkan client dmabuf texture cached" + << "buffer" << pointerAddress(buffer) + << "rhiTexture" << entry->rhiTexture + << "size" << entry->size + << "format" << Qt::hex << entry->drmFormat << Qt::dec + << "modifier" << Qt::hex << entry->drmModifier << Qt::dec + << "entryBytes" << entry->costBytes + << "cacheBytes" << stats.cachedBytes + << "budgetBytes" << stats.budgetBytes + << "entries" << stats.entries + << "activeRefs" << stats.activeRefs; + + return entry; + } + + void release(VulkanSharedDmabufTexture *entry) + { + if (!entry) + return; + + QMutexLocker locker(&m_mutex); + if (entry->refCount > 0) + --entry->refCount; + entry->lastUsed = ++m_useSerial; + pruneInvalidLocked(); + pruneOverBudgetLocked(0); + } + + void processRenderEnd(WOutputRenderWindow *window) + { + processDeferredReleases(window, false); + + QMutexLocker locker(&m_mutex); + pruneInvalidLocked(); + pruneOverBudgetLocked(0); + } + + void forceProcessDeferredReleases(WOutputRenderWindow *window) + { + processDeferredReleases(window, true); + } + + void releaseWindow(WOutputRenderWindow *window) + { + QVector cleanups; + { + QMutexLocker locker(&m_mutex); + for (int i = m_entries.size() - 1; i >= 0; --i) { + auto *entry = m_entries[i]; + if (entry->window && entry->window != window) + continue; + + if (entry->refCount > 0) { + qCWarning(lcWlQtQuickTexture) + << "Vulkan client dmabuf cache kept active entry for destroyed window" + << "window" << pointerAddress(window) + << "buffer" << pointerAddress(entry->buffer.data()) + << "rhiTexture" << entry->rhiTexture + << "refs" << entry->refCount; + continue; + } + + queueDestroyEntryLocked(i, "window-destroyed", 0); + } + + for (int i = m_deferredReleases.size() - 1; i >= 0; --i) { + const auto &release = m_deferredReleases[i]; + if (release.window && release.window != window) + continue; + cleanups.append(release.nativeCleanup); + m_deferredReleases.removeAt(i); + } + + for (int i = m_connectedWindows.size() - 1; i >= 0; --i) { + if (!m_connectedWindows[i] || m_connectedWindows[i] == window) + m_connectedWindows.removeAt(i); + } + } + + for (auto cleanup : cleanups) + WRenderHelper::releaseNativeTexture(&cleanup); + } + + VulkanDmabufTextureCacheStats stats() const + { + QMutexLocker locker(&m_mutex); + return statsLocked(); + } + + void ensureRenderEndConnection(WOutputRenderWindow *window); + +private: + struct DeferredRelease { + QPointer window; + WRenderHelper::NativeTextureCleanup nativeCleanup; + int framesLeft = 0; + qsizetype costBytes = 0; + }; + + VulkanSharedDmabufTexture *acquireExisting(QRhi *rhi, qw_buffer *buffer, + const wlr_dmabuf_attributes &dmabuf, + wlr_surface *surface, + quint32 contentSerial) + { + VulkanSharedDmabufTexture *entry = nullptr; + { + QMutexLocker locker(&m_mutex); + pruneInvalidLocked(); + entry = findEntryLocked(rhi, buffer, dmabuf); + if (!entry) + return nullptr; + + ++entry->refCount; + entry->lastUsed = ++m_useSerial; + } + + if (!acquireImportedEntry(entry, rhi, buffer, surface, contentSerial)) { + release(entry); + return nullptr; + } + + return entry; + } + + bool acquireImportedEntry(VulkanSharedDmabufTexture *entry, QRhi *rhi, + qw_buffer *buffer, wlr_surface *surface, + quint32 contentSerial) + { + if (!entry) + return false; + + entry->lastAcquireCacheHit = true; + entry->lastAcquireReacquired = false; + + if (contentSerial != 0 && entry->lastContentSerial == contentSerial) + return true; + + if (!WRenderHelper::acquireImportedVulkanTextureFromBuffer(rhi, buffer, + surface, + &entry->nativeCleanup)) { + return false; + } + + entry->lastContentSerial = contentSerial; + entry->lastAcquireReacquired = true; + return true; + } + + VulkanSharedDmabufTexture *findEntryLocked(QRhi *rhi, qw_buffer *buffer, + const wlr_dmabuf_attributes &dmabuf) const + { + const QSize size(dmabuf.width, dmabuf.height); + for (auto *entry : m_entries) { + if (entry->rhi == rhi + && entry->buffer == buffer + && entry->size == size + && entry->drmFormat == dmabuf.format + && entry->drmModifier == dmabuf.modifier) { + return entry; + } + } + + return nullptr; + } + + bool prepareForImport(qsizetype costBytes, bool allowOverBudgetImport, + qw_buffer *buffer, const wlr_dmabuf_attributes &dmabuf) + { + QMutexLocker locker(&m_mutex); + pruneInvalidLocked(); + pruneOverBudgetLocked(costBytes); + + const bool entryLimitReached = m_maxEntries > 0 + && m_entries.size() + 1 > m_maxEntries; + const bool budgetReached = m_budgetBytes > 0 + && m_cachedBytes + costBytes > m_budgetBytes; + + if (entryLimitReached || (budgetReached && !allowOverBudgetImport)) { + qCDebug(lcWlQtQuickTexture) + << "Vulkan client dmabuf texture import throttled by cache budget" + << "buffer" << pointerAddress(buffer) + << "size" << QSize(dmabuf.width, dmabuf.height) + << "format" << Qt::hex << dmabuf.format << Qt::dec + << "modifier" << Qt::hex << dmabuf.modifier << Qt::dec + << "entryBytes" << costBytes + << "cacheBytes" << m_cachedBytes + << "budgetBytes" << m_budgetBytes + << "entries" << m_entries.size() + << "maxEntries" << m_maxEntries + << "allowOverBudgetImport" << allowOverBudgetImport; + return false; + } + + return true; + } + + void pruneInvalidLocked() + { + for (int i = m_entries.size() - 1; i >= 0; --i) { + const auto *entry = m_entries[i]; + if (entry->refCount == 0 && !entry->buffer) + queueDestroyEntryLocked(i, "buffer-destroyed"); + } + } + + void pruneOverBudgetLocked(qsizetype incomingCost) + { + while (true) { + const bool overEntryLimit = m_maxEntries > 0 + && m_entries.size() + (incomingCost > 0 ? 1 : 0) > m_maxEntries; + const bool overBudget = m_budgetBytes > 0 + && m_cachedBytes + incomingCost > m_budgetBytes; + if (!overEntryLimit && !overBudget) + return; + + int evictIndex = -1; + quint64 oldestUse = std::numeric_limits::max(); + for (int i = 0; i < m_entries.size(); ++i) { + const auto *entry = m_entries[i]; + if (entry->refCount > 0) + continue; + + if (entry->lastUsed < oldestUse) { + oldestUse = entry->lastUsed; + evictIndex = i; + } + } + + if (evictIndex < 0) + return; + + queueDestroyEntryLocked(evictIndex, "over-budget"); + } + } + + void queueDestroyEntryLocked(int index, const char *reason, + int framesLeft = s_deferredVulkanTextureReleaseFrames) + { + if (index < 0 || index >= m_entries.size()) + return; + + auto *entry = m_entries.takeAt(index); + m_cachedBytes -= entry->costBytes; + + qCDebug(lcWlQtQuickTexture) + << "Vulkan client dmabuf texture cache evicted" + << "reason" << reason + << "buffer" << pointerAddress(entry->buffer.data()) + << "rhiTexture" << entry->rhiTexture + << "size" << entry->size + << "format" << Qt::hex << entry->drmFormat << Qt::dec + << "modifier" << Qt::hex << entry->drmModifier << Qt::dec + << "entryBytes" << entry->costBytes + << "remainingBytes" << m_cachedBytes + << "remainingEntries" << m_entries.size(); + + if (entry->rhiTexture) + entry->rhiTexture->deleteLater(); + + if (entry->nativeCleanup.type != WRenderHelper::NativeTextureCleanup::Type::None) { + m_deferredReleases.append({ + entry->window, + entry->nativeCleanup, + framesLeft, + entry->costBytes, + }); + entry->nativeCleanup = {}; + } + + delete entry; + } + + void deleteImportedEntry(VulkanSharedDmabufTexture *entry) + { + if (!entry) + return; + + WRenderHelper::ImportedVulkanTexture imported; + imported.texture = entry->rhiTexture; + imported.nativeCleanup = entry->nativeCleanup; + WRenderHelper::releaseImportedVulkanTexture(&imported); + delete entry; + } + + void processDeferredReleases(WOutputRenderWindow *window, bool force) + { + QVector cleanups; + { + QMutexLocker locker(&m_mutex); + for (int i = m_deferredReleases.size() - 1; i >= 0; --i) { + auto &release = m_deferredReleases[i]; + if (window && release.window != window) + continue; + + if (!force && --release.framesLeft > 0) + continue; + + cleanups.append(release.nativeCleanup); + m_deferredReleases.removeAt(i); + } + } + + for (auto cleanup : cleanups) + WRenderHelper::releaseNativeTexture(&cleanup); + } + + VulkanDmabufTextureCacheStats statsLocked() const + { + VulkanDmabufTextureCacheStats stats; + stats.cachedBytes = m_cachedBytes; + stats.budgetBytes = m_budgetBytes; + stats.entries = m_entries.size(); + stats.maxEntries = m_maxEntries; + stats.deferredReleases = m_deferredReleases.size(); + for (const auto *entry : m_entries) + stats.activeRefs += entry->refCount; + return stats; + } + + mutable QMutex m_mutex; + QVector m_entries; + QVector m_deferredReleases; + QVector> m_connectedWindows; + qsizetype m_cachedBytes = 0; + quint64 m_useSerial = 0; + const qsizetype m_budgetBytes = + envMiBValue("WAYLIB_VK_CLIENT_DMABUF_CACHE_MB", + "TREELAND_VK_CLIENT_DMABUF_CACHE_MB", + 512); + const int m_maxEntries = + envIntValue("WAYLIB_VK_CLIENT_DMABUF_CACHE_ENTRIES", + "TREELAND_VK_CLIENT_DMABUF_CACHE_ENTRIES", + 128); +}; + +Q_GLOBAL_STATIC(VulkanDmabufTextureCache, s_vulkanDmabufTextureCache) + +void VulkanDmabufTextureCache::ensureRenderEndConnection(WOutputRenderWindow *window) +{ + if (!window) + return; + + bool needsConnection = false; + { + QMutexLocker locker(&m_mutex); + for (int i = m_connectedWindows.size() - 1; i >= 0; --i) { + if (!m_connectedWindows[i]) { + m_connectedWindows.removeAt(i); + continue; + } + + if (m_connectedWindows[i] == window) + return; + } + + m_connectedWindows.append(window); + needsConnection = true; + } + + if (!needsConnection) + return; + + QObject::connect(window, &WOutputRenderWindow::renderEnd, + window, [window] { + s_vulkanDmabufTextureCache->processRenderEnd(window); + }); + QObject::connect(window, &QObject::destroyed, + window, [window] { + s_vulkanDmabufTextureCache->releaseWindow(window); + }); +} #endif class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate @@ -108,18 +684,6 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate int framesLeft = 0; }; - struct VulkanDmabufTextureCacheEntry { - QPointer buffer; - QRhiTexture *rhiTexture = nullptr; - WRenderHelper::NativeTextureCleanup nativeCleanup; - QSize size; - uint32_t drmFormat = 0; - uint64_t drmModifier = 0; - bool hasAlpha = false; - quint32 contentSerial = 0; - quint64 lastUsed = 0; - }; - void ensureDeferredVulkanTextureReleaseConnection() { if (deferredVulkanTextureReleaseConnection || !window) @@ -135,9 +699,6 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate void processDeferredVulkanTextureReleases(bool force) { - if (deferredVulkanTextureReleases.isEmpty()) - return; - if (force && window && window->rhi()) window->rhi()->finish(); @@ -150,6 +711,9 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate deferredVulkanTextureReleases.removeAt(i); WRenderHelper::releaseNativeTexture(&cleanup); } + + if (force) + s_vulkanDmabufTextureCache->forceProcessDeferredReleases(window); } void deferVulkanNativeTextureRelease(WRenderHelper::NativeTextureCleanup *cleanup, @@ -175,106 +739,48 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate ensureDeferredVulkanTextureReleaseConnection(); } - int findVulkanDmabufTextureCacheEntry(qw_buffer *newBuffer) const + void releaseActiveVulkanDmabufTexture() { - if (!newBuffer) - return -1; - - for (int i = 0; i < vulkanDmabufTextureCache.size(); ++i) { - if (vulkanDmabufTextureCache[i].buffer == newBuffer) - return i; - } - - return -1; - } - - void releaseVulkanDmabufTextureCacheEntry(int index) - { - if (index < 0 || index >= vulkanDmabufTextureCache.size()) + if (!activeVulkanDmabufTexture) return; - auto entry = vulkanDmabufTextureCache.takeAt(index); - if (entry.rhiTexture == rhiTexture) { - qtTexture.setTexture(nullptr); - rhiTexture = nullptr; - nativeCleanup = {}; - if (buffer == entry.buffer) - buffer = nullptr; - bufferContentSerial = 0; - } - - qCDebug(lcWlQtQuickTexture) - << "Vulkan renderer dmabuf texture cache evicted" - << "buffer" << pointerAddress(entry.buffer.data()) - << "rhiTexture" << entry.rhiTexture - << "size" << entry.size - << "format" << Qt::hex << entry.drmFormat << Qt::dec - << "modifier" << Qt::hex << entry.drmModifier << Qt::dec - << "remaining" << vulkanDmabufTextureCache.size(); - - releaseDetachedTexture(nullptr, false, entry.rhiTexture, entry.nativeCleanup); + s_vulkanDmabufTextureCache->release(activeVulkanDmabufTexture); + activeVulkanDmabufTexture = nullptr; } - void trimVulkanDmabufTextureCache() + void activateSharedVulkanDmabufTexture(VulkanSharedDmabufTexture *entry, + qw_buffer *newBuffer, + quint32 newBufferContentSerial, + const char *backendName, + bool cacheHit, + bool reacquired) { - for (int i = vulkanDmabufTextureCache.size() - 1; i >= 0; --i) { - const auto &entry = vulkanDmabufTextureCache[i]; - if (!entry.buffer && entry.rhiTexture != rhiTexture) - releaseVulkanDmabufTextureCacheEntry(i); - } - - while (vulkanDmabufTextureCache.size() > s_vulkanDmabufTextureCacheCapacity) { - int evictIndex = -1; - quint64 oldestUse = std::numeric_limits::max(); - for (int i = 0; i < vulkanDmabufTextureCache.size(); ++i) { - const auto &entry = vulkanDmabufTextureCache[i]; - if (entry.rhiTexture == rhiTexture) - continue; - - if (entry.lastUsed < oldestUse) { - oldestUse = entry.lastUsed; - evictIndex = i; - } - } - - if (evictIndex < 0) - break; - - releaseVulkanDmabufTextureCacheEntry(evictIndex); - } - } - - void clearVulkanDmabufTextureCache() - { - while (!vulkanDmabufTextureCache.isEmpty()) - releaseVulkanDmabufTextureCacheEntry(vulkanDmabufTextureCache.size() - 1); - } - - void activateVulkanDmabufTextureCacheEntry(int index, qw_buffer *newBuffer, - quint32 newBufferContentSerial, - const char *backendName, bool cacheHit, - bool reacquired) - { - auto &entry = vulkanDmabufTextureCache[index]; - entry.lastUsed = ++vulkanDmabufTextureUseSerial; - if (newBufferContentSerial != 0) - entry.contentSerial = newBufferContentSerial; - auto oldTexture = texture; const bool oldOwnsTexture = ownsTexture; - auto oldRhiTexture = rhiTexture; - auto oldNativeCleanup = nativeCleanup; + auto oldRhiTexture = activeVulkanDmabufTexture ? nullptr : rhiTexture; + auto oldNativeCleanup = activeVulkanDmabufTexture + ? WRenderHelper::NativeTextureCleanup{} + : nativeCleanup; + auto *oldActiveVulkanDmabufTexture = activeVulkanDmabufTexture; texture = nullptr; ownsTexture = false; buffer = newBuffer; bufferContentSerial = newBufferContentSerial; qtTexture.setOwnsTexture(false); - qtTexture.setTexture(entry.rhiTexture); - qtTexture.setHasAlphaChannel(entry.hasAlpha); - qtTexture.setTextureSize(entry.size); - rhiTexture = entry.rhiTexture; + qtTexture.setTexture(entry->rhiTexture); + qtTexture.setHasAlphaChannel(entry->hasAlpha); + qtTexture.setTextureSize(entry->size); + rhiTexture = entry->rhiTexture; + hasPendingImageTexture = false; nativeCleanup = {}; + if (entry == oldActiveVulkanDmabufTexture) { + s_vulkanDmabufTextureCache->release(entry); + } else { + activeVulkanDmabufTexture = entry; + } + + const auto stats = s_vulkanDmabufTextureCache->stats(); qCDebug(lcWlQtQuickTexture) << "Vulkan renderer dmabuf texture import path" @@ -284,14 +790,22 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate << "buffer" << pointerAddress(newBuffer) << "contentSerial" << newBufferContentSerial << "rhiTexture" << rhiTexture - << "size" << entry.size - << "format" << Qt::hex << entry.drmFormat << Qt::dec - << "modifier" << Qt::hex << entry.drmModifier << Qt::dec + << "size" << entry->size + << "format" << Qt::hex << entry->drmFormat << Qt::dec + << "modifier" << Qt::hex << entry->drmModifier << Qt::dec << "locks" << bufferLocks(newBuffer) << "alpha" << qtTexture.hasAlphaChannel() - << "cacheSize" << vulkanDmabufTextureCache.size(); + << "entryBytes" << entry->costBytes + << "cacheBytes" << stats.cachedBytes + << "budgetBytes" << stats.budgetBytes + << "cacheEntries" << stats.entries + << "activeRefs" << stats.activeRefs + << "deferredReleases" << stats.deferredReleases; releaseDetachedTexture(oldTexture, oldOwnsTexture, oldRhiTexture, oldNativeCleanup); + if (oldActiveVulkanDmabufTexture && oldActiveVulkanDmabufTexture != entry) + s_vulkanDmabufTextureCache->release(oldActiveVulkanDmabufTexture); + ensureDeferredVulkanTextureReleaseConnection(); } #endif @@ -323,10 +837,11 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate // together with their import objects. QImage fallbacks switch the // QSGPlainTexture back to owned mode, so setTexture(nullptr) deletes // those Qt-created textures normally. - if (rhiTexture) { + if (rhiTexture || hasPendingImageTexture) { qtTexture.setTexture(nullptr); rhiTexture = nullptr; } + hasPendingImageTexture = false; releaseDetachedTexture(nullptr, false, importedWrapper, nativeCleanup); nativeCleanup = {}; @@ -337,7 +852,7 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate buffer = nullptr; bufferContentSerial = 0; #ifdef ENABLE_VULKAN_RENDER - clearVulkanDmabufTextureCache(); + releaseActiveVulkanDmabufTexture(); #endif } @@ -378,20 +893,36 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate auto oldTexture = texture; const bool oldOwnsTexture = ownsTexture; +#ifdef ENABLE_VULKAN_RENDER + auto oldRhiTexture = activeVulkanDmabufTexture ? nullptr : rhiTexture; + auto oldNativeCleanup = activeVulkanDmabufTexture + ? WRenderHelper::NativeTextureCleanup{} + : nativeCleanup; + auto *oldActiveVulkanDmabufTexture = activeVulkanDmabufTexture; +#else auto oldRhiTexture = rhiTexture; auto oldNativeCleanup = nativeCleanup; +#endif texture = newTexture; ownsTexture = newOwnsTexture; buffer = newBuffer; bufferContentSerial = newBufferContentSerial; rhiTexture = qtTexture.rhiTexture(); + hasPendingImageTexture = !rhiTexture && !qtTexture.textureSize().isEmpty(); nativeCleanup = newCleanup; +#ifdef ENABLE_VULKAN_RENDER + activeVulkanDmabufTexture = nullptr; +#endif releaseDetachedTexture(oldTexture == newTexture ? nullptr : oldTexture, oldTexture == newTexture ? false : oldOwnsTexture, oldRhiTexture, oldNativeCleanup); +#ifdef ENABLE_VULKAN_RENDER + if (oldActiveVulkanDmabufTexture) + s_vulkanDmabufTextureCache->release(oldActiveVulkanDmabufTexture); +#endif return true; } @@ -412,61 +943,17 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate &qtTexture, &newCleanup); } else if (backend == QRhi::Vulkan) { backendName = "Vulkan RHI"; - trimVulkanDmabufTextureCache(); - - int cacheIndex = findVulkanDmabufTextureCacheEntry(newBuffer); - if (cacheIndex >= 0) { - auto &entry = vulkanDmabufTextureCache[cacheIndex]; - if (newBufferContentSerial != 0 - && entry.contentSerial == newBufferContentSerial - && entry.rhiTexture) { - activateVulkanDmabufTextureCacheEntry(cacheIndex, newBuffer, - newBufferContentSerial, - backendName, true, false); - return true; - } - - if (WRenderHelper::acquireImportedVulkanTextureFromBuffer(window->rhi(), - newBuffer, - surface, - &entry.nativeCleanup)) { - activateVulkanDmabufTextureCacheEntry(cacheIndex, newBuffer, - newBufferContentSerial, - backendName, true, true); - return true; - } - - qCDebug(lcWlQtQuickTexture) - << "Vulkan renderer dmabuf texture cache entry could not be reacquired," - " evicting and reimporting" - << "buffer" << pointerAddress(newBuffer) - << "rhiTexture" << entry.rhiTexture - << "size" << entry.size; - releaseVulkanDmabufTextureCacheEntry(cacheIndex); - } - - WRenderHelper::ImportedVulkanTexture importedTexture; - imported = WRenderHelper::importVulkanTextureFromBuffer(window->rhi(), newBuffer, - surface, &importedTexture); - if (imported) { - VulkanDmabufTextureCacheEntry entry; - entry.buffer = newBuffer; - entry.rhiTexture = importedTexture.texture; - entry.nativeCleanup = importedTexture.nativeCleanup; - entry.size = importedTexture.size; - entry.drmFormat = importedTexture.drmFormat; - entry.drmModifier = importedTexture.drmModifier; - entry.hasAlpha = importedTexture.hasAlpha; - entry.contentSerial = newBufferContentSerial; - importedTexture.texture = nullptr; - importedTexture.nativeCleanup = {}; - - vulkanDmabufTextureCache.append(entry); - cacheIndex = vulkanDmabufTextureCache.size() - 1; - activateVulkanDmabufTextureCacheEntry(cacheIndex, newBuffer, - newBufferContentSerial, - backendName, false, true); - trimVulkanDmabufTextureCache(); + const bool allowOverBudgetImport = !hasTexture(); + auto *entry = s_vulkanDmabufTextureCache->acquire(window->rhi(), window, + newBuffer, surface, + newBufferContentSerial, + allowOverBudgetImport); + if (entry) { + activateSharedVulkanDmabufTexture(entry, newBuffer, + newBufferContentSerial, + backendName, + entry->lastAcquireCacheHit, + entry->lastAcquireReacquired); return true; } } @@ -477,15 +964,20 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate auto oldTexture = texture; const bool oldOwnsTexture = ownsTexture; - auto oldRhiTexture = rhiTexture; - auto oldNativeCleanup = nativeCleanup; + auto oldRhiTexture = activeVulkanDmabufTexture ? nullptr : rhiTexture; + auto oldNativeCleanup = activeVulkanDmabufTexture + ? WRenderHelper::NativeTextureCleanup{} + : nativeCleanup; + auto *oldActiveVulkanDmabufTexture = activeVulkanDmabufTexture; texture = nullptr; ownsTexture = false; buffer = newBuffer; bufferContentSerial = newBufferContentSerial; rhiTexture = qtTexture.rhiTexture(); + hasPendingImageTexture = false; nativeCleanup = newCleanup; + activeVulkanDmabufTexture = nullptr; qCDebug(lcWlQtQuickTexture) << "Vulkan renderer dmabuf texture import path" @@ -496,16 +988,80 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate << "alpha" << qtTexture.hasAlphaChannel(); releaseDetachedTexture(oldTexture, oldOwnsTexture, oldRhiTexture, oldNativeCleanup); + if (oldActiveVulkanDmabufTexture) + s_vulkanDmabufTextureCache->release(oldActiveVulkanDmabufTexture); + return true; +#else + Q_UNUSED(newBuffer); + Q_UNUSED(surface); + Q_UNUSED(newBufferContentSerial); + return false; +#endif + } + + bool replaceBufferWithNonDmabufUpload(qw_buffer *newBuffer, + wlr_surface *surface, + QRhiCommandBuffer *commandBuffer, + quint32 newBufferContentSerial) + { +#ifdef ENABLE_VULKAN_RENDER + if (!newBuffer || !window || !window->rhi() + || window->rhi()->backend() != QRhi::Vulkan + || bufferHasDmabuf(newBuffer)) { + return false; + } + + if (!WRenderHelper::makeVulkanTextureFromNonDmabufBuffer(window->rhi(), + newBuffer, + &qtTexture, + surface, + commandBuffer)) { + return false; + } + + auto oldTexture = texture; + const bool oldOwnsTexture = ownsTexture; + auto oldRhiTexture = activeVulkanDmabufTexture ? nullptr : rhiTexture; + auto oldNativeCleanup = activeVulkanDmabufTexture + ? WRenderHelper::NativeTextureCleanup{} + : nativeCleanup; + auto *oldActiveVulkanDmabufTexture = activeVulkanDmabufTexture; + + texture = nullptr; + ownsTexture = false; + buffer = newBuffer; + bufferContentSerial = newBufferContentSerial; + rhiTexture = qtTexture.rhiTexture(); + hasPendingImageTexture = !rhiTexture && !qtTexture.textureSize().isEmpty(); + nativeCleanup = {}; + activeVulkanDmabufTexture = nullptr; + + qCDebug(lcWlQtQuickTexture) + << "Vulkan renderer non-dmabuf buffer uploaded without wlroots texture wrapper" + << "buffer" << pointerAddress(newBuffer) + << "contentSerial" << newBufferContentSerial + << "size" << bufferSize(newBuffer) + << "locks" << bufferLocks(newBuffer) + << "rhiTexture" << rhiTexture + << "alpha" << qtTexture.hasAlphaChannel() + << "commandBuffer" << pointerAddress(commandBuffer); + + releaseDetachedTexture(oldTexture, oldOwnsTexture, oldRhiTexture, oldNativeCleanup); + if (oldActiveVulkanDmabufTexture) + s_vulkanDmabufTextureCache->release(oldActiveVulkanDmabufTexture); return true; #else Q_UNUSED(newBuffer); + Q_UNUSED(surface); + Q_UNUSED(commandBuffer); + Q_UNUSED(newBufferContentSerial); return false; #endif } bool hasTexture() const { - return texture || rhiTexture; + return texture || rhiTexture || hasPendingImageTexture; } void updateRhiTexture(QRhiCommandBuffer *commandBuffer = nullptr) { @@ -528,6 +1084,7 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate } rhiTexture = qtTexture.rhiTexture(); + hasPendingImageTexture = !rhiTexture && !qtTexture.textureSize().isEmpty(); nativeCleanup = newCleanup; } @@ -544,13 +1101,13 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate QSGPlainTexture qtTexture; QRhiTexture *rhiTexture = nullptr; WRenderHelper::NativeTextureCleanup nativeCleanup; + bool hasPendingImageTexture = false; bool smooth = true; bool directBufferImportAllowed = false; quint32 bufferContentSerial = 0; #ifdef ENABLE_VULKAN_RENDER QVector deferredVulkanTextureReleases; - QVector vulkanDmabufTextureCache; - quint64 vulkanDmabufTextureUseSerial = 0; + VulkanSharedDmabufTexture *activeVulkanDmabufTexture = nullptr; QMetaObject::Connection deferredVulkanTextureReleaseConnection; #endif }; @@ -697,6 +1254,24 @@ bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface, << "locks" << bufferLocks(buffer); } + if (!bufferHasNativeDmabuf + && WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan) { + if (d->replaceBufferWithNonDmabufUpload(buffer, surface, commandBuffer, + bufferContentSerial)) { + Q_EMIT textureChanged(); + return true; + } + + qCDebug(lcWlQtQuickTexture) + << "Vulkan renderer direct non-dmabuf upload unavailable," + " falling back to wlroots texture path" + << "buffer" << pointerAddress(buffer) + << "sameBuffer" << sameBuffer + << "size" << bufferSize(buffer) + << "locks" << bufferLocks(buffer) + << "commandBuffer" << pointerAddress(commandBuffer); + } + bool ownsTexture = false; qw_texture *texture = nullptr; if (auto clientBuffer = qw_client_buffer::get(*buffer)) {