eglfs_kms: Output cloning support

{
    "device": "/dev/dri/card0",
    "outputs": [
	{ "name": "HDMI1", "mode": "1920x1080" },
	{ "name": "DP1", "mode": "1920x1080", "clones": "HDMI1" }
   ]
}

Here, assuming the QScreen for DP1 is unused and the resolution is the same,
DP1 will simply mirror whatever is on HDMI1.

The plane-based mouse cursor is not currently supported. Same goes for any
form of scaling since this simply scans out the same framebuffer on all
target CRTCs.

Task-number: QTBUG-62262
Change-Id: I391be204264284a1bff752ebc2a1dbe5c8592013
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2017-08-03 13:01:38 +02:00
parent 372f5e1fae
commit 886773eef2
6 changed files with 224 additions and 61 deletions

View File

@ -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,8 +201,8 @@ QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources,
qWarning("Invalid mode \"%s\" for output %s", mode.constData(), connectorName.constData());
configuration = OutputConfigPreferred;
}
if (vinfo) {
*vinfo = VirtualDesktopInfo();
*vinfo = ScreenInfo();
vinfo->virtualIndex = userConnectorConfig.value(QStringLiteral("virtualIndex"), INT_MAX).toInt();
if (userConnectorConfig.contains(QStringLiteral("virtualPos"))) {
const QByteArray vpos = userConnectorConfig.value(QStringLiteral("virtualPos")).toByteArray();
@ -211,7 +212,6 @@ QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources,
}
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<QPlatformScreen *> 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<QPlatformScreen *> siblings;
QVector<QPoint> virtualPositions;
@ -567,6 +599,16 @@ void QKmsDevice::createScreens()
}
}
// not all subclasses support screen cloning
void QKmsDevice::registerScreenCloning(QPlatformScreen *screen,
QPlatformScreen *screenThisScreenClones,
const QVector<QPlatformScreen *> &screensCloningThisScreen)
{
Q_UNUSED(screen);
Q_UNUSED(screenThisScreenClones);
Q_UNUSED(screensCloningThisScreen);
}
int QKmsDevice::fd() const
{
return m_dri_fd;

View File

@ -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<QPlatformScreen *> &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);

View File

@ -161,4 +161,15 @@ QPlatformScreen *QEglFSKmsGbmDevice::createScreen(const QKmsOutput &output)
return screen;
}
void QEglFSKmsGbmDevice::registerScreenCloning(QPlatformScreen *screen,
QPlatformScreen *screenThisScreenClones,
const QVector<QPlatformScreen *> &screensCloningThisScreen)
{
if (!screenThisScreenClones && screensCloningThisScreen.isEmpty())
return;
QEglFSKmsGbmScreen *gbmScreen = static_cast<QEglFSKmsGbmScreen *>(screen);
gbmScreen->initCloning(screenThisScreenClones, screensCloningThisScreen);
}
QT_END_NAMESPACE

View File

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

View File

@ -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<QPlatformScreen *> &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<QEglFSKmsGbmScreen *>(screenThisScreenClones);
QMutexLocker lock(&m_waitForFlipMutex);
while (m_gbm_bo_next)
static_cast<QEglFSKmsGbmDevice *>(device())->handleDrmEvent();
// clone sources need to know their additional destinations
for (QPlatformScreen *s : screensCloningThisScreen) {
CloneDestination d;
d.screen = static_cast<QEglFSKmsGbmScreen *>(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<QEglFSKmsGbmDevice *>(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);

View File

@ -62,25 +62,39 @@ public:
gbm_surface *createSurface();
void resetSurface();
void initCloning(QPlatformScreen *screenThisScreenClones,
const QVector<QPlatformScreen *> &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<QEglFSKmsGbmCursor> 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<CloneDestination> m_cloneDests;
static QMutex m_waitForFlipMutex;
};