linuxfb: add experimental support for DRM dumb buffers

Add a QFbScreen implementation that uses DRM dumb buffers and page
flipping, thus implementing a vsynced, double (triple) buffered plain
software platform plugin. Besides better supporting systems that by
now only do fbdev via (possibly buggy) emulation, this can also be an
enabler for implementing certain types of 2D accelerated compositors
e.g. for Wayland.

Enable with QT_QPA_FB_DRM=1. Right now there is no good way to
autodetect the support for dumb buffers early enough so the default is
always the traditional fbdev path.

Long-term this will also enable proper multi-display support like with
eglfs' DRM backends. Multiple QScreens are not yet reported currently
due to the single-screen limitations of fbconvenience.

The generic DRM code is shared with eglfs, meaning configuration
happens via the JSON file specified in QT_QPA_(EGLFS_)KMS_CONFIG. This
allows changing the screen resolution, disabling outputs, etc.

[ChangeLog][Platform Specific Changes][linuxfb] Added experimental
support for DRM dumb buffers. Enable by setting the environment
variable QT_QPA_FB_DRM to 1.

Task-number: QTBUG-56306
Change-Id: I4bdcf16fff67412183220dffa0c4ea235c5cad3d
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2016-11-10 12:32:28 +01:00
parent 705e3f68df
commit 1f81ba45ee
8 changed files with 512 additions and 9 deletions

View File

@ -190,6 +190,11 @@ void QFbScreen::setGeometry(const QRect &rect)
resizeMaximizedWindows();
}
bool QFbScreen::initialize()
{
return true;
}
QRegion QFbScreen::doRedraw()
{
const QPoint screenOffset = mGeometry.topLeft();

View File

@ -76,6 +76,8 @@ public:
QFbScreen();
~QFbScreen();
virtual bool initialize();
QRect geometry() const Q_DECL_OVERRIDE { return mGeometry; }
int depth() const Q_DECL_OVERRIDE { return mDepth; }
QImage::Format format() const Q_DECL_OVERRIDE { return mFormat; }

View File

@ -10,8 +10,18 @@ QT += \
qtHaveModule(input_support-private): \
QT += input_support-private
SOURCES = main.cpp qlinuxfbintegration.cpp qlinuxfbscreen.cpp
HEADERS = qlinuxfbintegration.h qlinuxfbscreen.h
SOURCES = main.cpp \
qlinuxfbintegration.cpp \
qlinuxfbscreen.cpp
HEADERS = qlinuxfbintegration.h \
qlinuxfbscreen.h
qtHaveModule(kms_support-private) {
QT += kms_support-private
SOURCES += qlinuxfbdrmscreen.cpp
HEADERS += qlinuxfbdrmscreen.h
}
OTHER_FILES += linuxfb.json

View File

@ -0,0 +1,409 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
// Experimental DRM dumb buffer backend.
//
// TODO:
// Multiscreen: QWindow-QScreen(-output) association. Needs some reorg (device cannot be owned by screen)
// Find card via devicediscovery like in eglfs_kms.
// Mode restore like QEglFSKmsInterruptHandler.
// Formats other then 32 bpp?
// grabWindow
#include "qlinuxfbdrmscreen.h"
#include <QLoggingCategory>
#include <QGuiApplication>
#include <QPainter>
#include <QtFbSupport/private/qfbcursor_p.h>
#include <QtFbSupport/private/qfbwindow_p.h>
#include <QtKmsSupport/private/qkmsdevice_p.h>
#include <QtCore/private/qcore_unix_p.h>
#include <sys/mman.h>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(qLcFbDrm, "qt.qpa.fb")
static const int BUFFER_COUNT = 2;
class QLinuxFbDevice : public QKmsDevice
{
public:
struct Framebuffer {
Framebuffer() : handle(0), pitch(0), size(0), fb(0), p(MAP_FAILED) { }
uint32_t handle;
uint32_t pitch;
uint64_t size;
uint32_t fb;
void *p;
QImage wrapper;
};
struct Output {
Output() : backFb(0), flipped(false) { }
QKmsOutput kmsOutput;
Framebuffer fb[BUFFER_COUNT];
QRegion dirty[BUFFER_COUNT];
int backFb;
bool flipped;
QSize currentRes() const {
const drmModeModeInfo &modeInfo(kmsOutput.modes[kmsOutput.mode]);
return QSize(modeInfo.hdisplay, modeInfo.vdisplay);
}
};
QLinuxFbDevice(QKmsScreenConfig *screenConfig);
bool open() override;
void close() override;
void createFramebuffers();
void destroyFramebuffers();
void setMode();
void swapBuffers(Output *output);
int outputCount() const { return m_outputs.count(); }
Output *output(int idx) { return &m_outputs[idx]; }
private:
void *nativeDisplay() const override;
QPlatformScreen *createScreen(const QKmsOutput &output) override;
void registerScreen(QPlatformScreen *screen,
const QPoint &virtualPos,
const QList<QPlatformScreen *> &virtualSiblings) override;
bool createFramebuffer(Output *output, int bufferIdx);
void destroyFramebuffer(Output *output, int bufferIdx);
static void pageFlipHandler(int fd, unsigned int sequence,
unsigned int tv_sec, unsigned int tv_usec, void *user_data);
QVector<Output> m_outputs;
};
QLinuxFbDevice::QLinuxFbDevice(QKmsScreenConfig *screenConfig)
: QKmsDevice(screenConfig, QStringLiteral("/dev/dri/card0"))
{
}
bool QLinuxFbDevice::open()
{
int fd = qt_safe_open(devicePath().toLocal8Bit().constData(), O_RDWR | O_CLOEXEC);
if (fd == -1) {
qErrnoWarning("Could not open DRM device %s", qPrintable(devicePath()));
return false;
}
uint64_t hasDumbBuf = 0;
if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &hasDumbBuf) == -1 || !hasDumbBuf) {
qWarning("Dumb buffers not supported");
qt_safe_close(fd);
return false;
}
setFd(fd);
qCDebug(qLcFbDrm, "DRM device %s opened", qPrintable(devicePath()));
return true;
}
void QLinuxFbDevice::close()
{
for (Output &output : m_outputs)
output.kmsOutput.cleanup(this); // restore mode
m_outputs.clear();
if (fd() != -1) {
qCDebug(qLcFbDrm, "Closing DRM device");
qt_safe_close(fd());
setFd(-1);
}
}
void *QLinuxFbDevice::nativeDisplay() const
{
Q_UNREACHABLE();
return nullptr;
}
QPlatformScreen *QLinuxFbDevice::createScreen(const QKmsOutput &output)
{
qCDebug(qLcFbDrm, "Got a new output: %s", qPrintable(output.name));
Output o;
o.kmsOutput = output;
m_outputs.append(o);
return nullptr; // no platformscreen, we are not a platform plugin
}
void QLinuxFbDevice::registerScreen(QPlatformScreen *screen,
const QPoint &virtualPos,
const QList<QPlatformScreen *> &virtualSiblings)
{
Q_UNUSED(screen);
Q_UNUSED(virtualPos);
Q_UNUSED(virtualSiblings);
Q_UNREACHABLE();
}
bool QLinuxFbDevice::createFramebuffer(QLinuxFbDevice::Output *output, int bufferIdx)
{
const QSize size = output->currentRes();
const uint32_t w = size.width();
const uint32_t h = size.height();
drm_mode_create_dumb creq = {
h,
w,
32,
0, 0, 0, 0
};
if (drmIoctl(fd(), DRM_IOCTL_MODE_CREATE_DUMB, &creq) == -1) {
qErrnoWarning(errno, "Failed to create dumb buffer");
return false;
}
Framebuffer &fb(output->fb[bufferIdx]);
fb.handle = creq.handle;
fb.pitch = creq.pitch;
fb.size = creq.size;
qCDebug(qLcFbDrm, "Got a dumb buffer for size %dx%d, handle %u, pitch %u, size %u",
w, h, fb.handle, fb.pitch, (uint) fb.size);
if (drmModeAddFB(fd(), w, h, 24, 32, fb.pitch, fb.handle, &fb.fb) == -1) {
qErrnoWarning(errno, "Failed to add FB");
return false;
}
drm_mode_map_dumb mreq = {
fb.handle,
0, 0
};
if (drmIoctl(fd(), DRM_IOCTL_MODE_MAP_DUMB, &mreq) == -1) {
qErrnoWarning(errno, "Failed to map dumb buffer");
return false;
}
fb.p = mmap(0, fb.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd(), mreq.offset);
if (fb.p == MAP_FAILED) {
qErrnoWarning(errno, "Failed to mmap dumb buffer");
return false;
}
qCDebug(qLcFbDrm, "FB is %u, mapped at %p", fb.fb, fb.p);
memset(fb.p, 0, fb.size);
fb.wrapper = QImage(static_cast<uchar *>(fb.p), w, h, fb.pitch, QImage::Format_ARGB32);
return true;
}
void QLinuxFbDevice::createFramebuffers()
{
for (Output &output : m_outputs) {
for (int i = 0; i < BUFFER_COUNT; ++i) {
if (!createFramebuffer(&output, i))
return;
}
output.backFb = 0;
output.flipped = false;
}
}
void QLinuxFbDevice::destroyFramebuffer(QLinuxFbDevice::Output *output, int bufferIdx)
{
Framebuffer &fb(output->fb[bufferIdx]);
if (fb.p != MAP_FAILED)
munmap(fb.p, fb.size);
if (fb.fb) {
if (drmModeRmFB(fd(), fb.fb) == -1)
qErrnoWarning("Failed to remove fb");
}
if (fb.handle) {
drm_mode_destroy_dumb dreq = { fb.handle };
if (drmIoctl(fd(), DRM_IOCTL_MODE_DESTROY_DUMB, &dreq) == -1)
qErrnoWarning(errno, "Failed to destroy dumb buffer %u", fb.handle);
}
fb = Framebuffer();
}
void QLinuxFbDevice::destroyFramebuffers()
{
for (Output &output : m_outputs) {
for (int i = 0; i < BUFFER_COUNT; ++i)
destroyFramebuffer(&output, i);
}
}
void QLinuxFbDevice::setMode()
{
for (Output &output : m_outputs) {
drmModeModeInfo &modeInfo(output.kmsOutput.modes[output.kmsOutput.mode]);
if (drmModeSetCrtc(fd(), output.kmsOutput.crtc_id, output.fb[0].fb, 0, 0,
&output.kmsOutput.connector_id, 1, &modeInfo) == -1) {
qErrnoWarning(errno, "Failed to set mode");
return;
}
output.kmsOutput.mode_set = true; // have cleanup() to restore the mode
output.kmsOutput.setPowerState(this, QPlatformScreen::PowerStateOn);
}
}
void QLinuxFbDevice::pageFlipHandler(int fd, unsigned int sequence,
unsigned int tv_sec, unsigned int tv_usec,
void *user_data)
{
Q_UNUSED(fd);
Q_UNUSED(sequence);
Q_UNUSED(tv_sec);
Q_UNUSED(tv_usec);
Output *output = static_cast<Output *>(user_data);
output->backFb = (output->backFb + 1) % BUFFER_COUNT;
}
void QLinuxFbDevice::swapBuffers(Output *output)
{
Framebuffer &fb(output->fb[output->backFb]);
if (drmModePageFlip(fd(), output->kmsOutput.crtc_id, fb.fb, DRM_MODE_PAGE_FLIP_EVENT, output) == -1) {
qErrnoWarning(errno, "Page flip failed");
return;
}
const int fbIdx = output->backFb;
while (output->backFb == fbIdx) {
drmEventContext drmEvent = {
DRM_EVENT_CONTEXT_VERSION,
nullptr,
pageFlipHandler
};
// Blocks until there is something to read on the drm fd
// and calls back pageFlipHandler once the flip completes.
drmHandleEvent(fd(), &drmEvent);
}
}
QLinuxFbDrmScreen::QLinuxFbDrmScreen(const QStringList &args)
: m_screenConfig(nullptr),
m_device(nullptr)
{
Q_UNUSED(args);
}
QLinuxFbDrmScreen::~QLinuxFbDrmScreen()
{
if (m_device) {
m_device->destroyFramebuffers();
m_device->close();
delete m_device;
}
delete m_screenConfig;
}
bool QLinuxFbDrmScreen::initialize()
{
m_screenConfig = new QKmsScreenConfig;
m_device = new QLinuxFbDevice(m_screenConfig);
if (!m_device->open())
return false;
// Discover outputs. Calls back Device::createScreen().
m_device->createScreens();
// Now off to dumb buffer specifics.
m_device->createFramebuffers();
// Do the modesetting.
m_device->setMode();
QLinuxFbDevice::Output *output(m_device->output(0));
mGeometry = QRect(QPoint(0, 0), output->currentRes());
mDepth = 32;
mFormat = QImage::Format_ARGB32;
mPhysicalSize = output->kmsOutput.physical_size;
qCDebug(qLcFbDrm) << mGeometry << mPhysicalSize;
QFbScreen::initializeCompositor();
mCursor = new QFbCursor(this);
return true;
}
QRegion QLinuxFbDrmScreen::doRedraw()
{
const QRegion dirty = QFbScreen::doRedraw();
if (dirty.isEmpty())
return dirty;
QLinuxFbDevice::Output *output(m_device->output(0));
for (int i = 0; i < BUFFER_COUNT; ++i)
output->dirty[i] += dirty;
if (output->fb[output->backFb].wrapper.isNull())
return dirty;
QPainter pntr(&output->fb[output->backFb].wrapper);
// Image has alpha but no need for blending at this stage.
// Do not waste time with the default SourceOver.
pntr.setCompositionMode(QPainter::CompositionMode_Source);
for (const QRect &rect : qAsConst(output->dirty[output->backFb]))
pntr.drawImage(rect, mScreenImage, rect);
pntr.end();
output->dirty[output->backFb] = QRegion();
m_device->swapBuffers(output);
return dirty;
}
QPixmap QLinuxFbDrmScreen::grabWindow(WId wid, int x, int y, int width, int height) const
{
Q_UNUSED(wid);
Q_UNUSED(x);
Q_UNUSED(y);
Q_UNUSED(width);
Q_UNUSED(height);
return QPixmap();
}
QT_END_NAMESPACE

View File

@ -0,0 +1,68 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QLINUXFBDRMSCREEN_H
#define QLINUXFBDRMSCREEN_H
#include <QtFbSupport/private/qfbscreen_p.h>
QT_BEGIN_NAMESPACE
class QKmsScreenConfig;
class QLinuxFbDevice;
class QLinuxFbDrmScreen : public QFbScreen
{
Q_OBJECT
public:
QLinuxFbDrmScreen(const QStringList &args);
~QLinuxFbDrmScreen();
bool initialize() override;
QRegion doRedraw() override;
QPixmap grabWindow(WId wid, int x, int y, int width, int height) const override;
private:
QKmsScreenConfig *m_screenConfig;
QLinuxFbDevice *m_device;
};
QT_END_NAMESPACE
#endif // QLINUXFBDRMSCREEN_H

View File

@ -39,6 +39,9 @@
#include "qlinuxfbintegration.h"
#include "qlinuxfbscreen.h"
#if QT_CONFIG(kms)
#include "qlinuxfbdrmscreen.h"
#endif
#include <QtFontDatabaseSupport/private/qgenericunixfontdatabase_p.h>
#include <QtServiceSupport/private/qgenericunixservices_p.h>
@ -69,10 +72,16 @@
QT_BEGIN_NAMESPACE
QLinuxFbIntegration::QLinuxFbIntegration(const QStringList &paramList)
: m_fontDb(new QGenericUnixFontDatabase),
: m_primaryScreen(nullptr),
m_fontDb(new QGenericUnixFontDatabase),
m_services(new QGenericUnixServices)
{
m_primaryScreen = new QLinuxFbScreen(paramList);
#if QT_CONFIG(kms)
if (qEnvironmentVariableIntValue("QT_QPA_FB_DRM") != 0)
m_primaryScreen = new QLinuxFbDrmScreen(paramList);
#endif
if (!m_primaryScreen)
m_primaryScreen = new QLinuxFbScreen(paramList);
}
QLinuxFbIntegration::~QLinuxFbIntegration()

View File

@ -46,7 +46,7 @@
QT_BEGIN_NAMESPACE
class QAbstractEventDispatcher;
class QLinuxFbScreen;
class QFbScreen;
class QFbVtHandler;
class QLinuxFbIntegration : public QPlatformIntegration, public QPlatformNativeInterface
@ -74,7 +74,7 @@ public:
private:
void createInputHandlers();
QLinuxFbScreen *m_primaryScreen;
QFbScreen *m_primaryScreen;
QPlatformInputContext *m_inputContext;
QScopedPointer<QPlatformFontDatabase> m_fontDb;
QScopedPointer<QPlatformServices> m_services;

View File

@ -54,11 +54,11 @@ public:
QLinuxFbScreen(const QStringList &args);
~QLinuxFbScreen();
bool initialize();
bool initialize() override;
QPixmap grabWindow(WId wid, int x, int y, int width, int height) const Q_DECL_OVERRIDE;
QPixmap grabWindow(WId wid, int x, int y, int width, int height) const override;
QRegion doRedraw() Q_DECL_OVERRIDE;
QRegion doRedraw() override;
private:
QStringList mArgs;