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.
This commit is contained in:
Vlad Zahorodnii 2023-04-15 22:28:12 +03:00
parent 15f6c910be
commit c693450976
20 changed files with 286 additions and 42 deletions

View file

@ -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<KWin::Window *>();
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);

View file

@ -38,6 +38,10 @@ private Q_SLOTS:
void DontCrashEmptyDecorationTest::initTestCase()
{
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qRegisterMetaType<KWin::Window *>();
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
QVERIFY(waylandServer()->init(s_socketName));

View file

@ -42,6 +42,10 @@ private Q_SLOTS:
void DontCrashNoBorder::initTestCase()
{
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qRegisterMetaType<KWin::Window *>();
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
QVERIFY(waylandServer()->init(s_socketName));

View file

@ -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<KWin::Window *>();

View file

@ -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<KWin::Window *>();

View file

@ -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<KWin::Window *>();

View file

@ -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<KWin::Window *>();

View file

@ -125,6 +125,10 @@ bool ScriptedEffectWithDebugSpy::load(const QString &name)
void ScriptedEffectsTest::initTestCase()
{
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qRegisterMetaType<KWin::Window *>();
qRegisterMetaType<KWin::Effect *>();
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);

View file

@ -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<KWin::Window *>();
qRegisterMetaType<KWin::Effect *>();

View file

@ -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<KWin::Window *>();

View file

@ -44,6 +44,10 @@ private Q_SLOTS:
void WobblyWindowsShadeTest::initTestCase()
{
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qRegisterMetaType<KWin::Window *>();
qRegisterMetaType<KWin::Effect *>();
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);

View file

@ -38,6 +38,10 @@ void GenericSceneOpenGLTest::cleanup()
void GenericSceneOpenGLTest::initTestCase()
{
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qRegisterMetaType<KWin::Window *>();
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
QVERIFY(waylandServer()->init(s_socketName));

View file

@ -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

View file

@ -174,6 +174,10 @@ std::pair<Window *, std::unique_ptr<KWayland::Client::Surface>> LockScreenTest::
void LockScreenTest::initTestCase()
{
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qRegisterMetaType<KWin::Window *>();
qRegisterMetaType<KWin::ElectricBorder>("ElectricBorder");

View file

@ -134,6 +134,10 @@ private:
void PointerInputTest::initTestCase()
{
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qRegisterMetaType<KWin::Window *>();
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
QVERIFY(waylandServer()->init(s_socketName));

View file

@ -50,6 +50,7 @@
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <xf86drm.h>
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<drmDevice *> 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);

View file

@ -12,21 +12,85 @@
#include "virtual_output.h"
#include "virtual_qpainter_backend.h"
#include <fcntl.h>
#include <gbm.h>
#include <xf86drm.h>
namespace KWin
{
static FileDescriptor findRenderDevice()
{
const int deviceCount = drmGetDevices2(0, nullptr, 0);
if (deviceCount <= 0) {
return FileDescriptor{};
}
QVector<drmDevice *> 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<CompositingType> VirtualBackend::supportedCompositors() const
{
QVector<CompositingType> compositingTypes;
if (m_gbmDevice) {
compositingTypes.append(OpenGLCompositing);
}
compositingTypes.append(QPainterCompositing);
return compositingTypes;
}
gbm_device *VirtualBackend::gbmDevice() const
{
return m_gbmDevice;
}
std::unique_ptr<QPainterBackend> VirtualBackend::createQPainterBackend()
{
return std::make_unique<VirtualQPainterBackend>(this);

View file

@ -9,12 +9,12 @@
#pragma once
#include "core/outputbackend.h"
#include "utils/filedescriptor.h"
#include <kwin_export.h>
#include <QObject>
#include <QRect>
struct gbm_device;
namespace KWin
{
class VirtualBackend;
@ -39,14 +39,13 @@ public:
Outputs outputs() const override;
QVector<CompositingType> supportedCompositors() const override
{
return QVector<CompositingType>{OpenGLCompositing, QPainterCompositing};
}
QVector<CompositingType> supportedCompositors() const override;
void setEglDisplay(std::unique_ptr<EglDisplay> &&display);
EglDisplay *sceneEglDisplayObject() const override;
gbm_device *gbmDevice() const;
Q_SIGNALS:
void virtualOutputsSet(bool countChanged);
@ -55,6 +54,8 @@ private:
QVector<VirtualOutput *> m_outputs;
std::unique_ptr<EglDisplay> m_display;
FileDescriptor m_drmFileDescriptor;
gbm_device *m_gbmDevice = nullptr;
};
} // namespace KWin

View file

@ -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 <drm_fourcc.h>
#ifndef EGL_PLATFORM_SURFACELESS_MESA
#define EGL_PLATFORM_SURFACELESS_MESA 0x31DD
#endif
#include <drm_fourcc.h>
namespace KWin
{
VirtualEglLayerBuffer::VirtualEglLayerBuffer(GbmGraphicsBuffer *buffer, VirtualEglBackend *backend)
: m_graphicsBuffer(buffer)
{
m_texture = backend->importDmaBufAsTexture(*buffer->dmabufAttributes());
m_framebuffer = std::make_unique<GLFramebuffer>(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<GLTexture> 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<GbmGraphicsBufferAllocator>(backend->backend()->gbmDevice()))
{
}
QSize VirtualEglSwapchain::size() const
{
return m_size;
}
std::shared_ptr<VirtualEglLayerBuffer> 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<VirtualEglLayerBuffer>(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<GLTexture> VirtualEglLayer::texture() const
{
return m_texture;
return m_current->texture();
}
std::optional<OutputLayerBeginFrameInfo> VirtualEglLayer::beginFrame()
@ -43,15 +99,13 @@ std::optional<OutputLayerBeginFrameInfo> 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<GLTexture>(GL_RGB8, nativeSize);
m_texture->setContentTransform(TextureTransform::MirrorY);
m_fbo = std::make_unique<GLFramebuffer>(m_texture.get());
if (!m_swapchain || m_swapchain->size() != nativeSize) {
m_swapchain = std::make_unique<VirtualEglSwapchain>(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,25 +136,30 @@ 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 (!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) {
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) {
return false;
}
setEglDisplay(display);
return true;
}

View file

@ -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<GLTexture> texture() const;
private:
GbmGraphicsBuffer *m_graphicsBuffer;
std::unique_ptr<GLFramebuffer> m_framebuffer;
std::shared_ptr<GLTexture> m_texture;
};
class VirtualEglSwapchain
{
public:
VirtualEglSwapchain(const QSize &size, uint32_t format, VirtualEglBackend *backend);
QSize size() const;
std::shared_ptr<VirtualEglLayerBuffer> acquire();
private:
VirtualEglBackend *m_backend;
QSize m_size;
uint32_t m_format;
std::unique_ptr<GbmGraphicsBufferAllocator> m_allocator;
QVector<std::shared_ptr<VirtualEglLayerBuffer>> m_buffers;
};
class VirtualEglLayer : public OutputLayer
{
public:
VirtualEglLayer(Output *output, VirtualEglBackend *backend);
~VirtualEglLayer() override;
std::optional<OutputLayerBeginFrameInfo> 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<GLFramebuffer> m_fbo;
std::shared_ptr<GLTexture> m_texture;
std::unique_ptr<VirtualEglSwapchain> m_swapchain;
std::shared_ptr<VirtualEglLayerBuffer> m_current;
};
/**
@ -55,6 +90,8 @@ public:
void present(Output *output) override;
void init() override;
VirtualBackend *backend() const;
private:
bool initializeEgl();
bool initRenderingContext();