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/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/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/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..9816ff260 100644 --- a/src/seat/helper.cpp +++ b/src/seat/helper.cpp @@ -67,7 +67,9 @@ #include #include +#include #include +#include #include #include #include @@ -1622,7 +1624,17 @@ 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); + 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); // free follow display diff --git a/waylib/src/server/CMakeLists.txt b/waylib/src/server/CMakeLists.txt index 90111db5f..aac271c64 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,8 @@ 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/wpresentation.cpp protocols/wextforeigntoplevellistv1.cpp protocols/wxdgdialogmanagerv1.cpp protocols/wsecuritycontextmanager.cpp @@ -294,6 +301,10 @@ set(HEADERS protocols/WCursorShapeManagerV1 protocols/woutputmanagerv1.h protocols/WOutputManagerV1 + protocols/wlinuxdmabufv1.h + protocols/WLinuxDmabufV1 + protocols/wpresentation.h + protocols/WPresentation protocols/wlayershell.h protocols/WLayerShell protocols/wxwayland.h @@ -408,6 +419,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/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/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/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/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/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/private/wbufferrenderer.cpp b/waylib/src/server/qtquick/private/wbufferrenderer.cpp index f12f4bf07..448983652 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 @@ -48,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); @@ -107,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; @@ -258,9 +293,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 +317,9 @@ qw_damage_ring *WBufferRenderer::damageRing() bool WBufferRenderer::isTextureProvider() const { + if (isPrimaryOutputRendererForVulkan()) + return false; + return true; } @@ -289,6 +330,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 +348,20 @@ 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; +} + QTransform WBufferRenderer::inputMapToOutput(const QRectF &sourceRect, const QRectF &targetRect, const QSize &pixelSize, const qreal devicePixelRatio) { @@ -330,6 +388,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) { @@ -341,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); @@ -351,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; } @@ -376,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); @@ -403,11 +470,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 +489,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 +506,50 @@ 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) +{ + 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); + } + + return providers; +} + +bool WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, const QRectF &sourceRect, const QRectF &targetRect, bool preserveColorContents) { @@ -449,6 +563,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 +598,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 +650,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 +691,37 @@ 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); + } } } } + QVector preparedTextures; + auto outputWindow = renderWindow(); + const auto activeTextureProviders = activeTextureProvidersForPass(isRootItem(source.source) + ? wd->contentItem + : source.source); + constexpr const char *samplingPurpose = "qt-render-pass-texture"; + if (outputWindow + && !outputWindow->prepareTextureSamplingForRenderPass(state.buffer.get(), + activeTextureProviders, + samplingPurpose, + sourceIndex, + &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 +736,9 @@ 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) { + wd->rhi->finish(); + } } else { state.dirty = softwareRenderer->flushRegion(); @@ -627,24 +778,55 @@ void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, dr->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates); } - if (shouldCacheBuffer()) + 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()); + } + + 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 (isPrimaryOutputRendererForVulkan()) { + if (m_textureProvider) + m_textureProvider->setBuffer(nullptr); + } else if (canExposeCompletedCache) { + if (auto provider = wTextureProvider()) + provider->setBuffer(buffer.get()); + } + } } + 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 +848,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 +866,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); } @@ -702,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 628684c71..f815f5dcd 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,9 @@ class WAYLIB_SERVER_EXPORT WBufferRenderer : public QQuickItem Q_SLOT void invalidateSceneGraph(); 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; @@ -130,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; @@ -142,7 +150,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/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 454167897..7e2fc8cb0 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.cpp +++ b/waylib/src/server/qtquick/woutputrenderwindow.cpp @@ -10,8 +10,12 @@ #include "woutputviewport_p.h" #include "wqmlhelper_p.h" #include "woutputlayer.h" +#include "wpresentation.h" #include "wbufferrenderer_p.h" #include "wquicktextureproxy.h" +#include "wquickcursor.h" +#include "wsgtextureprovider.h" +#include "wsurface.h" #include "weventjunkman.h" #include "winputdevice.h" #include "wseat.h" @@ -39,7 +43,9 @@ #include #include #include +#include #include +#include #include #include @@ -239,7 +245,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 +447,22 @@ 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); + void cleanupRetiredRenderResources(bool force); + bool prepareTextureSamplingForRenderPass(qw_buffer *currentBuffer, + const QVector &activeProviders, + const char *purpose, + int sourceIndex, + QVector *preparedTextures); + 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); @@ -468,6 +490,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; @@ -595,11 +620,24 @@ 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); + 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; } static QQuickItem *createVisualRectangle(QQuickItem *target, const QColor &color) { @@ -656,8 +694,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(); @@ -679,8 +720,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) { @@ -720,7 +772,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; } @@ -762,15 +814,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 +887,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 +1039,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 +1123,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 +1203,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 +1303,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 +1436,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 +1608,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 +1638,267 @@ 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); + } +} + +void WOutputRenderWindowPrivate::cleanupRetiredRenderResources(bool force) +{ + QSet 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, + int sourceIndex, + QVector *preparedTextures) +{ + Q_ASSERT(preparedTextures); + preparedTextures->clear(); + + if (WRenderHelper::getGraphicsApi(rc()) != QSGRendererInterface::Vulkan) + return true; + + QSet seenTextures; + qsizetype skippedCurrentRenderTarget = 0; + qsizetype invalidProviderCount = 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; + continue; + } + } + } + + auto texture = provider->qwTexture(); + if (!texture || !provider->hasTexture()) { + ++invalidProviderCount; + continue; + } + + 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() + << "qtTextureSize" << (sampleTexture ? sampleTexture->textureSize() : QSize()) + << "activeProviderCount" << activeProviders.size() + << "preparedTextureCount" << preparedTextures->size() + << "skippedCurrentRenderTarget" << skippedCurrentRenderTarget + << "invalidProviderCount" << invalidProviderCount; + provider->setTexture(nullptr, nullptr); + continue; + } + + preparedTextures->append(texture); + } + + return true; +} + +bool WOutputRenderWindowPrivate::finishTextureSamplingForRenderPass(const QVector &preparedTextures, + const char *purpose, + int sourceIndex) +{ + 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) + 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; + ok = false; + } + } + + return ok; +} + +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) { @@ -1537,6 +1927,7 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, return; inRendering = true; + clearPresentationFeedback(); W_Q(WOutputRenderWindow); for (OutputLayer *layer : std::as_const(layers)) { @@ -1545,7 +1936,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,13 +1945,19 @@ 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(); + cleanupRetiredRenderResources(false); + } // prevent gles2-render exception in wlroots. // wlroots may have render operations after commit, so do @@ -1571,10 +1969,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()) @@ -1583,6 +1982,8 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, committedOutputs.append(output); } } + } else { + clearPresentationFeedbackForOutput(output); } if (i.second->currentBuffer()) { @@ -1591,7 +1992,10 @@ void WOutputRenderWindowPrivate::doRender(qw_output *needsFrameOutput, i.first->resetState(); } + } else { + clearPresentationFeedback(); } + clearPresentationFeedback(); resetGlState(); @@ -1891,6 +2295,42 @@ 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, + int sourceIndex, + QVector *preparedTextures) +{ + Q_D(WOutputRenderWindow); + return d->prepareTextureSamplingForRenderPass(currentBuffer, + activeProviders, + purpose, + sourceIndex, + 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..72c38e1e1 100644 --- a/waylib/src/server/qtquick/woutputrenderwindow.h +++ b/waylib/src/server/qtquick/woutputrenderwindow.h @@ -9,16 +9,26 @@ #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 WPresentation; +class WSurface; class WOutputRenderWindowPrivate; + class WAYLIB_SERVER_EXPORT WOutputRenderWindow : public QQuickWindow, public QQmlParserStatus { Q_OBJECT @@ -53,6 +63,17 @@ 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, + const char *purpose, + int sourceIndex, + QVector *preparedTextures); + bool finishTextureSamplingForRenderPass(const QVector &preparedTextures, + const char *purpose, + int sourceIndex); qreal width() const; qreal height() 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..875aea0c1 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" << "Vulkan backdrop source unavailable"; + 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..39d0cfdff 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,11 +44,14 @@ extern "C" { #endif } #include -#include + +#include QW_USE_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE +static constexpr int s_retiredVulkanResourceFrameDelay = 4; + struct Q_DECL_HIDDEN RhiRenderEntry { const QRhiRenderTarget *renderTarget; const QRhiTexture *texture; @@ -51,6 +60,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 +160,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 +176,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 +208,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 +238,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 +249,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 +273,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 +282,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 +298,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 +314,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) @@ -237,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 { - qDeleteAll(buffers); +#ifdef ENABLE_VULKAN_RENDER + return renderer && wlr_renderer_is_vk(renderer->handle()); +#else + return false; +#endif +} + +void WRenderHelperPrivate::resetRenderBuffer(bool defer) +{ + 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()); @@ -294,6 +434,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; } @@ -316,11 +488,224 @@ 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 + 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 +814,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 +921,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 +950,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 +990,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 +1014,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 +1121,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 +1192,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 +1216,240 @@ 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; + } + + const bool ok = wlr_vk_renderer_prepare_texture_for_sampling(renderer->handle(), + texture->handle(), + handles->commandBuffer, + nullptr); + 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; + } + +#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; + } + +#else + Q_UNUSED(rc); + Q_UNUSED(renderer); + Q_UNUSED(texture); + Q_UNUSED(purpose); +#endif return true; } @@ -897,10 +1504,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..08b9218d8 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,10 @@ 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); + 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); @@ -55,7 +64,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..d428bb2ca 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.cpp +++ b/waylib/src/server/qtquick/wsgtextureprovider.cpp @@ -14,8 +14,68 @@ #include #include +extern "C" { +#include +} + WAYLIB_SERVER_BEGIN_NAMESPACE +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 +94,96 @@ 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; + + 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)) + { + } + + void run() override { - 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; + 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(); + 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) + << "bufferSize" << (bufferHandle ? QSize(bufferHandle->handle()->width, + bufferHandle->handle()->height) + : QSize()); + return false; } rhiTexture = qtTexture.rhiTexture(); + updateMipmapFiltering(); + return true; } W_DECLARE_PUBLIC(WSGTextureProvider) @@ -79,7 +193,7 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate // wlroots resources qw_texture *texture = nullptr; bool ownsTexture = false; - qw_buffer *buffer = nullptr; + BufferRef buffer; // qt resources QSGPlainTexture qtTexture; @@ -90,7 +204,6 @@ class Q_DECL_HIDDEN WSGTextureProviderPrivate : public WObjectPrivate WSGTextureProvider::WSGTextureProvider(WOutputRenderWindow *window) : WObject(*new WSGTextureProviderPrivate(this, window)) { - } WOutputRenderWindow *WSGTextureProvider::window() const @@ -111,7 +224,7 @@ void WSGTextureProvider::setBuffer(qw_buffer *buffer) W_D(WSGTextureProvider); d->cleanTexture(); - d->buffer = buffer; + d->buffer.reset(buffer); if (buffer) { Q_ASSERT(d->window); @@ -132,7 +245,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 +258,10 @@ 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->ownsTexture = false; - if (texture) - d->updateRhiTexture(); + if (texture && !d->updateRhiTexture()) + d->cleanTexture(); Q_EMIT textureChanged(); } @@ -167,6 +281,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 +296,7 @@ qw_texture *WSGTextureProvider::qwTexture() const qw_buffer *WSGTextureProvider::qwBuffer() const { W_DC(WSGTextureProvider); - return d->buffer; + return d->buffer.get(); } bool WSGTextureProvider::smooth() const @@ -193,8 +313,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..ea7d69c37 100644 --- a/waylib/src/server/qtquick/wsgtextureprovider.h +++ b/waylib/src/server/qtquick/wsgtextureprovider.h @@ -32,6 +32,7 @@ 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; 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 383778255..246202d4e 100644 --- a/waylib/src/server/wayliblogging.cpp +++ b/waylib/src/server/wayliblogging.cpp @@ -50,6 +50,8 @@ 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 +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 38763942a..240703a98 100644 --- a/waylib/src/server/wayliblogging.h +++ b/waylib/src/server/wayliblogging.h @@ -45,6 +45,8 @@ 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) +Q_DECLARE_LOGGING_CATEGORY(lcWlPresentation) // Layer shell Q_DECLARE_LOGGING_CATEGORY(lcWlLayerShell)