Revision control

Copy as Markdown

// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qvsp2blendingdevice.h"
#include <qeglfskmshelpers_p.h>
#include <QDebug>
#include <QtCore/QLoggingCategory>
#include <drm_fourcc.h>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug)
//TODO: is this the right place for these conversion functions?
static inline uint drmFormatToV4l2PixelFormat(uint drmFormat) {
//ARGB8888 == ABGR32 because linux media list stuff in the opposite order, but the fourcc is the same
Q_ASSERT(DRM_FORMAT_ARGB8888 == V4L2_PIX_FMT_ABGR32);
return drmFormat;
}
static uint drmFormatToMediaBusFormat(uint drmFormat)
{
switch (drmFormat) {
case DRM_FORMAT_RGB888:
return MEDIA_BUS_FMT_RGB888_1X24;
case DRM_FORMAT_BGR888:
return MEDIA_BUS_FMT_BGR888_1X24;
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_XBGR8888:
// return MEDIA_BUS_FMT_RGB888_1X32_PADHI; // doesn't work on renesas m3, just use fallthrough to argb for now
case DRM_FORMAT_RGBX8888:
case DRM_FORMAT_BGRX8888:
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_BGRA8888:
return MEDIA_BUS_FMT_ARGB8888_1X32;
default:
qWarning() << "Unknown drm format" << q_fourccToString(drmFormat) << "defaulting to argb8888";
return MEDIA_BUS_FMT_ARGB8888_1X32;
}
}
QVsp2BlendingDevice::QVsp2BlendingDevice(const QSize &screenSize)
: m_mediaDevice("/dev/media0")
, m_screenSize(screenSize)
{
QLinuxMediaDevice &md = m_mediaDevice;
QString deviceName = md.deviceName();
if (md.model() != QString("VSP2"))
qWarning() << "Unsupported media device model:" << md.model();
if (deviceName != "fe960000.vsp")
qWarning() << "Unknown media device name:" << deviceName;
const int numInputs = 5;
for (int i = 0; i < numInputs; ++i) {
Input input;
input.linkToBru = md.parseLink(QString("'%1 rpf.%2':1 -> '%1 bru':%2").arg(deviceName).arg(i));
input.inputFormatPad = md.parsePad(QString("'%1 rpf.%2':0").arg(deviceName).arg(i));
input.outputFormatPad = md.parsePad(QString("'%1 rpf.%2':1").arg(deviceName).arg(i));
input.outputFormatFd = QLinuxMediaDevice::openVideoDevice(input.outputFormatPad);
input.bruInputFormatPad = md.parsePad(QString("'%1 bru':%2").arg(deviceName).arg(i));
input.rpfInput = new QLinuxMediaDevice::OutputSubDevice(&md, QString("%1 rpf.%2 input").arg(deviceName).arg(i));
m_inputs.append(input);
}
m_wpfOutput = new QLinuxMediaDevice::CaptureSubDevice(&md, QString("%1 wpf.0 output").arg(deviceName));
// Setup links for output
md.enableLink(md.parseLink(QString("'%1 bru':5 -> '%1 wpf.0':0").arg(deviceName)));
md.enableLink(md.parseLink(QString("'%1 wpf.0':1 -> '%1 wpf.0 output':0").arg(deviceName)));
// Output pads
auto bruOutputFormatPad = md.parsePad(QString("'%1 bru':5").arg(deviceName));
auto wpfInputFormatPad = md.parsePad(QString("'%1 wpf.0':0").arg(deviceName));
auto wpfOutputFormatPad = md.parsePad(QString("'%1 wpf.0':1").arg(deviceName));
m_wpfOutput->setFormat(screenSize);
QLinuxMediaDevice::setSubdevFormat(bruOutputFormatPad, screenSize);
QLinuxMediaDevice::setSubdevFormat(wpfInputFormatPad, screenSize);
QLinuxMediaDevice::setSubdevFormat(wpfOutputFormatPad, screenSize);
m_wpfOutput->requestBuffer();
}
bool QVsp2BlendingDevice::enableInput(int i, const QRect &bufferGeometry, uint drmFormat, uint bytesPerLine)
{
qCDebug(qLcEglfsKmsDebug) << "Blend unit: Enabling input" << i;
if (m_inputs[i].enabled) {
qWarning("Vsp2: Input %d already enabled", i);
return false;
}
if (!bufferGeometry.isValid()) { //TODO: bounds checking as well?
qWarning() << "Vsp2: Invalid buffer geometry";
return false;
}
Input &input = m_inputs[i];
if (!m_mediaDevice.enableLink(input.linkToBru))
return false;
uint pixelFormat = drmFormatToV4l2PixelFormat(drmFormat);
if (!setInputFormat(i, bufferGeometry, pixelFormat, bytesPerLine)) {
disableInput(i);
return false;
}
input.rpfInput->requestBuffer();
return true;
}
int QVsp2BlendingDevice::enableInput(const QRect &bufferGeometry, uint drmFormat, uint bytesPerLine)
{
for (int i = 0; i < m_inputs.size(); ++i) {
if (!m_inputs[i].enabled)
return enableInput(i, bufferGeometry, drmFormat, bytesPerLine) ? i : -1;
}
qWarning() << "Vsp2: No more inputs available in blend unit";
return -1;
}
bool QVsp2BlendingDevice::disableInput(int i)
{
qCDebug(qLcEglfsKmsDebug) << "Vsp2: disabling input" << i;
if (!m_inputs[i].enabled) {
qWarning("Vsp2: Input %d already disabled", i);
return false;
}
m_mediaDevice.disableLink(m_inputs[i].linkToBru);
m_inputs[i].rpfInput->clearBuffers();
m_inputs[i].enabled = false;
return true;
}
bool QVsp2BlendingDevice::setInputBuffer(int index, int dmabufFd)
{
Input &input = m_inputs[index];
if (!input.enabled) {
qWarning() << "Vsp2: Can't queue on disabled input" << index;
return false;
}
// Don't queue the buffer yet, store it here and wait until blending
if (input.dmabuf.fd != dmabufFd) {
m_dirty = true;
input.dmabuf.fd = dmabufFd;
}
return true;
}
bool QVsp2BlendingDevice::setInputPosition(int index, const QPoint &position)
{
Input &input = m_inputs[index];
if (input.geometry.topLeft() == position)
return true;
m_dirty = true;
input.geometry.moveTopLeft(position);
return QLinuxMediaDevice::setSubdevCompose(input.bruInputFormatPad, input.geometry);
}
bool QVsp2BlendingDevice::setInputAlpha(int index, qreal alpha)
{
Input &input = m_inputs[index];
if (input.alpha == alpha)
return true;
m_dirty = true;
input.alpha = alpha;
return QLinuxMediaDevice::setSubdevAlpha(input.outputFormatFd, alpha);
}
bool QVsp2BlendingDevice::blend(int outputDmabufFd)
{
if (!m_dirty)
qWarning("Blending without being dirty, should not be necessary");
if (!hasContinuousLayers()) {
qWarning("Vsp2: Can't blend when layers are not enabled in order from 0 without gaps.");
return false;
}
bool queueingFailed = false;
// Queue dma input buffers
for (int i=0; i < m_inputs.size(); ++i) {
auto &input = m_inputs[i];
if (input.enabled) {
if (!input.rpfInput->queueBuffer(input.dmabuf.fd, input.dmabuf.bytesUsed, input.dmabuf.length)) {
qWarning() << "Vsp2: Failed to queue buffer for input" << i
<< "with dmabuf" << input.dmabuf.fd
<< "and size" << input.geometry.size();
queueingFailed = true;
}
}
}
if (queueingFailed) {
qWarning() << "Vsp2: Trying to clean up queued buffers";
for (auto &input : std::as_const(m_inputs)) {
if (input.enabled) {
if (!input.rpfInput->clearBuffers())
qWarning() << "Vsp2: Failed to remove buffers after an aborted blend";
}
}
return false;
}
if (!m_wpfOutput->queueBuffer(outputDmabufFd, m_screenSize)) {
qWarning() << "Vsp2: Failed to queue blending output buffer" << outputDmabufFd << m_screenSize;
return false;
}
if (!streamOn()) {
qWarning() << "Vsp2: Failed to start streaming";
return false;
}
if (!m_wpfOutput->dequeueBuffer()) {
qWarning() << "Vsp2: Failed to dequeue blending output buffer";
if (!streamOff())
qWarning() << "Vsp2: Failed to stop streaming when recovering after a broken blend.";
return false;
}
if (!streamOff()) {
qWarning() << "Vsp2: Failed to stop streaming";
return false;
}
m_dirty = false;
return true;
}
int QVsp2BlendingDevice::numInputs() const
{
return m_inputs.size();
}
bool QVsp2BlendingDevice::hasContinuousLayers() const
{
bool seenDisabled = false;
for (auto &input : std::as_const(m_inputs)) {
if (seenDisabled && input.enabled)
return false;
seenDisabled |= !input.enabled;
}
return m_inputs[0].enabled;
}
bool QVsp2BlendingDevice::streamOn()
{
for (auto &input : m_inputs) {
if (input.enabled) {
if (!input.rpfInput->streamOn()) {
//TODO: perhaps it's better to try to continue with the other inputs?
return false;
}
}
}
return m_wpfOutput->streamOn();
}
bool QVsp2BlendingDevice::streamOff()
{
bool succeeded = m_wpfOutput->streamOff();
for (auto &input : m_inputs) {
if (input.enabled)
succeeded &= input.rpfInput->streamOff();
}
return succeeded;
}
bool QVsp2BlendingDevice::setInputFormat(int i, const QRect &bufferGeometry, uint pixelFormat, uint bytesPerLine)
{
Input &input = m_inputs[i];
Q_ASSERT(bufferGeometry.isValid());
const uint bpp = 4; //TODO: don't hardcode bpp, get it from pixelFormat?
input.enabled = true;
input.geometry = bufferGeometry;
input.dmabuf.bytesUsed = bpp * static_cast<uint>(bufferGeometry.width()) * static_cast<uint>(bufferGeometry.height());
input.dmabuf.length = static_cast<uint>(bufferGeometry.height()) * bytesPerLine;
const QSize size = bufferGeometry.size();
if (!input.rpfInput->setFormat(size, pixelFormat, bytesPerLine)) // rpf.x input
return false;
const uint mediaBusFormat = drmFormatToMediaBusFormat(pixelFormat);
if (!QLinuxMediaDevice::setSubdevFormat(input.inputFormatPad, size, mediaBusFormat)) // rpf.x:0
return false;
if (!QLinuxMediaDevice::setSubdevFormat(input.outputFormatPad, size, mediaBusFormat)) // rpf.x:1
return false;
if (!QLinuxMediaDevice::setSubdevFormat(input.bruInputFormatPad, size, mediaBusFormat)) // bru:x
return false;
if (!QLinuxMediaDevice::setSubdevCrop(input.inputFormatPad, QRect(QPoint(0, 0), size)))
return false;
if (!QLinuxMediaDevice::setSubdevCompose(input.bruInputFormatPad, bufferGeometry))
return false;
return true;
}
QT_END_NAMESPACE