From cf24d203850cd87e31bc123407e7ae126be9961c Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Fri, 4 Aug 2017 13:31:20 +0200 Subject: 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 Reviewed-by: Andy Nichols --- src/platformsupport/kmsconvenience/qkmsdevice.cpp | 39 +++++++++++++++++++--- src/platformsupport/kmsconvenience/qkmsdevice_p.h | 31 +++++++++-------- .../eglfs_kms/qeglfskmsgbmdevice.cpp | 7 +++- .../eglfs_kms/qeglfskmsgbmdevice.h | 1 + .../eglfs_kms/qeglfskmsgbmscreen.cpp | 15 ++++++--- .../eglfs_kms/qeglfskmsgbmscreen.h | 2 +- .../eglfs_kms_support/qeglfskmsscreen.cpp | 11 ++++-- .../eglfs_kms_support/qeglfskmsscreen.h | 4 ++- 8 files changed, 83 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/platformsupport/kmsconvenience/qkmsdevice.cpp b/src/platformsupport/kmsconvenience/qkmsdevice.cpp index 401a2aec81..753fdb07af 100644 --- a/src/platformsupport/kmsconvenience/qkmsdevice.cpp +++ b/src/platformsupport/kmsconvenience/qkmsdevice.cpp @@ -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 ** 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()); + 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" diff --git a/src/platformsupport/kmsconvenience/qkmsdevice_p.h b/src/platformsupport/kmsconvenience/qkmsdevice_p.h index 390cc52e4d..bbc03b8e2a 100644 --- a/src/platformsupport/kmsconvenience/qkmsdevice_p.h +++ b/src/platformsupport/kmsconvenience/qkmsdevice_p.h @@ -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 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 &screensCloningThisScreen); diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp index 47752510b6..79c9687dc6 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp @@ -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 &screensCloningThisScreen) diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h index 93be197792..ad5a4d6e6a 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h @@ -68,6 +68,7 @@ public: void handleDrmEvent(); QPlatformScreen *createScreen(const QKmsOutput &output) override; + QPlatformScreen *createHeadlessScreen() override; void registerScreenCloning(QPlatformScreen *screen, QPlatformScreen *screenThisScreenClones, const QVector &screensCloningThisScreen) override; diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp index d3573a4512..ac6f8944ed 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp @@ -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(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; diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h index f6ee68d1c4..4541e500d5 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h @@ -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; diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp index 533695ea0f..cc70ab5a3c 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp @@ -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(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; } diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.h index a2e3402bf2..aacea90eae 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.h @@ -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 -- cgit v1.2.3