diff --git a/src/platformsupport/kmsconvenience/qkmsdevice.cpp b/src/platformsupport/kmsconvenience/qkmsdevice.cpp index d8eb2f11de..8d8be4a919 100644 --- a/src/platformsupport/kmsconvenience/qkmsdevice.cpp +++ b/src/platformsupport/kmsconvenience/qkmsdevice.cpp @@ -162,8 +162,9 @@ static bool parseModeline(const QByteArray &text, drmModeModeInfoPtr mode) QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources, drmModeConnectorPtr connector, - VirtualDesktopInfo *vinfo) + ScreenInfo *vinfo) { + Q_ASSERT(vinfo); const QByteArray connectorName = nameForConnector(connector); const int crtc = crtcForConnector(resources, connector); @@ -200,18 +201,17 @@ QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources, qWarning("Invalid mode \"%s\" for output %s", mode.constData(), connectorName.constData()); configuration = OutputConfigPreferred; } - if (vinfo) { - *vinfo = VirtualDesktopInfo(); - vinfo->virtualIndex = userConnectorConfig.value(QStringLiteral("virtualIndex"), INT_MAX).toInt(); - if (userConnectorConfig.contains(QStringLiteral("virtualPos"))) { - const QByteArray vpos = userConnectorConfig.value(QStringLiteral("virtualPos")).toByteArray(); - const QByteArrayList vposComp = vpos.split(','); - if (vposComp.count() == 2) - vinfo->virtualPos = QPoint(vposComp[0].trimmed().toInt(), vposComp[1].trimmed().toInt()); - } - if (userConnectorConfig.value(QStringLiteral("primary")).toBool()) - vinfo->isPrimary = true; + + *vinfo = ScreenInfo(); + vinfo->virtualIndex = userConnectorConfig.value(QStringLiteral("virtualIndex"), INT_MAX).toInt(); + if (userConnectorConfig.contains(QStringLiteral("virtualPos"))) { + const QByteArray vpos = userConnectorConfig.value(QStringLiteral("virtualPos")).toByteArray(); + const QByteArrayList vposComp = vpos.split(','); + if (vposComp.count() == 2) + vinfo->virtualPos = QPoint(vposComp[0].trimmed().toInt(), vposComp[1].trimmed().toInt()); } + if (userConnectorConfig.value(QStringLiteral("primary")).toBool()) + vinfo->isPrimary = true; const uint32_t crtc_id = resources->crtcs[crtc]; @@ -359,24 +359,28 @@ QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources, drmFormat = DRM_FORMAT_XRGB8888; } - QKmsOutput output = { - QString::fromUtf8(connectorName), - connector->connector_id, - crtc_id, - physSize, - preferred >= 0 ? preferred : selected_mode, - selected_mode, - false, // mode_set - drmModeGetCrtc(m_dri_fd, crtc_id), // saved_crtc - modes, - connector->subpixel, - connectorProperty(connector, QByteArrayLiteral("DPMS")), - connectorPropertyBlob(connector, QByteArrayLiteral("EDID")), - false, // wants_plane - 0, // plane_id - false, // plane_set - drmFormat - }; + const QString cloneSource = userConnectorConfig.value(QStringLiteral("clones")).toString(); + if (!cloneSource.isEmpty()) + qCDebug(qLcKmsDebug) << "Output" << connectorName << " clones output " << cloneSource; + + QKmsOutput output; + output.name = QString::fromUtf8(connectorName); + output.connector_id = connector->connector_id; + output.crtc_id = crtc_id; + output.physical_size = physSize; + output.preferred_mode = preferred >= 0 ? preferred : selected_mode; + output.mode = selected_mode; + output.mode_set = false; + output.saved_crtc = drmModeGetCrtc(m_dri_fd, crtc_id); + output.modes = modes; + output.subpixel = connector->subpixel; + output.dpms_prop = connectorProperty(connector, QByteArrayLiteral("DPMS")); + output.edid_blob = connectorPropertyBlob(connector, QByteArrayLiteral("EDID")); + output.wants_plane = false; + output.plane_id = 0; + output.plane_set = false; + output.drm_format = drmFormat; + output.clone_source = cloneSource; bool ok; int idx = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_PLANE_INDEX", &ok); @@ -401,6 +405,8 @@ QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources, m_crtc_allocator |= (1 << output.crtc_id); m_connector_allocator |= (1 << output.connector_id); + vinfo->output = output; + return createScreen(output); } @@ -461,10 +467,10 @@ QKmsDevice::~QKmsDevice() struct OrderedScreen { OrderedScreen() : screen(nullptr) { } - OrderedScreen(QPlatformScreen *screen, const QKmsDevice::VirtualDesktopInfo &vinfo) + OrderedScreen(QPlatformScreen *screen, const QKmsDevice::ScreenInfo &vinfo) : screen(screen), vinfo(vinfo) { } QPlatformScreen *screen; - QKmsDevice::VirtualDesktopInfo vinfo; + QKmsDevice::ScreenInfo vinfo; }; QDebug operator<<(QDebug dbg, const OrderedScreen &s) @@ -511,7 +517,7 @@ void QKmsDevice::createScreens() if (!connector) continue; - VirtualDesktopInfo vinfo; + ScreenInfo vinfo; QPlatformScreen *screen = createScreenForConnector(resources, connector, &vinfo); if (screen) screens.append(OrderedScreen(screen, vinfo)); @@ -526,6 +532,32 @@ void QKmsDevice::createScreens() std::stable_sort(screens.begin(), screens.end(), orderedScreenLessThan); qCDebug(qLcKmsDebug) << "Sorted screen list:" << screens; + // The final list of screens is available, so do the second phase setup. + // Hook up clone sources and targets. + for (const OrderedScreen &orderedScreen : screens) { + QVector screensCloningThisScreen; + for (const OrderedScreen &s : screens) { + if (s.vinfo.output.clone_source == orderedScreen.vinfo.output.name) + screensCloningThisScreen.append(s.screen); + } + QPlatformScreen *screenThisScreenClones = nullptr; + if (!orderedScreen.vinfo.output.clone_source.isEmpty()) { + for (const OrderedScreen &s : screens) { + if (s.vinfo.output.name == orderedScreen.vinfo.output.clone_source) { + screenThisScreenClones = s.screen; + break; + } + } + } + if (screenThisScreenClones) + qCDebug(qLcKmsDebug) << orderedScreen.screen->name() << "clones" << screenThisScreenClones; + if (!screensCloningThisScreen.isEmpty()) + qCDebug(qLcKmsDebug) << orderedScreen.screen->name() << "is cloned by" << screensCloningThisScreen; + + registerScreenCloning(orderedScreen.screen, screenThisScreenClones, screensCloningThisScreen); + } + + // Figure out the virtual desktop and register the screens to QPA/QGuiApplication. QPoint pos(0, 0); QList siblings; QVector virtualPositions; @@ -567,6 +599,16 @@ void QKmsDevice::createScreens() } } +// not all subclasses support screen cloning +void QKmsDevice::registerScreenCloning(QPlatformScreen *screen, + QPlatformScreen *screenThisScreenClones, + const QVector &screensCloningThisScreen) +{ + Q_UNUSED(screen); + Q_UNUSED(screenThisScreenClones); + Q_UNUSED(screensCloningThisScreen); +} + int QKmsDevice::fd() const { return m_dri_fd; diff --git a/src/platformsupport/kmsconvenience/qkmsdevice_p.h b/src/platformsupport/kmsconvenience/qkmsdevice_p.h index c33a958f4c..14e430351c 100644 --- a/src/platformsupport/kmsconvenience/qkmsdevice_p.h +++ b/src/platformsupport/kmsconvenience/qkmsdevice_p.h @@ -113,6 +113,7 @@ struct QKmsOutput uint32_t plane_id; bool plane_set; uint32_t drm_format; + QString clone_source; void restoreMode(QKmsDevice *device); void cleanup(QKmsDevice *device); @@ -123,11 +124,11 @@ struct QKmsOutput class QKmsDevice { public: - struct VirtualDesktopInfo { - VirtualDesktopInfo() : virtualIndex(0), isPrimary(false) { } - int virtualIndex; + struct ScreenInfo { + int virtualIndex = 0; QPoint virtualPos; - bool isPrimary; + bool isPrimary = false; + QKmsOutput output; }; QKmsDevice(QKmsScreenConfig *screenConfig, const QString &path = QString()); @@ -146,6 +147,9 @@ public: protected: virtual QPlatformScreen *createScreen(const QKmsOutput &output) = 0; + virtual void registerScreenCloning(QPlatformScreen *screen, + QPlatformScreen *screenThisScreenClones, + const QVector &screensCloningThisScreen); virtual void registerScreen(QPlatformScreen *screen, bool isPrimary, const QPoint &virtualPos, @@ -155,7 +159,7 @@ protected: int crtcForConnector(drmModeResPtr resources, drmModeConnectorPtr connector); QPlatformScreen *createScreenForConnector(drmModeResPtr resources, drmModeConnectorPtr connector, - VirtualDesktopInfo *vinfo); + ScreenInfo *vinfo); drmModePropertyPtr connectorProperty(drmModeConnectorPtr connector, const QByteArray &name); drmModePropertyBlobPtr connectorPropertyBlob(drmModeConnectorPtr connector, const QByteArray &name); diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp index e218d580a2..47752510b6 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp @@ -161,4 +161,15 @@ QPlatformScreen *QEglFSKmsGbmDevice::createScreen(const QKmsOutput &output) return screen; } +void QEglFSKmsGbmDevice::registerScreenCloning(QPlatformScreen *screen, + QPlatformScreen *screenThisScreenClones, + const QVector &screensCloningThisScreen) +{ + if (!screenThisScreenClones && screensCloningThisScreen.isEmpty()) + return; + + QEglFSKmsGbmScreen *gbmScreen = static_cast(screen); + gbmScreen->initCloning(screenThisScreenClones, screensCloningThisScreen); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h index 08ca28d48e..93be197792 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h @@ -68,6 +68,9 @@ public: void handleDrmEvent(); QPlatformScreen *createScreen(const QKmsOutput &output) override; + void registerScreenCloning(QPlatformScreen *screen, + QPlatformScreen *screenThisScreenClones, + const QVector &screensCloningThisScreen) override; private: Q_DISABLE_COPY(QEglFSKmsGbmDevice) diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp index 55cfd90537..d3573a4512 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp @@ -114,7 +114,9 @@ QEglFSKmsGbmScreen::QEglFSKmsGbmScreen(QKmsDevice *device, const QKmsOutput &out , m_gbm_surface(Q_NULLPTR) , m_gbm_bo_current(Q_NULLPTR) , m_gbm_bo_next(Q_NULLPTR) + , m_flipPending(false) , m_cursor(Q_NULLPTR) + , m_cloneSource(Q_NULLPTR) { } @@ -163,32 +165,28 @@ void QEglFSKmsGbmScreen::resetSurface() m_gbm_surface = nullptr; } -void QEglFSKmsGbmScreen::waitForFlip() +void QEglFSKmsGbmScreen::initCloning(QPlatformScreen *screenThisScreenClones, + const QVector &screensCloningThisScreen) { - // Don't lock the mutex unless we actually need to - if (!m_gbm_bo_next) + // clone destinations need to know the clone source + const bool clonesAnother = screenThisScreenClones != nullptr; + if (clonesAnother && !screensCloningThisScreen.isEmpty()) { + qWarning("QEglFSKmsGbmScreen %s cannot be clone source and destination at the same time", qPrintable(name())); return; + } + if (clonesAnother) + m_cloneSource = static_cast(screenThisScreenClones); - QMutexLocker lock(&m_waitForFlipMutex); - while (m_gbm_bo_next) - static_cast(device())->handleDrmEvent(); + // clone sources need to know their additional destinations + for (QPlatformScreen *s : screensCloningThisScreen) { + CloneDestination d; + d.screen = static_cast(s); + m_cloneDests.append(d); + } } -void QEglFSKmsGbmScreen::flip() +void QEglFSKmsGbmScreen::ensureModeSet(uint32_t fb) { - if (!m_gbm_surface) { - qWarning("Cannot sync before platform init!"); - return; - } - - m_gbm_bo_next = gbm_surface_lock_front_buffer(m_gbm_surface); - if (!m_gbm_bo_next) { - qWarning("Could not lock GBM surface front buffer!"); - return; - } - - FrameBuffer *fb = framebufferForBufferObject(m_gbm_bo_next); - QKmsOutput &op(output()); const int fd = device()->fd(); const uint32_t w = op.modes[op.mode].hdisplay; @@ -214,7 +212,7 @@ void QEglFSKmsGbmScreen::flip() qCDebug(qLcEglfsKmsDebug, "Setting mode for screen %s", qPrintable(name())); int ret = drmModeSetCrtc(fd, op.crtc_id, - fb->fb, + fb, 0, 0, &op.connector_id, 1, &op.modes[op.mode]); @@ -238,7 +236,48 @@ void QEglFSKmsGbmScreen::flip() qErrnoWarning(errno, "drmModeSetPlane failed"); } } +} +void QEglFSKmsGbmScreen::waitForFlip() +{ + if (m_cloneSource) { + qWarning("Screen %s clones another screen. swapBuffers() not allowed.", qPrintable(name())); + return; + } + + // Don't lock the mutex unless we actually need to + if (!m_gbm_bo_next) + return; + + QMutexLocker lock(&m_waitForFlipMutex); + while (m_gbm_bo_next) + static_cast(device())->handleDrmEvent(); +} + +void QEglFSKmsGbmScreen::flip() +{ + if (m_cloneSource) { + qWarning("Screen %s clones another screen. swapBuffers() not allowed.", qPrintable(name())); + return; + } + + if (!m_gbm_surface) { + qWarning("Cannot sync before platform init!"); + return; + } + + m_gbm_bo_next = gbm_surface_lock_front_buffer(m_gbm_surface); + if (!m_gbm_bo_next) { + qWarning("Could not lock GBM surface front buffer!"); + return; + } + + FrameBuffer *fb = framebufferForBufferObject(m_gbm_bo_next); + ensureModeSet(fb->fb); + + QKmsOutput &op(output()); + const int fd = device()->fd(); + m_flipPending = true; int ret = drmModePageFlip(fd, op.crtc_id, fb->fb, @@ -246,13 +285,63 @@ void QEglFSKmsGbmScreen::flip() this); if (ret) { qErrnoWarning("Could not queue DRM page flip on screen %s", qPrintable(name())); + m_flipPending = false; gbm_surface_release_buffer(m_gbm_surface, m_gbm_bo_next); m_gbm_bo_next = Q_NULLPTR; + return; + } + + for (CloneDestination &d : m_cloneDests) { + if (d.screen != this) { + d.screen->ensureModeSet(fb->fb); + d.cloneFlipPending = true; + int ret = drmModePageFlip(fd, + d.screen->output().crtc_id, + fb->fb, + DRM_MODE_PAGE_FLIP_EVENT, + d.screen); + if (ret) { + qErrnoWarning("Could not queue DRM page flip for clone screen %s", qPrintable(name())); + d.cloneFlipPending = false; + } + } } } void QEglFSKmsGbmScreen::flipFinished() { + if (m_cloneSource) { + m_cloneSource->cloneDestFlipFinished(this); + return; + } + + m_flipPending = false; + updateFlipStatus(); +} + +void QEglFSKmsGbmScreen::cloneDestFlipFinished(QEglFSKmsGbmScreen *cloneDestScreen) +{ + for (CloneDestination &d : m_cloneDests) { + if (d.screen == cloneDestScreen) { + d.cloneFlipPending = false; + break; + } + } + updateFlipStatus(); +} + +void QEglFSKmsGbmScreen::updateFlipStatus() +{ + Q_ASSERT(!m_cloneSource); + + if (m_flipPending) + return; + + for (const CloneDestination &d : m_cloneDests) { + if (d.cloneFlipPending) + return; + } + if (m_gbm_bo_current) gbm_surface_release_buffer(m_gbm_surface, m_gbm_bo_current); diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h index 3680afa7ec..f6ee68d1c4 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h @@ -62,25 +62,39 @@ public: gbm_surface *createSurface(); void resetSurface(); + void initCloning(QPlatformScreen *screenThisScreenClones, + const QVector &screensCloningThisScreen); + void waitForFlip() override; void flip() override; void flipFinished() override; private: + void ensureModeSet(uint32_t fb); + void cloneDestFlipFinished(QEglFSKmsGbmScreen *cloneDestScreen); + void updateFlipStatus(); + gbm_surface *m_gbm_surface; gbm_bo *m_gbm_bo_current; gbm_bo *m_gbm_bo_next; + bool m_flipPending; QScopedPointer m_cursor; struct FrameBuffer { - FrameBuffer() : fb(0) {} - uint32_t fb; + uint32_t fb = 0; }; static void bufferDestroyedHandler(gbm_bo *bo, void *data); FrameBuffer *framebufferForBufferObject(gbm_bo *bo); + QEglFSKmsGbmScreen *m_cloneSource; + struct CloneDestination { + QEglFSKmsGbmScreen *screen = nullptr; + bool cloneFlipPending = false; + }; + QVector m_cloneDests; + static QMutex m_waitForFlipMutex; };