From 67b8ccf240837af16d602ab6454cf0303c094098 Mon Sep 17 00:00:00 2001 From: LFRon Date: Wed, 8 Jul 2026 12:02:34 +0800 Subject: [PATCH 1/5] feat(waylib): support Qt Quick Vulkan on wlroots render buffers (Need patched wlroots) Select Qt Quick Vulkan when WLR_RENDERER=vulkan is requested while keeping the default OpenGL/GLES path unchanged. Wire linux-dmabuf to the wlroots renderer and use the patched wlroots Vulkan helpers to render Qt Quick directly into compositor-owned output buffers. Keep client buffers on the wlroots texture path: surfaces still become wlroots-provided QSGTextures for read-only Qt sampling, with per-pass sampling preparation and provider lifetime fixes. Do not add syncobj support or a Treeland-owned client dmabuf cache/fence/direct-import pipeline. Acquire and release wlroots render buffers around Qt RenderControl frames. Disable Vulkan backdrop blits that copy from the output buffer, use a software cursor for the first Vulkan path, and create a separate preserve render target for overlay passes so Vulkan load/store state is fixed before QRhi creates the render pass. With the patched wlroots 0.19 helpers installed, the explicit Vulkan path now displays DDM, dde-shell, DTK and ordinary clients correctly. --- qwlroots/src/types/qwlinuxdmabufv1.h | 3 + src/main.cpp | 15 +- src/seat/helper.cpp | 8 +- waylib/src/server/CMakeLists.txt | 15 + waylib/src/server/protocols/WLinuxDmabufV1 | 1 + .../src/server/protocols/wlinuxdmabufv1.cpp | 81 ++ waylib/src/server/protocols/wlinuxdmabufv1.h | 35 + .../qtquick/private/wbufferrenderer.cpp | 253 +++++- .../qtquick/private/wbufferrenderer_p.h | 8 +- .../qtquick/private/wrenderbuffernode.cpp | 60 +- .../server/qtquick/woutputrenderwindow.cpp | 392 +++++++++- .../src/server/qtquick/woutputrenderwindow.h | 19 + .../src/server/qtquick/wquicktextureproxy.cpp | 15 +- .../server/qtquick/wrenderbufferblitter.cpp | 21 +- waylib/src/server/qtquick/wrenderhelper.cpp | 723 ++++++++++++++++-- waylib/src/server/qtquick/wrenderhelper.h | 19 +- .../src/server/qtquick/wsgtextureprovider.cpp | 249 +++++- .../src/server/qtquick/wsgtextureprovider.h | 8 + waylib/src/server/wayliblogging.cpp | 1 + waylib/src/server/wayliblogging.h | 1 + 20 files changed, 1752 insertions(+), 175 deletions(-) create mode 100644 waylib/src/server/protocols/WLinuxDmabufV1 create mode 100644 waylib/src/server/protocols/wlinuxdmabufv1.cpp create mode 100644 waylib/src/server/protocols/wlinuxdmabufv1.h diff --git a/qwlroots/src/types/qwlinuxdmabufv1.h b/qwlroots/src/types/qwlinuxdmabufv1.h index 8cef307c9..8925e2aa7 100644 --- a/qwlroots/src/types/qwlinuxdmabufv1.h +++ b/qwlroots/src/types/qwlinuxdmabufv1.h @@ -9,6 +9,8 @@ extern "C" { #include } +struct wlr_renderer; + QW_BEGIN_NAMESPACE class QW_CLASS_BOX(linux_dmabuf_feedback_v1) @@ -32,6 +34,7 @@ class QW_CLASS_OBJECT(linux_dmabuf_v1) public: QW_FUNC_STATIC(linux_dmabuf_v1, create, qw_linux_dmabuf_v1 *, wl_display *display, uint32_t version, const wlr_linux_dmabuf_feedback_v1 *default_feedback) + QW_FUNC_STATIC(linux_dmabuf_v1, create_with_renderer, qw_linux_dmabuf_v1 *, wl_display *display, uint32_t version, wlr_renderer *renderer) }; QW_END_NAMESPACE diff --git a/src/main.cpp b/src/main.cpp index 9d3909978..00cdf96bc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "core/treeland.h" +#include "common/treelandlogging.h" #include "utils/cmdline.h" #include @@ -12,9 +13,11 @@ #include #include +#include #include #include #include +#include #if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) # include @@ -46,6 +49,9 @@ class QDeepinTheme : public QGenericUnixTheme int main(int argc, char *argv[]) { qw_log::init(); + const QByteArray wlrRenderer = qgetenv("WLR_RENDERER"); + const bool explicitVulkanRenderer = wlrRenderer == "vulkan"; + DTK_GUI_NAMESPACE::DGuiApplicationHelper::setAttribute( DTK_GUI_NAMESPACE::DGuiApplicationHelper::DontSaveApplicationTheme, true); @@ -54,7 +60,11 @@ int main(int argc, char *argv[]) }); // QQuickStyle::setStyle("Material"); - QGuiApplication::setAttribute(Qt::AA_UseOpenGLES); + if (explicitVulkanRenderer) + QQuickWindow::setGraphicsApi(QSGRendererInterface::Vulkan); + else + QGuiApplication::setAttribute(Qt::AA_UseOpenGLES); + QGuiApplication::setHighDpiScaleFactorRoundingPolicy( Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); QGuiApplication::setQuitOnLastWindowClosed(false); @@ -76,6 +86,9 @@ int main(int argc, char *argv[]) } #endif DLogManager::registerJournalAppender(); + if (explicitVulkanRenderer) { + qCInfo(lcTlCore) << "Explicit Vulkan renderer requested; Qt::AA_UseOpenGLES was not set"; + } WRenderHelper::setupRendererBackend(); if (CmdLine::ref().tryExec()) diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 63938fdeb..9a6f1a430 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -67,6 +67,7 @@ #include #include +#include #include #include #include @@ -1622,7 +1623,12 @@ void Helper::init(Treeland::Treeland *treeland) } m_allocator = qw_allocator::autocreate(*m_backend->handle(), *m_renderer); - m_renderer->init_wl_display(*m_server->handle()); + if (!m_renderer->init_wl_shm(*m_server->handle())) + qCFatal(lcTlCore) << "Failed to initialize wl_shm for renderer"; + + qCInfo(lcTlCore) << "Initialized wl_shm for renderer"; + m_server->attach(m_renderer); + qw_drm::create(*m_server->handle(), *m_renderer); // free follow display diff --git a/waylib/src/server/CMakeLists.txt b/waylib/src/server/CMakeLists.txt index 90111db5f..2533d5b14 100644 --- a/waylib/src/server/CMakeLists.txt +++ b/waylib/src/server/CMakeLists.txt @@ -31,6 +31,11 @@ pkg_search_module(PIXMAN REQUIRED IMPORTED_TARGET pixman-1) pkg_search_module(XKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon) pkg_search_module(XCB REQUIRED IMPORTED_TARGET xcb) pkg_search_module(EGL REQUIRED IMPORTED_TARGET egl) +pkg_get_variable(WAYLIB_WLR_HAVE_VULKAN_RENDERER wlroots-0.19 have_vulkan_renderer) + +if(WAYLIB_WLR_HAVE_VULKAN_RENDERER) + find_package(Vulkan REQUIRED) +endif() add_compile_definitions(WLR_PRIVATE=) @@ -197,6 +202,7 @@ set(SOURCES protocols/ext_foreign_toplevel_image_capture_source.c #TODO: Remove after wlroots 0.20 protocols/wcursorshapemanagerv1.cpp protocols/woutputmanagerv1.cpp + protocols/wlinuxdmabufv1.cpp protocols/wextforeigntoplevellistv1.cpp protocols/wxdgdialogmanagerv1.cpp protocols/wsecuritycontextmanager.cpp @@ -294,6 +300,8 @@ set(HEADERS protocols/WCursorShapeManagerV1 protocols/woutputmanagerv1.h protocols/WOutputManagerV1 + protocols/wlinuxdmabufv1.h + protocols/WLinuxDmabufV1 protocols/wlayershell.h protocols/WLayerShell protocols/wxwayland.h @@ -408,6 +416,13 @@ target_link_libraries(${TARGET} PkgConfig::EGL ) +if(TARGET Vulkan::Vulkan) + target_link_libraries(${TARGET} + PRIVATE + Vulkan::Vulkan + ) +endif() + target_link_libraries(${TARGET} PUBLIC QWlroots::QWlroots diff --git a/waylib/src/server/protocols/WLinuxDmabufV1 b/waylib/src/server/protocols/WLinuxDmabufV1 new file mode 100644 index 000000000..5587ab15b --- /dev/null +++ b/waylib/src/server/protocols/WLinuxDmabufV1 @@ -0,0 +1 @@ +#include "wlinuxdmabufv1.h" diff --git a/waylib/src/server/protocols/wlinuxdmabufv1.cpp b/waylib/src/server/protocols/wlinuxdmabufv1.cpp new file mode 100644 index 000000000..89ae9df5d --- /dev/null +++ b/waylib/src/server/protocols/wlinuxdmabufv1.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "wlinuxdmabufv1.h" + +#include "wayliblogging.h" + +#include +#include +#include + +QW_USE_NAMESPACE +WAYLIB_SERVER_BEGIN_NAMESPACE + +constexpr uint32_t LinuxDmabufV1Version = 4; + +WLinuxDmabufV1::WLinuxDmabufV1(qw_renderer *renderer, QObject *parent) + : QObject(parent) + , m_renderer(renderer) +{ +} + +qw_linux_dmabuf_v1 *WLinuxDmabufV1::handle() const +{ + return nativeInterface(); +} + +QByteArrayView WLinuxDmabufV1::interfaceName() const +{ + return "zwp_linux_dmabuf_v1"; +} + +void WLinuxDmabufV1::create(WServer *server) +{ + if (m_handle) + return; + + if (!m_renderer || !m_renderer->handle()) { + qCWarning(lcWlLinuxDmabuf) << "Cannot create linux-dmabuf global without renderer"; + return; + } + + const auto *formats = m_renderer->get_texture_formats(WLR_BUFFER_CAP_DMABUF); + if (!formats) { + qCInfo(lcWlLinuxDmabuf) << "Skipping linux-dmabuf global: renderer does not expose DMA-BUF texture formats"; + return; + } + + const int drmFd = m_renderer->get_drm_fd(); + if (drmFd < 0) { + qCInfo(lcWlLinuxDmabuf) << "Skipping linux-dmabuf global: renderer has no DRM fd"; + return; + } + + m_handle = qw_linux_dmabuf_v1::create_with_renderer(*server->handle(), + LinuxDmabufV1Version, + *m_renderer); + if (!m_handle) { + qCWarning(lcWlLinuxDmabuf) << "Failed to create renderer-backed linux-dmabuf global"; + return; + } + + qCInfo(lcWlLinuxDmabuf) << "Created renderer-backed linux-dmabuf global" + << "version" << LinuxDmabufV1Version + << "drm fd" << drmFd; +} + +void WLinuxDmabufV1::destroy([[maybe_unused]] WServer *server) +{ + // wlroots owns this global and destroys it from the display destroy listener. +} + +wl_global *WLinuxDmabufV1::global() const +{ + if (auto *native = handle()) + return native->handle()->global; + + return nullptr; +} + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/protocols/wlinuxdmabufv1.h b/waylib/src/server/protocols/wlinuxdmabufv1.h new file mode 100644 index 000000000..3dc2bdda0 --- /dev/null +++ b/waylib/src/server/protocols/wlinuxdmabufv1.h @@ -0,0 +1,35 @@ +// 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 + +QW_BEGIN_NAMESPACE +class qw_linux_dmabuf_v1; +class qw_renderer; +QW_END_NAMESPACE + +WAYLIB_SERVER_BEGIN_NAMESPACE + +class WAYLIB_SERVER_EXPORT WLinuxDmabufV1 : public QObject, public WServerInterface +{ + Q_OBJECT + +public: + explicit WLinuxDmabufV1(QW_NAMESPACE::qw_renderer *renderer, QObject *parent = nullptr); + + QW_NAMESPACE::qw_linux_dmabuf_v1 *handle() const; + + QByteArrayView interfaceName() const override; + +protected: + void create(WServer *server) override; + void destroy(WServer *server) override; + wl_global *global() const override; + +private: + QW_NAMESPACE::qw_renderer *m_renderer = nullptr; +}; + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/qtquick/private/wbufferrenderer.cpp b/waylib/src/server/qtquick/private/wbufferrenderer.cpp index f12f4bf07..7c9e6eef1 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer.cpp +++ b/waylib/src/server/qtquick/private/wbufferrenderer.cpp @@ -5,6 +5,7 @@ #include "wayliblogging.h" #include "wrenderhelper.h" #include "wqmlhelper_p.h" +#include "woutputviewport_p.h" #include "wtools.h" #include "wsgtextureprovider.h" #include "private/wprivateaccessor_p.h" @@ -19,6 +20,7 @@ #include #include +#include #include #include @@ -258,9 +260,12 @@ qw_buffer *WBufferRenderer::lastBuffer() const QRhiTexture *WBufferRenderer::currentRenderTarget() const { - if (!state.sgRenderTarget.rt) + auto renderTarget = state.activeSgRenderTarget.rt + ? state.activeSgRenderTarget.rt + : state.sgRenderTarget.rt; + if (!renderTarget) return nullptr; - auto textureRT = static_cast(state.sgRenderTarget.rt); + auto textureRT = static_cast(renderTarget); auto colorAttachment = textureRT->description().colorAttachmentAt(0); if (!colorAttachment) return nullptr; @@ -279,6 +284,9 @@ qw_damage_ring *WBufferRenderer::damageRing() bool WBufferRenderer::isTextureProvider() const { + if (isPrimaryOutputRendererForVulkan()) + return false; + return true; } @@ -289,6 +297,9 @@ QSGTextureProvider *WBufferRenderer::textureProvider() const WSGTextureProvider *WBufferRenderer::wTextureProvider() const { + if (isPrimaryOutputRendererForVulkan()) + return nullptr; + auto w = qobject_cast(window()); auto d = QQuickItemPrivate::get(this); if (!w || !d->sceneGraphRenderContext() || QThread::currentThread() != d->sceneGraphRenderContext()->thread()) { @@ -304,6 +315,25 @@ WSGTextureProvider *WBufferRenderer::wTextureProvider() const return m_textureProvider.get(); } +bool WBufferRenderer::isPrimaryOutputRendererForVulkan() const +{ + auto wd = window() ? QQuickWindowPrivate::get(window()) : nullptr; + if (!wd || !wd->rhi || wd->rhi->backend() != QRhi::Vulkan) + return false; + + auto viewport = qobject_cast(parentItem()); + if (!viewport) + return false; + + auto vd = WOutputViewportPrivate::get(viewport); + return vd && vd->attached && vd->bufferRenderer == this; +} + +bool WBufferRenderer::shouldExposeVulkanCacheProvider() const +{ + return !isPrimaryOutputRendererForVulkan(); +} + QTransform WBufferRenderer::inputMapToOutput(const QRectF &sourceRect, const QRectF &targetRect, const QSize &pixelSize, const qreal devicePixelRatio) { @@ -330,6 +360,8 @@ QTransform WBufferRenderer::inputMapToOutput(const QRectF &sourceRect, const QRe return t; } +static QSGRenderTarget toRhiSgRenderTarget(QQuickWindowPrivate *wd, const QQuickRenderTarget &rt); + qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixelRatio, uint32_t format, RenderFlags flags) { @@ -403,11 +435,7 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe } else { state.dirty = QRegion(); - Q_ASSERT(rtd->type == QQuickRenderTargetPrivate::Type::RhiRenderTarget); - sgRT.rt = rtd->u.rhiRt; - sgRT.cb = wd->redirect.commandBuffer; - Q_ASSERT(sgRT.cb); - sgRT.rpDesc = rtd->u.rhiRt->renderPassDescriptor(); + sgRT = toRhiSgRenderTarget(wd, rt); #ifndef QT_NO_OPENGL if (wd->rhi->backend() == QRhi::OpenGLES2) { @@ -426,6 +454,14 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe state.buffer.reset(buffer); state.renderTarget = rt; state.sgRenderTarget = sgRT; + state.activeSgRenderTarget = sgRT; + state.preserveRenderTarget = m_renderHelper->preserveRenderTarget(buffer); + if (!state.preserveRenderTarget.isNull()) { + auto preserveRtd = QQuickRenderTargetPrivate::get(&state.preserveRenderTarget); + if (preserveRtd->type == QQuickRenderTargetPrivate::Type::RhiRenderTarget) + state.preserveSgRenderTarget = toRhiSgRenderTarget(wd, state.preserveRenderTarget); + } + state.renderBufferReleasedForCache = false; return buffer; } @@ -435,7 +471,72 @@ inline static QRect scaleToRect(const QRectF &s, qreal scale) { (s.size() * scale).toSize()); } -void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, +static QSGRenderTarget toRhiSgRenderTarget(QQuickWindowPrivate *wd, const QQuickRenderTarget &rt) +{ + auto rtd = QQuickRenderTargetPrivate::get(&rt); + Q_ASSERT(rtd->type == QQuickRenderTargetPrivate::Type::RhiRenderTarget); + + QSGRenderTarget sgRT; + sgRT.rt = rtd->u.rhiRt; + sgRT.cb = wd->redirect.commandBuffer; + Q_ASSERT(sgRT.cb); + sgRT.rpDesc = rtd->u.rhiRt->renderPassDescriptor(); + return sgRT; +} + +static QVector activeTextureProvidersForPass(QQuickItem *root, + qw_buffer *currentBuffer, + int sourceIndex) +{ + QVector providers; + if (!root) + return providers; + + QSet seen; + const auto items = WOutputRenderWindow::paintOrderItemList(root, [] (QQuickItem *item) { + return item + && item->isVisible() + && item->flags().testFlag(QQuickItem::ItemHasContents) + && item->isTextureProvider(); + }); + + providers.reserve(items.size()); + for (auto item : items) { + if (!item) + continue; + + auto provider = qobject_cast(item->textureProvider()); + if (!provider || seen.contains(provider)) + continue; + + seen.insert(provider); + providers.append(provider); + + if (lcWlQtQuickTexture().isDebugEnabled()) { + auto providerBuffer = provider->qwBuffer(); + auto providerTexture = provider->qwTexture(); + const auto itemType = item->metaObject() ? item->metaObject()->className() : ""; + qCDebug(lcWlQtQuickTexture) << "Collected active Vulkan texture provider for render pass" + << "sourceIndex" << sourceIndex + << "sourceItem" << item + << "sourceItemType" << itemType + << "sourceObjectName" << item->objectName() + << "provider" << provider + << "providerBuffer" << providerBuffer + << "providerWlrBuffer" << (providerBuffer ? providerBuffer->handle() : nullptr) + << "providerTexture" << providerTexture + << "wlrTexture" << (providerTexture ? providerTexture->handle() : nullptr) + << "currentBuffer" << currentBuffer + << "currentWlrBuffer" << (currentBuffer ? currentBuffer->handle() : nullptr) + << "policy" << int(provider->vulkanSamplingPolicy()) + << "textureSize" << (provider->texture() ? provider->texture()->textureSize() : QSize()); + } + } + + return providers; +} + +bool WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, const QRectF &sourceRect, const QRectF &targetRect, bool preserveColorContents) { @@ -449,6 +550,19 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, state.renderer = renderer; state.batchRenderer = dynamic_cast(renderer); state.worldTransform = renderMatrix; + const bool isVulkanRhi = wd->rhi && wd->rhi->backend() == QRhi::Vulkan; + auto activeRenderTarget = state.renderTarget; + auto activeSgRenderTarget = state.sgRenderTarget; + if (isVulkanRhi && preserveColorContents && state.preserveSgRenderTarget.rt) { + activeRenderTarget = state.preserveRenderTarget; + activeSgRenderTarget = state.preserveSgRenderTarget; + qCDebug(lcWlBufferRenderer) << "Using Vulkan preserve render target for render pass" + << "renderer" << this + << "sourceIndex" << sourceIndex + << "buffer" << state.buffer.get() + << "renderTarget" << activeSgRenderTarget.rt; + } + state.activeSgRenderTarget = activeSgRenderTarget; // The renderer should always receive the window's DPR (Device Pixel Ratio) // because, regardless of the DPR used for rendering, all resources within // a window are loaded based on the window's own DPR. @@ -471,7 +585,7 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, // are always consistent. Otherwise, some Items might render incorrectly. renderer->setDevicePixelRatio(window()->effectiveDevicePixelRatio()); renderer->setDeviceRect(QRect(QPoint(0, 0), state.pixelSize)); - renderer->setRenderTarget(state.sgRenderTarget); + renderer->setRenderTarget(activeSgRenderTarget); const auto viewportRect = scaleToRect(targetRect, devicePixelRatio); auto softwareRenderer = dynamic_cast(renderer); @@ -523,7 +637,7 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, state.worldTransform.optimize(); bool flipY = wd->rhi ? !wd->rhi->isYUpInNDC() : false; - if (state.renderTarget.mirrorVertically()) + if (activeRenderTarget.mirrorVertically()) flipY = !flipY; if (viewportRect.isValid()) { @@ -564,15 +678,43 @@ 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); + auto textureRT = static_cast(activeSgRenderTarget.rt); + if (!isVulkanRhi) { + if (preserveColorContents) { + textureRT->setFlags(textureRT->flags() | QRhiTextureRenderTarget::PreserveColorContents); + } else { + textureRT->setFlags(textureRT->flags() & ~QRhiTextureRenderTarget::PreserveColorContents); + } } } } + qsizetype uploadFlushCount = 0; + qsizetype preparedTextureCount = 0; + QVector preparedTextures; + auto outputWindow = renderWindow(); + const auto activeTextureProviders = activeTextureProvidersForPass(isRootItem(source.source) + ? wd->contentItem + : source.source, + state.buffer.get(), + sourceIndex); + constexpr const char *samplingPurpose = "qt-render-pass-texture"; + if (outputWindow + && !outputWindow->prepareTextureSamplingForRenderPass(state.buffer.get(), + activeTextureProviders, + samplingPurpose, + sourceIndex, + &uploadFlushCount, + &preparedTextureCount, + &preparedTextures)) { + qCWarning(lcWlBufferRenderer) << "Skipping render pass because Vulkan texture sampling prepare failed" + << "renderer" << this + << "sourceIndex" << sourceIndex + << "currentBuffer" << state.buffer.get() + << "wlrBuffer" << (state.buffer ? state.buffer->handle() : nullptr); + return false; + } + state.context->renderNextFrame(renderer); { // after render @@ -587,7 +729,14 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, // sourceIndex, we should let the RHI (Rendering Hardware Interface) // complete the results of this drawing here to ensure the current // drawing result is available for use. - wd->rhi->finish(); + if (isVulkanRhi) { + qCDebug(lcWlBufferRenderer) << "Deferring Vulkan QRhi finish to render-control endFrame" + << "renderer" << this + << "sourceIndex" << sourceIndex + << "buffer" << state.buffer.get(); + } else { + wd->rhi->finish(); + } } else { state.dirty = softwareRenderer->flushRegion(); @@ -622,29 +771,83 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, } } + if (isVulkanRhi) { + qCDebug(lcWlBufferRenderer) << "Keeping Vulkan sampled textures in shader-read layout for render pass" + << "renderer" << this + << "sourceIndex" << sourceIndex + << "currentBuffer" << state.buffer.get() + << "wlrBuffer" << (state.buffer ? state.buffer->handle() : nullptr) + << "uploadFlushCount" << uploadFlushCount + << "preparedTextureCount" << preparedTextureCount; + } + if (auto dr = qobject_cast(state.context)) { QRhiResourceUpdateBatch *resourceUpdates = wd->rhi->nextResourceUpdateBatch(); dr->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates); } - if (shouldCacheBuffer()) + if (shouldCacheBuffer() && !isVulkanRhi) { wTextureProvider()->setBuffer(state.buffer.get()); + } else if (shouldCacheBuffer() && !shouldExposeVulkanCacheProvider()) { + qCDebug(lcWlBufferRenderer) << "Skipping Vulkan primary output cache provider update during render" + << "renderer" << this + << "sourceIndex" << sourceIndex + << "currentBuffer" << state.buffer.get() + << "wlrBuffer" << (state.buffer ? state.buffer->handle() : nullptr); + } else if (shouldCacheBuffer()) { + qCDebug(lcWlBufferRenderer) << "Deferring Vulkan cache provider update until render buffer release" + << "renderer" << this + << "sourceIndex" << sourceIndex + << "currentBuffer" << state.buffer.get() + << "wlrBuffer" << (state.buffer ? state.buffer->handle() : nullptr) + << "releasedForCache" << state.renderBufferReleasedForCache; + } + + return true; } void WBufferRenderer::endRender() { Q_ASSERT(state.buffer.get()); + auto wd = QQuickWindowPrivate::get(window()); + const bool isVulkanRhi = wd->rhi && wd->rhi->backend() == QRhi::Vulkan; + const bool canExposeCompletedCache = state.renderBufferReleasedForCache; { std::unique_ptr buffer; buffer.swap(state.buffer); state.renderer = nullptr; state.batchRenderer = nullptr; + state.activeSgRenderTarget = {}; + state.preserveRenderTarget = {}; + state.preserveSgRenderTarget = {}; m_lastBuffer = buffer.get(); + if (shouldCacheBuffer() && isVulkanRhi) { + if (!shouldExposeVulkanCacheProvider()) { + if (m_textureProvider) + m_textureProvider->setBuffer(nullptr); + qCDebug(lcWlBufferRenderer) << "Skipped Vulkan primary output cache provider after render buffer release" + << "renderer" << this + << "buffer" << buffer.get() + << "wlrBuffer" << buffer->handle(); + } else if (canExposeCompletedCache) { + if (auto provider = wTextureProvider()) + provider->setBuffer(buffer.get()); + qCDebug(lcWlBufferRenderer) << "Updated Vulkan cache provider after render buffer release" + << "renderer" << this + << "buffer" << buffer.get() + << "wlrBuffer" << buffer->handle(); + } else { + qCDebug(lcWlBufferRenderer) << "Skipping Vulkan cache provider update because render buffer is not cacheable" + << "renderer" << this + << "buffer" << buffer.get() + << "wlrBuffer" << buffer->handle(); + } + } } + state.renderBufferReleasedForCache = false; #ifndef QT_NO_OPENGL - auto wd = QQuickWindowPrivate::get(window()); if (state.flags.testFlag(RedirectOpenGLContextDefaultFrameBufferObject) && wd->rhi && wd->rhi->backend() == QRhi::OpenGLES2) { auto glContext = QOpenGLContext::currentContext(); @@ -666,6 +869,11 @@ void WBufferRenderer::updateTextureProvider() if (!m_textureProvider) return; + if (isPrimaryOutputRendererForVulkan()) { + m_textureProvider->setBuffer(nullptr); + return; + } + if (shouldCacheBuffer()) { const bool hasCachedBuffer = m_textureProvider->qwBuffer(); // Ensure only update the buffer when the "shouldCacheBuffer" state is changed. @@ -679,12 +887,19 @@ void WBufferRenderer::updateTextureProvider() QSGNode *WBufferRenderer::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { + auto texture = m_textureProvider ? m_textureProvider->texture() : nullptr; + if (!texture) { + delete oldNode; + return nullptr; + } + auto node = static_cast(oldNode); if (Q_UNLIKELY(!node)) { node = window()->createImageNode(); node->setOwnsTexture(false); - node->setTexture(m_textureProvider->texture()); + node->setTexture(texture); } else { + node->setTexture(texture); node->markDirty(QSGNode::DirtyMaterial); } diff --git a/waylib/src/server/qtquick/private/wbufferrenderer_p.h b/waylib/src/server/qtquick/private/wbufferrenderer_p.h index 628684c71..35a349dd5 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer_p.h +++ b/waylib/src/server/qtquick/private/wbufferrenderer_p.h @@ -96,7 +96,7 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem protected: QW_NAMESPACE::qw_buffer *beginRender(const QSize &pixelSize, qreal devicePixelRatio, uint32_t format, RenderFlags flags = {}); - void render(int sourceIndex, const QMatrix4x4 &renderMatrix, + bool render(int sourceIndex, const QMatrix4x4 &renderMatrix, const QRectF &sourceRect = {}, const QRectF &targetRect = {}, bool preserveColorContents = false); void endRender(); @@ -117,6 +117,8 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem Q_SLOT void invalidateSceneGraph(); void releaseResources() override; void cleanTextureProvider(); + bool isPrimaryOutputRendererForVulkan() const; + bool shouldExposeVulkanCacheProvider() const; inline bool isRootItem(const QQuickItem *source) const { return nullptr == source; @@ -142,7 +144,11 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem std::unique_ptr buffer; QQuickRenderTarget renderTarget; QSGRenderTarget sgRenderTarget; + QQuickRenderTarget preserveRenderTarget; + QSGRenderTarget preserveSgRenderTarget; + QSGRenderTarget activeSgRenderTarget; QRegion dirty; + bool renderBufferReleasedForCache = false; } state; QPointer m_output; diff --git a/waylib/src/server/qtquick/private/wrenderbuffernode.cpp b/waylib/src/server/qtquick/private/wrenderbuffernode.cpp index 2077bb719..04e4fae2b 100644 --- a/waylib/src/server/qtquick/private/wrenderbuffernode.cpp +++ b/waylib/src/server/qtquick/private/wrenderbuffernode.cpp @@ -347,9 +347,9 @@ class Q_DECL_HIDDEN DataManager : public DataManagerBase }; struct WlrAndRhiTexture { - struct wlr_buffer *buffer = nullptr; - qw_texture *wlrTexture = nullptr; QRhiTexture *rhiTexture = nullptr; + uint32_t drmFormat = 0; + uint64_t drmModifier = 0; }; class Q_DECL_HIDDEN RhiTextureManager : public DataManager @@ -364,32 +364,30 @@ class Q_DECL_HIDDEN RhiTextureManager : public DataManagerbuffer; - wlr_dmabuf_attributes attribs; - if (!wlr_buffer_get_dmabuf(buffer, &attribs)) { - qCWarning(lcWlRenderBuffer) << "Failed to get dmabuf attributes for texture" << texture << "with buffer" << buffer - << ", Can't check texture without dmabuf attributes"; - return false; - } - return attribs.format == drmFormat && attribs.modifier == drmModifier && texture->rhiTexture->pixelSize() == size; + return texture->drmFormat == drmFormat + && texture->drmModifier == drmModifier + && texture->rhiTexture + && texture->rhiTexture->pixelSize() == size; } WlrAndRhiTexture *create(QRhiTexture::Format format, uint32_t drmFormat, uint64_t drmModifier, const QSize &size) { - auto ow = qobject_cast(owner()); - auto texture = WRenderHelper::newTexture(ow->allocator(), ow->renderer(), - drmFormat, drmModifier, owner()->rhi(), - size, format, QRhiTexture::RenderTarget); - if (!texture.rhiTexture) + auto texture = owner()->rhi()->newTexture(format, size, 1, QRhiTexture::RenderTarget); + texture->setName(QByteArrayLiteral("WaylibRenderBufferNodeTexture")); + if (!texture->create()) { + qCWarning(lcWlRenderBuffer) << "Failed to create QRhi texture for WRenderBufferNode" + << "format" << format + << "drmFormat" << drmFormat + << "modifier" << drmModifier + << "size" << size; + delete texture; return nullptr; + } - return new WlrAndRhiTexture{texture.buffer, texture.texture, texture.rhiTexture}; + return new WlrAndRhiTexture{texture, drmFormat, drmModifier}; } static void destroy(WlrAndRhiTexture *texture) { delete texture->rhiTexture; - delete texture->wlrTexture; - if (texture->buffer) - wlr_buffer_drop(texture->buffer); delete texture; } }; @@ -657,10 +655,14 @@ class Q_DECL_HIDDEN RhiNode : public WRenderBufferNode { } StateFlags changedStates() const override { - if (Q_UNLIKELY(renderData && !contentNode)) - return (RenderTargetState | ViewportState); - - return {0}; + return DepthState + | StencilState + | ScissorState + | ColorState + | BlendState + | CullState + | ViewportState + | RenderTargetState; } // ###: Should disable DepthAwareRendering here, because @@ -831,6 +833,18 @@ class Q_DECL_HIDDEN RhiNode : public WRenderBufferNode { auto ct = currentRenderTexture(); Q_ASSERT(ct); + if (renderWindow()->rhi() + && renderWindow()->rhi()->backend() == QRhi::Vulkan) { + if (auto renderTargetBuffer = WRenderHelper::lookupBuffer(ct)) { + qCDebug(lcWlRenderBuffer) << "Skipping WRenderBufferNode copy from Vulkan wlroots render buffer" + << "textureSize" << ct->pixelSize() + << "renderTargetBuffer" << renderTargetBuffer + << "wlrBuffer" << renderTargetBuffer->handle(); + reset(); + return; + } + } + if (renderData) { renderData->texture.setTexture(ct); renderData->texture.setTextureSize(ct->pixelSize()); diff --git a/waylib/src/server/qtquick/woutputrenderwindow.cpp b/waylib/src/server/qtquick/woutputrenderwindow.cpp index 454167897..3c502565c 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.cpp +++ b/waylib/src/server/qtquick/woutputrenderwindow.cpp @@ -12,6 +12,7 @@ #include "woutputlayer.h" #include "wbufferrenderer_p.h" #include "wquicktextureproxy.h" +#include "wsgtextureprovider.h" #include "weventjunkman.h" #include "winputdevice.h" #include "wseat.h" @@ -39,7 +40,9 @@ #include #include #include +#include #include +#include #include #include @@ -239,7 +242,7 @@ class Q_DECL_HIDDEN OutputHelper : public WOutputHelper inline qw_buffer *beginRender(WBufferRenderer *renderer, const QSize &pixelSize, uint32_t format, WBufferRenderer::RenderFlags flags); - inline void render(WBufferRenderer *renderer, int sourceIndex, const QMatrix4x4 &renderMatrix, + inline bool render(WBufferRenderer *renderer, int sourceIndex, const QMatrix4x4 &renderMatrix, const QRectF &sourceRect, const QRectF &viewportRect, bool preserveColorContents); static bool visualizeLayers() { @@ -441,6 +444,18 @@ class Q_DECL_HIDDEN WOutputRenderWindowPrivate : public QQuickWindowPrivate QVector> doRenderOutputs(qw_output *needsFrameOutput, const QList &outputs, bool forceRender); + bool releaseRenderBuffer(WBufferRenderer *renderer, const char *purpose); + void releaseRenderBuffers(QVector> &needsCommit); + bool prepareTextureSamplingForRenderPass(qw_buffer *currentBuffer, + const QVector &activeProviders, + const char *purpose, + int sourceIndex, + qsizetype *uploadFlushCount, + qsizetype *preparedTextureCount, + QVector *preparedTextures); + bool finishTextureSamplingForRenderPass(const QVector &preparedTextures, + const char *purpose, + int sourceIndex); void doRender(qw_output *needsFrameOutput, const QList &outputs, bool forceRender, bool doCommit); @@ -595,11 +610,19 @@ qw_buffer *OutputHelper::beginRender(WBufferRenderer *renderer, return renderer->beginRender(pixelSize, devicePixelRatio(), format, flags); } -void OutputHelper::render(WBufferRenderer *renderer, int sourceIndex, const QMatrix4x4 &renderMatrix, +bool OutputHelper::render(WBufferRenderer *renderer, int sourceIndex, const QMatrix4x4 &renderMatrix, const QRectF &sourceRect, const QRectF &targetRect, bool preserveColorContents) { renderWindowD()->pushRenderer(renderer); - renderer->render(sourceIndex, renderMatrix, sourceRect, targetRect, preserveColorContents); + const bool ok = renderer->render(sourceIndex, renderMatrix, sourceRect, targetRect, preserveColorContents); + if (!ok) { + qCWarning(lcWlBufferRenderer) << "WBufferRenderer render pass failed" + << "renderer" << renderer + << "sourceIndex" << sourceIndex + << "currentBuffer" << renderer->currentBuffer() + << "lastBuffer" << renderer->lastBuffer(); + } + return ok; } static QQuickItem *createVisualRectangle(QQuickItem *target, const QColor &color) { @@ -762,15 +785,31 @@ qw_buffer *OutputHelper::renderLayer(LayerData *layer, bool *dontEndRenderAndRet const QRectF sr = QRectF(layer->mapRect.topLeft() - layer->noClipMapRect.topLeft(), layer->mapRect.size()); const QRectF tr(QPointF(0, 0), layer->mapRect.size()); - render(layer->renderer, 0, layer->renderMatrix, sr, tr, - layer->layer->layer->flags().testFlag(WOutputLayer::PreserveColorContents)); + bool renderOk = render(layer->renderer, 0, layer->renderMatrix, sr, tr, + layer->layer->layer->flags().testFlag(WOutputLayer::PreserveColorContents)); if (visualizeLayers()) - render(layer->renderer, 1, layer->renderMatrix, sr, tr, true); + renderOk = renderOk && render(layer->renderer, 1, layer->renderMatrix, sr, tr, true); + + if (!renderOk) { + qCWarning(lcWlBufferRenderer) << "Skipping layer buffer because render pass failed" + << "renderer" << layer->renderer + << "currentBuffer" << layer->renderer->currentBuffer() + << "layer" << layer->layer; + (void)renderWindowD()->releaseRenderBuffer(layer->renderer, "layer-render-buffer-abort"); + layer->renderer->endRender(); + if (dontEndRenderAndReturnNeedsEndRender) + *dontEndRenderAndReturnNeedsEndRender = false; + return nullptr; + } if (dontEndRenderAndReturnNeedsEndRender) { *dontEndRenderAndReturnNeedsEndRender = true; } else { + if (!renderWindowD()->releaseRenderBuffer(layer->renderer, "layer-render-buffer")) { + layer->renderer->endRender(); + return nullptr; + } layer->renderer->endRender(); } } else if (dontEndRenderAndReturnNeedsEndRender) { @@ -819,6 +858,12 @@ WBufferRenderer *OutputHelper::afterRender() if (!buffer) continue; + if (needsEndBuffer + && !renderWindowD()->releaseRenderBuffer(i->renderer, "layer-render-buffer")) { + i->renderer->endRender(); + continue; + } + layers.append({ .layer = i->wlrLayer->handle(), .buffer = buffer->handle(), @@ -965,7 +1010,13 @@ WBufferRenderer *OutputHelper::compositeLayers(const QList layers, b { Q_ASSERT(!layers.isEmpty()); - const bool usingShadowRenderer = forceShadowRenderer; + const bool usingShadowRenderer = forceShadowRenderer + && WRenderHelper::getGraphicsApi(renderWindowD()->rc()) != QSGRendererInterface::Vulkan; + if (forceShadowRenderer && !usingShadowRenderer) { + qCDebug(lcWlBufferRenderer) << "Disabled shadow layer compositor on Vulkan; compositing layers into current output buffer" + << "output" << m_output + << "layerCount" << layers.size(); + } if (!m_layerPorxyContainer) { m_layerPorxyContainer = new QQuickItem(renderWindow()->contentItem()); @@ -1043,15 +1094,36 @@ WBufferRenderer *OutputHelper::compositeLayers(const QList layers, b if (ok) { // stop primary render - if (bufferRenderer()->currentBuffer()) + if (bufferRenderer()->currentBuffer()) { + if (!renderWindowD()->releaseRenderBuffer(bufferRenderer(), "shadow-source-render-buffer")) { + bufferRenderer()->endRender(); + return nullptr; + } bufferRenderer()->endRender(); - render(bufferRenderer2(), 0, {}, m_output->effectiveSourceRect(), m_output->targetRect(), true); + } + if (!render(bufferRenderer2(), 0, {}, m_output->effectiveSourceRect(), m_output->targetRect(), true)) { + qCWarning(lcWlBufferRenderer) << "Skipping shadow composite buffer because render pass failed" + << "renderer" << bufferRenderer2() + << "currentBuffer" << bufferRenderer2()->currentBuffer() + << "output" << m_output; + (void)renderWindowD()->releaseRenderBuffer(bufferRenderer2(), "shadow-composite-render-buffer-abort"); + bufferRenderer2()->endRender(); + return nullptr; + } return bufferRenderer2(); } } else { if (bufferRenderer()->currentBuffer()) { - render(bufferRenderer(), 1, {}, m_output->effectiveSourceRect(), m_output->targetRect(), true); + if (!render(bufferRenderer(), 1, {}, m_output->effectiveSourceRect(), m_output->targetRect(), true)) { + qCWarning(lcWlBufferRenderer) << "Skipping layer composite into output because render pass failed" + << "renderer" << bufferRenderer() + << "currentBuffer" << bufferRenderer()->currentBuffer() + << "output" << m_output; + (void)renderWindowD()->releaseRenderBuffer(bufferRenderer(), "layer-composite-render-buffer-abort"); + bufferRenderer()->endRender(); + return nullptr; + } } else { // ###(zccrs): Maybe because contents is not dirty, so not do render // in WOutputRenderWindowPrivate::doRenderOutputs, force mark the @@ -1102,8 +1174,12 @@ bool OutputHelper::tryToHardwareCursor(const LayerData *layer) return true; } - if (qwoutput()->handle()->software_cursor_locks > 0) + if (qwoutput()->handle()->software_cursor_locks > 0) { + qCDebug(lcWlCursor) << "Skipping hardware cursor because software cursor is locked" + << "output" << output()->output() + << "locks" << qwoutput()->handle()->software_cursor_locks; break; + } auto move_cursor = qwoutput()->handle()->impl->move_cursor; @@ -1198,11 +1274,19 @@ bool OutputHelper::tryToHardwareCursor(const LayerData *layer) newBuffer = m_cursorRenderer->beginRender(pixelSize, 1.0, DRM_FORMAT_ARGB8888, WBufferRenderer::UseCursorFormats); if (newBuffer) { - m_cursorRenderer->render(0, {}); + if (!m_cursorRenderer->render(0, {})) { + qCWarning(lcWlBufferRenderer) << "Skipping cursor buffer because render pass failed" + << "renderer" << m_cursorRenderer + << "currentBuffer" << m_cursorRenderer->currentBuffer(); + (void)renderWindowD()->releaseRenderBuffer(m_cursorRenderer, "cursor-render-buffer-abort"); + newBuffer = nullptr; + } else if (!renderWindowD()->releaseRenderBuffer(m_cursorRenderer, "cursor-render-buffer")) { + newBuffer = nullptr; + } m_cursorRenderer->endRender(); } - m_cursorDirty = false; + m_cursorDirty = !newBuffer; } if (newBuffer) { @@ -1323,6 +1407,13 @@ void WOutputRenderWindowPrivate::init() void WOutputRenderWindowPrivate::init(OutputHelper *helper) { + if (graphicsApi() == QSGRendererInterface::Vulkan + && !helper->output()->output()->forceSoftwareCursor()) { + qCInfo(lcWlCursor) << "Forcing software cursor for Vulkan render path" + << "output" << helper->output()->output(); + helper->output()->output()->setForceSoftwareCursor(true); + } + QMetaObject::invokeMethod(helper, &WOutputHelper::scheduleFrame, Qt::QueuedConnection); helper->init(); QObject::connect(helper->output(), &WOutputViewport::dependsChanged, helper, [this] { @@ -1488,10 +1579,19 @@ WOutputRenderWindowPrivate::doRenderOutputs(qw_output *needsFrameOutput, const Q WBufferRenderer::RedirectOpenGLContextDefaultFrameBufferObject); Q_ASSERT(buffer == helper->bufferRenderer()->currentBuffer()); if (buffer) { - helper->render(helper->bufferRenderer(), 0, renderMatrix, - helper->output()->effectiveSourceRect(), - helper->output()->targetRect(), - helper->output()->preserveColorContents()); + if (!helper->render(helper->bufferRenderer(), 0, renderMatrix, + helper->output()->effectiveSourceRect(), + helper->output()->targetRect(), + helper->output()->preserveColorContents())) { + qCWarning(lcWlBufferRenderer) << "Skipping output buffer because render pass failed" + << "renderer" << helper->bufferRenderer() + << "currentBuffer" << helper->bufferRenderer()->currentBuffer() + << "output" << helper->output(); + (void)releaseRenderBuffer(helper->bufferRenderer(), "compositor-render-buffer-abort"); + helper->bufferRenderer()->endRender(); + helper->resetState(); + continue; + } } renderResults.append(helper); } @@ -1509,6 +1609,227 @@ WOutputRenderWindowPrivate::doRenderOutputs(qw_output *needsFrameOutput, const Q return needsCommit; } +bool WOutputRenderWindowPrivate::releaseRenderBuffer(WBufferRenderer *renderer, const char *purpose) +{ + if (WRenderHelper::getGraphicsApi(rc()) != QSGRendererInterface::Vulkan) + return true; + + if (!renderer || !renderer->currentBuffer()) + return true; + + if (!renderer->m_renderHelper) { + qCWarning(lcWlRenderHelper) << "Vulkan render buffer release failed: missing render helper" + << "purpose" << purpose + << "renderer" << renderer + << "qwBuffer" << renderer->currentBuffer() + << "wlrBuffer" << renderer->currentBuffer()->handle(); + return false; + } + + const bool ok = renderer->m_renderHelper->releaseRenderBuffer(rc(), + renderer->currentBuffer(), + renderer->currentRenderTarget(), + purpose); + const bool cacheable = !purpose || !std::strstr(purpose, "abort"); + if (ok && cacheable) { + renderer->state.renderBufferReleasedForCache = true; + qCDebug(lcWlRenderHelper) << "Marked Vulkan render buffer released for cache sampling" + << "purpose" << purpose + << "renderer" << renderer + << "qwBuffer" << renderer->currentBuffer() + << "wlrBuffer" << renderer->currentBuffer()->handle() + << "renderTarget" << renderer->currentRenderTarget(); + } else if (ok) { + qCDebug(lcWlRenderHelper) << "Vulkan render buffer release succeeded but cache update is skipped" + << "purpose" << purpose + << "renderer" << renderer + << "qwBuffer" << renderer->currentBuffer() + << "wlrBuffer" << renderer->currentBuffer()->handle() + << "renderTarget" << renderer->currentRenderTarget(); + } + return ok; +} + +void WOutputRenderWindowPrivate::releaseRenderBuffers(QVector> &needsCommit) +{ + if (WRenderHelper::getGraphicsApi(rc()) != QSGRendererInterface::Vulkan) + return; + + for (qsizetype i = 0; i < needsCommit.size();) { + auto helper = needsCommit[i].first; + auto renderer = needsCommit[i].second; + if (!renderer || !renderer->currentBuffer()) { + ++i; + continue; + } + + if (releaseRenderBuffer(renderer, "compositor-render-buffer")) { + ++i; + continue; + } + + qCWarning(lcWlRenderHelper) << "Skipping wlroots commit because Vulkan render buffer release failed" + << "renderer" << renderer + << "qwBuffer" << renderer->currentBuffer() + << "wlrBuffer" << renderer->currentBuffer()->handle(); + renderer->endRender(); + helper->resetState(); + needsCommit.removeAt(i); + } +} + +bool WOutputRenderWindowPrivate::prepareTextureSamplingForRenderPass(qw_buffer *currentBuffer, + const QVector &activeProviders, + const char *purpose, + int sourceIndex, + qsizetype *uploadFlushCount, + qsizetype *preparedTextureCount, + QVector *preparedTextures) +{ + Q_ASSERT(uploadFlushCount); + Q_ASSERT(preparedTextureCount); + Q_ASSERT(preparedTextures); + *uploadFlushCount = 0; + *preparedTextureCount = 0; + preparedTextures->clear(); + + if (WRenderHelper::getGraphicsApi(rc()) != QSGRendererInterface::Vulkan) + return true; + + QSet seenTextures; + qsizetype skippedCurrentRenderTarget = 0; + qsizetype invalidProviderCount = 0; + qsizetype rawTextureCount = 0; + qsizetype shmUploadProviderCount = 0; + for (auto provider : activeProviders) { + if (!provider) { + ++invalidProviderCount; + continue; + } + + auto providerBuffer = provider->qwBuffer(); + if (currentBuffer && providerBuffer == currentBuffer) { + ++skippedCurrentRenderTarget; + continue; + } + + auto sampleTexture = provider->texture(); + if (sampleTexture && currentBuffer) { + if (auto rhiTexture = sampleTexture->rhiTexture()) { + auto renderTargetBuffer = WRenderHelper::lookupBuffer(rhiTexture); + if (renderTargetBuffer == currentBuffer) { + ++skippedCurrentRenderTarget; + qCDebug(lcWlQtQuickTexture) << "Skipping current Vulkan render target texture during sampling prepare" + << "purpose" << purpose + << "sourceIndex" << sourceIndex + << "currentBuffer" << currentBuffer + << "currentWlrBuffer" << currentBuffer->handle() + << "provider" << provider + << "providerBuffer" << providerBuffer + << "providerWlrBuffer" << (providerBuffer ? providerBuffer->handle() : nullptr) + << "rhiTexture" << rhiTexture + << "textureSize" << sampleTexture->textureSize(); + continue; + } + } + } + + auto texture = provider->qwTexture(); + if (!texture || !provider->hasTexture()) { + ++invalidProviderCount; + continue; + } + + const auto samplingPolicy = provider->vulkanSamplingPolicy(); + if (samplingPolicy == WSGTextureProvider::VulkanSamplingPolicy::Invalid) { + ++invalidProviderCount; + continue; + } + + if (samplingPolicy != WSGTextureProvider::VulkanSamplingPolicy::ShmUpload) + ++rawTextureCount; + else + ++shmUploadProviderCount; + + if (seenTextures.contains(texture)) + continue; + + seenTextures.insert(texture); + if (!WRenderHelper::prepareTextureForSampling(rc(), m_renderer, texture, purpose)) { + qCWarning(lcWlQtQuickTexture) << "Skipping wlroots texture for this Vulkan render pass because sampling prepare failed" + << "purpose" << purpose + << "sourceIndex" << sourceIndex + << "currentBuffer" << currentBuffer + << "currentWlrBuffer" << (currentBuffer ? currentBuffer->handle() : nullptr) + << "provider" << provider + << "providerBuffer" << providerBuffer + << "providerWlrBuffer" << (providerBuffer ? providerBuffer->handle() : nullptr) + << "qwTexture" << texture + << "wlrTexture" << texture->handle() + << "policy" << int(samplingPolicy) + << "qtTextureSize" << (sampleTexture ? sampleTexture->textureSize() : QSize()) + << "activeProviderCount" << activeProviders.size() + << "uploadFlushCount" << *uploadFlushCount + << "preparedTextureCount" << *preparedTextureCount + << "rawTextureCount" << rawTextureCount + << "skippedCurrentRenderTarget" << skippedCurrentRenderTarget + << "invalidProviderCount" << invalidProviderCount; + provider->setTexture(nullptr, nullptr); + continue; + } + + preparedTextures->append(texture); + ++(*preparedTextureCount); + if (samplingPolicy == WSGTextureProvider::VulkanSamplingPolicy::ShmUpload) + ++(*uploadFlushCount); + } + + qCDebug(lcWlQtQuickTexture) << "Prepared Vulkan texture sampling for render pass" + << "purpose" << purpose + << "sourceIndex" << sourceIndex + << "currentBuffer" << currentBuffer + << "currentWlrBuffer" << (currentBuffer ? currentBuffer->handle() : nullptr) + << "activeProviderCount" << activeProviders.size() + << "uploadFlushCount" << *uploadFlushCount + << "preparedTextureCount" << *preparedTextureCount + << "rawTextureCount" << rawTextureCount + << "shmUploadProviderCount" << shmUploadProviderCount + << "skippedCurrentRenderTarget" << skippedCurrentRenderTarget + << "invalidProviderCount" << invalidProviderCount; + return true; +} + +bool WOutputRenderWindowPrivate::finishTextureSamplingForRenderPass(const QVector &preparedTextures, + const char *purpose, + int sourceIndex) +{ + if (WRenderHelper::getGraphicsApi(rc()) != QSGRendererInterface::Vulkan) + return true; + + for (qsizetype i = preparedTextures.size() - 1; i >= 0; --i) { + auto texture = preparedTextures.at(i); + if (!texture) + continue; + + if (!WRenderHelper::finishTextureSampling(rc(), m_renderer, texture, purpose)) { + qCWarning(lcWlQtQuickTexture) << "Vulkan texture sampling finish failed for render pass" + << "purpose" << purpose + << "sourceIndex" << sourceIndex + << "qwTexture" << texture + << "wlrTexture" << texture->handle() + << "preparedTextureCount" << preparedTextures.size() + << "releaseIndex" << i; + return false; + } + } + + qCDebug(lcWlQtQuickTexture) << "Finished Vulkan texture sampling for render pass" + << "purpose" << purpose + << "sourceIndex" << sourceIndex + << "preparedTextureCount" << preparedTextures.size(); + return true; +} + // ###: QQuickAnimatorController::advance symbol not export static void QQuickAnimatorController_advance(QQuickAnimatorController *ac) { @@ -1545,7 +1866,8 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, rc()->polishItems(); - if (QSGRendererInterface::isApiRhiBased(WRenderHelper::getGraphicsApi())) + const bool rhiBased = QSGRendererInterface::isApiRhiBased(WRenderHelper::getGraphicsApi()); + if (rhiBased) rc()->beginFrame(); rc()->sync(); @@ -1553,12 +1875,16 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, Q_EMIT q->beforeRendering(); runAndClearJobs(&beforeRenderingJobs); - auto needsCommit = doRenderOutputs(needsFrameOutput, outputs, forceRender); + QVector> needsCommit; + needsCommit = doRenderOutputs(needsFrameOutput, outputs, forceRender); Q_EMIT q->afterRendering(); runAndClearJobs(&afterRenderingJobs); - if (QSGRendererInterface::isApiRhiBased(WRenderHelper::getGraphicsApi())) + if (rhiBased) + releaseRenderBuffers(needsCommit); + + if (rhiBased) rc()->endFrame(); // prevent gles2-render exception in wlroots. @@ -1891,6 +2217,32 @@ qw_allocator *WOutputRenderWindow::allocator() const return d->m_allocator; } +bool WOutputRenderWindow::prepareTextureSamplingForRenderPass(qw_buffer *currentBuffer, + const QVector &activeProviders, + const char *purpose, + int sourceIndex, + qsizetype *uploadFlushCount, + qsizetype *preparedTextureCount, + QVector *preparedTextures) +{ + Q_D(WOutputRenderWindow); + return d->prepareTextureSamplingForRenderPass(currentBuffer, + activeProviders, + purpose, + sourceIndex, + uploadFlushCount, + preparedTextureCount, + preparedTextures); +} + +bool WOutputRenderWindow::finishTextureSamplingForRenderPass(const QVector &preparedTextures, + const char *purpose, + int sourceIndex) +{ + Q_D(WOutputRenderWindow); + return d->finishTextureSamplingForRenderPass(preparedTextures, purpose, sourceIndex); +} + WBufferRenderer *WOutputRenderWindow::currentRenderer() const { Q_D(const WOutputRenderWindow); diff --git a/waylib/src/server/qtquick/woutputrenderwindow.h b/waylib/src/server/qtquick/woutputrenderwindow.h index 41e61dbbf..edaf1d42f 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.h +++ b/waylib/src/server/qtquick/woutputrenderwindow.h @@ -9,16 +9,24 @@ #include #include +#include Q_MOC_INCLUDE() +QW_BEGIN_NAMESPACE +class qw_buffer; +class qw_texture; +QW_END_NAMESPACE + WAYLIB_SERVER_BEGIN_NAMESPACE class WOutputViewport; class WOutputLayer; class WBufferRenderer; class WOutputHelper; +class WSGTextureProvider; class WOutputRenderWindowPrivate; + class WAYLIB_SERVER_EXPORT WOutputRenderWindow : public QQuickWindow, public QQmlParserStatus { Q_OBJECT @@ -54,6 +62,17 @@ class WAYLIB_SERVER_EXPORT WOutputRenderWindow : public QQuickWindow, public QQm QW_NAMESPACE::qw_renderer *renderer() const; QW_NAMESPACE::qw_allocator *allocator() const; + bool prepareTextureSamplingForRenderPass(QW_NAMESPACE::qw_buffer *currentBuffer, + const QVector &activeProviders, + const char *purpose, + int sourceIndex, + qsizetype *uploadFlushCount, + qsizetype *preparedTextureCount, + QVector *preparedTextures); + bool finishTextureSamplingForRenderPass(const QVector &preparedTextures, + const char *purpose, + int sourceIndex); + qreal width() const; qreal height() const; WBufferRenderer *currentRenderer() const; diff --git a/waylib/src/server/qtquick/wquicktextureproxy.cpp b/waylib/src/server/qtquick/wquicktextureproxy.cpp index ee4de967d..175ca1dbe 100644 --- a/waylib/src/server/qtquick/wquicktextureproxy.cpp +++ b/waylib/src/server/qtquick/wquicktextureproxy.cpp @@ -188,8 +188,10 @@ class Q_DECL_HIDDEN SGTextureProviderNode : public QObject, public QSGNode m_tp = tp; onTextureChanged(); - connect(tp, &QSGTextureProvider::textureChanged, - this, &SGTextureProviderNode::onTextureChanged, Qt::DirectConnection); + if (tp) { + connect(tp, &QSGTextureProvider::textureChanged, + this, &SGTextureProviderNode::onTextureChanged, Qt::DirectConnection); + } } inline QSGTextureProvider *textureProvider() const { @@ -202,8 +204,13 @@ class Q_DECL_HIDDEN SGTextureProviderNode : public QObject, public QSGNode appendChildNode(image); image->setFlag(QSGNode::OwnedByParent); if (m_tp) { - m_image->setTexture(m_tp->texture()); - Q_ASSERT(m_image->texture()); + auto texture = m_tp->texture(); + if (texture) { + m_image->setTexture(texture); + } else { + delete m_image; + m_image = nullptr; + } } } diff --git a/waylib/src/server/qtquick/wrenderbufferblitter.cpp b/waylib/src/server/qtquick/wrenderbufferblitter.cpp index 9443bd646..6c884ed34 100644 --- a/waylib/src/server/qtquick/wrenderbufferblitter.cpp +++ b/waylib/src/server/qtquick/wrenderbufferblitter.cpp @@ -3,6 +3,7 @@ #include "wrenderbufferblitter.h" #include "wrenderbuffernode_p.h" +#include "wayliblogging.h" #include "private/wglobal_p.h" #include @@ -95,6 +96,7 @@ class Q_DECL_HIDDEN WRenderBufferBlitterPrivate : public WObjectPrivate Content *content = nullptr; QQuickItem *container = nullptr; mutable BlitTextureProvider *tp = nullptr; + bool vulkanDisabledLogged = false; }; class Q_DECL_HIDDEN Content : public QQuickItem @@ -260,6 +262,24 @@ static void onTextureChanged(WRenderBufferNode *node, void *data) { QSGNode *WRenderBufferBlitter::updatePaintNode(QSGNode *oldNode, [[maybe_unused]] QQuickItem::UpdatePaintNodeData *oldData) { + W_D(WRenderBufferBlitter); + if (window()->graphicsApi() == QSGRendererInterface::Vulkan) { + if (d->tp) { + d->tp->setTexture(nullptr); + Q_EMIT d->tp->textureChanged(); + } + delete oldNode; + + if (!d->vulkanDisabledLogged) { + qCInfo(lcWlRenderBuffer) << "Disabled RenderBufferBlitter on Vulkan" + << "window" << window() + << "itemSize" << size() + << "reason" << "wlroots render buffer lacks TRANSFER_SRC usage"; + d->vulkanDisabledLogged = true; + } + + return nullptr; + } auto node = static_cast(oldNode); if (Q_LIKELY(node)) { @@ -267,7 +287,6 @@ QSGNode *WRenderBufferBlitter::updatePaintNode(QSGNode *oldNode, [[maybe_unused] return node; } - W_D(WRenderBufferBlitter); if (window()->graphicsApi() == QSGRendererInterface::Software) { node = WRenderBufferNode::createSoftwareNode(this); } else { diff --git a/waylib/src/server/qtquick/wrenderhelper.cpp b/waylib/src/server/qtquick/wrenderhelper.cpp index 0f0063159..04922bfaf 100644 --- a/waylib/src/server/qtquick/wrenderhelper.cpp +++ b/waylib/src/server/qtquick/wrenderhelper.cpp @@ -20,6 +20,8 @@ #include #include +#include +#include #include #include #include @@ -28,6 +30,10 @@ #include #include +#ifdef ENABLE_VULKAN_RENDER +#include +#endif + extern "C" { #define static #include @@ -38,7 +44,8 @@ extern "C" { #endif } #include -#include + +#include QW_USE_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE @@ -51,6 +58,92 @@ struct Q_DECL_HIDDEN RhiRenderEntry { Q_GLOBAL_STATIC(QVector, s_rhiRenderBuffers) +#ifdef ENABLE_VULKAN_RENDER +static QString hex32(uint32_t value) +{ + return QStringLiteral("0x%1").arg(value, 8, 16, QLatin1Char('0')); +} + +static QString hex64(uint64_t value) +{ + return QStringLiteral("0x%1").arg(value, 16, 16, QLatin1Char('0')); +} + +static quint64 vkImageValue(VkImage image) +{ + if constexpr (std::is_pointer_v) { + return quint64(reinterpret_cast(image)); + } else { + return quint64(image); + } +} + +static QString vkImageName(VkImage image) +{ + return hex64(vkImageValue(image)); +} + +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_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"; + default: + return "UNKNOWN"; + } +} + +static QSize wlrBufferSize(qw_buffer *buffer) +{ + return QSize(buffer->handle()->width, buffer->handle()->height); +} + +static QSize wlrTextureSize(qw_texture *texture) +{ + return QSize(texture->handle()->width, texture->handle()->height); +} + +static bool getVulkanRenderBufferAttribs(qw_renderer *renderer, + qw_buffer *buffer, + const char *purpose, + wlr_vk_image_attribs *attribs) +{ + if (!renderer || !buffer || !wlr_renderer_is_vk(renderer->handle())) + return false; + + if (wlr_vk_renderer_get_render_buffer_attribs(renderer->handle(), buffer->handle(), attribs)) { + qCDebug(lcWlRenderHelper) << "Got wlroots Vulkan render buffer attributes" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs->image) + << "layout" << vkImageLayoutName(attribs->layout) + << "format" << hex32(attribs->format) + << "size" << wlrBufferSize(buffer); + return true; + } + + qCWarning(lcWlRenderHelper) << "Failed to get wlroots Vulkan render buffer attributes" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "size" << wlrBufferSize(buffer); + return false; +} +#endif + struct Q_DECL_HIDDEN BufferData { BufferData() { @@ -65,13 +158,15 @@ struct Q_DECL_HIDDEN BufferData { WImageRenderTarget paintDevice; QQuickRenderTarget renderTarget; QQuickWindowRenderTarget windowRenderTarget; + QQuickRenderTarget preserveRenderTarget; + QQuickWindowRenderTarget preserveWindowRenderTarget; - inline void resetWindowRenderTarget() { + static inline void cleanupWindowRenderTarget(QQuickWindowRenderTarget &target) { #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) { auto it = s_rhiRenderBuffers->begin(); while (it != s_rhiRenderBuffers->end()) { - if (windowRenderTarget.rt.renderTarget == it->renderTarget) { + if (target.rt.renderTarget == it->renderTarget) { it = s_rhiRenderBuffers->erase(it); break; } @@ -79,31 +174,31 @@ struct Q_DECL_HIDDEN BufferData { } } - if (windowRenderTarget.rt.owns) - delete windowRenderTarget.rt.renderTarget; + if (target.rt.owns) + delete target.rt.renderTarget; - delete windowRenderTarget.res.texture; - delete windowRenderTarget.res.renderBuffer; - delete windowRenderTarget.res.rpDesc; + delete target.res.texture; + delete target.res.renderBuffer; + delete target.res.rpDesc; - windowRenderTarget.rt = {}; - windowRenderTarget.res = {}; - { // windowRenderTarget.implicitBuffers.reset(rhi); - delete windowRenderTarget.implicitBuffers.depthStencil; - delete windowRenderTarget.implicitBuffers.depthStencilTexture; - delete windowRenderTarget.implicitBuffers.multisampleTexture; - windowRenderTarget.implicitBuffers = {}; + target.rt = {}; + target.res = {}; + { // target.implicitBuffers.reset(rhi); + delete target.implicitBuffers.depthStencil; + delete target.implicitBuffers.depthStencilTexture; + delete target.implicitBuffers.multisampleTexture; + target.implicitBuffers = {}; } - if (windowRenderTarget.sw.owns) - delete windowRenderTarget.sw.paintDevice; + if (target.sw.owns) + delete target.sw.paintDevice; - windowRenderTarget.sw = {}; + target.sw = {}; #else { auto it = s_rhiRenderBuffers->begin(); while (it != s_rhiRenderBuffers->end()) { - if (windowRenderTarget.renderTarget == it->renderTarget) { + if (target.renderTarget == it->renderTarget) { it = s_rhiRenderBuffers->erase(it); break; } @@ -111,24 +206,29 @@ struct Q_DECL_HIDDEN BufferData { } } - if (windowRenderTarget.owns) { - delete windowRenderTarget.renderTarget; - delete windowRenderTarget.rpDesc; - delete windowRenderTarget.texture; - delete windowRenderTarget.renderBuffer; - delete windowRenderTarget.depthStencil; - delete windowRenderTarget.paintDevice; + if (target.owns) { + delete target.renderTarget; + delete target.rpDesc; + delete target.texture; + delete target.renderBuffer; + delete target.depthStencil; + delete target.paintDevice; } - windowRenderTarget.renderTarget = nullptr; - windowRenderTarget.rpDesc = nullptr; - windowRenderTarget.texture = nullptr; - windowRenderTarget.renderBuffer = nullptr; - windowRenderTarget.depthStencil = nullptr; - windowRenderTarget.paintDevice = nullptr; - windowRenderTarget.owns = false; + target.renderTarget = nullptr; + target.rpDesc = nullptr; + target.texture = nullptr; + target.renderBuffer = nullptr; + target.depthStencil = nullptr; + target.paintDevice = nullptr; + target.owns = false; #endif } + + inline void resetWindowRenderTarget() { + cleanupWindowRenderTarget(windowRenderTarget); + cleanupWindowRenderTarget(preserveWindowRenderTarget); + } }; // Copy from qquickrendertarget.cpp @@ -136,7 +236,8 @@ static bool createRhiRenderTarget(const QRhiColorAttachment &colorAttachment, const QSize &pixelSize, int sampleCount, QRhi *rhi, - QQuickWindowRenderTarget &dst) + QQuickWindowRenderTarget &dst, + QRhiTextureRenderTarget::Flags flags = {}) { std::unique_ptr depthStencil(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount)); if (!depthStencil->create()) { @@ -146,7 +247,7 @@ static bool createRhiRenderTarget(const QRhiColorAttachment &colorAttachment, QRhiTextureRenderTargetDescription rtDesc(colorAttachment); rtDesc.setDepthStencilBuffer(depthStencil.get()); - std::unique_ptr rt(rhi->newTextureRenderTarget(rtDesc)); + std::unique_ptr rt(rhi->newTextureRenderTarget(rtDesc, flags)); std::unique_ptr rp(rt->newCompatibleRenderPassDescriptor()); rt->setRenderPassDescriptor(rp.get()); @@ -170,7 +271,8 @@ static bool createRhiRenderTarget(const QRhiColorAttachment &colorAttachment, return true; } -bool createRhiRenderTarget(QRhi *rhi, const QQuickRenderTarget &source, QQuickWindowRenderTarget &dst) +bool createRhiRenderTarget(QRhi *rhi, const QQuickRenderTarget &source, QQuickWindowRenderTarget &dst, + QRhiTextureRenderTarget::Flags rtFlags = {}) { auto rtd = QQuickRenderTargetPrivate::get(&source); @@ -178,14 +280,14 @@ bool createRhiRenderTarget(QRhi *rhi, const QQuickRenderTarget &source, QQuickWi case QQuickRenderTargetPrivate::Type::NativeTexture: { const auto format = rtd->u.nativeTexture.rhiFormat == QRhiTexture::UnknownFormat ? QRhiTexture::RGBA8 : QRhiTexture::Format(rtd->u.nativeTexture.rhiFormat); - const auto flags = QRhiTexture::RenderTarget | QRhiTexture::Flags( + const auto textureFlags = QRhiTexture::RenderTarget | QRhiTexture::Flags( #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) rtd->u.nativeTexture.rhiFormatFlags #else rtd->u.nativeTexture.rhiFlags #endif ); - std::unique_ptr texture(rhi->newTexture(format, rtd->pixelSize, rtd->sampleCount, flags)); + std::unique_ptr texture(rhi->newTexture(format, rtd->pixelSize, rtd->sampleCount, textureFlags)); texture->setName(QByteArrayLiteral("WaylibTexture")); #if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) if (!texture->createFrom({ rtd->u.nativeTexture.object, rtd->u.nativeTexture.layout })) @@ -194,7 +296,7 @@ bool createRhiRenderTarget(QRhi *rhi, const QQuickRenderTarget &source, QQuickWi #endif return false; QRhiColorAttachment att(texture.get()); - if (!createRhiRenderTarget(att, rtd->pixelSize, rtd->sampleCount, rhi, dst)) + if (!createRhiRenderTarget(att, rtd->pixelSize, rtd->sampleCount, rhi, dst, rtFlags)) return false; #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) dst.res.texture = texture.release(); @@ -210,7 +312,7 @@ bool createRhiRenderTarget(QRhi *rhi, const QQuickRenderTarget &source, QQuickWi return false; } QRhiColorAttachment att(renderbuffer.get()); - if (!createRhiRenderTarget(att, rtd->pixelSize, rtd->sampleCount, rhi, dst)) + if (!createRhiRenderTarget(att, rtd->pixelSize, rtd->sampleCount, rhi, dst, rtFlags)) return false; renderbuffer->setName(QByteArrayLiteral("WaylibRenderBuffer")); #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) @@ -294,6 +396,38 @@ bool WRenderHelperPrivate::ensureRhiRenderTarget(QQuickRenderControl *rc, Buffer data->renderTarget.setDevicePixelRatio(tmp.devicePixelRatio()); data->renderTarget.setMirrorVertically(tmp.mirrorVertically()); + if (rhi->backend() == QRhi::Vulkan) { + auto rtd = QQuickRenderTargetPrivate::get(&tmp); +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + auto colorTexture = data->windowRenderTarget.res.texture; +#else + auto colorTexture = data->windowRenderTarget.texture; +#endif + if (!colorTexture) { + qCWarning(lcWlRenderHelper) << "Failed to build Vulkan preserve render target: missing shared QRhi texture"; + return false; + } + + QRhiColorAttachment colorAttachment(colorTexture); + ok = createRhiRenderTarget(colorAttachment, + rtd->pixelSize, + rtd->sampleCount, + rhi, + data->preserveWindowRenderTarget, + QRhiTextureRenderTarget::PreserveColorContents); + if (!ok) { + qCWarning(lcWlRenderHelper) << "Failed to build Vulkan preserve render target for QQuickRenderTarget"; + 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()); + } + return true; } @@ -321,6 +455,213 @@ void WRenderHelper::setSize(const QSize &size) Q_EMIT sizeChanged(); } +bool WRenderHelper::acquireRenderBuffer(QQuickRenderControl *rc, qw_buffer *buffer, const char *purpose) +{ +#ifdef ENABLE_VULKAN_RENDER + W_D(WRenderHelper); + if (!d->renderer || !wlr_renderer_is_vk(d->renderer->handle())) + return true; + + wlr_vk_image_attribs attribs = {}; + if (!getVulkanRenderBufferAttribs(d->renderer, buffer, purpose, &attribs)) + return false; + + if (!rc || !rc->rhi() || rc->rhi()->backend() != QRhi::Vulkan) { + qCWarning(lcWlRenderHelper) << "Vulkan render buffer acquire failed: missing Vulkan QRhi" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); + return false; + } + + auto commandBuffer = rc->commandBuffer(); + if (!commandBuffer) { + qCWarning(lcWlRenderHelper) << "Vulkan render buffer acquire failed: missing QRhi command buffer" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); + return false; + } + + commandBuffer->beginExternal(); + auto handles = static_cast(commandBuffer->nativeHandles()); + if (!handles || handles->commandBuffer == VK_NULL_HANDLE) { + commandBuffer->endExternal(); + qCWarning(lcWlRenderHelper) << "Vulkan render buffer acquire failed: missing native Vulkan command buffer" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); + return false; + } + + const bool ok = wlr_vk_renderer_record_render_buffer_acquire(d->renderer->handle(), + buffer->handle(), + handles->commandBuffer); + commandBuffer->endExternal(); + + if (!ok) { + qCWarning(lcWlRenderHelper) << "Vulkan render buffer acquire failed" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); + return false; + } + + qCDebug(lcWlRenderHelper) << "Vulkan render buffer acquire recorded" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); +#else + Q_UNUSED(rc); + Q_UNUSED(buffer); + Q_UNUSED(purpose); +#endif + return true; +} + +bool WRenderHelper::releaseRenderBuffer(QQuickRenderControl *rc, + qw_buffer *buffer, + QRhiTexture *renderTargetTexture, + const char *purpose) +{ +#ifdef ENABLE_VULKAN_RENDER + W_D(WRenderHelper); + if (!d->renderer || !wlr_renderer_is_vk(d->renderer->handle())) + return true; + + wlr_vk_image_attribs attribs = {}; + if (!getVulkanRenderBufferAttribs(d->renderer, buffer, purpose, &attribs)) + return false; + + if (!renderTargetTexture) { + qCWarning(lcWlRenderHelper) << "Vulkan render buffer release failed: missing Qt render target texture" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); + return false; + } + + if (!rc || !rc->rhi() || rc->rhi()->backend() != QRhi::Vulkan) { + qCWarning(lcWlRenderHelper) << "Vulkan render buffer release failed: missing Vulkan QRhi" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); + return false; + } + + auto commandBuffer = rc->commandBuffer(); + if (!commandBuffer) { + qCWarning(lcWlRenderHelper) << "Vulkan render buffer release failed: missing QRhi command buffer" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); + return false; + } + + const auto nativeTexture = renderTargetTexture->nativeTexture(); +#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) + const auto oldLayout = VkImageLayout(nativeTexture.layout); +#else + const auto oldLayout = VkImageLayout(nativeTexture.layout); +#endif + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED) { + qCWarning(lcWlRenderHelper) << "Vulkan render buffer release failed: Qt render target layout is undefined" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "wlrootsLayout" << vkImageLayoutName(attribs.layout) + << "oldLayout" << vkImageLayoutName(oldLayout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); + return false; + } + + commandBuffer->beginExternal(); + auto handles = static_cast(commandBuffer->nativeHandles()); + if (!handles || handles->commandBuffer == VK_NULL_HANDLE) { + commandBuffer->endExternal(); + qCWarning(lcWlRenderHelper) << "Vulkan render buffer release failed: missing native Vulkan command buffer" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "wlrootsLayout" << vkImageLayoutName(attribs.layout) + << "oldLayout" << vkImageLayoutName(oldLayout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); + return false; + } + + const bool ok = wlr_vk_renderer_record_render_buffer_release(d->renderer->handle(), + buffer->handle(), + handles->commandBuffer, + oldLayout); + commandBuffer->endExternal(); + + if (!ok) { + qCWarning(lcWlRenderHelper) << "Vulkan render buffer release failed" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "wlrootsLayout" << vkImageLayoutName(attribs.layout) + << "oldLayout" << vkImageLayoutName(oldLayout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); + return false; + } + + qCDebug(lcWlRenderHelper) << "Vulkan render buffer release recorded" + << "purpose" << purpose + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "wlrootsLayout" << vkImageLayoutName(attribs.layout) + << "oldLayout" << vkImageLayoutName(oldLayout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); +#else + Q_UNUSED(rc); + Q_UNUSED(buffer); + Q_UNUSED(renderTargetTexture); + Q_UNUSED(purpose); +#endif + return true; +} + QSGRendererInterface::GraphicsApi WRenderHelper::getGraphicsApi(QQuickRenderControl *rc) { auto d = QQuickRenderControlPrivate::get(rc); @@ -429,17 +770,6 @@ VkTextureBuffer::VkTextureBuffer(VkInstance instance, VkDevice device, QSGTextur bool VkTextureBuffer::get_dmabuf([[maybe_unused]] wlr_dmabuf_attributes *attribs) { -// static auto vkGetInstanceProcAddr = -// reinterpret_cast(::dlsym(RTLD_DEFAULT, "vkGetInstanceProcAddr")); -// static auto vkGetMemoryFdKHR = -// reinterpret_cast(vkGetInstanceProcAddr(m_instance, "vkGetMemoryFdKHR")); -// static auto vkGetImageMemoryRequirements = -// reinterpret_cast(vkGetInstanceProcAddr(m_instance, "vkGetImageMemoryRequirements")); -// static auto vkGetImageSparseMemoryRequirements = -// reinterpret_cast(vkGetInstanceProcAddr(m_instance, "vkGetImageSparseMemoryRequirements")); -// static auto vkGetImageSubresourceLayout = -// reinterpret_cast(vkGetInstanceProcAddr(m_instance, "vkGetImageSubresourceLayout")); - // TODO return false; } @@ -547,17 +877,26 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q auto data = d->buffers[i]; if (data->buffer == buffer) { d->lastBuffer = data; + if (!acquireRenderBuffer(rc, buffer, "cached-compositor-render-target")) + return {}; return data->renderTarget; } } std::unique_ptr bufferData(new BufferData); bufferData->buffer = buffer; - auto texture = qw_texture::from_buffer(*d->renderer, *buffer); + bool importAsTexture = true; +#ifdef ENABLE_VULKAN_RENDER + if (wlr_renderer_is_vk(d->renderer->handle())) + importAsTexture = false; +#endif + qw_texture *texture = importAsTexture ? qw_texture::from_buffer(*d->renderer, *buffer) : nullptr; QQuickRenderTarget rt; + bool needsVulkanRenderBufferAcquire = false; if (wlr_renderer_is_pixman(d->renderer->handle())) { + Q_ASSERT(texture); pixman_image_t *image = wlr_pixman_texture_get_image(texture->handle()); void *data = pixman_image_get_data(image); if (bufferData->paintDevice.constBits() != data) @@ -567,12 +906,26 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q } #ifdef ENABLE_VULKAN_RENDER else if (wlr_renderer_is_vk(d->renderer->handle())) { - wlr_vk_image_attribs attribs; - wlr_vk_texture_get_image_attribs(texture->handle(), &attribs); - rt = QQuickRenderTarget::fromVulkanImage(attribs.image, attribs.layout, attribs.format, d->size); + wlr_vk_image_attribs attribs = {}; + if (getVulkanRenderBufferAttribs(d->renderer, buffer, "new-compositor-render-target", &attribs)) { + rt = QQuickRenderTarget::fromVulkanImage(attribs.image, + attribs.layout, + attribs.format, + wlrBufferSize(buffer)); + needsVulkanRenderBufferAcquire = true; + qCDebug(lcWlRenderHelper) << "Created Qt Vulkan render target from wlroots render buffer" + << "purpose" << "new-compositor-render-target" + << "qwBuffer" << buffer + << "wlrBuffer" << buffer->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrBufferSize(buffer); + } } #endif else if (wlr_renderer_is_gles2(d->renderer->handle())) { + Q_ASSERT(texture); wlr_gles2_texture_attribs attribs; wlr_gles2_texture_get_attribs(texture->handle(), &attribs); @@ -593,9 +946,18 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q if (bufferData->renderTarget.isNull()) return {}; + if (needsVulkanRenderBufferAcquire + && !acquireRenderBuffer(rc, buffer, "new-compositor-render-target")) { + 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 }); + } } } @@ -608,6 +970,17 @@ QQuickRenderTarget WRenderHelper::acquireRenderTarget(QQuickRenderControl *rc, q return d->buffers.last()->renderTarget; } +QQuickRenderTarget WRenderHelper::preserveRenderTarget(qw_buffer *buffer) const +{ + W_DC(WRenderHelper); + for (auto data : std::as_const(d->buffers)) { + if (data->buffer == buffer) + return data->preserveRenderTarget; + } + + return {}; +} + std::pair WRenderHelper::lastRenderTarget() const { W_DC(WRenderHelper); @@ -704,16 +1077,20 @@ void WRenderHelper::setupRendererBackend(qw_backend *testBackend) } QQuickWindow::setGraphicsApi(WRenderHelper::probe(testBackend, apiList)); } else if (wlrRenderer == "gles2") { + qCInfo(lcWlRenderHelper) << "Using explicit WLR_RENDERER=gles2 with Qt OpenGL scene graph"; QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); } else if (wlrRenderer == "vulkan") { #ifdef ENABLE_VULKAN_RENDER QQuickWindow::setGraphicsApi(QSGRendererInterface::Vulkan); #else + qCCritical(lcWlRenderHelper) << "WLR_RENDERER=vulkan was requested, but Vulkan support is not enabled"; qFatal("Vulkan support is not enabled"); #endif } else if (wlrRenderer == "pixman") { + qCInfo(lcWlRenderHelper) << "Using explicit WLR_RENDERER=pixman with Qt software scene graph"; QQuickWindow::setGraphicsApi(QSGRendererInterface::Software); } else { + qCCritical(lcWlRenderHelper) << "Unknown or unsupported WLR_RENDERER:" << wlrRenderer; qFatal() << "Unknown/Unsupported wlr renderer: " << wlrRenderer; } } @@ -771,7 +1148,7 @@ QSGRendererInterface::GraphicsApi WRenderHelper::probe(qw_backend *testBackend, return acceptApi; } -static void updateGLTexture(QRhi *rhi, qw_texture *handle, QSGPlainTexture *texture) { +static void updateGLTexture(QRhi *rhi, qw_texture *handle, QSGPlainTexture *texture, bool) { wlr_gles2_texture_attribs attribs; wlr_gles2_texture_get_attribs(handle->handle(), &attribs); QSize size(handle->handle()->width, handle->handle()->height); @@ -795,54 +1172,256 @@ static inline quint64 vkimage_cast(void *image) { } #ifdef ENABLE_VULKAN_RENDER -static void updateVKTexture(QRhi *rhi, qw_texture *handle, QSGPlainTexture *texture) { +static void updateVKTexture(QRhi *rhi, qw_texture *handle, QSGPlainTexture *texture, + bool forceShaderReadOnlyLayout) { wlr_vk_image_attribs attribs; wlr_vk_texture_get_image_attribs(handle->handle(), &attribs); QSize size(handle->handle()->width, handle->handle()->height); + const VkImageLayout qtSampleLayout = forceShaderReadOnlyLayout + ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + : attribs.layout; texture->setTextureFromNativeTexture(rhi, vkimage_cast(attribs.image), - attribs.layout, attribs.format, size, + qtSampleLayout, + attribs.format, size, {}, {}); texture->setHasAlphaChannel(wlr_vk_texture_has_alpha(handle->handle())); texture->setTextureSize(size); + qCDebug(lcWlQtQuickTexture) << "Updated Qt Vulkan texture from wlroots texture" + << "wlrTexture" << handle->handle() + << "image" << vkImageName(attribs.image) + << "wlrootsLayout" << vkImageLayoutName(attribs.layout) + << "qtSampleLayout" << vkImageLayoutName(qtSampleLayout) + << "layoutPolicy" << (forceShaderReadOnlyLayout ? "shader-read-only" : "wlroots-raw") + << "format" << hex32(attribs.format) + << "size" << size; } #endif -static void updateImage(QRhi *, qw_texture *handle, QSGPlainTexture *texture) { +static void updateImage(QRhi *, qw_texture *handle, QSGPlainTexture *texture, bool) { auto image = wlr_pixman_texture_get_image(handle->handle()); texture->setImage(WTools::fromPixmanImage(image)); } -typedef void(*UpdateTextureFunction)(QRhi *, qw_texture *, QSGPlainTexture *); +typedef void(*UpdateTextureFunction)(QRhi *, qw_texture *, QSGPlainTexture *, bool); static UpdateTextureFunction getUpdateTextFunction(qw_texture *handle) { const auto api = WRenderHelper::getGraphicsApi(); if (api == QSGRendererInterface::OpenGL) { - Q_ASSERT(wlr_texture_is_gles2(handle->handle())); + if (!wlr_texture_is_gles2(handle->handle())) + return nullptr; return updateGLTexture; } #ifdef ENABLE_VULKAN_RENDER else if (api == QSGRendererInterface::Vulkan) { - Q_ASSERT(wlr_texture_is_vk(handle->handle())); + if (!wlr_texture_is_vk(handle->handle())) + return nullptr; return updateVKTexture; } #endif else if (api == QSGRendererInterface::Software) { - Q_ASSERT(wlr_texture_is_pixman(handle->handle())); + if (!wlr_texture_is_pixman(handle->handle())) + return nullptr; return updateImage; } return nullptr; } -bool WRenderHelper::makeTexture(QRhi *rhi, qw_texture *handle, QSGPlainTexture *texture) +bool WRenderHelper::makeTexture(QRhi *rhi, qw_texture *handle, QSGPlainTexture *texture, + bool forceVulkanShaderReadOnlyLayout) { auto updateTexture = getUpdateTextFunction(handle); if (Q_UNLIKELY(!updateTexture)) return false; - updateTexture(rhi, handle, texture); + updateTexture(rhi, handle, texture, forceVulkanShaderReadOnlyLayout); + return true; +} + +bool WRenderHelper::prepareTextureForSampling(QQuickRenderControl *rc, + qw_renderer *renderer, + qw_texture *texture, + const char *purpose) +{ +#ifdef ENABLE_VULKAN_RENDER + if (!renderer || !texture) + return true; + + if (!wlr_renderer_is_vk(renderer->handle())) + return true; + + if (!wlr_texture_is_vk(texture->handle())) { + qCWarning(lcWlQtQuickTexture) << "Vulkan texture sampling prepare failed: non-Vulkan wlroots texture" + << "purpose" << purpose + << "wlrTexture" << texture->handle(); + return false; + } + + wlr_vk_image_attribs rawAttribs = {}; + wlr_vk_texture_get_image_attribs(texture->handle(), &rawAttribs); + + if (!rc || !rc->rhi() || rc->rhi()->backend() != QRhi::Vulkan) { + qCWarning(lcWlQtQuickTexture) << "Vulkan texture sampling prepare failed: missing Vulkan QRhi" + << "purpose" << purpose + << "wlrTexture" << texture->handle() + << "image" << vkImageName(rawAttribs.image) + << "wlrootsLayout" << vkImageLayoutName(rawAttribs.layout) + << "format" << hex32(rawAttribs.format) + << "size" << wlrTextureSize(texture); + return false; + } + + auto commandBuffer = rc->commandBuffer(); + if (!commandBuffer) { + qCWarning(lcWlQtQuickTexture) << "Vulkan texture sampling prepare failed: missing QRhi command buffer" + << "purpose" << purpose + << "wlrTexture" << texture->handle() + << "image" << vkImageName(rawAttribs.image) + << "wlrootsLayout" << vkImageLayoutName(rawAttribs.layout) + << "format" << hex32(rawAttribs.format) + << "size" << wlrTextureSize(texture); + return false; + } + + commandBuffer->beginExternal(); + auto handles = static_cast(commandBuffer->nativeHandles()); + if (!handles || handles->commandBuffer == VK_NULL_HANDLE) { + commandBuffer->endExternal(); + qCWarning(lcWlQtQuickTexture) << "Vulkan texture sampling prepare failed: missing native Vulkan command buffer" + << "purpose" << purpose + << "wlrTexture" << texture->handle() + << "image" << vkImageName(rawAttribs.image) + << "wlrootsLayout" << vkImageLayoutName(rawAttribs.layout) + << "format" << hex32(rawAttribs.format) + << "size" << wlrTextureSize(texture); + return false; + } + + wlr_vk_image_attribs samplingAttribs = {}; + const bool ok = wlr_vk_renderer_prepare_texture_for_sampling(renderer->handle(), + texture->handle(), + handles->commandBuffer, + &samplingAttribs); + commandBuffer->endExternal(); + + if (!ok) { + qCWarning(lcWlQtQuickTexture) << "Vulkan texture sampling prepare failed" + << "purpose" << purpose + << "wlrTexture" << texture->handle() + << "image" << vkImageName(rawAttribs.image) + << "wlrootsLayout" << vkImageLayoutName(rawAttribs.layout) + << "format" << hex32(rawAttribs.format) + << "size" << wlrTextureSize(texture); + return false; + } + + qCDebug(lcWlQtQuickTexture) << "Vulkan texture prepared for sampling" + << "purpose" << purpose + << "wlrTexture" << texture->handle() + << "image" << vkImageName(samplingAttribs.image) + << "rawLayout" << vkImageLayoutName(rawAttribs.layout) + << "sampleLayout" << vkImageLayoutName(samplingAttribs.layout) + << "format" << hex32(samplingAttribs.format) + << "size" << wlrTextureSize(texture); +#else + Q_UNUSED(rc); + Q_UNUSED(renderer); + Q_UNUSED(texture); + Q_UNUSED(purpose); +#endif + return true; +} + +bool WRenderHelper::finishTextureSampling(QQuickRenderControl *rc, + qw_renderer *renderer, + qw_texture *texture, + const char *purpose) +{ +#ifdef ENABLE_VULKAN_RENDER + if (!renderer || !texture) + return true; + + if (!wlr_renderer_is_vk(renderer->handle())) + return true; + + if (!wlr_texture_is_vk(texture->handle())) { + qCWarning(lcWlQtQuickTexture) << "Vulkan texture sampling finish failed: non-Vulkan wlroots texture" + << "purpose" << purpose + << "wlrTexture" << texture->handle(); + return false; + } + + wlr_vk_image_attribs attribs = {}; + wlr_vk_texture_get_image_attribs(texture->handle(), &attribs); + + if (!rc || !rc->rhi() || rc->rhi()->backend() != QRhi::Vulkan) { + qCWarning(lcWlQtQuickTexture) << "Vulkan texture sampling finish failed: missing Vulkan QRhi" + << "purpose" << purpose + << "wlrTexture" << texture->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrTextureSize(texture); + return false; + } + + auto commandBuffer = rc->commandBuffer(); + if (!commandBuffer) { + qCWarning(lcWlQtQuickTexture) << "Vulkan texture sampling finish failed: missing QRhi command buffer" + << "purpose" << purpose + << "wlrTexture" << texture->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrTextureSize(texture); + return false; + } + + commandBuffer->beginExternal(); + auto handles = static_cast(commandBuffer->nativeHandles()); + if (!handles || handles->commandBuffer == VK_NULL_HANDLE) { + commandBuffer->endExternal(); + qCWarning(lcWlQtQuickTexture) << "Vulkan texture sampling finish failed: missing native Vulkan command buffer" + << "purpose" << purpose + << "wlrTexture" << texture->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrTextureSize(texture); + return false; + } + + const bool ok = wlr_vk_renderer_finish_texture_sampling(renderer->handle(), + texture->handle(), + handles->commandBuffer); + commandBuffer->endExternal(); + + if (!ok) { + qCWarning(lcWlQtQuickTexture) << "Vulkan texture sampling finish failed" + << "purpose" << purpose + << "wlrTexture" << texture->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrTextureSize(texture); + return false; + } + + qCDebug(lcWlQtQuickTexture) << "Vulkan texture sampling finished" + << "purpose" << purpose + << "wlrTexture" << texture->handle() + << "image" << vkImageName(attribs.image) + << "layout" << vkImageLayoutName(attribs.layout) + << "format" << hex32(attribs.format) + << "size" << wlrTextureSize(texture); +#else + Q_UNUSED(rc); + Q_UNUSED(renderer); + Q_UNUSED(texture); + Q_UNUSED(purpose); +#endif return true; } @@ -897,10 +1476,18 @@ WRenderHelper::newTexture(qw_allocator *allocator, qw_renderer *renderer, 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); + wlr_vk_image_attribs attribs = {}; + if (!wlr_vk_renderer_get_render_buffer_attribs(renderer->handle(), buffer, &attribs)) { + qCWarning(lcWlRenderHelper) << "Failed to create QRhiTexture from wlroots Vulkan render buffer attributes" + << "purpose" << "internal-rhi-texture" + << "wlrBuffer" << buffer + << "size" << size; + wlr_buffer_drop(buffer); + return {}; + } - if (!rhiTexture->createFrom({vkimage_cast(attribs.image), attribs.layout})) { + if (!rhiTexture->createFrom({vkimage_cast(attribs.image), + attribs.layout})) { qCCritical(lcWlRenderHelper, "Failed to create QRhiTexture from Vulkan image"); wlr_buffer_drop(buffer); return {}; diff --git a/waylib/src/server/qtquick/wrenderhelper.h b/waylib/src/server/qtquick/wrenderhelper.h index 789edb5ca..8b1ade4c6 100644 --- a/waylib/src/server/qtquick/wrenderhelper.h +++ b/waylib/src/server/qtquick/wrenderhelper.h @@ -10,11 +10,15 @@ #include #include +#include + QT_BEGIN_NAMESPACE class QQuickRenderControl; class QSGTexture; class QSGPlainTexture; class QRhi; +class QRhiRenderTarget; +class QRhiTexture; QT_END_NAMESPACE QW_BEGIN_NAMESPACE @@ -26,6 +30,7 @@ class qw_texture; QW_END_NAMESPACE struct wlr_buffer; +struct wlr_texture; WAYLIB_SERVER_BEGIN_NAMESPACE @@ -48,6 +53,9 @@ class WAYLIB_SERVER_EXPORT WRenderHelper : public QObject, public WObject static QW_NAMESPACE::qw_buffer *toBuffer(QW_NAMESPACE::qw_renderer *renderer, QSGTexture *texture, QSGRendererInterface::GraphicsApi api); QQuickRenderTarget acquireRenderTarget(QQuickRenderControl *rc, QW_NAMESPACE::qw_buffer *buffer); + QQuickRenderTarget preserveRenderTarget(QW_NAMESPACE::qw_buffer *buffer) const; + bool acquireRenderBuffer(QQuickRenderControl *rc, QW_NAMESPACE::qw_buffer *buffer, const char *purpose); + bool releaseRenderBuffer(QQuickRenderControl *rc, QW_NAMESPACE::qw_buffer *buffer, QRhiTexture *renderTargetTexture, const char *purpose); std::pair lastRenderTarget() const; static QW_NAMESPACE::qw_renderer *createRenderer(QW_NAMESPACE::qw_backend *backend); static QW_NAMESPACE::qw_renderer *createRenderer(QW_NAMESPACE::qw_backend *backend, QSGRendererInterface::GraphicsApi api); @@ -55,7 +63,16 @@ class WAYLIB_SERVER_EXPORT WRenderHelper : public QObject, public WObject 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); + static bool makeTexture(QRhi *rhi, QW_NAMESPACE::qw_texture *handle, QSGPlainTexture *texture, + bool forceVulkanShaderReadOnlyLayout = false); + static bool prepareTextureForSampling(QQuickRenderControl *rc, + QW_NAMESPACE::qw_renderer *renderer, + QW_NAMESPACE::qw_texture *texture, + const char *purpose); + static bool finishTextureSampling(QQuickRenderControl *rc, + QW_NAMESPACE::qw_renderer *renderer, + QW_NAMESPACE::qw_texture *texture, + const char *purpose); struct TextureEntry { wlr_buffer *buffer; diff --git a/waylib/src/server/qtquick/wsgtextureprovider.cpp b/waylib/src/server/qtquick/wsgtextureprovider.cpp index 362c84160..55dd9bcd7 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.cpp +++ b/waylib/src/server/qtquick/wsgtextureprovider.cpp @@ -14,8 +14,106 @@ #include #include +extern "C" { +#include +} + WAYLIB_SERVER_BEGIN_NAMESPACE +static const char *vulkanSamplingPolicyName(WSGTextureProvider::VulkanSamplingPolicy policy) +{ + switch (policy) { + case WSGTextureProvider::VulkanSamplingPolicy::Invalid: + return "invalid"; + case WSGTextureProvider::VulkanSamplingPolicy::Raw: + return "raw"; + case WSGTextureProvider::VulkanSamplingPolicy::ShmUpload: + return "shm-upload"; + } + + return "unknown"; +} + +static WSGTextureProvider::VulkanSamplingPolicy classifyVulkanSamplingPolicy(qw_buffer *buffer) +{ + if (!buffer) + return WSGTextureProvider::VulkanSamplingPolicy::Raw; + + wlr_dmabuf_attributes dmabuf = {}; + if (wlr_buffer_get_dmabuf(buffer->handle(), &dmabuf)) + return WSGTextureProvider::VulkanSamplingPolicy::Raw; + + void *data = nullptr; + uint32_t format = 0; + size_t stride = 0; + if (wlr_buffer_begin_data_ptr_access(buffer->handle(), + WLR_BUFFER_DATA_PTR_ACCESS_READ, + &data, + &format, + &stride)) { + wlr_buffer_end_data_ptr_access(buffer->handle()); + return WSGTextureProvider::VulkanSamplingPolicy::ShmUpload; + } + + return WSGTextureProvider::VulkanSamplingPolicy::Raw; +} + +class Q_DECL_HIDDEN BufferRef +{ +public: + BufferRef() = default; + ~BufferRef() { reset(); } + + BufferRef(const BufferRef &) = delete; + BufferRef &operator=(const BufferRef &) = delete; + + BufferRef(BufferRef &&other) noexcept + { + std::swap(m_buffer, other.m_buffer); + } + + BufferRef &operator=(BufferRef &&other) noexcept + { + if (this == &other) + return *this; + + reset(); + std::swap(m_buffer, other.m_buffer); + return *this; + } + + void reset(qw_buffer *buffer = nullptr) + { + if (m_buffer == buffer) + return; + + if (buffer) { + buffer->lock(); + if (auto clientBuffer = qw_client_buffer::get(*buffer)) + clientBuffer->handle()->n_ignore_locks++; + } + + release(); + m_buffer = buffer; + } + + qw_buffer *get() const { return m_buffer; } + +private: + void release() + { + if (!m_buffer) + return; + + if (auto clientBuffer = qw_client_buffer::get(*m_buffer)) + clientBuffer->handle()->n_ignore_locks--; + m_buffer->unlock(); + m_buffer = nullptr; + } + + qw_buffer *m_buffer = nullptr; +}; + class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate { public: @@ -34,42 +132,107 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate cleanTexture(); } - void cleanTexture() { - if (rhiTexture) { - Q_ASSERT(window); - class TextureCleanupJob : public QRunnable + void cleanTexture() + { + auto oldRhiTexture = rhiTexture; + auto oldTexture = texture; + const bool oldOwnsTexture = ownsTexture; + auto oldBuffer = std::move(buffer); + + rhiTexture = nullptr; + texture = nullptr; + ownsTexture = false; + vulkanSamplingPolicy = WSGTextureProvider::VulkanSamplingPolicy::Invalid; + + if (!oldRhiTexture && !oldTexture && !oldBuffer.get()) + return; + + class TextureCleanupJob : public QRunnable + { + public: + TextureCleanupJob(QRhiTexture *rhiTexture, + qw_texture *texture, + bool ownsTexture, + BufferRef buffer) + : rhiTexture(rhiTexture) + , texture(texture) + , ownsTexture(ownsTexture) + , buffer(std::move(buffer)) { - 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); - rhiTexture = nullptr; + } + + void run() override + { + if (rhiTexture) + rhiTexture->deleteLater(); + if (ownsTexture && texture) + delete texture; + } + + QRhiTexture *rhiTexture = nullptr; + qw_texture *texture = nullptr; + bool ownsTexture = false; + BufferRef buffer; + }; + + if (window) { + window->scheduleRenderJob(new TextureCleanupJob(oldRhiTexture, + oldTexture, + oldOwnsTexture, + std::move(oldBuffer)), + QQuickWindow::AfterRenderingStage); + return; } - if (ownsTexture && texture) - delete texture; - texture = nullptr; + if (oldRhiTexture) + oldRhiTexture->deleteLater(); + if (oldOwnsTexture && oldTexture) + delete oldTexture; } - void updateRhiTexture() { + bool isVulkanRhi() const + { + return window && window->rhi() && window->rhi()->backend() == QRhi::Vulkan; + } + + void updateMipmapFiltering() + { + qtTexture.setMipmapFiltering(isVulkanRhi() + ? QSGTexture::None + : (smooth ? QSGTexture::Linear : QSGTexture::Nearest)); + } + + bool updateRhiTexture() { Q_ASSERT(texture); - bool ok = WRenderHelper::makeTexture(window->rhi(), texture, &qtTexture); + const bool forceShaderReadOnlyLayout = isVulkanRhi() + && vulkanSamplingPolicy != WSGTextureProvider::VulkanSamplingPolicy::Invalid; + bool ok = WRenderHelper::makeTexture(window->rhi(), texture, &qtTexture, forceShaderReadOnlyLayout); if (Q_UNLIKELY(!ok)) { - qCWarning(lcWlQtQuickTexture) << "Failed to make texture:" << texture - << ", width height:" << texture->handle()->width - << texture->handle()->height; - return; + auto bufferHandle = buffer.get(); + qCWarning(lcWlQtQuickTexture) << "Failed to make Qt texture from wlroots texture" + << "provider" << q_func() + << "qwTexture" << texture + << "wlrTexture" << texture->handle() + << "qwBuffer" << bufferHandle + << "wlrBuffer" << (bufferHandle ? bufferHandle->handle() : nullptr) + << "policy" << vulkanSamplingPolicyName(vulkanSamplingPolicy) + << "bufferSize" << (bufferHandle ? QSize(bufferHandle->handle()->width, + bufferHandle->handle()->height) + : QSize()); + return false; } rhiTexture = qtTexture.rhiTexture(); + updateMipmapFiltering(); + qCDebug(lcWlQtQuickTexture) << "Updated Qt texture provider sampling policy" + << "provider" << q_func() + << "qwTexture" << texture + << "wlrTexture" << texture->handle() + << "qwBuffer" << buffer.get() + << "wlrBuffer" << (buffer.get() ? buffer.get()->handle() : nullptr) + << "policy" << vulkanSamplingPolicyName(vulkanSamplingPolicy) + << "forceShaderReadOnlyLayout" << forceShaderReadOnlyLayout; + return true; } W_DECLARE_PUBLIC(WSGTextureProvider) @@ -79,7 +242,8 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate // wlroots resources qw_texture *texture = nullptr; bool ownsTexture = false; - qw_buffer *buffer = nullptr; + BufferRef buffer; + WSGTextureProvider::VulkanSamplingPolicy vulkanSamplingPolicy = WSGTextureProvider::VulkanSamplingPolicy::Invalid; // qt resources QSGPlainTexture qtTexture; @@ -90,7 +254,6 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate WSGTextureProvider::WSGTextureProvider(WOutputRenderWindow *window) : WObject(*new WSGTextureProviderPrivate(this, window)) { - } WOutputRenderWindow *WSGTextureProvider::window() const @@ -111,7 +274,8 @@ void WSGTextureProvider::setBuffer(qw_buffer *buffer) W_D(WSGTextureProvider); d->cleanTexture(); - d->buffer = buffer; + d->buffer.reset(buffer); + d->vulkanSamplingPolicy = classifyVulkanSamplingPolicy(buffer); if (buffer) { Q_ASSERT(d->window); @@ -132,7 +296,8 @@ void WSGTextureProvider::setBuffer(qw_buffer *buffer) << buffer->handle()->height << ", n_locks:" << buffer->handle()->n_locks; } else { - d->updateRhiTexture(); + if (!d->updateRhiTexture()) + d->cleanTexture(); } } @@ -144,10 +309,11 @@ void WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer) W_D(WSGTextureProvider); d->cleanTexture(); d->texture = texture; - d->buffer = srcBuffer; + d->buffer.reset(srcBuffer); + d->vulkanSamplingPolicy = classifyVulkanSamplingPolicy(srcBuffer); d->ownsTexture = false; - if (texture) - d->updateRhiTexture(); + if (texture && !d->updateRhiTexture()) + d->cleanTexture(); Q_EMIT textureChanged(); } @@ -167,6 +333,12 @@ QSGTexture *WSGTextureProvider::texture() const return d->texture ? const_cast(&d->qtTexture) : nullptr; } +bool WSGTextureProvider::hasTexture() const +{ + W_DC(WSGTextureProvider); + return d->texture && d->rhiTexture; +} + qw_texture *WSGTextureProvider::qwTexture() const { W_DC(WSGTextureProvider); @@ -176,7 +348,13 @@ qw_texture *WSGTextureProvider::qwTexture() const qw_buffer *WSGTextureProvider::qwBuffer() const { W_DC(WSGTextureProvider); - return d->buffer; + return d->buffer.get(); +} + +WSGTextureProvider::VulkanSamplingPolicy WSGTextureProvider::vulkanSamplingPolicy() const +{ + W_DC(WSGTextureProvider); + return d->vulkanSamplingPolicy; } bool WSGTextureProvider::smooth() const @@ -193,8 +371,7 @@ void WSGTextureProvider::setSmooth(bool newSmooth) d->smooth = newSmooth; d->qtTexture.setFiltering(newSmooth ? QSGTexture::Linear : QSGTexture::Nearest); - d->qtTexture.setMipmapFiltering(newSmooth ? QSGTexture::Linear - : QSGTexture::Nearest); + d->updateMipmapFiltering(); Q_EMIT smoothChanged(); } diff --git a/waylib/src/server/qtquick/wsgtextureprovider.h b/waylib/src/server/qtquick/wsgtextureprovider.h index 8e6e6b3ad..bef9bd01d 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.h +++ b/waylib/src/server/qtquick/wsgtextureprovider.h @@ -23,6 +23,12 @@ class WAYLIB_SERVER_EXPORT WSGTextureProvider : public QSGTextureProvider, publi Q_PROPERTY(bool smooth READ smooth WRITE setSmooth NOTIFY smoothChanged FINAL) public: + enum class VulkanSamplingPolicy { + Invalid, + Raw, + ShmUpload, + }; + explicit WSGTextureProvider(WOutputRenderWindow *window); WOutputRenderWindow *window() const; @@ -32,8 +38,10 @@ class WAYLIB_SERVER_EXPORT WSGTextureProvider : public QSGTextureProvider, publi void invalidate(); QSGTexture *texture() const override; + bool hasTexture() const; virtual QW_NAMESPACE::qw_texture *qwTexture() const; virtual QW_NAMESPACE::qw_buffer *qwBuffer() const; + VulkanSamplingPolicy vulkanSamplingPolicy() const; bool smooth() const; void setSmooth(bool newSmooth); diff --git a/waylib/src/server/wayliblogging.cpp b/waylib/src/server/wayliblogging.cpp index 383778255..949244015 100644 --- a/waylib/src/server/wayliblogging.cpp +++ b/waylib/src/server/wayliblogging.cpp @@ -50,6 +50,7 @@ Q_LOGGING_CATEGORY(lcWlForeignToplevel, "waylib.protocols.foreigntoplevel", QtWa Q_LOGGING_CATEGORY(lcWlInputMethod, "waylib.protocols.inputmethod", QtInfoMsg) // Input method v2 and virtual keyboard Q_LOGGING_CATEGORY(lcWlExtForeignToplevel, "waylib.protocols.extforeigntoplevel", QtWarningMsg) // ext-foreign-toplevel-list-v1 Q_LOGGING_CATEGORY(lcWlTextInput, "waylib.protocols.textinput", QtInfoMsg) // Text input v1/v2/v3 protocol +Q_LOGGING_CATEGORY(lcWlLinuxDmabuf, "waylib.protocols.linuxdmabuf", QtInfoMsg) // Renderer-backed linux-dmabuf global // Layer shell Q_LOGGING_CATEGORY(lcWlLayerShell, "waylib.layer.shell", QtWarningMsg) // wlr-layer-shell surface requests diff --git a/waylib/src/server/wayliblogging.h b/waylib/src/server/wayliblogging.h index 38763942a..5f37131d2 100644 --- a/waylib/src/server/wayliblogging.h +++ b/waylib/src/server/wayliblogging.h @@ -45,6 +45,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcWlForeignToplevel) Q_DECLARE_LOGGING_CATEGORY(lcWlInputMethod) Q_DECLARE_LOGGING_CATEGORY(lcWlExtForeignToplevel) Q_DECLARE_LOGGING_CATEGORY(lcWlTextInput) +Q_DECLARE_LOGGING_CATEGORY(lcWlLinuxDmabuf) // Layer shell Q_DECLARE_LOGGING_CATEGORY(lcWlLayerShell) From 5f3ad170c86dd6bc6294ea7fddb0488336e8161c Mon Sep 17 00:00:00 2001 From: LFRon Date: Wed, 8 Jul 2026 12:45:52 +0800 Subject: [PATCH 2/5] feat(waylib): simplify Vulkan texture sampling plumbing Remove the branch-local VulkanSamplingPolicy API and the Raw/ShmUpload classification in WSGTextureProvider. All valid wlroots Vulkan textures are now declared to Qt as shader-read sampled textures, while per-render-pass sampling still uses wlroots' Vulkan helper to prepare and finish the active texture set. Keep the established Vulkan correctness boundaries intact: client buffers remain wlroots-provided QSGTextures, output rendering still uses wlroots render-buffer acquire/release, and primary output renderers are not exposed as texture providers on Vulkan. Also remove high-frequency success debug logs from the Vulkan sampling/cache paths and keep the Vulkan RenderBufferBlitter fallback as an explicit backdrop source policy. --- .../qtquick/private/wbufferrenderer.cpp | 82 +------------------ .../qtquick/private/wbufferrenderer_p.h | 1 - .../server/qtquick/woutputrenderwindow.cpp | 63 +------------- .../src/server/qtquick/woutputrenderwindow.h | 2 - .../server/qtquick/wrenderbufferblitter.cpp | 2 +- waylib/src/server/qtquick/wrenderhelper.cpp | 18 +--- .../src/server/qtquick/wsgtextureprovider.cpp | 60 +------------- .../src/server/qtquick/wsgtextureprovider.h | 7 -- 8 files changed, 10 insertions(+), 225 deletions(-) diff --git a/waylib/src/server/qtquick/private/wbufferrenderer.cpp b/waylib/src/server/qtquick/private/wbufferrenderer.cpp index 7c9e6eef1..6c26c2bbb 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer.cpp +++ b/waylib/src/server/qtquick/private/wbufferrenderer.cpp @@ -329,11 +329,6 @@ bool WBufferRenderer::isPrimaryOutputRendererForVulkan() const return vd && vd->attached && vd->bufferRenderer == this; } -bool WBufferRenderer::shouldExposeVulkanCacheProvider() const -{ - return !isPrimaryOutputRendererForVulkan(); -} - QTransform WBufferRenderer::inputMapToOutput(const QRectF &sourceRect, const QRectF &targetRect, const QSize &pixelSize, const qreal devicePixelRatio) { @@ -484,9 +479,7 @@ static QSGRenderTarget toRhiSgRenderTarget(QQuickWindowPrivate *wd, const QQuick return sgRT; } -static QVector activeTextureProvidersForPass(QQuickItem *root, - qw_buffer *currentBuffer, - int sourceIndex) +static QVector activeTextureProvidersForPass(QQuickItem *root) { QVector providers; if (!root) @@ -511,26 +504,6 @@ static QVector activeTextureProvidersForPass(QQuickItem *r seen.insert(provider); providers.append(provider); - - if (lcWlQtQuickTexture().isDebugEnabled()) { - auto providerBuffer = provider->qwBuffer(); - auto providerTexture = provider->qwTexture(); - const auto itemType = item->metaObject() ? item->metaObject()->className() : ""; - qCDebug(lcWlQtQuickTexture) << "Collected active Vulkan texture provider for render pass" - << "sourceIndex" << sourceIndex - << "sourceItem" << item - << "sourceItemType" << itemType - << "sourceObjectName" << item->objectName() - << "provider" << provider - << "providerBuffer" << providerBuffer - << "providerWlrBuffer" << (providerBuffer ? providerBuffer->handle() : nullptr) - << "providerTexture" << providerTexture - << "wlrTexture" << (providerTexture ? providerTexture->handle() : nullptr) - << "currentBuffer" << currentBuffer - << "currentWlrBuffer" << (currentBuffer ? currentBuffer->handle() : nullptr) - << "policy" << int(provider->vulkanSamplingPolicy()) - << "textureSize" << (provider->texture() ? provider->texture()->textureSize() : QSize()); - } } return providers; @@ -689,23 +662,17 @@ bool WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, } } - qsizetype uploadFlushCount = 0; - qsizetype preparedTextureCount = 0; QVector preparedTextures; auto outputWindow = renderWindow(); const auto activeTextureProviders = activeTextureProvidersForPass(isRootItem(source.source) ? wd->contentItem - : source.source, - state.buffer.get(), - sourceIndex); + : source.source); constexpr const char *samplingPurpose = "qt-render-pass-texture"; if (outputWindow && !outputWindow->prepareTextureSamplingForRenderPass(state.buffer.get(), activeTextureProviders, samplingPurpose, sourceIndex, - &uploadFlushCount, - &preparedTextureCount, &preparedTextures)) { qCWarning(lcWlBufferRenderer) << "Skipping render pass because Vulkan texture sampling prepare failed" << "renderer" << this @@ -729,12 +696,7 @@ bool WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, // sourceIndex, we should let the RHI (Rendering Hardware Interface) // complete the results of this drawing here to ensure the current // drawing result is available for use. - if (isVulkanRhi) { - qCDebug(lcWlBufferRenderer) << "Deferring Vulkan QRhi finish to render-control endFrame" - << "renderer" << this - << "sourceIndex" << sourceIndex - << "buffer" << state.buffer.get(); - } else { + if (!isVulkanRhi) { wd->rhi->finish(); } } else { @@ -771,16 +733,6 @@ bool WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, } } - if (isVulkanRhi) { - qCDebug(lcWlBufferRenderer) << "Keeping Vulkan sampled textures in shader-read layout for render pass" - << "renderer" << this - << "sourceIndex" << sourceIndex - << "currentBuffer" << state.buffer.get() - << "wlrBuffer" << (state.buffer ? state.buffer->handle() : nullptr) - << "uploadFlushCount" << uploadFlushCount - << "preparedTextureCount" << preparedTextureCount; - } - if (auto dr = qobject_cast(state.context)) { QRhiResourceUpdateBatch *resourceUpdates = wd->rhi->nextResourceUpdateBatch(); dr->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates); @@ -788,19 +740,6 @@ bool WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, if (shouldCacheBuffer() && !isVulkanRhi) { wTextureProvider()->setBuffer(state.buffer.get()); - } else if (shouldCacheBuffer() && !shouldExposeVulkanCacheProvider()) { - qCDebug(lcWlBufferRenderer) << "Skipping Vulkan primary output cache provider update during render" - << "renderer" << this - << "sourceIndex" << sourceIndex - << "currentBuffer" << state.buffer.get() - << "wlrBuffer" << (state.buffer ? state.buffer->handle() : nullptr); - } else if (shouldCacheBuffer()) { - qCDebug(lcWlBufferRenderer) << "Deferring Vulkan cache provider update until render buffer release" - << "renderer" << this - << "sourceIndex" << sourceIndex - << "currentBuffer" << state.buffer.get() - << "wlrBuffer" << (state.buffer ? state.buffer->handle() : nullptr) - << "releasedForCache" << state.renderBufferReleasedForCache; } return true; @@ -823,25 +762,12 @@ void WBufferRenderer::endRender() m_lastBuffer = buffer.get(); if (shouldCacheBuffer() && isVulkanRhi) { - if (!shouldExposeVulkanCacheProvider()) { + if (isPrimaryOutputRendererForVulkan()) { if (m_textureProvider) m_textureProvider->setBuffer(nullptr); - qCDebug(lcWlBufferRenderer) << "Skipped Vulkan primary output cache provider after render buffer release" - << "renderer" << this - << "buffer" << buffer.get() - << "wlrBuffer" << buffer->handle(); } else if (canExposeCompletedCache) { if (auto provider = wTextureProvider()) provider->setBuffer(buffer.get()); - qCDebug(lcWlBufferRenderer) << "Updated Vulkan cache provider after render buffer release" - << "renderer" << this - << "buffer" << buffer.get() - << "wlrBuffer" << buffer->handle(); - } else { - qCDebug(lcWlBufferRenderer) << "Skipping Vulkan cache provider update because render buffer is not cacheable" - << "renderer" << this - << "buffer" << buffer.get() - << "wlrBuffer" << buffer->handle(); } } } diff --git a/waylib/src/server/qtquick/private/wbufferrenderer_p.h b/waylib/src/server/qtquick/private/wbufferrenderer_p.h index 35a349dd5..042736c55 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer_p.h +++ b/waylib/src/server/qtquick/private/wbufferrenderer_p.h @@ -118,7 +118,6 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem void releaseResources() override; void cleanTextureProvider(); bool isPrimaryOutputRendererForVulkan() const; - bool shouldExposeVulkanCacheProvider() const; inline bool isRootItem(const QQuickItem *source) const { return nullptr == source; diff --git a/waylib/src/server/qtquick/woutputrenderwindow.cpp b/waylib/src/server/qtquick/woutputrenderwindow.cpp index 3c502565c..b1cb99a56 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.cpp +++ b/waylib/src/server/qtquick/woutputrenderwindow.cpp @@ -450,8 +450,6 @@ class Q_DECL_HIDDEN WOutputRenderWindowPrivate : public QQuickWindowPrivate const QVector &activeProviders, const char *purpose, int sourceIndex, - qsizetype *uploadFlushCount, - qsizetype *preparedTextureCount, QVector *preparedTextures); bool finishTextureSamplingForRenderPass(const QVector &preparedTextures, const char *purpose, @@ -1682,15 +1680,9 @@ bool WOutputRenderWindowPrivate::prepareTextureSamplingForRenderPass(qw_buffer * const QVector &activeProviders, const char *purpose, int sourceIndex, - qsizetype *uploadFlushCount, - qsizetype *preparedTextureCount, QVector *preparedTextures) { - Q_ASSERT(uploadFlushCount); - Q_ASSERT(preparedTextureCount); Q_ASSERT(preparedTextures); - *uploadFlushCount = 0; - *preparedTextureCount = 0; preparedTextures->clear(); if (WRenderHelper::getGraphicsApi(rc()) != QSGRendererInterface::Vulkan) @@ -1699,8 +1691,6 @@ bool WOutputRenderWindowPrivate::prepareTextureSamplingForRenderPass(qw_buffer * QSet seenTextures; qsizetype skippedCurrentRenderTarget = 0; qsizetype invalidProviderCount = 0; - qsizetype rawTextureCount = 0; - qsizetype shmUploadProviderCount = 0; for (auto provider : activeProviders) { if (!provider) { ++invalidProviderCount; @@ -1719,16 +1709,6 @@ bool WOutputRenderWindowPrivate::prepareTextureSamplingForRenderPass(qw_buffer * auto renderTargetBuffer = WRenderHelper::lookupBuffer(rhiTexture); if (renderTargetBuffer == currentBuffer) { ++skippedCurrentRenderTarget; - qCDebug(lcWlQtQuickTexture) << "Skipping current Vulkan render target texture during sampling prepare" - << "purpose" << purpose - << "sourceIndex" << sourceIndex - << "currentBuffer" << currentBuffer - << "currentWlrBuffer" << currentBuffer->handle() - << "provider" << provider - << "providerBuffer" << providerBuffer - << "providerWlrBuffer" << (providerBuffer ? providerBuffer->handle() : nullptr) - << "rhiTexture" << rhiTexture - << "textureSize" << sampleTexture->textureSize(); continue; } } @@ -1740,17 +1720,6 @@ bool WOutputRenderWindowPrivate::prepareTextureSamplingForRenderPass(qw_buffer * continue; } - const auto samplingPolicy = provider->vulkanSamplingPolicy(); - if (samplingPolicy == WSGTextureProvider::VulkanSamplingPolicy::Invalid) { - ++invalidProviderCount; - continue; - } - - if (samplingPolicy != WSGTextureProvider::VulkanSamplingPolicy::ShmUpload) - ++rawTextureCount; - else - ++shmUploadProviderCount; - if (seenTextures.contains(texture)) continue; @@ -1766,12 +1735,9 @@ bool WOutputRenderWindowPrivate::prepareTextureSamplingForRenderPass(qw_buffer * << "providerWlrBuffer" << (providerBuffer ? providerBuffer->handle() : nullptr) << "qwTexture" << texture << "wlrTexture" << texture->handle() - << "policy" << int(samplingPolicy) << "qtTextureSize" << (sampleTexture ? sampleTexture->textureSize() : QSize()) << "activeProviderCount" << activeProviders.size() - << "uploadFlushCount" << *uploadFlushCount - << "preparedTextureCount" << *preparedTextureCount - << "rawTextureCount" << rawTextureCount + << "preparedTextureCount" << preparedTextures->size() << "skippedCurrentRenderTarget" << skippedCurrentRenderTarget << "invalidProviderCount" << invalidProviderCount; provider->setTexture(nullptr, nullptr); @@ -1779,23 +1745,8 @@ bool WOutputRenderWindowPrivate::prepareTextureSamplingForRenderPass(qw_buffer * } preparedTextures->append(texture); - ++(*preparedTextureCount); - if (samplingPolicy == WSGTextureProvider::VulkanSamplingPolicy::ShmUpload) - ++(*uploadFlushCount); - } - - qCDebug(lcWlQtQuickTexture) << "Prepared Vulkan texture sampling for render pass" - << "purpose" << purpose - << "sourceIndex" << sourceIndex - << "currentBuffer" << currentBuffer - << "currentWlrBuffer" << (currentBuffer ? currentBuffer->handle() : nullptr) - << "activeProviderCount" << activeProviders.size() - << "uploadFlushCount" << *uploadFlushCount - << "preparedTextureCount" << *preparedTextureCount - << "rawTextureCount" << rawTextureCount - << "shmUploadProviderCount" << shmUploadProviderCount - << "skippedCurrentRenderTarget" << skippedCurrentRenderTarget - << "invalidProviderCount" << invalidProviderCount; + } + return true; } @@ -1823,10 +1774,6 @@ bool WOutputRenderWindowPrivate::finishTextureSamplingForRenderPass(const QVecto } } - qCDebug(lcWlQtQuickTexture) << "Finished Vulkan texture sampling for render pass" - << "purpose" << purpose - << "sourceIndex" << sourceIndex - << "preparedTextureCount" << preparedTextures.size(); return true; } @@ -2221,8 +2168,6 @@ bool WOutputRenderWindow::prepareTextureSamplingForRenderPass(qw_buffer *current const QVector &activeProviders, const char *purpose, int sourceIndex, - qsizetype *uploadFlushCount, - qsizetype *preparedTextureCount, QVector *preparedTextures) { Q_D(WOutputRenderWindow); @@ -2230,8 +2175,6 @@ bool WOutputRenderWindow::prepareTextureSamplingForRenderPass(qw_buffer *current activeProviders, purpose, sourceIndex, - uploadFlushCount, - preparedTextureCount, preparedTextures); } diff --git a/waylib/src/server/qtquick/woutputrenderwindow.h b/waylib/src/server/qtquick/woutputrenderwindow.h index edaf1d42f..d7db14a3e 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.h +++ b/waylib/src/server/qtquick/woutputrenderwindow.h @@ -66,8 +66,6 @@ class WAYLIB_SERVER_EXPORT WOutputRenderWindow : public QQuickWindow, public QQm const QVector &activeProviders, const char *purpose, int sourceIndex, - qsizetype *uploadFlushCount, - qsizetype *preparedTextureCount, QVector *preparedTextures); bool finishTextureSamplingForRenderPass(const QVector &preparedTextures, const char *purpose, diff --git a/waylib/src/server/qtquick/wrenderbufferblitter.cpp b/waylib/src/server/qtquick/wrenderbufferblitter.cpp index 6c884ed34..875aea0c1 100644 --- a/waylib/src/server/qtquick/wrenderbufferblitter.cpp +++ b/waylib/src/server/qtquick/wrenderbufferblitter.cpp @@ -274,7 +274,7 @@ QSGNode *WRenderBufferBlitter::updatePaintNode(QSGNode *oldNode, [[maybe_unused] qCInfo(lcWlRenderBuffer) << "Disabled RenderBufferBlitter on Vulkan" << "window" << window() << "itemSize" << size() - << "reason" << "wlroots render buffer lacks TRANSFER_SRC usage"; + << "reason" << "Vulkan backdrop source unavailable"; d->vulkanDisabledLogged = true; } diff --git a/waylib/src/server/qtquick/wrenderhelper.cpp b/waylib/src/server/qtquick/wrenderhelper.cpp index 04922bfaf..7f834f16d 100644 --- a/waylib/src/server/qtquick/wrenderhelper.cpp +++ b/waylib/src/server/qtquick/wrenderhelper.cpp @@ -1299,11 +1299,10 @@ bool WRenderHelper::prepareTextureForSampling(QQuickRenderControl *rc, return false; } - wlr_vk_image_attribs samplingAttribs = {}; const bool ok = wlr_vk_renderer_prepare_texture_for_sampling(renderer->handle(), texture->handle(), handles->commandBuffer, - &samplingAttribs); + nullptr); commandBuffer->endExternal(); if (!ok) { @@ -1317,14 +1316,6 @@ bool WRenderHelper::prepareTextureForSampling(QQuickRenderControl *rc, return false; } - qCDebug(lcWlQtQuickTexture) << "Vulkan texture prepared for sampling" - << "purpose" << purpose - << "wlrTexture" << texture->handle() - << "image" << vkImageName(samplingAttribs.image) - << "rawLayout" << vkImageLayoutName(rawAttribs.layout) - << "sampleLayout" << vkImageLayoutName(samplingAttribs.layout) - << "format" << hex32(samplingAttribs.format) - << "size" << wlrTextureSize(texture); #else Q_UNUSED(rc); Q_UNUSED(renderer); @@ -1409,13 +1400,6 @@ bool WRenderHelper::finishTextureSampling(QQuickRenderControl *rc, return false; } - qCDebug(lcWlQtQuickTexture) << "Vulkan texture sampling finished" - << "purpose" << purpose - << "wlrTexture" << texture->handle() - << "image" << vkImageName(attribs.image) - << "layout" << vkImageLayoutName(attribs.layout) - << "format" << hex32(attribs.format) - << "size" << wlrTextureSize(texture); #else Q_UNUSED(rc); Q_UNUSED(renderer); diff --git a/waylib/src/server/qtquick/wsgtextureprovider.cpp b/waylib/src/server/qtquick/wsgtextureprovider.cpp index 55dd9bcd7..d428bb2ca 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.cpp +++ b/waylib/src/server/qtquick/wsgtextureprovider.cpp @@ -20,44 +20,6 @@ extern "C" { WAYLIB_SERVER_BEGIN_NAMESPACE -static const char *vulkanSamplingPolicyName(WSGTextureProvider::VulkanSamplingPolicy policy) -{ - switch (policy) { - case WSGTextureProvider::VulkanSamplingPolicy::Invalid: - return "invalid"; - case WSGTextureProvider::VulkanSamplingPolicy::Raw: - return "raw"; - case WSGTextureProvider::VulkanSamplingPolicy::ShmUpload: - return "shm-upload"; - } - - return "unknown"; -} - -static WSGTextureProvider::VulkanSamplingPolicy classifyVulkanSamplingPolicy(qw_buffer *buffer) -{ - if (!buffer) - return WSGTextureProvider::VulkanSamplingPolicy::Raw; - - wlr_dmabuf_attributes dmabuf = {}; - if (wlr_buffer_get_dmabuf(buffer->handle(), &dmabuf)) - return WSGTextureProvider::VulkanSamplingPolicy::Raw; - - void *data = nullptr; - uint32_t format = 0; - size_t stride = 0; - if (wlr_buffer_begin_data_ptr_access(buffer->handle(), - WLR_BUFFER_DATA_PTR_ACCESS_READ, - &data, - &format, - &stride)) { - wlr_buffer_end_data_ptr_access(buffer->handle()); - return WSGTextureProvider::VulkanSamplingPolicy::ShmUpload; - } - - return WSGTextureProvider::VulkanSamplingPolicy::Raw; -} - class Q_DECL_HIDDEN BufferRef { public: @@ -142,7 +104,6 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate rhiTexture = nullptr; texture = nullptr; ownsTexture = false; - vulkanSamplingPolicy = WSGTextureProvider::VulkanSamplingPolicy::Invalid; if (!oldRhiTexture && !oldTexture && !oldBuffer.get()) return; @@ -204,8 +165,7 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate bool updateRhiTexture() { Q_ASSERT(texture); - const bool forceShaderReadOnlyLayout = isVulkanRhi() - && vulkanSamplingPolicy != WSGTextureProvider::VulkanSamplingPolicy::Invalid; + const bool forceShaderReadOnlyLayout = isVulkanRhi(); bool ok = WRenderHelper::makeTexture(window->rhi(), texture, &qtTexture, forceShaderReadOnlyLayout); if (Q_UNLIKELY(!ok)) { auto bufferHandle = buffer.get(); @@ -215,7 +175,6 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate << "wlrTexture" << texture->handle() << "qwBuffer" << bufferHandle << "wlrBuffer" << (bufferHandle ? bufferHandle->handle() : nullptr) - << "policy" << vulkanSamplingPolicyName(vulkanSamplingPolicy) << "bufferSize" << (bufferHandle ? QSize(bufferHandle->handle()->width, bufferHandle->handle()->height) : QSize()); @@ -224,14 +183,6 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate rhiTexture = qtTexture.rhiTexture(); updateMipmapFiltering(); - qCDebug(lcWlQtQuickTexture) << "Updated Qt texture provider sampling policy" - << "provider" << q_func() - << "qwTexture" << texture - << "wlrTexture" << texture->handle() - << "qwBuffer" << buffer.get() - << "wlrBuffer" << (buffer.get() ? buffer.get()->handle() : nullptr) - << "policy" << vulkanSamplingPolicyName(vulkanSamplingPolicy) - << "forceShaderReadOnlyLayout" << forceShaderReadOnlyLayout; return true; } @@ -243,7 +194,6 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate qw_texture *texture = nullptr; bool ownsTexture = false; BufferRef buffer; - WSGTextureProvider::VulkanSamplingPolicy vulkanSamplingPolicy = WSGTextureProvider::VulkanSamplingPolicy::Invalid; // qt resources QSGPlainTexture qtTexture; @@ -275,7 +225,6 @@ void WSGTextureProvider::setBuffer(qw_buffer *buffer) W_D(WSGTextureProvider); d->cleanTexture(); d->buffer.reset(buffer); - d->vulkanSamplingPolicy = classifyVulkanSamplingPolicy(buffer); if (buffer) { Q_ASSERT(d->window); @@ -310,7 +259,6 @@ void WSGTextureProvider::setTexture(qw_texture *texture, qw_buffer *srcBuffer) d->cleanTexture(); d->texture = texture; d->buffer.reset(srcBuffer); - d->vulkanSamplingPolicy = classifyVulkanSamplingPolicy(srcBuffer); d->ownsTexture = false; if (texture && !d->updateRhiTexture()) d->cleanTexture(); @@ -351,12 +299,6 @@ qw_buffer *WSGTextureProvider::qwBuffer() const return d->buffer.get(); } -WSGTextureProvider::VulkanSamplingPolicy WSGTextureProvider::vulkanSamplingPolicy() const -{ - W_DC(WSGTextureProvider); - return d->vulkanSamplingPolicy; -} - bool WSGTextureProvider::smooth() const { W_DC(WSGTextureProvider); diff --git a/waylib/src/server/qtquick/wsgtextureprovider.h b/waylib/src/server/qtquick/wsgtextureprovider.h index bef9bd01d..ea7d69c37 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.h +++ b/waylib/src/server/qtquick/wsgtextureprovider.h @@ -23,12 +23,6 @@ class WAYLIB_SERVER_EXPORT WSGTextureProvider : public QSGTextureProvider, publi Q_PROPERTY(bool smooth READ smooth WRITE setSmooth NOTIFY smoothChanged FINAL) public: - enum class VulkanSamplingPolicy { - Invalid, - Raw, - ShmUpload, - }; - explicit WSGTextureProvider(WOutputRenderWindow *window); WOutputRenderWindow *window() const; @@ -41,7 +35,6 @@ class WAYLIB_SERVER_EXPORT WSGTextureProvider : public QSGTextureProvider, publi bool hasTexture() const; virtual QW_NAMESPACE::qw_texture *qwTexture() const; virtual QW_NAMESPACE::qw_buffer *qwBuffer() const; - VulkanSamplingPolicy vulkanSamplingPolicy() const; bool smooth() const; void setSmooth(bool newSmooth); From 151e2339915d8a7f2b3da17cf62caa47c44f2458 Mon Sep 17 00:00:00 2001 From: LFRon Date: Wed, 8 Jul 2026 18:43:10 +0800 Subject: [PATCH 3/5] feat(vulkan): add presentation feedback Add a waylib WPresentation wrapper around wlroots wp_presentation and attach it for the Vulkan backend. Track surfaces actually sampled by the Qt Quick render pass, then submit textured-on-output feedback before committing the matching wlroots output buffer. Fix the qwlroots wlroots-0.19 presentation create signature, keep the presentation global lifetime owned by wlroots, and add the centralized waylib.protocols.presentation logging category. --- qwlroots/src/types/qwpresentation.h | 8 +- src/seat/helper.cpp | 6 ++ waylib/src/server/CMakeLists.txt | 3 + waylib/src/server/protocols/WPresentation | 1 + waylib/src/server/protocols/wpresentation.cpp | 83 ++++++++++++++++ waylib/src/server/protocols/wpresentation.h | 40 ++++++++ .../server/qtquick/woutputrenderwindow.cpp | 99 ++++++++++++++++++- .../src/server/qtquick/woutputrenderwindow.h | 4 + waylib/src/server/qtquick/wsurfaceitem.cpp | 8 +- waylib/src/server/wayliblogging.cpp | 1 + waylib/src/server/wayliblogging.h | 1 + 11 files changed, 246 insertions(+), 8 deletions(-) create mode 100644 waylib/src/server/protocols/WPresentation create mode 100644 waylib/src/server/protocols/wpresentation.cpp create mode 100644 waylib/src/server/protocols/wpresentation.h diff --git a/qwlroots/src/types/qwpresentation.h b/qwlroots/src/types/qwpresentation.h index 0a3d0eea4..b20c530a1 100644 --- a/qwlroots/src/types/qwpresentation.h +++ b/qwlroots/src/types/qwpresentation.h @@ -17,11 +17,11 @@ class QW_CLASS_OBJECT(presentation) Q_OBJECT public: - QW_FUNC_STATIC(presentation, create, qw_presentation *, wl_display *display, wlr_backend *backend) + QW_FUNC_STATIC(presentation, create, qw_presentation *, wl_display *display, wlr_backend *backend, uint32_t version) - QW_FUNC_MEMBER(presentation, surface_sampled, wlr_presentation_feedback *, wlr_surface *surface) - QW_FUNC_MEMBER(presentation, surface_textured_on_output, void, wlr_surface *surface, wlr_output *output) - QW_FUNC_MEMBER(presentation, surface_scanned_out_on_output, void, wlr_surface *surface, wlr_output *output) + QW_FUNC_STATIC(presentation, surface_sampled, wlr_presentation_feedback *, wlr_surface *surface) + QW_FUNC_STATIC(presentation, surface_textured_on_output, void, wlr_surface *surface, wlr_output *output) + QW_FUNC_STATIC(presentation, surface_scanned_out_on_output, void, wlr_surface *surface, wlr_output *output) }; class QW_CLASS_REINTERPRET_CAST(presentation_event) diff --git a/src/seat/helper.cpp b/src/seat/helper.cpp index 9a6f1a430..9816ff260 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -69,6 +69,7 @@ #include #include #include +#include #include #include #include @@ -1628,6 +1629,11 @@ void Helper::init(Treeland::Treeland *treeland) qCInfo(lcTlCore) << "Initialized wl_shm for renderer"; m_server->attach(m_renderer); + if (WRenderHelper::getGraphicsApi() == QSGRendererInterface::Vulkan) { + auto *presentation = m_server->attach(m_backend->handle()); + m_renderWindow->setPresentation(presentation); + qCInfo(lcTlCore) << "Attached presentation-time protocol for Vulkan renderer"; + } qw_drm::create(*m_server->handle(), *m_renderer); diff --git a/waylib/src/server/CMakeLists.txt b/waylib/src/server/CMakeLists.txt index 2533d5b14..aac271c64 100644 --- a/waylib/src/server/CMakeLists.txt +++ b/waylib/src/server/CMakeLists.txt @@ -203,6 +203,7 @@ set(SOURCES protocols/wcursorshapemanagerv1.cpp protocols/woutputmanagerv1.cpp protocols/wlinuxdmabufv1.cpp + protocols/wpresentation.cpp protocols/wextforeigntoplevellistv1.cpp protocols/wxdgdialogmanagerv1.cpp protocols/wsecuritycontextmanager.cpp @@ -302,6 +303,8 @@ set(HEADERS protocols/WOutputManagerV1 protocols/wlinuxdmabufv1.h protocols/WLinuxDmabufV1 + protocols/wpresentation.h + protocols/WPresentation protocols/wlayershell.h protocols/WLayerShell protocols/wxwayland.h diff --git a/waylib/src/server/protocols/WPresentation b/waylib/src/server/protocols/WPresentation new file mode 100644 index 000000000..cfb6717e8 --- /dev/null +++ b/waylib/src/server/protocols/WPresentation @@ -0,0 +1 @@ +#include "wpresentation.h" diff --git a/waylib/src/server/protocols/wpresentation.cpp b/waylib/src/server/protocols/wpresentation.cpp new file mode 100644 index 000000000..d055845f2 --- /dev/null +++ b/waylib/src/server/protocols/wpresentation.cpp @@ -0,0 +1,83 @@ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "wpresentation.h" + +#include "wayliblogging.h" +#include "woutput.h" +#include "wsurface.h" + +#include +#include +#include +#include + +QW_USE_NAMESPACE +WAYLIB_SERVER_BEGIN_NAMESPACE + +constexpr uint32_t PresentationVersion = 2; + +WPresentation::WPresentation(qw_backend *backend, QObject *parent) + : QObject(parent) + , m_backend(backend) +{ +} + +qw_presentation *WPresentation::handle() const +{ + return nativeInterface(); +} + +QByteArrayView WPresentation::interfaceName() const +{ + return "wp_presentation"; +} + +void WPresentation::surfaceTexturedOnOutput(WSurface *surface, WOutput *output) +{ + auto *native = handle(); + if (!native || !surface || !surface->handle() || !output || !output->nativeHandle()) + return; + + qw_presentation::surface_textured_on_output(surface->handle()->handle(), output->nativeHandle()); + qCDebug(lcWlPresentation) << "Marked surface textured on output" + << "surface" << surface + << "wlrSurface" << surface->handle()->handle() + << "output" << output + << "wlrOutput" << output->nativeHandle(); +} + +void WPresentation::create(WServer *server) +{ + if (m_handle) + return; + + if (!m_backend || !m_backend->handle()) { + qCWarning(lcWlPresentation) << "Cannot create presentation-time global without backend"; + return; + } + + m_handle = qw_presentation::create(*server->handle(), *m_backend, PresentationVersion); + if (!m_handle) { + qCWarning(lcWlPresentation) << "Failed to create presentation-time global"; + return; + } + + qCInfo(lcWlPresentation) << "Created presentation-time global" + << "version" << PresentationVersion; +} + +void WPresentation::destroy([[maybe_unused]] WServer *server) +{ + // wlroots owns this global and destroys it from the display destroy listener. +} + +wl_global *WPresentation::global() const +{ + if (auto *native = handle()) + return native->handle()->global; + + return nullptr; +} + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/protocols/wpresentation.h b/waylib/src/server/protocols/wpresentation.h new file mode 100644 index 000000000..596d42b40 --- /dev/null +++ b/waylib/src/server/protocols/wpresentation.h @@ -0,0 +1,40 @@ +// 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 + +QW_BEGIN_NAMESPACE +class qw_backend; +class qw_presentation; +QW_END_NAMESPACE + +WAYLIB_SERVER_BEGIN_NAMESPACE + +class WOutput; +class WSurface; + +class WAYLIB_SERVER_EXPORT WPresentation : public QObject, public WServerInterface +{ + Q_OBJECT + +public: + explicit WPresentation(QW_NAMESPACE::qw_backend *backend, QObject *parent = nullptr); + + QW_NAMESPACE::qw_presentation *handle() const; + + QByteArrayView interfaceName() const override; + + void surfaceTexturedOnOutput(WSurface *surface, WOutput *output); + +protected: + void create(WServer *server) override; + void destroy(WServer *server) override; + wl_global *global() const override; + +private: + QW_NAMESPACE::qw_backend *m_backend = nullptr; +}; + +WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/qtquick/woutputrenderwindow.cpp b/waylib/src/server/qtquick/woutputrenderwindow.cpp index b1cb99a56..1d9fe71a7 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.cpp +++ b/waylib/src/server/qtquick/woutputrenderwindow.cpp @@ -10,9 +10,11 @@ #include "woutputviewport_p.h" #include "wqmlhelper_p.h" #include "woutputlayer.h" +#include "wpresentation.h" #include "wbufferrenderer_p.h" #include "wquicktextureproxy.h" #include "wsgtextureprovider.h" +#include "wsurface.h" #include "weventjunkman.h" #include "winputdevice.h" #include "wseat.h" @@ -454,6 +456,11 @@ class Q_DECL_HIDDEN WOutputRenderWindowPrivate : public QQuickWindowPrivate bool finishTextureSamplingForRenderPass(const QVector &preparedTextures, const char *purpose, int sourceIndex); + void setCurrentPresentationOutput(WOutput *output); + void markSurfaceTexturedForPresentation(WSurface *surface); + void submitPresentationFeedbackForOutput(WOutput *output); + void clearPresentationFeedbackForOutput(WOutput *output); + void clearPresentationFeedback(); void doRender(qw_output *needsFrameOutput, const QList &outputs, bool forceRender, bool doCommit); @@ -481,6 +488,9 @@ class Q_DECL_HIDDEN WOutputRenderWindowPrivate : public QQuickWindowPrivate QPointer m_renderer; QPointer m_allocator; + QPointer m_presentation; + QPointer m_currentPresentationOutput; + QHash>> m_pendingPresentationSurfaces; QList outputs; QList layers; @@ -611,14 +621,19 @@ qw_buffer *OutputHelper::beginRender(WBufferRenderer *renderer, bool OutputHelper::render(WBufferRenderer *renderer, int sourceIndex, const QMatrix4x4 &renderMatrix, const QRectF &sourceRect, const QRectF &targetRect, bool preserveColorContents) { - renderWindowD()->pushRenderer(renderer); + auto *windowPrivate = renderWindowD(); + auto *presentationOutput = m_output ? m_output->output() : nullptr; + windowPrivate->setCurrentPresentationOutput(presentationOutput); + windowPrivate->pushRenderer(renderer); const bool ok = renderer->render(sourceIndex, renderMatrix, sourceRect, targetRect, preserveColorContents); + windowPrivate->setCurrentPresentationOutput(nullptr); if (!ok) { qCWarning(lcWlBufferRenderer) << "WBufferRenderer render pass failed" << "renderer" << renderer << "sourceIndex" << sourceIndex << "currentBuffer" << renderer->currentBuffer() << "lastBuffer" << renderer->lastBuffer(); + windowPrivate->clearPresentationFeedbackForOutput(presentationOutput); } return ok; } @@ -1777,6 +1792,65 @@ bool WOutputRenderWindowPrivate::finishTextureSamplingForRenderPass(const QVecto return true; } +void WOutputRenderWindowPrivate::setCurrentPresentationOutput(WOutput *output) +{ + m_currentPresentationOutput = output; +} + +void WOutputRenderWindowPrivate::markSurfaceTexturedForPresentation(WSurface *surface) +{ + auto *output = m_currentPresentationOutput.data(); + if (!m_presentation || !m_presentation->isValid() || !surface || !output) + return; + + if (surface->framePacingOutput() != output) + return; + + auto &surfaces = m_pendingPresentationSurfaces[output]; + for (const auto &existing : std::as_const(surfaces)) { + if (existing == surface) + return; + } + + surfaces.append(surface); +} + +void WOutputRenderWindowPrivate::submitPresentationFeedbackForOutput(WOutput *output) +{ + if (!output) { + clearPresentationFeedback(); + return; + } + + const auto surfaces = m_pendingPresentationSurfaces.take(output); + if (surfaces.isEmpty() || !m_presentation || !m_presentation->isValid()) + return; + + int submittedCount = 0; + for (const auto &surface : surfaces) { + if (!surface || surface->framePacingOutput() != output) + continue; + + m_presentation->surfaceTexturedOnOutput(surface, output); + ++submittedCount; + } + + qCDebug(lcWlPresentation) << "Submitted presentation feedback for output" + << "output" << output + << "surfaceCount" << submittedCount; +} + +void WOutputRenderWindowPrivate::clearPresentationFeedbackForOutput(WOutput *output) +{ + if (output) + m_pendingPresentationSurfaces.remove(output); +} + +void WOutputRenderWindowPrivate::clearPresentationFeedback() +{ + m_pendingPresentationSurfaces.clear(); +} + // ###: QQuickAnimatorController::advance symbol not export static void QQuickAnimatorController_advance(QQuickAnimatorController *ac) { @@ -1805,6 +1879,7 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, return; inRendering = true; + clearPresentationFeedback(); W_Q(WOutputRenderWindow); for (OutputLayer *layer : std::as_const(layers)) { @@ -1844,10 +1919,11 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, if (doCommit) { committedOutputs.reserve(needsCommit.size()); for (auto i : std::as_const(needsCommit)) { + auto output = i.first->output()->output(); if (Q_UNLIKELY(!i.first->framePending())) { + submitPresentationFeedbackForOutput(output); if (Q_LIKELY(i.first->commit(i.second))) { // Make sure the output is still valid after commit - auto output = i.first->output()->output(); if (Q_LIKELY(needsFrameOutput)) { Q_ASSERT(output->handle() == needsFrameOutput); if (committedOutputs.isEmpty()) @@ -1856,6 +1932,8 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, committedOutputs.append(output); } } + } else { + clearPresentationFeedbackForOutput(output); } if (i.second->currentBuffer()) { @@ -1864,7 +1942,10 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, i.first->resetState(); } + } else { + clearPresentationFeedback(); } + clearPresentationFeedback(); resetGlState(); @@ -2164,6 +2245,20 @@ qw_allocator *WOutputRenderWindow::allocator() const return d->m_allocator; } +void WOutputRenderWindow::setPresentation(WPresentation *presentation) +{ + Q_D(WOutputRenderWindow); + d->m_presentation = presentation; + if (!presentation) + d->clearPresentationFeedback(); +} + +void WOutputRenderWindow::markSurfaceTexturedForPresentation(WSurface *surface) +{ + Q_D(WOutputRenderWindow); + d->markSurfaceTexturedForPresentation(surface); +} + bool WOutputRenderWindow::prepareTextureSamplingForRenderPass(qw_buffer *currentBuffer, const QVector &activeProviders, const char *purpose, diff --git a/waylib/src/server/qtquick/woutputrenderwindow.h b/waylib/src/server/qtquick/woutputrenderwindow.h index d7db14a3e..72c38e1e1 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.h +++ b/waylib/src/server/qtquick/woutputrenderwindow.h @@ -25,6 +25,8 @@ class WOutputLayer; class WBufferRenderer; class WOutputHelper; class WSGTextureProvider; +class WPresentation; +class WSurface; class WOutputRenderWindowPrivate; class WAYLIB_SERVER_EXPORT WOutputRenderWindow : public QQuickWindow, public QQmlParserStatus @@ -61,6 +63,8 @@ class WAYLIB_SERVER_EXPORT WOutputRenderWindow : public QQuickWindow, public QQm void init(QW_NAMESPACE::qw_renderer *renderer, QW_NAMESPACE::qw_allocator *allocator); QW_NAMESPACE::qw_renderer *renderer() const; QW_NAMESPACE::qw_allocator *allocator() const; + void setPresentation(WPresentation *presentation); + void markSurfaceTexturedForPresentation(WSurface *surface); bool prepareTextureSamplingForRenderPass(QW_NAMESPACE::qw_buffer *currentBuffer, const QVector &activeProviders, diff --git a/waylib/src/server/qtquick/wsurfaceitem.cpp b/waylib/src/server/qtquick/wsurfaceitem.cpp index 4654ce21b..d779853ef 100644 --- a/waylib/src/server/qtquick/wsurfaceitem.cpp +++ b/waylib/src/server/qtquick/wsurfaceitem.cpp @@ -577,8 +577,12 @@ class Q_DECL_HIDDEN WSGRenderFootprintNode: public QSGRenderNode void render(const RenderState*) override { - if (Q_LIKELY(m_owner)) - m_owner->d_func()->rendered = true; + if (Q_LIKELY(m_owner)) { + auto *ownerPrivate = m_owner->d_func(); + ownerPrivate->rendered = true; + if (auto *renderWindow = m_owner->outputRenderWindow()) + renderWindow->markSurfaceTexturedForPresentation(ownerPrivate->surface); + } } RenderingFlags flags() const override diff --git a/waylib/src/server/wayliblogging.cpp b/waylib/src/server/wayliblogging.cpp index 949244015..246202d4e 100644 --- a/waylib/src/server/wayliblogging.cpp +++ b/waylib/src/server/wayliblogging.cpp @@ -51,6 +51,7 @@ Q_LOGGING_CATEGORY(lcWlInputMethod, "waylib.protocols.inputmethod", QtInfoMsg) / Q_LOGGING_CATEGORY(lcWlExtForeignToplevel, "waylib.protocols.extforeigntoplevel", QtWarningMsg) // ext-foreign-toplevel-list-v1 Q_LOGGING_CATEGORY(lcWlTextInput, "waylib.protocols.textinput", QtInfoMsg) // Text input v1/v2/v3 protocol Q_LOGGING_CATEGORY(lcWlLinuxDmabuf, "waylib.protocols.linuxdmabuf", QtInfoMsg) // Renderer-backed linux-dmabuf global +Q_LOGGING_CATEGORY(lcWlPresentation, "waylib.protocols.presentation", QtInfoMsg) // Presentation-time feedback // Layer shell Q_LOGGING_CATEGORY(lcWlLayerShell, "waylib.layer.shell", QtWarningMsg) // wlr-layer-shell surface requests diff --git a/waylib/src/server/wayliblogging.h b/waylib/src/server/wayliblogging.h index 5f37131d2..240703a98 100644 --- a/waylib/src/server/wayliblogging.h +++ b/waylib/src/server/wayliblogging.h @@ -46,6 +46,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcWlInputMethod) Q_DECLARE_LOGGING_CATEGORY(lcWlExtForeignToplevel) Q_DECLARE_LOGGING_CATEGORY(lcWlTextInput) Q_DECLARE_LOGGING_CATEGORY(lcWlLinuxDmabuf) +Q_DECLARE_LOGGING_CATEGORY(lcWlPresentation) // Layer shell Q_DECLARE_LOGGING_CATEGORY(lcWlLayerShell) From 1de9b422846aaaf3a710d6a12aa8e3b64ec5334a Mon Sep 17 00:00:00 2001 From: LFRon Date: Wed, 8 Jul 2026 18:43:50 +0800 Subject: [PATCH 4/5] feat(cursor): detach cursor layer from QML scene Keep cursor contents on the existing QtQuick/software cursor path, but stop binding cursor item geometry to the live cursor position when it is rendered through OutputLayer. Schedule output frames on cursor motion and compute cursor layer placement from wlroots cursor/output coordinates during layer rendering. This avoids dirtying the main QML scene on cursor movement while preserving the existing cursor content generation and fallback behavior. --- src/core/qml/PrimaryOutput.qml | 9 +++++--- waylib/src/server/qtquick/woutputitem.cpp | 3 +++ .../server/qtquick/woutputrenderwindow.cpp | 23 +++++++++++++++---- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/core/qml/PrimaryOutput.qml b/src/core/qml/PrimaryOutput.qml index c8ce8f9f5..fa5e75e70 100644 --- a/src/core/qml/PrimaryOutput.qml +++ b/src/core/qml/PrimaryOutput.qml @@ -17,7 +17,10 @@ OutputItem { id: cursorItem required property QtObject outputCursor - readonly property point rawPosition: parent.mapFromGlobal(cursor.position.x, cursor.position.y) + readonly property bool detachedCursorLayer: OutputLayer.enabled + readonly property point rawPosition: detachedCursorLayer + ? Qt.point(0, 0) + : parent.mapFromGlobal(cursor.position.x, cursor.position.y) readonly property real effectiveScale: rootOutputItem.devicePixelRatio || 1.0 // Align cursor position to pixel grid to prevent blur on fractional DPR displays @@ -32,8 +35,8 @@ OutputItem { cursor: outputCursor.cursor output: outputCursor.output.output - x: position.x - hotSpot.x - y: position.y - hotSpot.y + x: detachedCursorLayer ? 0 : position.x - hotSpot.x + y: detachedCursorLayer ? 0 : position.y - hotSpot.y visible: valid && outputCursor.visible OutputLayer.enabled: !outputCursor.output.forceSoftwareCursor OutputLayer.keepLayer: true diff --git a/waylib/src/server/qtquick/woutputitem.cpp b/waylib/src/server/qtquick/woutputitem.cpp index b5214a31d..70213046f 100644 --- a/waylib/src/server/qtquick/woutputitem.cpp +++ b/waylib/src/server/qtquick/woutputitem.cpp @@ -160,6 +160,9 @@ void WOutputItemPrivate::updateCursors() } updateCursorVisible(safeOc.data()); + if (safeOc->output() && safeOc->output()->output()) { + safeOc->output()->output()->scheduleFrame(); + } }); } diff --git a/waylib/src/server/qtquick/woutputrenderwindow.cpp b/waylib/src/server/qtquick/woutputrenderwindow.cpp index 1d9fe71a7..7a844a2be 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.cpp +++ b/waylib/src/server/qtquick/woutputrenderwindow.cpp @@ -13,6 +13,7 @@ #include "wpresentation.h" #include "wbufferrenderer_p.h" #include "wquicktextureproxy.h" +#include "wquickcursor.h" #include "wsgtextureprovider.h" #include "wsurface.h" #include "weventjunkman.h" @@ -692,8 +693,11 @@ qw_buffer *OutputHelper::renderLayer(LayerData *layer, bool *dontEndRenderAndRet const auto layerFlags = layer->layer->layer->flags(); const bool sizeSensitive = layerFlags & WOutputLayer::SizeSensitive; + const bool cursorLayer = layerFlags & WOutputLayer::Cursor; const bool isRef = layer->mapFrom && layer->mapTo; - if (isRef) { + if (cursorLayer) { + viewportMatrix = output()->mapToViewport(source->parentItem()); + } else if (isRef) { viewportMatrix = output()->mapToViewport(layer->mapTo); const auto xScale = layer->mapTo->width() / layer->mapFrom->output()->width(); const auto yScale = layer->mapTo->height() / layer->mapFrom->output()->height(); @@ -715,8 +719,19 @@ qw_buffer *OutputHelper::renderLayer(LayerData *layer, bool *dontEndRenderAndRet // matrix function: map source to output buffer const auto outputMatrix = viewportMatrix * output()->sourceRectToTargetRectTransfrom(); - noClipMapRect = outputMatrix.mapRect(noClipMapRect); - mapRect = outputMatrix.mapRect(mapRect); + if (cursorLayer) { + auto cursorItem = qobject_cast(source); + if (!cursorItem || !cursorItem->cursor()) + return nullptr; + + const QPointF outputLocalPosition = cursorItem->cursor()->position() + - QPointF(output()->output()->position()); + noClipMapRect = QRectF(outputLocalPosition - cursorItem->hotSpot(), source->size()); + mapRect = noClipMapRect; + } else { + noClipMapRect = outputMatrix.mapRect(noClipMapRect); + mapRect = outputMatrix.mapRect(mapRect); + } QTransform revertScaleTransform; if (!sizeSensitive) { @@ -756,7 +771,7 @@ qw_buffer *OutputHelper::renderLayer(LayerData *layer, bool *dontEndRenderAndRet } Q_ASSERT(!pixelSize.isEmpty()); - QMatrix4x4 renderMatrix = revertScaleTransform * viewportMatrix; + QMatrix4x4 renderMatrix = cursorLayer ? QMatrix4x4() : revertScaleTransform * viewportMatrix; if (isRef) { renderMatrix = layer->mapFromLayer->renderMatrix * renderMatrix; } From fb2b9bf7c5c45920793d0b5630a2411984671abf Mon Sep 17 00:00:00 2001 From: LFRon Date: Thu, 9 Jul 2026 17:57:49 +0800 Subject: [PATCH 5/5] fix(vulkan): close render resource lifetime gaps Pair Vulkan texture sampling prepare with a matching per-pass finish. Release prepared wlroots textures in LIFO order after Qt records the pass commands, continue best-effort on failures, and fail the render pass if any release cannot be recorded. Defer destruction of Vulkan render resources that may still be referenced by the current QRhi frame. Let WOutput swapchain configuration optionally return the replaced swapchain, retire old Vulkan swapchains in WBufferRenderer, and retire old WRenderHelper BufferData/render target wrappers until several completed frames after rc()->endFrame(). Keep non-Vulkan paths on the existing immediate cleanup behavior. This prevents stale foreign texture ownership and avoids destroying wlroots VkImages while Qt/Vulkan command buffers may still reference them. --- waylib/src/server/kernel/woutput.cpp | 39 +++++++++-- waylib/src/server/kernel/woutput.h | 6 +- .../qtquick/private/wbufferrenderer.cpp | 65 +++++++++++++++++-- .../qtquick/private/wbufferrenderer_p.h | 7 ++ .../server/qtquick/woutputrenderwindow.cpp | 41 +++++++++++- waylib/src/server/qtquick/wrenderhelper.cpp | 54 +++++++++++++-- waylib/src/server/qtquick/wrenderhelper.h | 1 + 7 files changed, 191 insertions(+), 22 deletions(-) diff --git a/waylib/src/server/kernel/woutput.cpp b/waylib/src/server/kernel/woutput.cpp index 3b87d5d47..b32c3b471 100644 --- a/waylib/src/server/kernel/woutput.cpp +++ b/waylib/src/server/kernel/woutput.cpp @@ -329,11 +329,14 @@ static bool test_swapchain(struct wlr_output *output, static bool wlr_output_configure_primary_swapchain(struct wlr_output *output, int width, int height, uint32_t format, struct wlr_swapchain **swapchain_ptr, - bool test) { + bool test, struct wlr_swapchain **replaced_swapchain_ptr) { wlr_output_state empty_state; wlr_output_state_init(&empty_state); wlr_output_state *state = &empty_state; + if (replaced_swapchain_ptr) + *replaced_swapchain_ptr = nullptr; + // Re-use the existing swapchain if possible struct wlr_swapchain *old_swapchain = *swapchain_ptr; if (old_swapchain != NULL && @@ -371,7 +374,11 @@ static bool wlr_output_configure_primary_swapchain(struct wlr_output *output, in } } - wlr_swapchain_destroy(*swapchain_ptr); + if (replaced_swapchain_ptr) { + *replaced_swapchain_ptr = *swapchain_ptr; + } else if (*swapchain_ptr) { + wlr_swapchain_destroy(*swapchain_ptr); + } *swapchain_ptr = swapchain; return true; } @@ -397,21 +404,33 @@ static bool output_pick_cursor_format(struct wlr_output *output, // End bool WOutput::configurePrimarySwapchain(const QSize &size, uint32_t format, - qw_swapchain **swapchain, bool doTest) + qw_swapchain **swapchain, bool doTest, + qw_swapchain **replacedSwapchain) { Q_ASSERT(!size.isEmpty()); - wlr_swapchain *sc = (*swapchain)->handle(); + if (replacedSwapchain) + *replacedSwapchain = nullptr; + + wlr_swapchain *sc = *swapchain ? (*swapchain)->handle() : nullptr; + wlr_swapchain *replacedSc = nullptr; bool ok = wlr_output_configure_primary_swapchain(nativeHandle(), size.width(), size.height(), - format, &sc, doTest); + format, &sc, doTest, + replacedSwapchain ? &replacedSc : nullptr); if (!ok) return false; *swapchain = qw_swapchain::from(sc); + if (replacedSwapchain) + *replacedSwapchain = qw_swapchain::from(replacedSc); return true; } -bool WOutput::configureCursorSwapchain(const QSize &size, uint32_t drmFormat, qw_swapchain **swapchain) +bool WOutput::configureCursorSwapchain(const QSize &size, uint32_t drmFormat, qw_swapchain **swapchain, + qw_swapchain **replacedSwapchain) { Q_ASSERT(!size.isEmpty()); + if (replacedSwapchain) + *replacedSwapchain = nullptr; + auto sc = *swapchain; if (!sc || sc->handle()->width != size.width() || sc->handle()->height != size.height()) { wlr_drm_format format = {}; @@ -420,13 +439,19 @@ bool WOutput::configureCursorSwapchain(const QSize &size, uint32_t drmFormat, qw return false; } - delete sc; + auto oldSc = sc; sc = qw_swapchain::create(*allocator(), size.width(), size.height(), &format); wlr_drm_format_finish(&format); if (!sc) { qCDebug(lcWlOutputBuffer) << "Failed to create cursor swapchain with selected format"; return false; } + + if (replacedSwapchain) { + *replacedSwapchain = oldSc; + } else { + delete oldSc; + } } *swapchain = sc; diff --git a/waylib/src/server/kernel/woutput.h b/waylib/src/server/kernel/woutput.h index 5867adbda..d24e1de40 100644 --- a/waylib/src/server/kernel/woutput.h +++ b/waylib/src/server/kernel/woutput.h @@ -72,9 +72,11 @@ class WAYLIB_SERVER_EXPORT WOutput : public WWrapObject QW_NAMESPACE::qw_allocator *allocator() const; bool configurePrimarySwapchain(const QSize &size, uint32_t format, QW_NAMESPACE::qw_swapchain **swapchain, - bool doTest = true); + bool doTest = true, + QW_NAMESPACE::qw_swapchain **replacedSwapchain = nullptr); bool configureCursorSwapchain(const QSize &size, uint32_t format, - QW_NAMESPACE::qw_swapchain **swapchain); + QW_NAMESPACE::qw_swapchain **swapchain, + QW_NAMESPACE::qw_swapchain **replacedSwapchain = nullptr); QW_NAMESPACE::qw_output *handle() const; wlr_output *nativeHandle() const; diff --git a/waylib/src/server/qtquick/private/wbufferrenderer.cpp b/waylib/src/server/qtquick/private/wbufferrenderer.cpp index 6c26c2bbb..448983652 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer.cpp +++ b/waylib/src/server/qtquick/private/wbufferrenderer.cpp @@ -50,6 +50,8 @@ W_DECLARE_PRIVATE_MEMBER(QSGSoftRenderableNode_m_opacity_tag, QSGSoftwareRendera QW_USE_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE +static constexpr int s_retiredVulkanResourceFrameDelay = 4; + inline static WImageRenderTarget *getImageFrom(const QQuickRenderTarget &rt) { auto d = QQuickRenderTargetPrivate::get(&rt); @@ -109,11 +111,42 @@ WBufferRenderer::~WBufferRenderer() { cleanTextureProvider(); resetSources(); + cleanupRetiredResources(true); delete m_renderHelper; delete m_swapchain; } +void WBufferRenderer::retireSwapchain(qw_swapchain *swapchain, bool defer) +{ + if (!swapchain) + return; + + if (!defer) { + delete swapchain; + return; + } + + m_retiredSwapchains.append({s_retiredVulkanResourceFrameDelay, swapchain}); +} + +void WBufferRenderer::cleanupRetiredResources(bool force) +{ + if (m_renderHelper) + m_renderHelper->cleanupRetiredRenderResources(force); + + for (qsizetype i = 0; i < m_retiredSwapchains.size();) { + auto &retired = m_retiredSwapchains[i]; + if (!force && retired.framesLeft-- > 0) { + ++i; + continue; + } + + delete retired.swapchain; + m_retiredSwapchains.removeAt(i); + } +} + WOutput *WBufferRenderer::output() const { return m_output; @@ -368,6 +401,9 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe Q_EMIT beforeRendering(); + auto wd = QQuickWindowPrivate::get(window()); + const bool isVulkanRhi = wd->rhi && wd->rhi->backend() == QRhi::Vulkan; + // configure swapchain if (flags.testFlag(RenderFlag::DontConfigureSwapchain)) { auto renderFormat = pickFormat(m_output->renderer(), format); @@ -378,17 +414,22 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe if (!m_swapchain || QSize(m_swapchain->handle()->width, m_swapchain->handle()->height) != pixelSize || m_swapchain->handle()->format.format != renderFormat->format) { - if (m_swapchain) - delete m_swapchain; + retireSwapchain(m_swapchain, isVulkanRhi); m_swapchain = qw_swapchain::create(m_output->allocator()->handle(), pixelSize.width(), pixelSize.height(), renderFormat); } } else if (flags.testFlag(RenderFlag::UseCursorFormats)) { - bool ok = m_output->configureCursorSwapchain(pixelSize, format, &m_swapchain); + qw_swapchain *replacedSwapchain = nullptr; + bool ok = m_output->configureCursorSwapchain(pixelSize, format, &m_swapchain, + isVulkanRhi ? &replacedSwapchain : nullptr); + retireSwapchain(replacedSwapchain, isVulkanRhi); if (!ok) return nullptr; } else { + qw_swapchain *replacedSwapchain = nullptr; bool ok = m_output->configurePrimarySwapchain(pixelSize, format, &m_swapchain, - !flags.testFlag(DontTestSwapchain)); + !flags.testFlag(DontTestSwapchain), + isVulkanRhi ? &replacedSwapchain : nullptr); + retireSwapchain(replacedSwapchain, isVulkanRhi); if (!ok) return nullptr; } @@ -403,7 +444,6 @@ qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixe m_renderHelper = new WRenderHelper(m_output->renderer()); m_renderHelper->setSize(pixelSize); - auto wd = QQuickWindowPrivate::get(window()); Q_ASSERT(wd->renderControl); auto lastRT = m_renderHelper->lastRenderTarget(); auto rt = m_renderHelper->acquireRenderTarget(wd->renderControl, buffer); @@ -738,6 +778,19 @@ bool WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, dr->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates); } + if (outputWindow + && !outputWindow->finishTextureSamplingForRenderPass(preparedTextures, + samplingPurpose, + sourceIndex)) { + qCWarning(lcWlBufferRenderer) << "Skipping render pass because Vulkan texture sampling finish failed" + << "renderer" << this + << "sourceIndex" << sourceIndex + << "currentBuffer" << state.buffer.get() + << "wlrBuffer" << (state.buffer ? state.buffer->handle() : nullptr) + << "preparedTextureCount" << preparedTextures.size(); + return false; + } + if (shouldCacheBuffer() && !isVulkanRhi) { wTextureProvider()->setBuffer(state.buffer.get()); } @@ -843,12 +896,14 @@ void WBufferRenderer::invalidateSceneGraph() { if (m_textureProvider) m_textureProvider.reset(); + cleanupRetiredResources(true); resetSources(); } void WBufferRenderer::releaseResources() { cleanTextureProvider(); + cleanupRetiredResources(true); resetSources(); } diff --git a/waylib/src/server/qtquick/private/wbufferrenderer_p.h b/waylib/src/server/qtquick/private/wbufferrenderer_p.h index 042736c55..f815f5dcd 100644 --- a/waylib/src/server/qtquick/private/wbufferrenderer_p.h +++ b/waylib/src/server/qtquick/private/wbufferrenderer_p.h @@ -118,6 +118,8 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem void releaseResources() override; void cleanTextureProvider(); bool isPrimaryOutputRendererForVulkan() const; + void retireSwapchain(QW_NAMESPACE::qw_swapchain *swapchain, bool defer); + void cleanupRetiredResources(bool force = false); inline bool isRootItem(const QQuickItem *source) const { return nullptr == source; @@ -131,6 +133,11 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem QW_NAMESPACE::qw_swapchain *m_swapchain = nullptr; WRenderHelper *m_renderHelper = nullptr; QPointer m_lastBuffer; + struct RetiredSwapchain { + int framesLeft = 0; + QW_NAMESPACE::qw_swapchain *swapchain = nullptr; + }; + QList m_retiredSwapchains; struct RenderState { RenderFlags flags; diff --git a/waylib/src/server/qtquick/woutputrenderwindow.cpp b/waylib/src/server/qtquick/woutputrenderwindow.cpp index 7a844a2be..7e2fc8cb0 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.cpp +++ b/waylib/src/server/qtquick/woutputrenderwindow.cpp @@ -449,6 +449,7 @@ class Q_DECL_HIDDEN WOutputRenderWindowPrivate : public QQuickWindowPrivate bool forceRender); bool releaseRenderBuffer(WBufferRenderer *renderer, const char *purpose); void releaseRenderBuffers(QVector> &needsCommit); + void cleanupRetiredRenderResources(bool force); bool prepareTextureSamplingForRenderPass(qw_buffer *currentBuffer, const QVector &activeProviders, const char *purpose, @@ -1706,6 +1707,37 @@ void WOutputRenderWindowPrivate::releaseRenderBuffers(QVector seenRenderers; + auto cleanupRenderer = [&seenRenderers, force] (WBufferRenderer *renderer) { + if (!renderer || seenRenderers.contains(renderer)) + return; + + seenRenderers.insert(renderer); + renderer->cleanupRetiredResources(force); + }; + + for (auto helper : std::as_const(outputs)) { + if (!helper) + continue; + + if (helper->m_output) + cleanupRenderer(helper->bufferRenderer()); + + if (helper->m_output2) + cleanupRenderer(helper->bufferRenderer2()); + + if (helper->m_cursorRenderer) + cleanupRenderer(helper->m_cursorRenderer); + + for (auto layer : std::as_const(helper->m_layers)) { + if (layer) + cleanupRenderer(layer->renderer); + } + } +} + bool WOutputRenderWindowPrivate::prepareTextureSamplingForRenderPass(qw_buffer *currentBuffer, const QVector &activeProviders, const char *purpose, @@ -1787,6 +1819,7 @@ bool WOutputRenderWindowPrivate::finishTextureSamplingForRenderPass(const QVecto if (WRenderHelper::getGraphicsApi(rc()) != QSGRendererInterface::Vulkan) return true; + bool ok = true; for (qsizetype i = preparedTextures.size() - 1; i >= 0; --i) { auto texture = preparedTextures.at(i); if (!texture) @@ -1800,11 +1833,11 @@ bool WOutputRenderWindowPrivate::finishTextureSamplingForRenderPass(const QVecto << "wlrTexture" << texture->handle() << "preparedTextureCount" << preparedTextures.size() << "releaseIndex" << i; - return false; + ok = false; } } - return true; + return ok; } void WOutputRenderWindowPrivate::setCurrentPresentationOutput(WOutput *output) @@ -1921,8 +1954,10 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, if (rhiBased) releaseRenderBuffers(needsCommit); - if (rhiBased) + if (rhiBased) { rc()->endFrame(); + cleanupRetiredRenderResources(false); + } // prevent gles2-render exception in wlroots. // wlroots may have render operations after commit, so do diff --git a/waylib/src/server/qtquick/wrenderhelper.cpp b/waylib/src/server/qtquick/wrenderhelper.cpp index 7f834f16d..39d0cfdff 100644 --- a/waylib/src/server/qtquick/wrenderhelper.cpp +++ b/waylib/src/server/qtquick/wrenderhelper.cpp @@ -50,6 +50,8 @@ extern "C" { QW_USE_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE +static constexpr int s_retiredVulkanResourceFrameDelay = 4; + struct Q_DECL_HIDDEN RhiRenderEntry { const QRhiRenderTarget *renderTarget; const QRhiTexture *texture; @@ -339,28 +341,64 @@ class Q_DECL_HIDDEN WRenderHelperPrivate : public WObjectPrivate , renderer(renderer) {} ~WRenderHelperPrivate() { - resetRenderBuffer(); + resetRenderBuffer(false); + cleanupRetiredRenderBuffers(true); } - void resetRenderBuffer(); + struct RetiredBuffers { + int framesLeft = 0; + QList buffers; + }; + + bool shouldDeferRenderBufferCleanup() const; + void resetRenderBuffer(bool defer); + void cleanupRetiredRenderBuffers(bool force); void onBufferDestroy(); static bool ensureRhiRenderTarget(QQuickRenderControl *rc, BufferData *data); W_DECLARE_PUBLIC(WRenderHelper) qw_renderer *renderer; QList buffers; + QList retiredBuffers; BufferData *lastBuffer = nullptr; QSize size; }; -void WRenderHelperPrivate::resetRenderBuffer() +bool WRenderHelperPrivate::shouldDeferRenderBufferCleanup() const +{ +#ifdef ENABLE_VULKAN_RENDER + return renderer && wlr_renderer_is_vk(renderer->handle()); +#else + return false; +#endif +} + +void WRenderHelperPrivate::resetRenderBuffer(bool defer) { - qDeleteAll(buffers); + if (defer && !buffers.isEmpty()) { + retiredBuffers.append({s_retiredVulkanResourceFrameDelay, buffers}); + } else { + qDeleteAll(buffers); + } lastBuffer = nullptr; buffers.clear(); } +void WRenderHelperPrivate::cleanupRetiredRenderBuffers(bool force) +{ + for (qsizetype i = 0; i < retiredBuffers.size();) { + auto &retired = retiredBuffers[i]; + if (!force && retired.framesLeft-- > 0) { + ++i; + continue; + } + + qDeleteAll(retired.buffers); + retiredBuffers.removeAt(i); + } +} + void WRenderHelperPrivate::onBufferDestroy() { qw_buffer *buffer = qobject_cast(q_func()->sender()); @@ -450,11 +488,17 @@ void WRenderHelper::setSize(const QSize &size) if (d->size == size) return; d->size = size; - d->resetRenderBuffer(); + d->resetRenderBuffer(d->shouldDeferRenderBufferCleanup()); Q_EMIT sizeChanged(); } +void WRenderHelper::cleanupRetiredRenderResources(bool force) +{ + W_D(WRenderHelper); + d->cleanupRetiredRenderBuffers(force); +} + bool WRenderHelper::acquireRenderBuffer(QQuickRenderControl *rc, qw_buffer *buffer, const char *purpose) { #ifdef ENABLE_VULKAN_RENDER diff --git a/waylib/src/server/qtquick/wrenderhelper.h b/waylib/src/server/qtquick/wrenderhelper.h index 8b1ade4c6..08b9218d8 100644 --- a/waylib/src/server/qtquick/wrenderhelper.h +++ b/waylib/src/server/qtquick/wrenderhelper.h @@ -56,6 +56,7 @@ class WAYLIB_SERVER_EXPORT WRenderHelper : public QObject, public WObject QQuickRenderTarget preserveRenderTarget(QW_NAMESPACE::qw_buffer *buffer) const; bool acquireRenderBuffer(QQuickRenderControl *rc, QW_NAMESPACE::qw_buffer *buffer, const char *purpose); bool releaseRenderBuffer(QQuickRenderControl *rc, QW_NAMESPACE::qw_buffer *buffer, QRhiTexture *renderTargetTexture, const char *purpose); + void cleanupRetiredRenderResources(bool force = false); std::pair lastRenderTarget() const; static QW_NAMESPACE::qw_renderer *createRenderer(QW_NAMESPACE::qw_backend *backend); static QW_NAMESPACE::qw_renderer *createRenderer(QW_NAMESPACE::qw_backend *backend, QSGRendererInterface::GraphicsApi api);