diff --git a/qwlroots/src/CMakeLists.txt b/qwlroots/src/CMakeLists.txt index 3131e131b..6c19f73e6 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/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 c035b3711..c8429ed5b 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -29,7 +29,9 @@ #include #include +#include #include +#include #include #include @@ -38,14 +40,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); @@ -54,6 +70,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, @@ -62,6 +79,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 54bbd2a96..f67cd1a46 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, @@ -609,6 +625,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()), @@ -647,6 +664,7 @@ void SurfaceWrapper::onPrelaunchGeometryAnimationFinished() m_geometryAnimation->disconnect(this); m_geometryAnimation->deleteLater(); m_geometryAnimation = nullptr; + Q_EMIT animationRunningChanged(); if (m_decoration) m_decoration->setVisible(true); @@ -1004,6 +1022,7 @@ void SurfaceWrapper::setSurfaceState(State newSurfaceState) m_geometryAnimation->disconnect(this); m_geometryAnimation->deleteLater(); m_geometryAnimation = nullptr; + Q_EMIT animationRunningChanged(); } doSetSurfaceState(newSurfaceState); @@ -1270,6 +1289,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) @@ -1413,6 +1434,7 @@ void SurfaceWrapper::onAnimationReady() m_geometryAnimation->disconnect(this); m_geometryAnimation->deleteLater(); m_geometryAnimation = nullptr; + Q_EMIT animationRunningChanged(); return; } @@ -1429,6 +1451,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) @@ -1438,6 +1461,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 b74bc191a..7c4055a0d 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) @@ -350,6 +351,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 c3409b043..6017b02af 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/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/qtquick/private/wbufferrenderer.cpp b/waylib/src/server/qtquick/private/wbufferrenderer.cpp index f12f4bf07..ce3ac87d4 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer.cpp +++ b/waylib/src/server/qtquick/private/wbufferrenderer.cpp @@ -37,6 +37,14 @@ #include #include #include +#include + +#include +#include + +extern "C" { +#include +} using QSGAbsSoftRenderer_NodesMap = QHash; W_DECLARE_PRIVATE_MEMBER(QSGAbsSoftRenderer_m_nodes_tag, QSGAbstractSoftwareRenderer, m_nodes, QSGAbsSoftRenderer_NodesMap); @@ -55,6 +63,35 @@ 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; +} + +#ifdef ENABLE_VULKAN_RENDER +static bool vulkanOutputProbeEnabled() +{ + static const bool enabled = [] { + bool ok = false; + const int value = qEnvironmentVariableIntValue("WAYLIB_VK_OUTPUT_PROBE", &ok); + return ok ? value != 0 : !qEnvironmentVariableIsEmpty("WAYLIB_VK_OUTPUT_PROBE"); + }(); + return enabled; +} + +static PFN_vkGetDeviceProcAddr resolveVkGetDeviceProcAddrForProbe() +{ + static PFN_vkGetDeviceProcAddr proc = + reinterpret_cast(dlsym(RTLD_DEFAULT, "vkGetDeviceProcAddr")); + return proc; +} +#endif + static const wlr_drm_format *pickFormat(qw_renderer *renderer, uint32_t format) { auto r = renderer->handle(); @@ -68,6 +105,120 @@ 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 copyDrmFormat(wlr_drm_format *dst, const wlr_drm_format *src) +{ + if (!dst || !src || src->len == 0) + return false; + + auto *modifiers = static_cast(malloc(sizeof(uint64_t) * src->len)); + if (!modifiers) + return false; + + memcpy(modifiers, src->modifiers, sizeof(uint64_t) * src->len); + wlr_drm_format_finish(dst); + dst->format = src->format; + dst->len = src->len; + dst->capacity = src->len; + dst->modifiers = modifiers; + 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 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) { + if (drmFormatHasModifier(interopFormat, DRM_FORMAT_MOD_LINEAR)) { + ok = copyDrmFormatWithSingleModifier(out, format, DRM_FORMAT_MOD_LINEAR); + } else if (drmFormatHasModifier(interopFormat, DRM_FORMAT_MOD_INVALID)) { + ok = copyDrmFormatWithSingleModifier(out, format, DRM_FORMAT_MOD_INVALID); + } else { + ok = copyDrmFormat(out, interopFormat); + } + } + + wlr_drm_format_set_finish(&intersection); + return ok; +} + static void applyTransform(QSGSoftwareRenderer *renderer, const QTransform &t) { if (t.isIdentity()) @@ -256,15 +407,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 +448,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 +506,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 +564,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 +594,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 +620,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 +643,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; } @@ -435,6 +653,153 @@ inline static QRect scaleToRect(const QRectF &s, qreal scale) { (s.size() * scale).toSize()); } +bool WBufferRenderer::recordVulkanOutputProbe(const char *phase, int sourceIndex, + const QColor &color, + bool preserveColorContents) +{ +#ifdef ENABLE_VULKAN_RENDER + if (!vulkanOutputProbeEnabled()) + return false; + if (!state.flags.testFlag(RedirectOpenGLContextDefaultFrameBufferObject)) + return false; + + auto wd = QQuickWindowPrivate::get(window()); + if (!wd || !wd->rhi || wd->rhi->backend() != QRhi::Vulkan) + return false; + if (!state.sgRenderTarget.rt || !state.sgRenderTarget.cb || state.pixelSize.isEmpty()) + return false; + + QSGRenderTarget probeTarget = state.sgRenderTarget; + if (preserveColorContents && m_renderHelper) { + QQuickRenderTarget preserveTarget = + m_renderHelper->renderTargetForBuffer(state.buffer.get(), true); + if (preserveTarget.isNull()) { + qCWarning(lcWlBufferRenderer) + << "Vulkan output probe skipped: preserve render target unavailable" + << "phase" << phase + << "sourceIndex" << sourceIndex + << "bufferPtr" << quintptr(state.buffer.get()); + return false; + } + + const auto *preserveData = QQuickRenderTargetPrivate::get(&preserveTarget); + Q_ASSERT(preserveData->type == QQuickRenderTargetPrivate::Type::RhiRenderTarget); + probeTarget.rt = preserveData->u.rhiRt; + probeTarget.rpDesc = preserveData->u.rhiRt->renderPassDescriptor(); + } + + auto *textureRT = static_cast(probeTarget.rt); + QRhiTexture *targetTexture = getColorTextureFrom(probeTarget.rt); + if (!targetTexture) { + qCWarning(lcWlBufferRenderer) + << "Vulkan output probe skipped: current render target has no color texture" + << "phase" << phase + << "sourceIndex" << sourceIndex + << "bufferPtr" << quintptr(state.buffer.get()); + return false; + } + + auto vkGetDeviceProcAddr = resolveVkGetDeviceProcAddrForProbe(); + const auto *rhiHandles = static_cast(wd->rhi->nativeHandles()); + if (!vkGetDeviceProcAddr || !rhiHandles || rhiHandles->dev == VK_NULL_HANDLE) { + qCWarning(lcWlBufferRenderer) + << "Vulkan output probe skipped: Vulkan device entry points unavailable" + << "phase" << phase + << "sourceIndex" << sourceIndex + << "hasGetDeviceProcAddr" << bool(vkGetDeviceProcAddr) + << "hasNativeHandles" << bool(rhiHandles); + return false; + } + + auto vkCmdClearAttachments = reinterpret_cast( + vkGetDeviceProcAddr(rhiHandles->dev, "vkCmdClearAttachments")); + if (!vkCmdClearAttachments) { + qCWarning(lcWlBufferRenderer) + << "Vulkan output probe skipped: vkCmdClearAttachments unavailable" + << "phase" << phase + << "sourceIndex" << sourceIndex; + return false; + } + + QRhiCommandBuffer *cb = probeTarget.cb; + const QRhiDepthStencilClearValue depthStencilClear(1.0f, 0); + cb->beginPass(textureRT, Qt::transparent, depthStencilClear, nullptr, + QRhiCommandBuffer::ExternalContent); + cb->beginExternal(); + + const auto *cbHandles = + static_cast(cb->nativeHandles()); + const VkCommandBuffer commandBuffer = cbHandles ? cbHandles->commandBuffer : VK_NULL_HANDLE; + bool recorded = commandBuffer != VK_NULL_HANDLE; + QRect probeRect; + if (recorded) { + const int markerSize = qMax(48, qMin(state.pixelSize.width(), state.pixelSize.height()) / 12); + const bool postProbe = qstrcmp(phase, "post-scene") == 0; + probeRect = QRect(postProbe ? state.pixelSize.width() - markerSize : 0, + 0, + markerSize, + markerSize); + + VkClearAttachment attachment = {}; + attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + attachment.colorAttachment = 0; + attachment.clearValue.color.float32[0] = float(color.redF()); + attachment.clearValue.color.float32[1] = float(color.greenF()); + attachment.clearValue.color.float32[2] = float(color.blueF()); + attachment.clearValue.color.float32[3] = float(color.alphaF()); + + VkClearRect clearRect = {}; + clearRect.rect.offset = { probeRect.x(), probeRect.y() }; + clearRect.rect.extent = { uint32_t(probeRect.width()), uint32_t(probeRect.height()) }; + clearRect.baseArrayLayer = 0; + clearRect.layerCount = 1; + + vkCmdClearAttachments(commandBuffer, 1, &attachment, 1, &clearRect); + } + + cb->endExternal(); + cb->endPass(); + + if (recorded) { + qCInfo(lcWlBufferRenderer) + << "Vulkan output probe marker recorded" + << "phase" << phase + << "sourceIndex" << sourceIndex + << "sourceCount" << m_sourceList.size() + << "preserveColorContents" << preserveColorContents + << "bufferPtr" << quintptr(state.buffer.get()) + << "bufferSize" << (state.buffer + ? QSize(state.buffer->handle()->width, + state.buffer->handle()->height) + : QSize()) + << "pixelSize" << state.pixelSize + << "rect" << probeRect + << "color" << color + << "commandBuffer" << Qt::hex << quintptr(commandBuffer) << Qt::dec + << "targetTexturePtr" << quintptr(targetTexture) + << "targetTextureSize" << targetTexture->pixelSize() + << "nativeTextureObject" << Qt::hex + << quintptr(targetTexture->nativeTexture().object) + << Qt::dec + << "nativeLayout" << targetTexture->nativeTexture().layout; + } else { + qCWarning(lcWlBufferRenderer) + << "Vulkan output probe marker failed: command buffer unavailable" + << "phase" << phase + << "sourceIndex" << sourceIndex + << "bufferPtr" << quintptr(state.buffer.get()); + } + + return recorded; +#else + Q_UNUSED(phase); + Q_UNUSED(sourceIndex); + Q_UNUSED(color); + Q_UNUSED(preserveColorContents); + return false; +#endif +} + void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, const QRectF &sourceRect, const QRectF &targetRect, bool preserveColorContents) @@ -469,12 +834,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 +910,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,17 +952,65 @@ 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 } } + if (sourceIndex == 0) + recordVulkanOutputProbe("pre-scene", sourceIndex, QColor(255, 0, 0, 255), false); + state.context->renderNextFrame(renderer); + if (sourceIndex == m_sourceList.size() - 1) + recordVulkanOutputProbe("post-scene", sourceIndex, QColor(0, 255, 0, 255), true); + { // after render if (!softwareRenderer) { // TODO: get damage area from QRhi renderer @@ -588,6 +1024,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 +1074,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 +1138,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 +1185,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..805cdbe60 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; @@ -126,6 +128,8 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem void destroySource(int index); int indexOfSource(QQuickItem *item); QSGRenderer *ensureRenderer(int sourceIndex, QSGRenderContext *rc); + bool recordVulkanOutputProbe(const char *phase, int sourceIndex, const QColor &color, + bool preserveColorContents); QW_NAMESPACE::qw_swapchain *m_swapchain = nullptr; WRenderHelper *m_renderHelper = nullptr; @@ -143,6 +147,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/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..9aa2e390b 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 @@ -64,6 +66,17 @@ extern "C" { #include #endif #include +#include + +// wlr_egl_create_with_drm_fd and wlr_egl_destroy are not in the public +// wlr/render/egl.h header, but are exported symbols in libwlroots (defined +// in render/egl.c). We declare them here to create an independent EGL +// display/context for GL Qt RHI when the wlroots renderer is Vulkan. +// These are stable within the wlroots 0.19 ABI. +extern "C" { +struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd); +void wlr_egl_destroy(struct wlr_egl *egl); +} } #include @@ -79,6 +92,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 +235,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 +340,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; @@ -384,6 +466,8 @@ class Q_DECL_HIDDEN WOutputRenderWindowPrivate : public QQuickWindowPrivate } ~WOutputRenderWindowPrivate() { qDeleteAll(layers); + if (m_independentEgl) + wlr_egl_destroy(m_independentEgl); } static inline WOutputRenderWindowPrivate *get(WOutputRenderWindow *qq) { @@ -467,6 +551,9 @@ class Q_DECL_HIDDEN WOutputRenderWindowPrivate : public QQuickWindowPrivate bool renderEnabled = true; QPointer m_renderer; + // Independent wlr_egl created when wlroots renderer is Vulkan but Qt RHI + // is GL. Used to create a QW::OpenGLContext with a valid EGL display/context. + struct wlr_egl *m_independentEgl = nullptr; QPointer m_allocator; QList outputs; @@ -1043,8 +1130,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 +1164,49 @@ bool OutputHelper::commit(WBufferRenderer *buffer) return WOutputHelper::commit(); } +#ifdef ENABLE_VULKAN_RENDER + const bool outputProbe = envFlagEnabledForVulkanRenderer("WAYLIB_VK_OUTPUT_PROBE") + && WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan; + const bool scanoutReady = buffer->currentBufferReadyForScanout(); + if (outputProbe) { + qCInfo(lcWlBufferRenderer) + << "Vulkan output probe commit attempt" + << qwoutput()->handle()->name + << "bufferPtr" << quintptr(buffer->currentBuffer()) + << "bufferSize" << QSize(buffer->currentBuffer()->handle()->width, + buffer->currentBuffer()->handle()->height) + << "scanoutReady" << scanoutReady + << "framePending" << framePending() + << "usesLayerCompositor" << usesVulkanOutputLayerCompositor(); + } + + 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()); + if (outputProbe) { + qCInfo(lcWlBufferRenderer) + << "Vulkan output probe layer commit result" + << qwoutput()->handle()->name + << "ok" << ok + << "bufferPtr" << quintptr(buffer->currentBuffer()); + } + return ok; + } +#endif + setBuffer(buffer->currentBuffer()); if (m_lastCommitBuffer == buffer) { @@ -1082,7 +1216,17 @@ bool OutputHelper::commit(WBufferRenderer *buffer) m_lastCommitBuffer = buffer; - return WOutputHelper::commit(); + const bool ok = WOutputHelper::commit(); +#ifdef ENABLE_VULKAN_RENDER + if (outputProbe) { + qCInfo(lcWlBufferRenderer) + << "Vulkan output probe primary commit result" + << qwoutput()->handle()->name + << "ok" << ok + << "bufferPtr" << quintptr(buffer->currentBuffer()); + } +#endif + return ok; } bool OutputHelper::tryToHardwareCursor(const LayerData *layer) @@ -1102,6 +1246,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 +1329,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 +1443,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 +1539,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,17 +1585,56 @@ bool WOutputRenderWindowPrivate::initRCWithRhi() } else #endif if (rhiSupport->rhiBackend() == QRhi::OpenGLES2) { - Q_ASSERT(wlr_renderer_is_gles2(m_renderer->handle())); - auto egl = wlr_gles2_renderer_get_egl(m_renderer->handle()); - auto display = wlr_egl_get_display(egl); - auto context = wlr_egl_get_context(egl); - - this->glContext = new QW::OpenGLContext(display, context, rc()); - bool ok = this->glContext->create(); - if (!ok) - return false; + if (m_renderer->is_gles2()) { + // GL wlroots renderer: adopt its EGL context (shared GL context, + // textures interoperable without dmabuf import). + auto egl = wlr_gles2_renderer_get_egl(m_renderer->handle()); + auto display = wlr_egl_get_display(egl); + auto context = wlr_egl_get_context(egl); + + this->glContext = new QW::OpenGLContext(display, context, rc()); + bool ok = this->glContext->create(); + if (!ok) + return false; + + q->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(this->glContext)); + } else { + // Vulkan wlroots renderer with GL Qt RHI: the waylib platform + // plugin (qwlrootsintegration.cpp) only accepts QW::OpenGLContext + // and uses its eglDisplay()/eglContext() directly for EGLConfig + // lookup and makeCurrent. We cannot pass placeholder EGL handles + // (EGL_NO_DISPLAY causes "Cannot find EGLConfig" → QRhi creation + // fails → crash). Instead, create an independent wlr_egl from the + // renderer's drm_fd (qw_renderer::get_drm_fd works for Vulkan + // renderers too), and adopt its EGL display/context into a + // QW::OpenGLContext. This EGL context is independent of the + // wlroots Vulkan renderer — it's used solely for Qt RHI GL + // rendering and EGL dmabuf import. + int drm_fd = m_renderer->get_drm_fd(); + if (drm_fd < 0) { + qCWarning(lcWlRenderer) << "Vulkan+GL: qw_renderer::get_drm_fd failed"; + return false; + } + struct wlr_egl *egl = wlr_egl_create_with_drm_fd(drm_fd); + if (!egl) { + qCWarning(lcWlRenderer) << "Vulkan+GL: wlr_egl_create_with_drm_fd failed"; + return false; + } - q->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(this->glContext)); + EGLDisplay display = wlr_egl_get_display(egl); + EGLContext context = wlr_egl_get_context(egl); + this->glContext = new QW::OpenGLContext(display, context, rc()); + bool ok = this->glContext->create(); + if (!ok) { + qCWarning(lcWlRenderer) << "Vulkan+GL: failed to create QW::OpenGLContext"; + wlr_egl_destroy(egl); + return false; + } + + q->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(this->glContext)); + m_independentEgl = egl; // destroyed when window is destroyed + qCInfo(lcWlRenderer) << "Vulkan wlroots renderer with GL Qt RHI: using independent EGL context for dmabuf import"; + } } else { return false; } @@ -1484,9 +1732,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 +1843,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 +1861,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 +1880,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..0b73b5d82 100644 --- a/waylib/src/server/qtquick/wrenderhelper.cpp +++ b/waylib/src/server/qtquick/wrenderhelper.cpp @@ -19,6 +19,15 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -35,10 +44,28 @@ extern "C" { #include #ifdef ENABLE_VULKAN_RENDER #include +#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 +78,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 +160,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 +228,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 +266,2724 @@ 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 = wlr_linux_drm_syncobj_v1_get_surface_state(surface); + if (!state || !state->acquire_timeline) + return true; + + if (usedExplicitAcquire) + *usedExplicitAcquire = true; + + const uint64_t acquirePoint = state->acquire_point; + const int syncFileFd = + wlr_drm_syncobj_timeline_export_sync_file(state->acquire_timeline, acquirePoint); + 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) +{ } @@ -483,129 +3112,814 @@ bool QImageBuffer::begin_data_ptr_access([[maybe_unused]] uint32_t flags, void * return true; } -void QImageBuffer::end_data_ptr_access() +void QImageBuffer::end_data_ptr_access() +{ + +} + +qw_buffer *WRenderHelper::toBuffer(qw_renderer *renderer, QSGTexture *texture, QSGRendererInterface::GraphicsApi api) +{ + const QSize size = texture->textureSize(); + + switch (api) { + case QSGRendererInterface::OpenGL: { + 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(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()); + } +#endif + case QSGRendererInterface::Software: { + QImage image; + if (auto t = qobject_cast(texture)) { + image = t->image(); + } else if (auto t = qobject_cast(texture)) { + image = t->toImage(); + } else if (QByteArrayView(texture->metaObject()->className()) + == QByteArrayView("QSGSoftwarePixmapTexture")) { + auto t = static_cast(texture); + image = t->pixmap().toImage(); + } else { + qFatal("Can't get QImage from QSGTexture, class name: %s", texture->metaObject()->className()); + } + + if (image.isNull()) + return nullptr; + + return qw_buffer::create(new QImageBuffer(image), image.width(), image.height()); + } + default: + qFatal("Can't get qw_buffer from QSGTexture, Not supported graphics API."); + break; + } + + return nullptr; +} + +QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, qw_buffer *buffer) +{ + W_D(WRenderHelper); + Q_ASSERT(buffer); + + 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; + } -} - -qw_buffer *WRenderHelper::toBuffer(qw_renderer *renderer, QSGTexture *texture, QSGRendererInterface::GraphicsApi api) -{ - const QSize size = texture->textureSize(); + BufferData *data = nullptr; + for (auto bufferData : std::as_const(d->buffers)) { + if (bufferData->buffer == buffer) { + data = bufferData; + break; + } + } - switch (api) { - case QSGRendererInterface::OpenGL: { - Q_ASSERT(wlr_renderer_is_gles2(renderer->handle())); - auto egl = wlr_gles2_renderer_get_egl(renderer->handle()); + if (!data || !data->vulkanRenderTarget.isValid()) + return true; - return qw_buffer::create(new GLTextureBuffer(egl, texture), size.width(), size.height()); + 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; } -#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()); - return qw_buffer::create(new VkTextureBuffer(instance, device, texture), size.width(), size.height()); + 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; } -#endif - case QSGRendererInterface::Software: { - QImage image; - if (auto t = qobject_cast(texture)) { - image = t->image(); - } else if (auto t = qobject_cast(texture)) { - image = t->toImage(); - } else if (QByteArrayView(texture->metaObject()->className()) - == QByteArrayView("QSGSoftwarePixmapTexture")) { - auto t = static_cast(texture); - image = t->pixmap().toImage(); - } else { - qFatal("Can't get QImage from QSGTexture, class name: %s", texture->metaObject()->className()); - } - if (image.isNull()) - return nullptr; + const auto *handles = static_cast(rhi->nativeHandles()); + if (!handles) { + qCWarning(lcWlRenderHelper) + << "Vulkan RHI output release failed: QRhi Vulkan native handles unavailable"; + return false; + } - return qw_buffer::create(new QImageBuffer(image), image.width(), image.height()); + 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); } - default: - qFatal("Can't get qw_buffer from QSGTexture, Not supported graphics API."); - break; + + 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; } - return nullptr; + 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 } -QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, qw_buffer *buffer) +void WRenderHelper::transitionVkImageToGeneral(QRhi *rhi, QRhiTexture *texture, + qw_buffer *buffer) { - W_D(WRenderHelper); - Q_ASSERT(buffer); - - if (d->size.isEmpty()) - return {}; +#ifdef ENABLE_VULKAN_RENDER + if (!rhi || !texture || !buffer) + return; - for (int i = 0; i < d->buffers.count(); ++i) { - auto data = d->buffers[i]; - if (data->buffer == buffer) { - d->lastBuffer = data; - return data->renderTarget; - } + // 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; } - std::unique_ptr bufferData(new BufferData); - bufferData->buffer = buffer; - auto texture = qw_texture::from_buffer(*d->renderer, *buffer); - - QQuickRenderTarget rt; - - if (wlr_renderer_is_pixman(d->renderer->handle())) { - 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); + 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; } -#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); + 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; } -#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); + // 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; } - 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 = {}; - } + 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 +3949,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 +4035,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 @@ -729,88 +4061,488 @@ QSGRendererInterface::GraphicsApi WRenderHelper::probe(qw_backend *testBackend, continue; } - const wlr_drm_format_set *formats = wlr_renderer_get_texture_formats(*renderer, WLR_BUFFER_CAP_DMABUF); + const wlr_drm_format_set *formats = wlr_renderer_get_texture_formats(*renderer, WLR_BUFFER_CAP_DMABUF); + + if (formats && formats->len == 0) { + qCInfo(lcWlRenderHelper) << GraphicsApiName(api) << " api don't support any format"; + continue; + } + + // TODO: how to test when formats gets NULL + if (formats && formats->len) { + std::unique_ptr alloc(qw_allocator::autocreate(*testBackend, *renderer.get())); + + bool hasSupportedFormat = false; + for (size_t formatId = 0; formatId < formats->len; formatId++) { + auto *format = &formats->formats[formatId]; + + std::unique_ptr swapchain(qw_swapchain::create(*alloc.get(), 1000, 800, format)); + auto wbuffer = swapchain->acquire(); + if (!wbuffer) { + continue; + } else { + std::unique_ptr buffer(qw_buffer::from(wbuffer)); + std::unique_ptr texture { qw_texture::from_buffer(*renderer.get(), *buffer.get()) }; + if (!texture) + continue; + hasSupportedFormat = true; + break; + } + } + + if (!hasSupportedFormat) { + qCInfo(lcWlRenderHelper) << GraphicsApiName(api) << " api failed to convert any buffer to texture"; + continue; + } + } + + 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 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; +} + +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 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) +{ + if (!buffer || !buffer->handle() || !handle || !handle->handle() || !texture) + return false; + + 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) + << "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); + QImage image = wrapped.copy(); + const auto sample = sampleVulkanTextureImage(image); + logVulkanTextureImageDiagnostics("buffer-data", image, 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; + } + 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 (formats && formats->len == 0) { - qCInfo(lcWlRenderHelper) << GraphicsApiName(api) << " api don't support any format"; - continue; - } + buffer->end_data_ptr_access(); + + if (updated) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture uploaded from buffer data" + << "buffer" << buffer + << "texture" << handle + << "size" << size + << "format" << drmFormatToName(drmFormat) + << "stride" << stride + << "alpha" << texture->hasAlphaChannel() + << "forcedOpaque" << forcedOpaque + << "surfaceOpaqueFull" << forceSurfaceOpaque + << "bufferExportsDmabuf" << false; + } else { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI client texture buffer-data upload unavailable" + << "buffer" << buffer + << "texture" << handle + << "size" << size + << "format" << drmFormatToName(drmFormat) + << "stride" << stride + << "imageFormat" << imageFormat; + } - // TODO: how to test when formats gets NULL - if (formats && formats->len) { - std::unique_ptr alloc(qw_allocator::autocreate(*testBackend, *renderer.get())); + return updated; +} - bool hasSupportedFormat = false; - for (size_t formatId = 0; formatId < formats->len; formatId++) { - auto *format = &formats->formats[formatId]; +static bool readVulkanTextureToImage(qw_texture *handle, QSGPlainTexture *texture, + uint32_t drmFormat, bool forceSurfaceOpaque) +{ + if (!handle || !handle->handle() || !texture) + return false; - std::unique_ptr swapchain(qw_swapchain::create(*alloc.get(), 1000, 800, format)); - auto wbuffer = swapchain->acquire(); - if (!wbuffer) { - continue; - } else { - std::unique_ptr buffer(qw_buffer::from(wbuffer)); - std::unique_ptr texture { qw_texture::from_buffer(*renderer.get(), *buffer.get()) }; - if (!texture) - continue; - hasSupportedFormat = true; - break; - } - } + 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; - if (!hasSupportedFormat) { - qCInfo(lcWlRenderHelper) << GraphicsApiName(api) << " api failed to convert any buffer to texture"; - continue; - } - } + QImage image(size, imageFormat); + if (image.isNull()) + return false; - acceptApi = api; - break; + 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 acceptApi; + return updateVulkanTextureFromImage(texture, + std::move(image), + sourceHasAlpha); } -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 updateVulkanTextureFromReadback(qw_buffer *buffer, qw_texture *handle, + QSGPlainTexture *texture, + wlr_surface *surface) +{ + 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); + 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; + } - texture->setHasAlphaChannel(attribs.has_alpha); - texture->setTextureSize(size); -} + 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, + }; -static inline quint64 vkimage_cast(void *image) { - return reinterpret_cast(image); -} + 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; -[[maybe_unused]] static inline quint64 vkimage_cast(quint64 image) { - return image; + 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) +{ + 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)) { + return true; + } + + if (vulkanNonDmabufForceReadbackEnabled()) { + qCDebug(lcWlRenderHelper) + << "Vulkan RHI non-dmabuf client texture buffer-data upload skipped by diagnostics env" + << "buffer" << buffer + << "texture" << handle + << "size" << QSize(handle->handle()->width, handle->handle()->height); + } + + 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 +4552,512 @@ 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) { +#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)) { + 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::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 +5077,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 +5231,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..67c09fa7e 100644 --- a/waylib/src/server/qtquick/wrenderhelper.h +++ b/waylib/src/server/qtquick/wrenderhelper.h @@ -10,8 +10,11 @@ #include #include +#include + QT_BEGIN_NAMESPACE class QQuickRenderControl; +class QRhiTexture; class QSGTexture; class QSGPlainTexture; class QRhi; @@ -26,6 +29,7 @@ class qw_texture; QW_END_NAMESPACE struct wlr_buffer; +struct wlr_surface; WAYLIB_SERVER_BEGIN_NAMESPACE @@ -48,19 +52,77 @@ 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 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); + 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..ca49e5a8b 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.cpp +++ b/waylib/src/server/qtquick/wsgtextureprovider.cpp @@ -11,11 +11,64 @@ #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 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 +85,252 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate ~WSGTextureProviderPrivate() { cleanTexture(); +#ifdef ENABLE_VULKAN_RENDER + processDeferredVulkanTextureReleases(true); +#endif + } + + 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; + }; + + 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(); + } +#endif + + 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) { - Q_ASSERT(window); - class TextureCleanupJob : public QRunnable - { - public: - TextureCleanupJob(QRhiTexture *texture) - : texture(texture) { } - void run() override { - texture->deleteLater(); - } - QRhiTexture *texture; - }; - - // Delay clean the qt rhi textures. - window->scheduleRenderJob(new TextureCleanupJob(rhiTexture), - QQuickWindow::AfterSynchronizingStage); + qtTexture.setTexture(nullptr); rhiTexture = nullptr; } + releaseDetachedTexture(nullptr, false, importedWrapper, nativeCleanup); + nativeCleanup = {}; if (ownsTexture && texture) delete texture; texture = nullptr; + ownsTexture = false; + buffer = nullptr; + } + + bool replaceTexture(qw_texture *newTexture, bool newOwnsTexture, qw_buffer *newBuffer, + bool allowBufferDirectImport = true, wlr_surface *surface = nullptr) { + Q_ASSERT(newTexture); + + WRenderHelper::NativeTextureCleanup newCleanup; + if (!WRenderHelper::makeTexture(window->rhi(), newTexture, &qtTexture, + newBuffer, &newCleanup, allowBufferDirectImport, + surface)) { + 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"; + imported = WRenderHelper::makeVulkanTextureFromBuffer(window->rhi(), newBuffer, + &qtTexture, &newCleanup, + surface); + } + + 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() { 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); 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 +345,13 @@ 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; + QMetaObject::Connection deferredVulkanTextureReleaseConnection; +#endif }; WSGTextureProvider::WSGTextureProvider(WOutputRenderWindow *window) @@ -99,17 +366,145 @@ WOutputRenderWindow *WSGTextureProvider::window() const return d->window; } -void WSGTextureProvider::setBuffer(qw_buffer *buffer) +bool WSGTextureProvider::prefersDirectBufferImport(WOutputRenderWindow *window) { - if (buffer == qwBuffer()) { +#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) +{ + return setBuffer(buffer, nullptr); +} + +bool WSGTextureProvider::setBuffer(qw_buffer *buffer, wlr_surface *surface) +{ + 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) + << "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)) { + Q_EMIT textureChanged(); + return true; + } + return false; } - W_D(WSGTextureProvider); d->cleanTexture(); d->buffer = buffer; @@ -127,21 +522,79 @@ 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; } -void WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer) +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) { 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) + << "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)) { + Q_EMIT textureChanged(); + return true; + } + return false; + } + d->cleanTexture(); d->texture = texture; d->buffer = srcBuffer; @@ -150,6 +603,7 @@ void WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer) d->updateRhiTexture(); Q_EMIT textureChanged(); + return true; } void WSGTextureProvider::invalidate() @@ -164,7 +618,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..539497a28 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.h +++ b/waylib/src/server/qtquick/wsgtextureprovider.h @@ -11,6 +11,7 @@ QW_BEGIN_NAMESPACE class qw_texture; class qw_buffer; QW_END_NAMESPACE +struct wlr_surface; WAYLIB_SERVER_BEGIN_NAMESPACE class WOutputRenderWindow; @@ -26,9 +27,16 @@ class WAYLIB_SERVER_EXPORT WSGTextureProvider : public QSGTextureProvider, publi explicit WSGTextureProvider(WOutputRenderWindow *window); WOutputRenderWindow *window() const; + static bool prefersDirectBufferImport(WOutputRenderWindow *window); - void setBuffer(QW_NAMESPACE::qw_buffer *buffer); - void setTexture(QW_NAMESPACE::qw_texture *texture, QW_NAMESPACE::qw_buffer *srcBuffer); + 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 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); void invalidate(); QSGTexture *texture() const override; diff --git a/waylib/src/server/qtquick/wsurfaceitem.cpp b/waylib/src/server/qtquick/wsurfaceitem.cpp index 4654ce21b..ce192d589 100644 --- a/waylib/src/server/qtquick/wsurfaceitem.cpp +++ b/waylib/src/server/qtquick/wsurfaceitem.cpp @@ -13,6 +13,12 @@ #include "wsurfaceitem_p.h" #include "wayliblogging.h" +#ifdef ENABLE_VULKAN_RENDER +extern "C" { +#include +} +#endif + #include #include @@ -23,15 +29,22 @@ #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); +#endif + class Q_DECL_HIDDEN SubsurfaceContainer : public QQuickItem { Q_OBJECT @@ -222,6 +235,11 @@ class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate } surface = nullptr; } + textureDirty = true; +#ifdef ENABLE_VULKAN_RENDER + textureRetryBackoffFrames = 0; + textureRetryDelayFrames = 0; +#endif if (frameDoneConnection) QObject::disconnect(frameDoneConnection); @@ -251,8 +269,12 @@ 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 + registerSurfaceBufferExplicitRelease(surface.data(), newBuffer); +#endif if (!live) { // Non-live mode: defer to pendingBuffer @@ -331,9 +353,42 @@ 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; + } +#endif + inline void swapBufferIfNeeded() { - if (pendingBuffer) + if (pendingBuffer) { buffer = std::move(pendingBuffer); + textureDirty = true; + } } inline void setDevicePixelRatio(qreal dpr) { @@ -369,9 +424,14 @@ 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 + int textureRetryBackoffFrames = 0; + int textureRetryDelayFrames = 0; +#endif }; @@ -459,16 +519,37 @@ 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)) { + textureUpdated = d->textureProvider->setBuffer(d->buffer.get(), surfaceHandle); + qCDebug(lcWlSurface) + << "Initialized surface texture provider through preferred direct client dmabuf path" + << "surface" << d->surface + << "buffer" << d->buffer.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; } } return d->textureProvider; @@ -508,6 +589,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 +652,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 +669,142 @@ 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 = + wlr_linux_drm_syncobj_v1_get_surface_state(surface->handle()->handle()); + if (!syncState || !syncState->release_timeline) + return; + + auto *wlrBuffer = buffer->handle(); + if (wlrBuffer->n_locks == 0) { + qCWarning(lcWlVulkanCompositor) + << "Vulkan surface explicit release skipped: buffer has no locks" + << "surface" << surface + << "buffer" << buffer; + return; + } + + if (!wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer(syncState, wlrBuffer)) { + 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; + 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())) { + textureUpdated = tp->setBuffer(d->buffer.get(), surfaceHandle); + qCDebug(lcWlSurface) + << "Updated surface texture through preferred direct client dmabuf path" + << "surface" << d->surface + << "buffer" << d->buffer.get() + << "updated" << textureUpdated; + } +#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); + } else if (!textureUpdated) { + textureUpdated = tp->setBuffer(d->buffer.get(), surfaceHandle); + } +#ifdef ENABLE_VULKAN_RENDER + d->noteTextureUpdateResult(textureUpdated); +#endif + if (textureUpdated) { + d->textureDirty = false; } } 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()) + << "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 +812,32 @@ 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()) + << "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 14f4006ac..b245fa35c 100644 --- a/waylib/src/server/wayliblogging.cpp +++ b/waylib/src/server/wayliblogging.cpp @@ -72,3 +72,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 8fd3de981..c70959d63 100644 --- a/waylib/src/server/wayliblogging.h +++ b/waylib/src/server/wayliblogging.h @@ -67,5 +67,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