1#include "libmythbase/mythconfig.h"
5#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
6#include <QtSystemDetection>
11#include <QGuiApplication>
13#if CONFIG_QTPRIVATEHEADERS
14#include <qpa/qplatformnativeinterface.h>
31#include <drm_fourcc.h>
34#define LOC (QString("%1: ").arg(m_deviceName))
121#if CONFIG_QTPRIVATEHEADERS
122MythDRMPtr MythDRMDevice::FindDevice(
bool NeedPlanes)
132 if (!s_mythDRMDevice.isEmpty())
134 LOG(VB_GENERAL, LOG_INFO, QString(
"Forcing '%1' as DRM device").arg(s_mythDRMDevice));
137 devices.append(s_mythDRMDevice);
140 for (
const auto & dev : std::as_const(devices))
141 if (
auto device =
MythDRMDevice::Create(
nullptr, root + dev, NeedPlanes); device && device->Authenticated())
150 if (CmdLine.
toBool(
"vrr"))
156 auto platform = CmdLine.
toString(
"platform");
157 if (platform.isEmpty())
158 platform = qEnvironmentVariable(
"QT_QPA_PLATFORM");
159 if (!platform.contains(
"eglfs", Qt::CaseInsensitive))
162 LOG(VB_GENERAL, LOG_INFO,
"'eglfs' not explicitly requested. Not configuring DRM.");
167 static const char * s_kmsPlaneIndex =
"QT_QPA_EGLFS_KMS_PLANE_INDEX";
168 static const char * s_kmsPlaneCRTCS =
"QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS";
169 static const char * s_kmsPlaneZpos =
"QT_QPA_EGLFS_KMS_ZPOS";
170 static const char * s_kmsConfigFile =
"QT_QPA_EGLFS_KMS_CONFIG";
171 static const char * s_kmsAtomic =
"QT_QPA_EGLFS_KMS_ATOMIC";
172 static const char * s_kmsSetMode =
"QT_QPA_EGLFS_ALWAYS_SET_MODE";
179 LOG(VB_GENERAL, LOG_INFO, QString(
"Exporting '%1=1'").arg(s_kmsAtomic));
180 setenv(s_kmsAtomic,
"1", 0);
183 LOG(VB_GENERAL, LOG_INFO, QString(
"Exporting '%1=1'").arg(s_kmsSetMode));
184 setenv(s_kmsSetMode,
"1", 0);
186 bool plane = qEnvironmentVariableIsSet(s_kmsPlaneIndex) ||
187 qEnvironmentVariableIsSet(s_kmsPlaneCRTCS);
188 bool config = qEnvironmentVariableIsSet(s_kmsConfigFile);
189 bool zpos = qEnvironmentVariableIsSet(s_kmsPlaneZpos);
190 bool custom = plane || config || zpos;
195 LOG(VB_GENERAL, LOG_INFO,
"QT_QPA_EGLFS_KMS user overrides detected");
201 LOG(VB_GENERAL, LOG_WARNING,
"Qt eglfs_kms custom plane settings detected"
202 " but planar support not requested.");
207 s_planarRequested =
true;
212 LOG(VB_GENERAL, LOG_WARNING, QString(
"%1 not detected - assuming not required")
213 .arg(s_kmsPlaneZpos));
217 if (!(plane && config))
219 LOG(VB_GENERAL, LOG_WARNING,
"Warning: DRM planar support requested but "
220 "it looks like not all environment variables have been set.");
221 LOG(VB_GENERAL, LOG_INFO,
222 QString(
"Minimum required: %1 and/or %2 for plane index and %3 for alpha blending")
223 .arg(s_kmsPlaneIndex, s_kmsPlaneCRTCS, s_kmsConfigFile));
227 LOG(VB_GENERAL, LOG_INFO,
"DRM planar support enabled for custom user settings");
235 LOG(VB_GENERAL, LOG_INFO,
"Qt eglfs_kms planar video not requested");
242 LOG(VB_GENERAL, LOG_WARNING,
"Failed to open any suitable DRM devices with privileges");
246 if (!(device->m_guiPlane.get() && device->m_guiPlane->m_id &&
247 device->m_videoPlane.get() && device->m_videoPlane->m_id))
249 LOG(VB_GENERAL, LOG_WARNING, QString(
"Failed to deduce correct planes for device '%1'")
250 .arg(drmGetDeviceNameFromFd2(device->GetFD())));
255 auto guiplane = device->m_guiPlane;
259 LOG(VB_GENERAL, LOG_WARNING,
"Failed to find alpha format for GUI. Quitting DRM setup.");
264 QString
confdir = qEnvironmentVariable(
"MYTHCONFDIR");
266 confdir = QDir::homePath() +
"/.mythtv";
270 if (!
file.open(QIODevice::WriteOnly))
272 LOG(VB_GENERAL, LOG_WARNING, QString(
"Failed to open '%1' for writing. Quitting DRM setup.")
277 static const QString s_json =
279 " \"device\": \"%1\",\n"
280 " \"outputs\": [ { \"name\": \"%2\", \"format\": \"%3\", \"mode\": \"%4\" } ]\n"
284 QString wrote = s_json.arg(drmGetDeviceNameFromFd2(device->GetFD()),
286 s_mythDRMVideoMode.isEmpty() ?
"current" : s_mythDRMVideoMode);
288 if (
file.write(qPrintable(wrote)))
290 LOG(VB_GENERAL, LOG_INFO, QString(
"Wrote %1:\r\n%2").arg(
filename, wrote));
291 LOG(VB_GENERAL, LOG_INFO, QString(
"Exporting '%1=%2'").arg(s_kmsConfigFile,
filename));
296 auto planeindex = QString::number(guiplane->m_index);
297 auto crtcplane = QString(
"%1,%2").arg(device->m_crtc->m_id).arg(guiplane->m_id);
298 LOG(VB_GENERAL, LOG_INFO, QString(
"Exporting '%1=%2'").arg(s_kmsPlaneIndex, planeindex));
299 LOG(VB_GENERAL, LOG_INFO, QString(
"Exporting '%1=%2'").arg(s_kmsPlaneCRTCS, crtcplane));
300 setenv(s_kmsPlaneIndex, qPrintable(planeindex), 1);
301 setenv(s_kmsPlaneCRTCS, qPrintable(crtcplane), 1);
308 auto val = QString::number(std::min(range->m_min + 1, range->m_max));
309 LOG(VB_GENERAL, LOG_INFO, QString(
"Exporting '%1=%2'").arg(s_kmsPlaneZpos, val));
310 setenv(s_kmsPlaneZpos, qPrintable(val), 1);
315 s_planarRequested =
true;
323 [[maybe_unused]]
bool NeedPlanes)
325#if CONFIG_QTPRIVATEHEADERS
326 auto * app =
dynamic_cast<QGuiApplication *
>(QCoreApplication::instance());
327 if (qScreen && app && QGuiApplication::platformName().contains(
"eglfs", Qt::CaseInsensitive))
331 uint32_t connector = 0;
332 bool useatomic =
false;
333 auto * pni = QGuiApplication::platformNativeInterface();
334 if (
auto * drifd = pni->nativeResourceForIntegration(
"dri_fd"); drifd)
335 fd =
static_cast<int>(
reinterpret_cast<qintptr
>(drifd));
336 if (
auto * crtcid = pni->nativeResourceForScreen(
"dri_crtcid", qScreen); crtcid)
337 crtc =
static_cast<uint32_t
>(
reinterpret_cast<qintptr
>(crtcid));
338 if (
auto * connid = pni->nativeResourceForScreen(
"dri_connectorid", qScreen); connid)
339 connector =
static_cast<uint32_t
>(
reinterpret_cast<qintptr
>(connid));
340 if (
auto * atomic = pni->nativeResourceForIntegration(
"dri_atomic_request"); atomic)
341 if (
auto * request =
reinterpret_cast<drmModeAtomicReq*
>(atomic); request !=
nullptr)
344 LOG(VB_GENERAL, LOG_INFO, QString(
"%1 Qt EGLFS/KMS Fd:%2 Crtc id:%3 Connector id:%4 Atomic: %5")
345 .arg(drmGetDeviceNameFromFd2(fd)).arg(fd).arg(crtc).arg(connector).arg(useatomic));
348 if (fd && crtc && connector)
350 if (
auto result = std::shared_ptr<MythDRMDevice>(
new MythDRMDevice(fd, crtc, connector, useatomic));
351 result.get() && result->m_valid)
362 result.get() && result->m_valid)
370#if CONFIG_QTPRIVATEHEADERS
371 if (
auto result = std::shared_ptr<MythDRMDevice>(
new MythDRMDevice(
Device, NeedPlanes)); result && result->m_valid)
380 const QString root(QString(DRM_DIR_NAME) +
"/");
382 QStringList namefilters;
384 namefilters.append(
"drm*");
386 namefilters.append(
"card*");
388 return { root, dir.entryList(namefilters, QDir::Files | QDir::System) };
400 m_verbose(
Device.isEmpty() ? LOG_INFO : LOG_DEBUG)
424#if CONFIG_QTPRIVATEHEADERS
434 : m_openedDevice(
false),
475 if (s_planarRequested)
478 if (m_videoPlane.get() && m_guiPlane.get() && m_videoPlane->m_id && m_guiPlane->m_id)
479 s_planarSetup =
true;
481 LOG(VB_GENERAL, LOG_INFO,
LOC +
"DRM device retrieved from Qt");
485 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Device setup failed");
488 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Multi-plane setup: Requested: %1 Setup: %2")
489 .arg(s_planarRequested).arg(s_planarSetup));
499 : m_deviceName(std::move(
Device)),
507 m_valid = drmSetClientCap(
m_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) == 0;
510 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to request universal planes");
519 if (!s_mythDRMConnector.isEmpty())
527 if (connector->m_state == DRM_MODE_CONNECTED)
537 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to find connector");
552 m_valid = m_videoPlane.get() && m_guiPlane.get();
662 auto index =
static_cast<size_t>(ModeIndex);
664 if (ModeIndex < 0 || index >=
m_connector->m_modes.size())
668#if CONFIG_QTPRIVATEHEADERS
671 if (crtcid.get() && modeid.get())
676 if (drmModeCreatePropertyBlob(
m_fd, &
m_connector->m_modes[index],
sizeof(drmModeModeInfo), &blobid) == 0)
679 {
m_crtc->m_id, modeid->m_id, blobid }} );
685 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to create mode blob");
703 int ret = drmSetMaster(
m_fd);
708 drm_magic_t magic = 0;
714 const auto * extra =
m_atomic ?
"" :
" but atomic operations required for mode switching";
719 LOG(VB_GENERAL,
m_verbose,
LOC +
"Not authenticated - mode switching not available");
743 LOG(VB_GENERAL,
m_verbose,
LOC +
"Will use first suitable connected device");
752 if (connector->m_state == DRM_MODE_CONNECTED)
754 if (serial.isEmpty())
769 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Matched connector with serial '%1'")
773 static_cast<int>(connector->m_mmHeight));
790 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Ignoring disconnected connector %1")
791 .arg(connector->m_name));
796 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
"No connected connectors");
814 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
"Failed to find crtc for encoder");
822 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
"Initialised");
832 if (devices.isEmpty())
836 if (devices.size() == 1)
837 return root + devices.first();
840 auto serial =
m_screen->serialNumber();
841 if (serial.isEmpty())
847 for (
const auto& dev : std::as_const(devices))
849 QString device = root + dev;
865 int fd = open(
Device.toLocal8Bit().constData(), O_RDWR);
868 drmVersionPtr
version = drmGetVersion(fd);
888#if CONFIG_QTPRIVATEHEADERS
889void MythDRMDevice::MainWindowReady()
921bool MythDRMDevice::QueueAtomics(
const MythAtomics& Atomics)
const
923 auto * app =
dynamic_cast<QGuiApplication *
>(QCoreApplication::instance());
927 auto * pni = QGuiApplication::platformNativeInterface();
928 if (
auto * dri = pni->nativeResourceForIntegration(
"dri_atomic_request"); dri)
930 if (
auto * request =
reinterpret_cast<drmModeAtomicReq*
>(dri); request !=
nullptr)
932 for (
const auto & a : Atomics)
933 drmModeAtomicAddProperty(request, std::get<0>(a), std::get<1>(a), std::get<2>(a));
940void MythDRMDevice::DisableVideoPlane()
942 if (m_videoPlane.get())
944 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Disabling video plane");
945 QueueAtomics( {{ m_videoPlane->m_id, m_videoPlane->m_fbIdProp->m_id, 0 },
946 { m_videoPlane->m_id, m_videoPlane->m_crtcIdProp->m_id, 0 }} );
950DRMPlane MythDRMDevice::GetVideoPlane()
const
955DRMPlane MythDRMDevice::GetGUIPlane()
const
979void MythDRMDevice::AnalysePlanes()
988 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Found %1 planes; %2 for this CRTC")
989 .arg(allplanes.size()).arg(
m_planes.size()));
1000 if (plane->m_type == DRM_PLANE_TYPE_PRIMARY)
1002 if (!plane->m_videoFormats.empty())
1003 primaryVideo.emplace_back(plane);
1005 primaryGUI.emplace_back(plane);
1007 else if (plane->m_type == DRM_PLANE_TYPE_OVERLAY)
1009 if (!plane->m_videoFormats.empty())
1010 overlayVideo.emplace_back(plane);
1012 overlayGUI.emplace_back(plane);
1016 LOG(VB_PLAYBACK, LOG_INFO,
LOC + plane->Description());
1020 if (primaryGUI.empty() && overlayGUI.empty())
1024 if (primaryVideo.empty() && overlayVideo.empty())
1026 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Found no planes with video support");
1031 auto nodupe = [](
const auto & Planes,
const auto & Plane)
1033 for (
const auto & plane : Planes)
1034 if (plane->m_id != Plane->m_id)
1043 if (primaryVideo.empty())
1045 m_videoPlane = overlayVideo.front();
1046 if (overlayGUI.empty())
1047 m_guiPlane = primaryGUI.front();
1049 m_guiPlane = nodupe(overlayGUI, m_videoPlane);
1053 m_videoPlane = primaryVideo.front();
1054 if (overlayGUI.empty())
1055 m_guiPlane = nodupe(primaryGUI, m_videoPlane);
1057 m_guiPlane = overlayGUI.front();
1060 if (!m_videoPlane.get())
1062 LOG(VB_GENERAL, LOG_ERR,
LOC +
"No video plane");
1066 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Selected Plane #%1 %2 for video")
1068 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Supported DRM video formats: %1")
1072 if (!m_guiPlane.get())
1074 LOG(VB_GENERAL, LOG_ERR,
LOC +
"No GUI plane");
1078 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Selected Plane #%1 %2 for GUI")
A device containing images (ie. USB stick, CD, storage group etc)
Parent class for defining application command line parsers.
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
uint toUInt(const QString &key) const
Returns stored QVariant as an unsigned integer, falling to default if not provided.
static DRMConns GetConnectors(int FD)
static DRMConn GetConnectorByName(const DRMConns &Connectors, const QString &Name)
static DRMConn GetConnector(const DRMConns &Connectors, uint32_t Id)
static DRMCrtcs GetCrtcs(int FD)
static DRMCrtc GetCrtc(const DRMCrtcs &Crtcs, uint32_t Id)
static MythDRMPtr Create(QScreen *qScreen, const QString &Device=QString(), bool NeedPlanes=true)
Create a MythDRMDevice instance.
QSize GetPhysicalSize() const
double m_adjustedRefreshRate
MythDRMDevice(QScreen *qScreen, const QString &Device=QString())
Constructor used when we have no DRM handles from Qt.
QScreen * GetScreen() const
static std::tuple< QString, QStringList > GetDeviceList()
static bool ConfirmDevice(const QString &Device)
bool Authenticated() const
QString GetSerialNumber() const
void Authenticate()
Attempt to acquire privileged DRM access.
bool CanSwitchModes() const
double GetRefreshRate() const
Return the refresh rate we think is in use.
bool SwitchMode(int ModeIndex)
Set the required video mode.
QSize GetResolution() const
DRMConn GetConnector() const
const DRMModes & GetModes() const
static DRMEncs GetEncoders(int FD)
static DRMEnc GetEncoder(const DRMEncs &Encoders, uint32_t Id)
static QString FormatsToString(const FOURCCVec &Formats)
static DRMPlanes GetPlanes(int FD, int CRTCFilter=-1)
static bool HasOverlayFormat(const FOURCCVec &Formats)
Enusure list of supplied formats contains a format that is suitable for OpenGL/Vulkan.
static QString FormatToString(uint32_t Format)
static QString PlaneTypeToString(uint64_t Type)
static uint32_t GetAlphaFormat(const FOURCCVec &Formats)
static DRMProp GetProperty(const QString &Name, const DRMProps &Properties)
static void ForceFreeSync(const MythDRMPtr &Device, bool Enable)
Force FreeSync on or off before the main app is started.
QStringList SerialNumbers() const
std::shared_ptr< class MythDRMConnector > DRMConn
std::shared_ptr< class MythDRMCrtc > DRMCrtc
static constexpr const char * DRM_QUIET
std::shared_ptr< class MythDRMDevice > MythDRMPtr
std::vector< MythAtomic > MythAtomics
std::vector< DRMMode > DRMModes
#define DRM_FORMAT_INVALID
std::vector< DRMPlane > DRMPlanes
std::shared_ptr< class MythDRMPlane > DRMPlane
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
VERBOSE_PREAMBLE Most true