eglfs_kms: Add headless mode for DRM render nodes

Attempting to switch /dev/dri/cardX to /dev/dri/renderDY is futile
on its own now since many output-related drm operations fail and we
eventually crash.

Add a new headless mode that skips the real screen stuff and registers
a fairly dummy, headless screen, does not bother with the mouse cursor,
and disallows rendering to the screen via a QWindow (while keeping the
actual rendering still fully functional).

Such applications will not need any special privileges and will run even
if there is a DRM master (X11, Wayland compositor) active.

For example the configuration can look like this:

{
    "device": "/dev/dri/renderD128",
    "headless": "1024x768"
}

After this applications have two choices to perform offscreen
rendering:

1. Use an ordinary window (and its default framebuffer, meaning the
gbm_surface), e.g. a QOpenGLWindow subclass

  MyOpenGLWindow w;
  w.show(); // will not actually show on screen
  w.grabFramebuffer().save("output.png");

Note that there is no vsync-based throttling. Also note that windows are
still sized to match the screen size, hence the need for specifying a size
in the headless property.

2. Or the typical offscreen approach with an extra FBO

  QOffscreenSurface s;
  s.setFormat(ctx.format());
  s.create();
  ctx.makeCurrent(&s0;
  QOpenGLFramebufferObject fbo(1024, 768);
  fbo.bind();
  ctx.functions()->glClearColor(1, 0, 0, 1);
  ctx.functions()->glClear(GL_COLOR_BUFFER_BIT);
  fbo.toImage().save("output.png");
  ctx.doneCurrent();

Task-number: QTBUG-62262
Change-Id: Ic1dbfa2b27b223bd5ef8ba36b665f0f61abf4f06
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2017-08-04 13:31:20 +02:00
parent f2289bbcbb
commit cf24d20385
8 changed files with 83 additions and 27 deletions

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2017 The Qt Company Ltd.
** Copyright (C) 2016 Pelagicore AG
** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
** Contact: https://www.qt.io/licensing/
@ -179,7 +179,7 @@ QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources,
drmModeModeInfo configurationModeline;
auto userConfig = m_screenConfig->outputSettings();
auto userConnectorConfig = userConfig.value(QString::fromUtf8(connectorName));
QVariantMap userConnectorConfig = userConfig.value(QString::fromUtf8(connectorName));
// default to the preferred mode unless overridden in the config
const QByteArray mode = userConnectorConfig.value(QStringLiteral("mode"), QStringLiteral("preferred"))
.toByteArray().toLower();
@ -489,9 +489,22 @@ static bool orderedScreenLessThan(const OrderedScreen &a, const OrderedScreen &b
void QKmsDevice::createScreens()
{
// Headless mode using a render node: cannot do any output related DRM
// stuff. Skip it all and register a dummy screen.
if (m_screenConfig->headless()) {
QPlatformScreen *screen = createHeadlessScreen();
if (screen) {
qCDebug(qLcKmsDebug, "Headless mode enabled");
registerScreen(screen, true, QPoint(0, 0), QList<QPlatformScreen *>());
return;
} else {
qWarning("QKmsDevice: Requested headless mode without support in the backend. Request is ignored.");
}
}
drmModeResPtr resources = drmModeGetResources(m_dri_fd);
if (!resources) {
qWarning("drmModeGetResources failed");
qErrnoWarning(errno, "drmModeGetResources failed");
return;
}
@ -597,6 +610,12 @@ void QKmsDevice::createScreens()
}
}
QPlatformScreen *QKmsDevice::createHeadlessScreen()
{
// headless mode not supported by default
return nullptr;
}
// not all subclasses support screen cloning
void QKmsDevice::registerScreenCloning(QPlatformScreen *screen,
QPlatformScreen *screenThisScreenClones,
@ -628,7 +647,8 @@ QKmsScreenConfig *QKmsDevice::screenConfig() const
}
QKmsScreenConfig::QKmsScreenConfig()
: m_hwCursor(true)
: m_headless(false)
, m_hwCursor(true)
, m_separateScreens(false)
, m_pbuffers(false)
, m_virtualDesktopLayout(VirtualDesktopLayoutHorizontal)
@ -663,6 +683,16 @@ void QKmsScreenConfig::loadConfig()
const QJsonObject object = doc.object();
const QString headlessStr = object.value(QLatin1String("headless")).toString();
const QByteArray headless = headlessStr.toUtf8();
QSize headlessSize;
if (sscanf(headless.constData(), "%dx%d", &headlessSize.rwidth(), &headlessSize.rheight()) == 2) {
m_headless = true;
m_headlessSize = headlessSize;
} else {
m_headless = false;
}
m_hwCursor = object.value(QLatin1String("hwcursor")).toBool(m_hwCursor);
m_pbuffers = object.value(QLatin1String("pbuffers")).toBool(m_pbuffers);
m_devicePath = object.value(QLatin1String("device")).toString();
@ -694,6 +724,7 @@ void QKmsScreenConfig::loadConfig()
}
qCDebug(qLcKmsDebug) << "Requested configuration (some settings may be ignored):\n"
<< "\theadless:" << m_headless << "\n"
<< "\thwcursor:" << m_hwCursor << "\n"
<< "\tpbuffers:" << m_pbuffers << "\n"
<< "\tseparateScreens:" << m_separateScreens << "\n"

View File

@ -77,6 +77,8 @@ public:
QString devicePath() const { return m_devicePath; }
bool headless() const { return m_headless; }
QSize headlessSize() const { return m_headlessSize; }
bool hwCursor() const { return m_hwCursor; }
bool separateScreens() const { return m_separateScreens; }
bool supportsPBuffers() const { return m_pbuffers; }
@ -88,6 +90,8 @@ private:
void loadConfig();
QString m_devicePath;
bool m_headless;
QSize m_headlessSize;
bool m_hwCursor;
bool m_separateScreens;
bool m_pbuffers;
@ -98,21 +102,21 @@ private:
struct QKmsOutput
{
QString name;
uint32_t connector_id;
uint32_t crtc_id;
uint32_t connector_id = 0;
uint32_t crtc_id = 0;
QSizeF physical_size;
int preferred_mode; // index of preferred mode in list below
int mode; // index of selected mode in list below
bool mode_set;
drmModeCrtcPtr saved_crtc;
int preferred_mode = -1; // index of preferred mode in list below
int mode = -1; // index of selected mode in list below
bool mode_set = false;
drmModeCrtcPtr saved_crtc = nullptr;
QList<drmModeModeInfo> modes;
int subpixel;
drmModePropertyPtr dpms_prop;
drmModePropertyBlobPtr edid_blob;
bool wants_plane;
uint32_t plane_id;
bool plane_set;
uint32_t drm_format;
int subpixel = DRM_MODE_SUBPIXEL_UNKNOWN;
drmModePropertyPtr dpms_prop = nullptr;
drmModePropertyBlobPtr edid_blob = nullptr;
bool wants_plane = false;
uint32_t plane_id = 0;
bool plane_set = false;
uint32_t drm_format = DRM_FORMAT_XRGB8888;
QString clone_source;
void restoreMode(QKmsDevice *device);
@ -147,6 +151,7 @@ public:
protected:
virtual QPlatformScreen *createScreen(const QKmsOutput &output) = 0;
virtual QPlatformScreen *createHeadlessScreen();
virtual void registerScreenCloning(QPlatformScreen *screen,
QPlatformScreen *screenThisScreenClones,
const QVector<QPlatformScreen *> &screensCloningThisScreen);

View File

@ -151,7 +151,7 @@ void QEglFSKmsGbmDevice::handleDrmEvent()
QPlatformScreen *QEglFSKmsGbmDevice::createScreen(const QKmsOutput &output)
{
QEglFSKmsGbmScreen *screen = new QEglFSKmsGbmScreen(this, output);
QEglFSKmsGbmScreen *screen = new QEglFSKmsGbmScreen(this, output, false);
if (!m_globalCursor && screenConfig()->hwCursor()) {
qCDebug(qLcEglfsKmsDebug, "Creating new global GBM mouse cursor");
@ -161,6 +161,11 @@ QPlatformScreen *QEglFSKmsGbmDevice::createScreen(const QKmsOutput &output)
return screen;
}
QPlatformScreen *QEglFSKmsGbmDevice::createHeadlessScreen()
{
return new QEglFSKmsGbmScreen(this, QKmsOutput(), true);
}
void QEglFSKmsGbmDevice::registerScreenCloning(QPlatformScreen *screen,
QPlatformScreen *screenThisScreenClones,
const QVector<QPlatformScreen *> &screensCloningThisScreen)

View File

@ -68,6 +68,7 @@ public:
void handleDrmEvent();
QPlatformScreen *createScreen(const QKmsOutput &output) override;
QPlatformScreen *createHeadlessScreen() override;
void registerScreenCloning(QPlatformScreen *screen,
QPlatformScreen *screenThisScreenClones,
const QVector<QPlatformScreen *> &screensCloningThisScreen) override;

View File

@ -109,8 +109,8 @@ QEglFSKmsGbmScreen::FrameBuffer *QEglFSKmsGbmScreen::framebufferForBufferObject(
return fb.take();
}
QEglFSKmsGbmScreen::QEglFSKmsGbmScreen(QKmsDevice *device, const QKmsOutput &output)
: QEglFSKmsScreen(device, output)
QEglFSKmsGbmScreen::QEglFSKmsGbmScreen(QKmsDevice *device, const QKmsOutput &output, bool headless)
: QEglFSKmsScreen(device, output, headless)
, m_gbm_surface(Q_NULLPTR)
, m_gbm_bo_current(Q_NULLPTR)
, m_gbm_bo_next(Q_NULLPTR)
@ -131,6 +131,8 @@ QEglFSKmsGbmScreen::~QEglFSKmsGbmScreen()
QPlatformCursor *QEglFSKmsGbmScreen::cursor() const
{
QKmsScreenConfig *config = device()->screenConfig();
if (config->headless())
return nullptr;
if (config->hwCursor()) {
if (!config->separateScreens())
return static_cast<QEglFSKmsGbmDevice *>(device())->globalCursor();
@ -240,10 +242,8 @@ void QEglFSKmsGbmScreen::ensureModeSet(uint32_t fb)
void QEglFSKmsGbmScreen::waitForFlip()
{
if (m_cloneSource) {
qWarning("Screen %s clones another screen. swapBuffers() not allowed.", qPrintable(name()));
if (m_headless || m_cloneSource)
return;
}
// Don't lock the mutex unless we actually need to
if (!m_gbm_bo_next)
@ -256,6 +256,11 @@ void QEglFSKmsGbmScreen::waitForFlip()
void QEglFSKmsGbmScreen::flip()
{
// For headless screen just return silently. It is not necessarily an error
// to end up here, so show no warnings.
if (m_headless)
return;
if (m_cloneSource) {
qWarning("Screen %s clones another screen. swapBuffers() not allowed.", qPrintable(name()));
return;

View File

@ -54,7 +54,7 @@ class QEglFSKmsGbmCursor;
class QEglFSKmsGbmScreen : public QEglFSKmsScreen
{
public:
QEglFSKmsGbmScreen(QKmsDevice *device, const QKmsOutput &output);
QEglFSKmsGbmScreen(QKmsDevice *device, const QKmsOutput &output, bool headless);
~QEglFSKmsGbmScreen();
QPlatformCursor *cursor() const override;

View File

@ -68,12 +68,13 @@ private:
QEglFSKmsScreen *m_screen;
};
QEglFSKmsScreen::QEglFSKmsScreen(QKmsDevice *device, const QKmsOutput &output)
QEglFSKmsScreen::QEglFSKmsScreen(QKmsDevice *device, const QKmsOutput &output, bool headless)
: QEglFSScreen(static_cast<QEglFSIntegration *>(QGuiApplicationPrivate::platformIntegration())->display())
, m_device(device)
, m_output(output)
, m_powerState(PowerStateOn)
, m_interruptHandler(new QEglFSKmsInterruptHandler(this))
, m_headless(headless)
{
m_siblings << this; // gets overridden later
@ -109,6 +110,9 @@ void QEglFSKmsScreen::setVirtualPosition(const QPoint &pos)
// geometry() calls rawGeometry() and may apply additional transforms.
QRect QEglFSKmsScreen::rawGeometry() const
{
if (m_headless)
return QRect(QPoint(0, 0), m_device->screenConfig()->headlessSize());
const int mode = m_output.mode;
return QRect(m_pos.x(), m_pos.y(),
m_output.modes[mode].hdisplay,
@ -177,7 +181,7 @@ Qt::ScreenOrientation QEglFSKmsScreen::orientation() const
QString QEglFSKmsScreen::name() const
{
return m_output.name;
return !m_headless ? m_output.name : QStringLiteral("qt_Headless");
}
QString QEglFSKmsScreen::manufacturer() const
@ -214,6 +218,9 @@ void QEglFSKmsScreen::restoreMode()
qreal QEglFSKmsScreen::refreshRate() const
{
if (m_headless)
return 60;
quint32 refresh = m_output.modes[m_output.mode].vrefresh;
return refresh > 0 ? refresh : 60;
}

View File

@ -56,7 +56,7 @@ class QEglFSKmsInterruptHandler;
class Q_EGLFS_EXPORT QEglFSKmsScreen : public QEglFSScreen
{
public:
QEglFSKmsScreen(QKmsDevice *device, const QKmsOutput &output);
QEglFSKmsScreen(QKmsDevice *device, const QKmsOutput &output, bool headless = false);
~QEglFSKmsScreen();
void setVirtualPosition(const QPoint &pos);
@ -113,6 +113,8 @@ protected:
PowerState m_powerState;
QEglFSKmsInterruptHandler *m_interruptHandler;
bool m_headless;
};
QT_END_NAMESPACE