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:
parent
f2289bbcbb
commit
cf24d20385
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user