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();