diff --git a/src/platformsupport/fbconvenience/qfbscreen.cpp b/src/platformsupport/fbconvenience/qfbscreen.cpp index 757995a1c0..2b4498157c 100644 --- a/src/platformsupport/fbconvenience/qfbscreen.cpp +++ b/src/platformsupport/fbconvenience/qfbscreen.cpp @@ -190,6 +190,11 @@ void QFbScreen::setGeometry(const QRect &rect) resizeMaximizedWindows(); } +bool QFbScreen::initialize() +{ + return true; +} + QRegion QFbScreen::doRedraw() { const QPoint screenOffset = mGeometry.topLeft(); diff --git a/src/platformsupport/fbconvenience/qfbscreen_p.h b/src/platformsupport/fbconvenience/qfbscreen_p.h index 82a660ea09..1c27a941cc 100644 --- a/src/platformsupport/fbconvenience/qfbscreen_p.h +++ b/src/platformsupport/fbconvenience/qfbscreen_p.h @@ -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; } diff --git a/src/plugins/platforms/linuxfb/linuxfb.pro b/src/plugins/platforms/linuxfb/linuxfb.pro index e2fa31211d..d3a4476f80 100644 --- a/src/plugins/platforms/linuxfb/linuxfb.pro +++ b/src/plugins/platforms/linuxfb/linuxfb.pro @@ -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 diff --git a/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp b/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp new file mode 100644 index 0000000000..bdf2634642 --- /dev/null +++ b/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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 &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 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 &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(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(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 diff --git a/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.h b/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.h new file mode 100644 index 0000000000..50a9576798 --- /dev/null +++ b/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.h @@ -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 + +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 diff --git a/src/plugins/platforms/linuxfb/qlinuxfbintegration.cpp b/src/plugins/platforms/linuxfb/qlinuxfbintegration.cpp index c1c235588e..ce193bdf90 100644 --- a/src/plugins/platforms/linuxfb/qlinuxfbintegration.cpp +++ b/src/plugins/platforms/linuxfb/qlinuxfbintegration.cpp @@ -39,6 +39,9 @@ #include "qlinuxfbintegration.h" #include "qlinuxfbscreen.h" +#if QT_CONFIG(kms) +#include "qlinuxfbdrmscreen.h" +#endif #include #include @@ -69,10 +72,16 @@ QT_BEGIN_NAMESPACE QLinuxFbIntegration::QLinuxFbIntegration(const QStringList ¶mList) - : 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() diff --git a/src/plugins/platforms/linuxfb/qlinuxfbintegration.h b/src/plugins/platforms/linuxfb/qlinuxfbintegration.h index e48a1bae28..9934a8cd54 100644 --- a/src/plugins/platforms/linuxfb/qlinuxfbintegration.h +++ b/src/plugins/platforms/linuxfb/qlinuxfbintegration.h @@ -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 m_fontDb; QScopedPointer m_services; diff --git a/src/plugins/platforms/linuxfb/qlinuxfbscreen.h b/src/plugins/platforms/linuxfb/qlinuxfbscreen.h index 1e98191569..c7ce455e6a 100644 --- a/src/plugins/platforms/linuxfb/qlinuxfbscreen.h +++ b/src/plugins/platforms/linuxfb/qlinuxfbscreen.h @@ -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;