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:
parent
372f5e1fae
commit
886773eef2
@ -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<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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user