From c6934509766453424c8338a91e4f2a1274e491d9 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Sat, 15 Apr 2023 22:28:12 +0300 Subject: [PATCH] backends/virtual: Port to gbm The virtual backend uses the surfaceless platform. On the other hand, we move in a direction where the graphics buffer type is explicit, which creates issues for the virtual backend. This change ports the virtual backend to gbm so we could manually allocate dmabuf buffers in order to unify buffer handling in kwin. Its main drawback is that you won't be able to use the virtual backend on setups without render nodes. On the other hand, given that the compositor is meaningless without clients being able to share buffers with it, it's reasonable to require some way to create and export prime buffers. --- .../dont_crash_aurorae_destroy_deco.cpp | 4 + .../integration/dont_crash_empty_deco.cpp | 4 + .../integration/dont_crash_no_border.cpp | 4 + .../dont_crash_reinitialize_compositor.cpp | 4 + .../desktop_switching_animation_test.cpp | 4 + .../effects/maximize_animation_test.cpp | 4 + .../effects/minimize_animation_test.cpp | 4 + .../effects/scripted_effects_test.cpp | 4 + .../effects/slidingpopups_test.cpp | 4 + .../toplevel_open_close_animation_test.cpp | 4 + .../integration/effects/wobbly_shade_test.cpp | 4 + .../integration/generic_scene_opengl_test.cpp | 4 + autotests/integration/kwin_wayland_test.h | 7 + autotests/integration/lockscreen.cpp | 4 + autotests/integration/pointer_input.cpp | 4 + autotests/integration/test_helpers.cpp | 21 +++ src/backends/virtual/virtual_backend.cpp | 66 +++++++++- src/backends/virtual/virtual_backend.h | 15 ++- src/backends/virtual/virtual_egl_backend.cpp | 120 +++++++++++++----- src/backends/virtual/virtual_egl_backend.h | 43 ++++++- 20 files changed, 286 insertions(+), 42 deletions(-) diff --git a/autotests/integration/dont_crash_aurorae_destroy_deco.cpp b/autotests/integration/dont_crash_aurorae_destroy_deco.cpp index 6d4a7883cf..74a4b8297a 100644 --- a/autotests/integration/dont_crash_aurorae_destroy_deco.cpp +++ b/autotests/integration/dont_crash_aurorae_destroy_deco.cpp @@ -40,6 +40,10 @@ private Q_SLOTS: void DontCrashAuroraeDestroyDecoTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qRegisterMetaType(); QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); diff --git a/autotests/integration/dont_crash_empty_deco.cpp b/autotests/integration/dont_crash_empty_deco.cpp index c692993238..b16dd430f9 100644 --- a/autotests/integration/dont_crash_empty_deco.cpp +++ b/autotests/integration/dont_crash_empty_deco.cpp @@ -38,6 +38,10 @@ private Q_SLOTS: void DontCrashEmptyDecorationTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qRegisterMetaType(); QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); QVERIFY(waylandServer()->init(s_socketName)); diff --git a/autotests/integration/dont_crash_no_border.cpp b/autotests/integration/dont_crash_no_border.cpp index 3648b0f08a..137b503d13 100644 --- a/autotests/integration/dont_crash_no_border.cpp +++ b/autotests/integration/dont_crash_no_border.cpp @@ -42,6 +42,10 @@ private Q_SLOTS: void DontCrashNoBorder::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qRegisterMetaType(); QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); QVERIFY(waylandServer()->init(s_socketName)); diff --git a/autotests/integration/dont_crash_reinitialize_compositor.cpp b/autotests/integration/dont_crash_reinitialize_compositor.cpp index f913452100..c39af64460 100644 --- a/autotests/integration/dont_crash_reinitialize_compositor.cpp +++ b/autotests/integration/dont_crash_reinitialize_compositor.cpp @@ -41,6 +41,10 @@ private Q_SLOTS: void DontCrashReinitializeCompositorTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qRegisterMetaType(); diff --git a/autotests/integration/effects/desktop_switching_animation_test.cpp b/autotests/integration/effects/desktop_switching_animation_test.cpp index a026bcafa8..6a8edf3923 100644 --- a/autotests/integration/effects/desktop_switching_animation_test.cpp +++ b/autotests/integration/effects/desktop_switching_animation_test.cpp @@ -40,6 +40,10 @@ private Q_SLOTS: void DesktopSwitchingAnimationTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qRegisterMetaType(); diff --git a/autotests/integration/effects/maximize_animation_test.cpp b/autotests/integration/effects/maximize_animation_test.cpp index e041840911..842843d13e 100644 --- a/autotests/integration/effects/maximize_animation_test.cpp +++ b/autotests/integration/effects/maximize_animation_test.cpp @@ -38,6 +38,10 @@ private Q_SLOTS: void MaximizeAnimationTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qRegisterMetaType(); diff --git a/autotests/integration/effects/minimize_animation_test.cpp b/autotests/integration/effects/minimize_animation_test.cpp index 2eb3a3b2ec..aedd370ea9 100644 --- a/autotests/integration/effects/minimize_animation_test.cpp +++ b/autotests/integration/effects/minimize_animation_test.cpp @@ -41,6 +41,10 @@ private Q_SLOTS: void MinimizeAnimationTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qRegisterMetaType(); diff --git a/autotests/integration/effects/scripted_effects_test.cpp b/autotests/integration/effects/scripted_effects_test.cpp index 7db029de49..aa8250c31e 100644 --- a/autotests/integration/effects/scripted_effects_test.cpp +++ b/autotests/integration/effects/scripted_effects_test.cpp @@ -125,6 +125,10 @@ bool ScriptedEffectWithDebugSpy::load(const QString &name) void ScriptedEffectsTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qRegisterMetaType(); qRegisterMetaType(); QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); diff --git a/autotests/integration/effects/slidingpopups_test.cpp b/autotests/integration/effects/slidingpopups_test.cpp index 5fe050b8c4..08c7decd70 100644 --- a/autotests/integration/effects/slidingpopups_test.cpp +++ b/autotests/integration/effects/slidingpopups_test.cpp @@ -46,6 +46,10 @@ private Q_SLOTS: void SlidingPopupsTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qRegisterMetaType(); qRegisterMetaType(); diff --git a/autotests/integration/effects/toplevel_open_close_animation_test.cpp b/autotests/integration/effects/toplevel_open_close_animation_test.cpp index 39c0d2e462..07c9fec710 100644 --- a/autotests/integration/effects/toplevel_open_close_animation_test.cpp +++ b/autotests/integration/effects/toplevel_open_close_animation_test.cpp @@ -41,6 +41,10 @@ private Q_SLOTS: void ToplevelOpenCloseAnimationTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qRegisterMetaType(); diff --git a/autotests/integration/effects/wobbly_shade_test.cpp b/autotests/integration/effects/wobbly_shade_test.cpp index ffdeb68bfb..52b640529d 100644 --- a/autotests/integration/effects/wobbly_shade_test.cpp +++ b/autotests/integration/effects/wobbly_shade_test.cpp @@ -44,6 +44,10 @@ private Q_SLOTS: void WobblyWindowsShadeTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qRegisterMetaType(); qRegisterMetaType(); QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); diff --git a/autotests/integration/generic_scene_opengl_test.cpp b/autotests/integration/generic_scene_opengl_test.cpp index 66c6432654..fcae045095 100644 --- a/autotests/integration/generic_scene_opengl_test.cpp +++ b/autotests/integration/generic_scene_opengl_test.cpp @@ -38,6 +38,10 @@ void GenericSceneOpenGLTest::cleanup() void GenericSceneOpenGLTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qRegisterMetaType(); QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); QVERIFY(waylandServer()->init(s_socketName)); diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h index 8917f5a4e2..e921b1ba04 100644 --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -684,6 +684,13 @@ bool lockScreen(); */ bool unlockScreen(); +/** + * Returns @c true if the system has at least one render node; otherwise returns @c false. + * + * This can be used to test whether the system is capable of allocating and sharing prime buffers, etc. + */ +bool renderNodeAvailable(); + /** * Creates an X11 connection * Internally a nested event loop is spawned whilst we connect to avoid a deadlock diff --git a/autotests/integration/lockscreen.cpp b/autotests/integration/lockscreen.cpp index 10eae02f99..f2f18d556f 100644 --- a/autotests/integration/lockscreen.cpp +++ b/autotests/integration/lockscreen.cpp @@ -174,6 +174,10 @@ std::pair> LockScreenTest:: void LockScreenTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qRegisterMetaType(); qRegisterMetaType("ElectricBorder"); diff --git a/autotests/integration/pointer_input.cpp b/autotests/integration/pointer_input.cpp index 8ce2d92493..bb0b9a25df 100644 --- a/autotests/integration/pointer_input.cpp +++ b/autotests/integration/pointer_input.cpp @@ -134,6 +134,10 @@ private: void PointerInputTest::initTestCase() { + if (!Test::renderNodeAvailable()) { + QSKIP("no render node available"); + return; + } qRegisterMetaType(); QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); QVERIFY(waylandServer()->init(s_socketName)); diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp index f4cb9a801b..92fad91db2 100644 --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -50,6 +50,7 @@ #include #include #include +#include namespace KWin { @@ -1008,6 +1009,26 @@ bool unlockScreen() } #endif // KWIN_BUILD_LOCKSCREEN +bool renderNodeAvailable() +{ + const int deviceCount = drmGetDevices2(0, nullptr, 0); + if (deviceCount <= 0) { + return false; + } + + QVector devices(deviceCount); + if (drmGetDevices2(0, devices.data(), devices.size()) < 0) { + return false; + } + auto deviceCleanup = qScopeGuard([&devices]() { + drmFreeDevices(devices.data(), devices.size()); + }); + + return std::any_of(devices.constBegin(), devices.constEnd(), [](drmDevice *device) { + return device->available_nodes & (1 << DRM_NODE_RENDER); + }); +} + void XcbConnectionDeleter::operator()(xcb_connection_t *pointer) { xcb_disconnect(pointer); diff --git a/src/backends/virtual/virtual_backend.cpp b/src/backends/virtual/virtual_backend.cpp index 74de726c6f..f4205082eb 100644 --- a/src/backends/virtual/virtual_backend.cpp +++ b/src/backends/virtual/virtual_backend.cpp @@ -12,21 +12,85 @@ #include "virtual_output.h" #include "virtual_qpainter_backend.h" +#include +#include +#include + namespace KWin { +static FileDescriptor findRenderDevice() +{ + const int deviceCount = drmGetDevices2(0, nullptr, 0); + if (deviceCount <= 0) { + return FileDescriptor{}; + } + + QVector devices(deviceCount); + if (drmGetDevices2(0, devices.data(), devices.size()) < 0) { + return FileDescriptor{}; + } + auto deviceCleanup = qScopeGuard([&devices]() { + drmFreeDevices(devices.data(), devices.size()); + }); + + for (drmDevice *device : std::as_const(devices)) { + // If it's a vgem device, prefer the primary node because gbm will attempt to allocate + // dumb buffers and they can be allocated only on the primary node. + int nodeType = DRM_NODE_RENDER; + if (device->bustype == DRM_BUS_PLATFORM) { + if (strcmp(device->businfo.platform->fullname, "vgem") == 0) { + nodeType = DRM_NODE_PRIMARY; + } + } + + if (device->available_nodes & (1 << nodeType)) { + FileDescriptor fd{open(device->nodes[nodeType], O_RDWR | O_CLOEXEC)}; + if (fd.isValid()) { + return fd; + } + } + } + + return FileDescriptor{}; +} + VirtualBackend::VirtualBackend(QObject *parent) : OutputBackend(parent) { + m_drmFileDescriptor = findRenderDevice(); + if (m_drmFileDescriptor.isValid()) { + m_gbmDevice = gbm_create_device(m_drmFileDescriptor.get()); + } } -VirtualBackend::~VirtualBackend() = default; +VirtualBackend::~VirtualBackend() +{ + if (m_gbmDevice) { + gbm_device_destroy(m_gbmDevice); + } +} bool VirtualBackend::initialize() { return true; } +QVector VirtualBackend::supportedCompositors() const +{ + QVector compositingTypes; + if (m_gbmDevice) { + compositingTypes.append(OpenGLCompositing); + } + compositingTypes.append(QPainterCompositing); + return compositingTypes; +} + +gbm_device *VirtualBackend::gbmDevice() const +{ + return m_gbmDevice; +} + std::unique_ptr VirtualBackend::createQPainterBackend() { return std::make_unique(this); diff --git a/src/backends/virtual/virtual_backend.h b/src/backends/virtual/virtual_backend.h index 4474dc6035..414e0c4c2a 100644 --- a/src/backends/virtual/virtual_backend.h +++ b/src/backends/virtual/virtual_backend.h @@ -9,12 +9,12 @@ #pragma once #include "core/outputbackend.h" +#include "utils/filedescriptor.h" -#include - -#include #include +struct gbm_device; + namespace KWin { class VirtualBackend; @@ -39,14 +39,13 @@ public: Outputs outputs() const override; - QVector supportedCompositors() const override - { - return QVector{OpenGLCompositing, QPainterCompositing}; - } + QVector supportedCompositors() const override; void setEglDisplay(std::unique_ptr &&display); EglDisplay *sceneEglDisplayObject() const override; + gbm_device *gbmDevice() const; + Q_SIGNALS: void virtualOutputsSet(bool countChanged); @@ -55,6 +54,8 @@ private: QVector m_outputs; std::unique_ptr m_display; + FileDescriptor m_drmFileDescriptor; + gbm_device *m_gbmDevice = nullptr; }; } // namespace KWin diff --git a/src/backends/virtual/virtual_egl_backend.cpp b/src/backends/virtual/virtual_egl_backend.cpp index 5d39f436c2..69363f1873 100644 --- a/src/backends/virtual/virtual_egl_backend.cpp +++ b/src/backends/virtual/virtual_egl_backend.cpp @@ -7,35 +7,91 @@ SPDX-License-Identifier: GPL-2.0-or-later */ #include "virtual_egl_backend.h" -// kwin +#include "core/gbmgraphicsbufferallocator.h" +#include "libkwineffects/kwinglutils.h" #include "platformsupport/scenes/opengl/basiceglsurfacetexture_internal.h" #include "platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h" #include "utils/softwarevsyncmonitor.h" #include "virtual_backend.h" #include "virtual_logging.h" #include "virtual_output.h" -// kwin libs -#include "libkwineffects/kwinglutils.h" -#include -#ifndef EGL_PLATFORM_SURFACELESS_MESA -#define EGL_PLATFORM_SURFACELESS_MESA 0x31DD -#endif +#include namespace KWin { +VirtualEglLayerBuffer::VirtualEglLayerBuffer(GbmGraphicsBuffer *buffer, VirtualEglBackend *backend) + : m_graphicsBuffer(buffer) +{ + m_texture = backend->importDmaBufAsTexture(*buffer->dmabufAttributes()); + m_framebuffer = std::make_unique(m_texture.get()); +} + +VirtualEglLayerBuffer::~VirtualEglLayerBuffer() +{ + m_texture.reset(); + m_framebuffer.reset(); + m_graphicsBuffer->drop(); +} + +GbmGraphicsBuffer *VirtualEglLayerBuffer::graphicsBuffer() const +{ + return m_graphicsBuffer; +} + +GLFramebuffer *VirtualEglLayerBuffer::framebuffer() const +{ + return m_framebuffer.get(); +} + +std::shared_ptr VirtualEglLayerBuffer::texture() const +{ + return m_texture; +} + +VirtualEglSwapchain::VirtualEglSwapchain(const QSize &size, uint32_t format, VirtualEglBackend *backend) + : m_backend(backend) + , m_size(size) + , m_format(format) + , m_allocator(std::make_unique(backend->backend()->gbmDevice())) +{ +} + +QSize VirtualEglSwapchain::size() const +{ + return m_size; +} + +std::shared_ptr VirtualEglSwapchain::acquire() +{ + for (const auto &buffer : std::as_const(m_buffers)) { + if (!buffer->graphicsBuffer()->isReferenced()) { + return buffer; + } + } + + GbmGraphicsBuffer *graphicsBuffer = m_allocator->allocate(m_size, m_format); + if (!graphicsBuffer) { + qCWarning(KWIN_VIRTUAL) << "Failed to allocate layer swapchain buffer"; + return nullptr; + } + + auto buffer = std::make_shared(graphicsBuffer, m_backend); + m_buffers.append(buffer); + + return buffer; +} + VirtualEglLayer::VirtualEglLayer(Output *output, VirtualEglBackend *backend) : m_backend(backend) , m_output(output) { } -VirtualEglLayer::~VirtualEglLayer() = default; - std::shared_ptr VirtualEglLayer::texture() const { - return m_texture; + return m_current->texture(); } std::optional VirtualEglLayer::beginFrame() @@ -43,15 +99,13 @@ std::optional VirtualEglLayer::beginFrame() m_backend->makeCurrent(); const QSize nativeSize = m_output->geometry().size() * m_output->scale(); - if (!m_texture || m_texture->size() != nativeSize) { - m_fbo.reset(); - m_texture = std::make_unique(GL_RGB8, nativeSize); - m_texture->setContentTransform(TextureTransform::MirrorY); - m_fbo = std::make_unique(m_texture.get()); + if (!m_swapchain || m_swapchain->size() != nativeSize) { + m_swapchain = std::make_unique(nativeSize, DRM_FORMAT_XRGB8888, m_backend); } + m_current = m_swapchain->acquire(); return OutputLayerBeginFrameInfo{ - .renderTarget = RenderTarget(m_fbo.get()), + .renderTarget = RenderTarget(m_current->framebuffer()), .repaint = infiniteRegion(), }; } @@ -65,8 +119,7 @@ bool VirtualEglLayer::endFrame(const QRegion &renderedRegion, const QRegion &dam quint32 VirtualEglLayer::format() const { - // While we are using GL_RGB8, it seems to be using 32bit pixels. - return DRM_FORMAT_XBGR8888; + return DRM_FORMAT_XRGB8888; } VirtualEglBackend::VirtualEglBackend(VirtualBackend *b) @@ -83,24 +136,29 @@ VirtualEglBackend::~VirtualEglBackend() cleanup(); } +VirtualBackend *VirtualEglBackend::backend() const +{ + return m_backend; +} + bool VirtualEglBackend::initializeEgl() { initClientExtensions(); - auto display = m_backend->sceneEglDisplayObject(); - // Use eglGetPlatformDisplayEXT() to get the display pointer - // if the implementation supports it. + if (!m_backend->sceneEglDisplayObject()) { + for (const QByteArray &extension : {QByteArrayLiteral("EGL_EXT_platform_base"), QByteArrayLiteral("EGL_KHR_platform_gbm")}) { + if (!hasClientExtension(extension)) { + qCWarning(KWIN_VIRTUAL) << extension << "client extension is not supported by the platform"; + return false; + } + } + + m_backend->setEglDisplay(EglDisplay::create(eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, m_backend->gbmDevice(), nullptr))); + } + + auto display = m_backend->sceneEglDisplayObject(); if (!display) { - // first try surfaceless - if (hasClientExtension(QByteArrayLiteral("EGL_MESA_platform_surfaceless"))) { - m_backend->setEglDisplay(EglDisplay::create(eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, nullptr))); - display = m_backend->sceneEglDisplayObject(); - } else { - qCWarning(KWIN_VIRTUAL) << "Extension EGL_MESA_platform_surfaceless not available"; - } - if (!display) { - return false; - } + return false; } setEglDisplay(display); return true; diff --git a/src/backends/virtual/virtual_egl_backend.h b/src/backends/virtual/virtual_egl_backend.h index 4238b7f090..06fbde42ec 100644 --- a/src/backends/virtual/virtual_egl_backend.h +++ b/src/backends/virtual/virtual_egl_backend.h @@ -14,16 +14,51 @@ namespace KWin { + +class GbmGraphicsBuffer; +class GbmGraphicsBufferAllocator; class VirtualBackend; class GLFramebuffer; class GLTexture; class VirtualEglBackend; +class VirtualEglLayerBuffer +{ +public: + VirtualEglLayerBuffer(GbmGraphicsBuffer *buffer, VirtualEglBackend *backend); + ~VirtualEglLayerBuffer(); + + GbmGraphicsBuffer *graphicsBuffer() const; + GLFramebuffer *framebuffer() const; + std::shared_ptr texture() const; + +private: + GbmGraphicsBuffer *m_graphicsBuffer; + std::unique_ptr m_framebuffer; + std::shared_ptr m_texture; +}; + +class VirtualEglSwapchain +{ +public: + VirtualEglSwapchain(const QSize &size, uint32_t format, VirtualEglBackend *backend); + + QSize size() const; + + std::shared_ptr acquire(); + +private: + VirtualEglBackend *m_backend; + QSize m_size; + uint32_t m_format; + std::unique_ptr m_allocator; + QVector> m_buffers; +}; + class VirtualEglLayer : public OutputLayer { public: VirtualEglLayer(Output *output, VirtualEglBackend *backend); - ~VirtualEglLayer() override; std::optional beginFrame() override; bool endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override; @@ -34,8 +69,8 @@ public: private: VirtualEglBackend *const m_backend; Output *m_output; - std::unique_ptr m_fbo; - std::shared_ptr m_texture; + std::unique_ptr m_swapchain; + std::shared_ptr m_current; }; /** @@ -55,6 +90,8 @@ public: void present(Output *output) override; void init() override; + VirtualBackend *backend() const; + private: bool initializeEgl(); bool initRenderingContext();