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() void DontCrashAuroraeDestroyDecoTest::initTestCase()
{ {
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8());
qRegisterMetaType<KWin::Window *>(); qRegisterMetaType<KWin::Window *>();
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);

View file

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

View file

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

View file

@ -41,6 +41,10 @@ private Q_SLOTS:
void DontCrashReinitializeCompositorTest::initTestCase() void DontCrashReinitializeCompositorTest::initTestCase()
{ {
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8());
qRegisterMetaType<KWin::Window *>(); qRegisterMetaType<KWin::Window *>();

View file

@ -40,6 +40,10 @@ private Q_SLOTS:
void DesktopSwitchingAnimationTest::initTestCase() void DesktopSwitchingAnimationTest::initTestCase()
{ {
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8());
qRegisterMetaType<KWin::Window *>(); qRegisterMetaType<KWin::Window *>();

View file

@ -38,6 +38,10 @@ private Q_SLOTS:
void MaximizeAnimationTest::initTestCase() void MaximizeAnimationTest::initTestCase()
{ {
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8());
qRegisterMetaType<KWin::Window *>(); qRegisterMetaType<KWin::Window *>();

View file

@ -41,6 +41,10 @@ private Q_SLOTS:
void MinimizeAnimationTest::initTestCase() void MinimizeAnimationTest::initTestCase()
{ {
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8());
qRegisterMetaType<KWin::Window *>(); qRegisterMetaType<KWin::Window *>();

View file

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

View file

@ -46,6 +46,10 @@ private Q_SLOTS:
void SlidingPopupsTest::initTestCase() void SlidingPopupsTest::initTestCase()
{ {
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8());
qRegisterMetaType<KWin::Window *>(); qRegisterMetaType<KWin::Window *>();
qRegisterMetaType<KWin::Effect *>(); qRegisterMetaType<KWin::Effect *>();

View file

@ -41,6 +41,10 @@ private Q_SLOTS:
void ToplevelOpenCloseAnimationTest::initTestCase() void ToplevelOpenCloseAnimationTest::initTestCase()
{ {
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8());
qRegisterMetaType<KWin::Window *>(); qRegisterMetaType<KWin::Window *>();

View file

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

View file

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

View file

@ -684,6 +684,13 @@ bool lockScreen();
*/ */
bool unlockScreen(); 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 * Creates an X11 connection
* Internally a nested event loop is spawned whilst we connect to avoid a deadlock * 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() void LockScreenTest::initTestCase()
{ {
if (!Test::renderNodeAvailable()) {
QSKIP("no render node available");
return;
}
qRegisterMetaType<KWin::Window *>(); qRegisterMetaType<KWin::Window *>();
qRegisterMetaType<KWin::ElectricBorder>("ElectricBorder"); qRegisterMetaType<KWin::ElectricBorder>("ElectricBorder");

View file

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

View file

@ -50,6 +50,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <xf86drm.h>
namespace KWin namespace KWin
{ {
@ -1008,6 +1009,26 @@ bool unlockScreen()
} }
#endif // KWIN_BUILD_LOCKSCREEN #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) void XcbConnectionDeleter::operator()(xcb_connection_t *pointer)
{ {
xcb_disconnect(pointer); xcb_disconnect(pointer);

View file

@ -12,21 +12,85 @@
#include "virtual_output.h" #include "virtual_output.h"
#include "virtual_qpainter_backend.h" #include "virtual_qpainter_backend.h"
#include <fcntl.h>
#include <gbm.h>
#include <xf86drm.h>
namespace KWin 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) VirtualBackend::VirtualBackend(QObject *parent)
: OutputBackend(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() bool VirtualBackend::initialize()
{ {
return true; 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() std::unique_ptr<QPainterBackend> VirtualBackend::createQPainterBackend()
{ {
return std::make_unique<VirtualQPainterBackend>(this); return std::make_unique<VirtualQPainterBackend>(this);

View file

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

View file

@ -7,35 +7,91 @@
SPDX-License-Identifier: GPL-2.0-or-later SPDX-License-Identifier: GPL-2.0-or-later
*/ */
#include "virtual_egl_backend.h" #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_internal.h"
#include "platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h" #include "platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h"
#include "utils/softwarevsyncmonitor.h" #include "utils/softwarevsyncmonitor.h"
#include "virtual_backend.h" #include "virtual_backend.h"
#include "virtual_logging.h" #include "virtual_logging.h"
#include "virtual_output.h" #include "virtual_output.h"
// kwin libs
#include "libkwineffects/kwinglutils.h"
#include <drm_fourcc.h>
#ifndef EGL_PLATFORM_SURFACELESS_MESA #include <drm_fourcc.h>
#define EGL_PLATFORM_SURFACELESS_MESA 0x31DD
#endif
namespace KWin 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) VirtualEglLayer::VirtualEglLayer(Output *output, VirtualEglBackend *backend)
: m_backend(backend) : m_backend(backend)
, m_output(output) , m_output(output)
{ {
} }
VirtualEglLayer::~VirtualEglLayer() = default;
std::shared_ptr<GLTexture> VirtualEglLayer::texture() const std::shared_ptr<GLTexture> VirtualEglLayer::texture() const
{ {
return m_texture; return m_current->texture();
} }
std::optional<OutputLayerBeginFrameInfo> VirtualEglLayer::beginFrame() std::optional<OutputLayerBeginFrameInfo> VirtualEglLayer::beginFrame()
@ -43,15 +99,13 @@ std::optional<OutputLayerBeginFrameInfo> VirtualEglLayer::beginFrame()
m_backend->makeCurrent(); m_backend->makeCurrent();
const QSize nativeSize = m_output->geometry().size() * m_output->scale(); const QSize nativeSize = m_output->geometry().size() * m_output->scale();
if (!m_texture || m_texture->size() != nativeSize) { if (!m_swapchain || m_swapchain->size() != nativeSize) {
m_fbo.reset(); m_swapchain = std::make_unique<VirtualEglSwapchain>(nativeSize, DRM_FORMAT_XRGB8888, m_backend);
m_texture = std::make_unique<GLTexture>(GL_RGB8, nativeSize);
m_texture->setContentTransform(TextureTransform::MirrorY);
m_fbo = std::make_unique<GLFramebuffer>(m_texture.get());
} }
m_current = m_swapchain->acquire();
return OutputLayerBeginFrameInfo{ return OutputLayerBeginFrameInfo{
.renderTarget = RenderTarget(m_fbo.get()), .renderTarget = RenderTarget(m_current->framebuffer()),
.repaint = infiniteRegion(), .repaint = infiniteRegion(),
}; };
} }
@ -65,8 +119,7 @@ bool VirtualEglLayer::endFrame(const QRegion &renderedRegion, const QRegion &dam
quint32 VirtualEglLayer::format() const quint32 VirtualEglLayer::format() const
{ {
// While we are using GL_RGB8, it seems to be using 32bit pixels. return DRM_FORMAT_XRGB8888;
return DRM_FORMAT_XBGR8888;
} }
VirtualEglBackend::VirtualEglBackend(VirtualBackend *b) VirtualEglBackend::VirtualEglBackend(VirtualBackend *b)
@ -83,24 +136,29 @@ VirtualEglBackend::~VirtualEglBackend()
cleanup(); cleanup();
} }
VirtualBackend *VirtualEglBackend::backend() const
{
return m_backend;
}
bool VirtualEglBackend::initializeEgl() bool VirtualEglBackend::initializeEgl()
{ {
initClientExtensions(); initClientExtensions();
auto display = m_backend->sceneEglDisplayObject();
// Use eglGetPlatformDisplayEXT() to get the display pointer if (!m_backend->sceneEglDisplayObject()) {
// if the implementation supports it. 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) { if (!display) {
// first try surfaceless return false;
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;
}
} }
setEglDisplay(display); setEglDisplay(display);
return true; return true;

View file

@ -14,16 +14,51 @@
namespace KWin namespace KWin
{ {
class GbmGraphicsBuffer;
class GbmGraphicsBufferAllocator;
class VirtualBackend; class VirtualBackend;
class GLFramebuffer; class GLFramebuffer;
class GLTexture; class GLTexture;
class VirtualEglBackend; 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 class VirtualEglLayer : public OutputLayer
{ {
public: public:
VirtualEglLayer(Output *output, VirtualEglBackend *backend); VirtualEglLayer(Output *output, VirtualEglBackend *backend);
~VirtualEglLayer() override;
std::optional<OutputLayerBeginFrameInfo> beginFrame() override; std::optional<OutputLayerBeginFrameInfo> beginFrame() override;
bool endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override; bool endFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override;
@ -34,8 +69,8 @@ public:
private: private:
VirtualEglBackend *const m_backend; VirtualEglBackend *const m_backend;
Output *m_output; Output *m_output;
std::unique_ptr<GLFramebuffer> m_fbo; std::unique_ptr<VirtualEglSwapchain> m_swapchain;
std::shared_ptr<GLTexture> m_texture; std::shared_ptr<VirtualEglLayerBuffer> m_current;
}; };
/** /**
@ -55,6 +90,8 @@ public:
void present(Output *output) override; void present(Output *output) override;
void init() override; void init() override;
VirtualBackend *backend() const;
private: private:
bool initializeEgl(); bool initializeEgl();
bool initRenderingContext(); bool initRenderingContext();