diff options
Diffstat (limited to 'src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2screen.cpp')
-rw-r--r-- | src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2screen.cpp | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2screen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2screen.cpp new file mode 100644 index 0000000000..88b401c920 --- /dev/null +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qeglfskmsvsp2screen.cpp @@ -0,0 +1,365 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> +** Copyright (C) 2016 Pelagicore AG +** 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$ +** +****************************************************************************/ + +#include "qeglfskmsvsp2screen.h" +#include "qeglfskmsvsp2device.h" +#include <qeglfskmshelpers.h> + +#include <QtCore/QLoggingCategory> +#include <QtGui/private/qguiapplication_p.h> + +#include <drm_fourcc.h> +#include <xf86drm.h> +#include <fcntl.h> + +//TODO move to qlinuxmediadevice? +#include <cstdlib> //this needs to go before mediactl/mediactl.h because it uses size_t without including it +extern "C" { +#include <mediactl/mediactl.h> +#include <mediactl/v4l2subdev.h> //needed in header for default arguments +} + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) + +static inline uint32_t drmFormatToGbmFormat(uint32_t drmFormat) +{ + Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888); + return drmFormat; +} + +static inline uint32_t gbmFormatToDrmFormat(uint32_t gbmFormat) //TODO: is this needed? +{ + Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888); + return gbmFormat; +} + +void QEglFSKmsVsp2Screen::dmaBufferDestroyedHandler(gbm_bo *gbmBo, void *data) +{ + Q_UNUSED(gbmBo); //TODO: do we need to do something with it after all? + auto dmabuf = static_cast<DmaBuffer *>(data); + //TODO: need some extra cleanup here? + delete dmabuf; +} + +QEglFSKmsVsp2Screen::DmaBuffer *QEglFSKmsVsp2Screen::dmaBufferForGbmBuffer(gbm_bo *gbmBo) +{ + auto existingBuffer = static_cast<DmaBuffer *>(gbm_bo_get_user_data(gbmBo)); + if (existingBuffer) + return existingBuffer; + + uint32_t handle = gbm_bo_get_handle(gbmBo).u32; + QScopedPointer<DmaBuffer> fb(new DmaBuffer); + int ret = drmPrimeHandleToFD(device()->fd(), handle, DRM_CLOEXEC, &fb->dmabufFd); + if (ret) { + qWarning("Failed to create dmabuf file descriptor for buffer with drmPrimeHandleToFd"); + return nullptr; + } + + gbm_bo_set_user_data(gbmBo, fb.data(), dmaBufferDestroyedHandler); + return fb.take(); +} + +QEglFSKmsVsp2Screen::QEglFSKmsVsp2Screen(QKmsDevice *device, const QKmsOutput &output) + : QEglFSKmsScreen(device, output) + , m_blender(new Blender(this)) +{ +} + +gbm_surface *QEglFSKmsVsp2Screen::createSurface() +{ + if (!m_gbmSurface) { + uint32_t gbmFormat = drmFormatToGbmFormat(m_output.drm_format); + qCDebug(qLcEglfsKmsDebug, "Creating gbm_surface for screen %s with format 0x%x", qPrintable(name()), gbmFormat); + Q_ASSERT(rawGeometry().isValid()); + m_gbmSurface = gbm_surface_create(static_cast<QEglFSKmsVsp2Device *>(device())->gbmDevice(), + uint(rawGeometry().width()), + uint(rawGeometry().height()), + gbmFormat, + GBM_BO_USE_RENDERING); + } + + if (!m_blendDevice) + initVsp2(); + + if (m_frameBuffers[m_backFb].dmabufFd == -1) + initDumbFrameBuffers(); + + return m_gbmSurface; // not owned, gets destroyed in QEglFSKmsGbmIntegration::destroyNativeWindow() +} + +void QEglFSKmsVsp2Screen::resetSurface() +{ + m_gbmSurface = nullptr; +} + +void QEglFSKmsVsp2Screen::initDumbFrameBuffers() +{ + for (auto &fb : m_frameBuffers) + initDumbFrameBuffer(fb); +} + +void QEglFSKmsVsp2Screen::initVsp2() +{ + qCDebug(qLcEglfsKmsDebug, "Initializing Vsp2 hardware"); + const QSize screenSize = rawGeometry().size(); + m_blendDevice.reset(new QVsp2BlendingDevice(screenSize)); + + // Enable input for main buffer drawn by the compositor (always on) + const uint bytesPerLine = uint(screenSize.width()) * 4; //TODO: is this ok? + bool formatSet = m_blendDevice->enableInput(m_qtLayer, QRect(QPoint(), screenSize), m_output.drm_format, bytesPerLine); + if (!formatSet) { + const uint32_t fallbackFormat = DRM_FORMAT_ARGB8888; + qWarning() << "Failed to set format" << q_fourccToString(m_output.drm_format) + << "falling back to" << q_fourccToString(fallbackFormat); + formatSet = m_blendDevice->enableInput(m_qtLayer, QRect(QPoint(), screenSize), fallbackFormat, bytesPerLine); + if (!formatSet) + qFatal("Failed to set vsp2 blending format"); + } +} + +int QEglFSKmsVsp2Screen::addLayer(int dmabufFd, const QSize &size, const QPoint &position, uint drmPixelFormat, uint bytesPerLine) +{ + int index = m_blendDevice->enableInput(QRect(position, size), drmPixelFormat, bytesPerLine); + if (index != -1) { + m_blendDevice->setInputBuffer(index, dmabufFd); + int id = index; //TODO: maybe make id something independent of layer index? + qCDebug(qLcEglfsKmsDebug) << "Enabled extra layer for vsp input" << index; + return id; + } + qWarning() << "Failed to add layer"; + return -1; +} + +void QEglFSKmsVsp2Screen::setLayerBuffer(int id, int dmabufFd) +{ + int layerIndex = id; + m_blendDevice->setInputBuffer(layerIndex, dmabufFd); + if (!m_blendScheduled) { + m_blendScheduled = true; + QCoreApplication::postEvent(m_blender.data(), new QEvent(QEvent::User)); + } +} + +void QEglFSKmsVsp2Screen::setLayerPosition(int id, const QPoint &position) +{ + int layerIndex = id; + m_blendDevice->setInputPosition(layerIndex, position); +} + +void QEglFSKmsVsp2Screen::setLayerAlpha(int id, qreal alpha) +{ + int layerIndex = id; + m_blendDevice->setInputAlpha(layerIndex, alpha); +} + +bool QEglFSKmsVsp2Screen::removeLayer(int id) +{ + int layerIndex = id; + m_blendDevice->disableInput(layerIndex); + return true; +} + +void QEglFSKmsVsp2Screen::addBlendListener(void (*callback)()) +{ + m_blendFinishedCallbacks.append(callback); +} + +void QEglFSKmsVsp2Screen::flip() +{ + if (!m_gbmSurface) { + qWarning("Cannot sync before platform init!"); + return; + } + + if (!m_blendScheduled && !m_nextGbmBo) { + m_nextGbmBo = gbm_surface_lock_front_buffer(m_gbmSurface); + + if (!m_nextGbmBo) { + qWarning("Could not lock GBM surface front buffer!"); + return; + } + + m_blendScheduled = true; + QCoreApplication::postEvent(m_blender.data(), new QEvent(QEvent::User)); + } +} + +void QEglFSKmsVsp2Screen::ensureModeSet() +{ + const int driFd = device()->fd(); + QKmsOutput &op(output()); + if (!op.mode_set) { + int ret = drmModeSetCrtc(driFd, + op.crtc_id, + m_frameBuffers[m_backFb].drmBufferId, + 0, 0, + &op.connector_id, 1, + &op.modes[op.mode]); + + if (ret == -1) { + qErrnoWarning(errno, "Could not set DRM mode!"); + } else { + op.mode_set = true; + setPowerState(PowerStateOn); + } + } +} + +void QEglFSKmsVsp2Screen::doDrmFlip() +{ + QKmsOutput &op(output()); + const int driFd = device()->fd(); + + int ret = drmModePageFlip(driFd, + op.crtc_id, + m_frameBuffers[m_backFb].drmBufferId, + 0, + this); + + if (ret) + qErrnoWarning("Could not queue DRM page flip on screen %s", qPrintable(name())); + + m_backFb = (m_backFb + 1) % 2; +} + +void QEglFSKmsVsp2Screen::blendAndFlipDrm() +{ + m_blendScheduled = false; + if (!m_nextGbmBo && !m_blendDevice->isDirty()) + return; + + FrameBuffer &backBuffer = m_frameBuffers[m_backFb]; + if (backBuffer.dmabufFd == -1) + initDumbFrameBuffers(); + + if (m_nextGbmBo) { + Q_ASSERT(m_nextGbmBo != m_currentGbmBo); + int compositorBackBufferDmaFd = dmaBufferForGbmBuffer(m_nextGbmBo)->dmabufFd; + m_blendDevice->setInputBuffer(m_qtLayer, compositorBackBufferDmaFd); + + if (m_currentGbmBo) + gbm_surface_release_buffer(m_gbmSurface, m_currentGbmBo); + m_currentGbmBo = m_nextGbmBo; + m_nextGbmBo = nullptr; + } + + ensureModeSet(); + + if (!m_blendDevice) + initVsp2(); + + if (!m_blendDevice->isDirty()) + return; + + const int driFd = device()->fd(); + drmVBlank vBlank; + vBlank.request.type = static_cast<drmVBlankSeqType>(DRM_VBLANK_RELATIVE | DRM_VBLANK_SECONDARY); //TODO: make secondary configurable (or automatic) + vBlank.request.sequence = 1; + vBlank.request.signal = 0; + drmWaitVBlank(driFd, &vBlank); + + if (!m_blendDevice->blend(backBuffer.dmabufFd)) + qWarning() << "Vsp2: blending failed"; + + for (auto cb : m_blendFinishedCallbacks) + cb(); + + doDrmFlip(); +} + +void QEglFSKmsVsp2Screen::initDumbFrameBuffer(FrameBuffer &fb) +{ + QKmsOutput &op(output()); + const uint32_t width = op.modes[op.mode].hdisplay; + const uint32_t height = op.modes[op.mode].vdisplay; + + Q_ASSERT(fb.dmabufFd == -1); + const uint32_t dumbBufferFlags = 0; //TODO: do we want some flags? What's possible? + const uint32_t bpp = 32; + + drm_mode_create_dumb creq = { + height, + width, + bpp, + dumbBufferFlags, + 0, 0, 0 //return values + }; + + const int driFd = device()->fd(); + if (drmIoctl(driFd, DRM_IOCTL_MODE_CREATE_DUMB, &creq) == -1) + qFatal("Failed to create dumb buffer: %s", strerror(errno)); + +// uint32_t handles[4] = { gbm_bo_get_handle(bo).u32 }; +// uint32_t strides[4] = { gbm_bo_get_stride(bo) }; +// uint32_t offsets[4] = { 0 }; +// uint32_t pixelFormat = gbmFormatToDrmFormat(gbm_bo_get_format(bo)); + + //TODO: support additional planes + uint32_t gbmBoHandles[4] = { creq.handle, 0, 0, 0 }; + uint32_t strides[4] = { creq.pitch, 0, 0, 0 }; + uint32_t offsets[4] = { 0 }; + uint32_t pixelFormat = DRM_FORMAT_ARGB8888; //TODO: support other formats? + uint32_t drmFlags = 0; + + qCDebug(qLcEglfsKmsDebug) << "Adding FB" << QSize(width, height) + << ", DRM format" << q_fourccToString(pixelFormat); + int ret = drmModeAddFB2(driFd, width, height, pixelFormat, + gbmBoHandles, strides, offsets, &fb.drmBufferId, drmFlags); + if (ret) + qFatal("drmModeAddFB2 failed: %s", strerror(errno)); + + drmPrimeHandleToFD(driFd, gbmBoHandles[0], DRM_CLOEXEC, &fb.dmabufFd); +} + +bool QEglFSKmsVsp2Screen::Blender::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::User: + m_screen->blendAndFlipDrm(); + return true; + default: + return QObject::event(event); + } +} + +QT_END_NAMESPACE |