1#include "libmythbase/mythconfig.h"
5#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
6#include <QtEnvironmentVariables>
7#include <QtSystemDetection>
12#include <QGuiApplication>
14#if CONFIG_QTPRIVATEHEADERS
15#include <qpa/qplatformnativeinterface.h>
32#include <drm_fourcc.h>
35#define LOC (QString("%1: ").arg(m_deviceName))
122#if CONFIG_QTPRIVATEHEADERS
123MythDRMPtr MythDRMDevice::FindDevice(
bool NeedPlanes)
133 if (!s_mythDRMDevice.isEmpty())
135 LOG(VB_GENERAL, LOG_INFO, QString(
"Forcing '%1' as DRM device").arg(s_mythDRMDevice));
138 devices.append(s_mythDRMDevice);
141 for (
const auto & dev : std::as_const(devices))
142 if (
auto device =
MythDRMDevice::Create(
nullptr, root + dev, NeedPlanes); device && device->Authenticated())
151 if (CmdLine.
toBool(
"vrr"))
157 auto platform = CmdLine.
toString(
"platform");
158 if (platform.isEmpty())
159 platform = qEnvironmentVariable(
"QT_QPA_PLATFORM");
160 if (!platform.contains(
"eglfs", Qt::CaseInsensitive))
163 LOG(VB_GENERAL, LOG_INFO,
"'eglfs' not explicitly requested. Not configuring DRM.");
168 static const char * s_kmsPlaneIndex =
"QT_QPA_EGLFS_KMS_PLANE_INDEX";
169 static const char * s_kmsPlaneCRTCS =
"QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS";
170 static const char * s_kmsPlaneZpos =
"QT_QPA_EGLFS_KMS_ZPOS";
171 static const char * s_kmsConfigFile =
"QT_QPA_EGLFS_KMS_CONFIG";
172 static const char * s_kmsAtomic =
"QT_QPA_EGLFS_KMS_ATOMIC";
173 static const char * s_kmsSetMode =
"QT_QPA_EGLFS_ALWAYS_SET_MODE";
180 LOG(VB_GENERAL, LOG_INFO, QString(
"Exporting '%1=1'").arg(s_kmsAtomic));
181 if (!qEnvironmentVariableIsSet(s_kmsAtomic))
183 qputenv(s_kmsAtomic,
"1");
187 LOG(VB_GENERAL, LOG_INFO, QString(
"Exporting '%1=1'").arg(s_kmsSetMode));
188 if (!qEnvironmentVariableIsSet(s_kmsSetMode))
190 qputenv(s_kmsSetMode,
"1");
193 bool plane = qEnvironmentVariableIsSet(s_kmsPlaneIndex) ||
194 qEnvironmentVariableIsSet(s_kmsPlaneCRTCS);
195 bool config = qEnvironmentVariableIsSet(s_kmsConfigFile);
196 bool zpos = qEnvironmentVariableIsSet(s_kmsPlaneZpos);
197 bool custom = plane || config || zpos;
202 LOG(VB_GENERAL, LOG_INFO,
"QT_QPA_EGLFS_KMS user overrides detected");
208 LOG(VB_GENERAL, LOG_WARNING,
"Qt eglfs_kms custom plane settings detected"
209 " but planar support not requested.");
214 s_planarRequested =
true;
219 LOG(VB_GENERAL, LOG_WARNING, QString(
"%1 not detected - assuming not required")
220 .arg(s_kmsPlaneZpos));
224 if (!(plane && config))
226 LOG(VB_GENERAL, LOG_WARNING,
"Warning: DRM planar support requested but "
227 "it looks like not all environment variables have been set.");
228 LOG(VB_GENERAL, LOG_INFO,
229 QString(
"Minimum required: %1 and/or %2 for plane index and %3 for alpha blending")
230 .arg(s_kmsPlaneIndex, s_kmsPlaneCRTCS, s_kmsConfigFile));
234 LOG(VB_GENERAL, LOG_INFO,
"DRM planar support enabled for custom user settings");
242 LOG(VB_GENERAL, LOG_INFO,
"Qt eglfs_kms planar video not requested");
249 LOG(VB_GENERAL, LOG_WARNING,
"Failed to open any suitable DRM devices with privileges");
253 if (!(device->m_guiPlane.get() && device->m_guiPlane->m_id &&
254 device->m_videoPlane.get() && device->m_videoPlane->m_id))
256 LOG(VB_GENERAL, LOG_WARNING, QString(
"Failed to deduce correct planes for device '%1'")
257 .arg(drmGetDeviceNameFromFd2(device->GetFD())));
262 auto guiplane = device->m_guiPlane;
266 LOG(VB_GENERAL, LOG_WARNING,
"Failed to find alpha format for GUI. Quitting DRM setup.");
271 QString
confdir = qEnvironmentVariable(
"MYTHCONFDIR");
273 confdir = QDir::homePath() +
"/.mythtv";
277 if (!
file.open(QIODevice::WriteOnly))
279 LOG(VB_GENERAL, LOG_WARNING, QString(
"Failed to open '%1' for writing. Quitting DRM setup.")
284 static const QString s_json =
286 " \"device\": \"%1\",\n"
287 " \"outputs\": [ { \"name\": \"%2\", \"format\": \"%3\", \"mode\": \"%4\" } ]\n"
291 QString wrote = s_json.arg(drmGetDeviceNameFromFd2(device->GetFD()),
293 s_mythDRMVideoMode.isEmpty() ?
"current" : s_mythDRMVideoMode);
295 if (
file.write(qPrintable(wrote)))
297 LOG(VB_GENERAL, LOG_INFO, QString(
"Wrote %1:\r\n%2").arg(
filename, wrote));
298 LOG(VB_GENERAL, LOG_INFO, QString(
"Exporting '%1=%2'").arg(s_kmsConfigFile,
filename));
299 qputenv(s_kmsConfigFile, qPrintable(
filename));
303 auto planeindex = QString::number(guiplane->m_index);
304 auto crtcplane = QString(
"%1,%2").arg(device->m_crtc->m_id).arg(guiplane->m_id);
305 LOG(VB_GENERAL, LOG_INFO, QString(
"Exporting '%1=%2'").arg(s_kmsPlaneIndex, planeindex));
306 LOG(VB_GENERAL, LOG_INFO, QString(
"Exporting '%1=%2'").arg(s_kmsPlaneCRTCS, crtcplane));
307 qputenv(s_kmsPlaneIndex, qPrintable(planeindex));
308 qputenv(s_kmsPlaneCRTCS, qPrintable(crtcplane));
315 auto val = QString::number(std::min(range->m_min + 1, range->m_max));
316 LOG(VB_GENERAL, LOG_INFO, QString(
"Exporting '%1=%2'").arg(s_kmsPlaneZpos, val));
317 qputenv(s_kmsPlaneZpos, qPrintable(val));
322 s_planarRequested =
true;
330 [[maybe_unused]]
bool NeedPlanes)
332#if CONFIG_QTPRIVATEHEADERS
333 auto * app =
dynamic_cast<QGuiApplication *
>(QCoreApplication::instance());
334 if (qScreen && app && QGuiApplication::platformName().contains(
"eglfs", Qt::CaseInsensitive))
338 uint32_t connector = 0;
339 bool useatomic =
false;
340 auto * pni = QGuiApplication::platformNativeInterface();
341 if (
auto * drifd = pni->nativeResourceForIntegration(
"dri_fd"); drifd)
342 fd =
static_cast<int>(
reinterpret_cast<qintptr
>(drifd));
343 if (
auto * crtcid = pni->nativeResourceForScreen(
"dri_crtcid", qScreen); crtcid)
344 crtc =
static_cast<uint32_t
>(
reinterpret_cast<qintptr
>(crtcid));
345 if (
auto * connid = pni->nativeResourceForScreen(
"dri_connectorid", qScreen); connid)
346 connector =
static_cast<uint32_t
>(
reinterpret_cast<qintptr
>(connid));
347 if (
auto * atomic = pni->nativeResourceForIntegration(
"dri_atomic_request"); atomic)
348 if (
auto * request =
reinterpret_cast<drmModeAtomicReq*
>(atomic); request !=
nullptr)
351 LOG(VB_GENERAL, LOG_INFO, QString(
"%1 Qt EGLFS/KMS Fd:%2 Crtc id:%3 Connector id:%4 Atomic: %5")
352 .arg(drmGetDeviceNameFromFd2(fd)).arg(fd).arg(crtc).arg(connector).arg(useatomic));
355 if (fd && crtc && connector)
357 if (
auto result = std::shared_ptr<MythDRMDevice>(
new MythDRMDevice(fd, crtc, connector, useatomic));
358 result.get() && result->m_valid)
369 result.get() && result->m_valid)
377#if CONFIG_QTPRIVATEHEADERS
378 if (
auto result = std::shared_ptr<MythDRMDevice>(
new MythDRMDevice(
Device, NeedPlanes)); result && result->m_valid)
387 const QString root(QString(DRM_DIR_NAME) +
"/");
389 QStringList namefilters;
391 namefilters.append(
"drm*");
393 namefilters.append(
"card*");
395 return { root, dir.entryList(namefilters, QDir::Files | QDir::System) };
407 m_verbose(
Device.isEmpty() ? LOG_INFO : LOG_DEBUG)
431#if CONFIG_QTPRIVATEHEADERS
441 : m_openedDevice(
false),
482 if (s_planarRequested)
485 if (m_videoPlane.get() && m_guiPlane.get() && m_videoPlane->m_id && m_guiPlane->m_id)
486 s_planarSetup =
true;
488 LOG(VB_GENERAL, LOG_INFO,
LOC +
"DRM device retrieved from Qt");
492 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Device setup failed");
495 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Multi-plane setup: Requested: %1 Setup: %2")
496 .arg(s_planarRequested).arg(s_planarSetup));
506 : m_deviceName(std::move(
Device)),
514 m_valid = drmSetClientCap(
m_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) == 0;
517 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to request universal planes");
526 if (!s_mythDRMConnector.isEmpty())
534 if (connector->m_state == DRM_MODE_CONNECTED)
544 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to find connector");
559 m_valid = m_videoPlane.get() && m_guiPlane.get();
669 auto index =
static_cast<size_t>(ModeIndex);
671 if (ModeIndex < 0 || index >=
m_connector->m_modes.size())
675#if CONFIG_QTPRIVATEHEADERS
678 if (crtcid.get() && modeid.get())
683 if (drmModeCreatePropertyBlob(
m_fd, &
m_connector->m_modes[index],
sizeof(drmModeModeInfo), &blobid) == 0)
686 {
m_crtc->m_id, modeid->m_id, blobid }} );
692 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to create mode blob");
710 int ret = drmSetMaster(
m_fd);
715 drm_magic_t magic = 0;
721 const auto * extra =
m_atomic ?
"" :
" but atomic operations required for mode switching";
726 LOG(VB_GENERAL,
m_verbose,
LOC +
"Not authenticated - mode switching not available");
750 LOG(VB_GENERAL,
m_verbose,
LOC +
"Will use first suitable connected device");
759 if (connector->m_state == DRM_MODE_CONNECTED)
761 if (serial.isEmpty())
776 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Matched connector with serial '%1'")
780 static_cast<int>(connector->m_mmHeight));
797 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Ignoring disconnected connector %1")
798 .arg(connector->m_name));
803 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
"No connected connectors");
821 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
"Failed to find crtc for encoder");
829 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
"Initialised");
839 if (devices.isEmpty())
843 if (devices.size() == 1)
844 return root + devices.first();
847 auto serial =
m_screen->serialNumber();
848 if (serial.isEmpty())
854 for (
const auto& dev : std::as_const(devices))
856 QString device = root + dev;
872 int fd = open(
Device.toLocal8Bit().constData(), O_RDWR);
875 drmVersionPtr
version = drmGetVersion(fd);
895#if CONFIG_QTPRIVATEHEADERS
896void MythDRMDevice::MainWindowReady()
928bool MythDRMDevice::QueueAtomics(
const MythAtomics& Atomics)
const
930 auto * app =
dynamic_cast<QGuiApplication *
>(QCoreApplication::instance());
934 auto * pni = QGuiApplication::platformNativeInterface();
935 if (
auto * dri = pni->nativeResourceForIntegration(
"dri_atomic_request"); dri)
937 if (
auto * request =
reinterpret_cast<drmModeAtomicReq*
>(dri); request !=
nullptr)
939 for (
const auto & a : Atomics)
940 drmModeAtomicAddProperty(request, std::get<0>(a), std::get<1>(a), std::get<2>(a));
947void MythDRMDevice::DisableVideoPlane()
949 if (m_videoPlane.get())
951 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Disabling video plane");
952 QueueAtomics( {{ m_videoPlane->m_id, m_videoPlane->m_fbIdProp->m_id, 0 },
953 { m_videoPlane->m_id, m_videoPlane->m_crtcIdProp->m_id, 0 }} );
957DRMPlane MythDRMDevice::GetVideoPlane()
const
962DRMPlane MythDRMDevice::GetGUIPlane()
const
986void MythDRMDevice::AnalysePlanes()
995 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Found %1 planes; %2 for this CRTC")
996 .arg(allplanes.size()).arg(
m_planes.size()));
1005 for (
const auto & plane :
m_planes)
1007 if (plane->m_type == DRM_PLANE_TYPE_PRIMARY)
1009 if (!plane->m_videoFormats.empty())
1010 primaryVideo.emplace_back(plane);
1012 primaryGUI.emplace_back(plane);
1014 else if (plane->m_type == DRM_PLANE_TYPE_OVERLAY)
1016 if (!plane->m_videoFormats.empty())
1017 overlayVideo.emplace_back(plane);
1019 overlayGUI.emplace_back(plane);
1023 LOG(VB_PLAYBACK, LOG_INFO,
LOC + plane->Description());
1027 if (primaryGUI.empty() && overlayGUI.empty())
1031 if (primaryVideo.empty() && overlayVideo.empty())
1033 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Found no planes with video support");
1038 auto nodupe = [](
const auto & Planes,
const auto & Plane)
1040 for (
const auto & plane : Planes)
1041 if (plane->m_id != Plane->m_id)
1050 if (primaryVideo.empty())
1052 m_videoPlane = overlayVideo.front();
1053 if (overlayGUI.empty())
1054 m_guiPlane = primaryGUI.front();
1056 m_guiPlane = nodupe(overlayGUI, m_videoPlane);
1060 m_videoPlane = primaryVideo.front();
1061 if (overlayGUI.empty())
1062 m_guiPlane = nodupe(primaryGUI, m_videoPlane);
1064 m_guiPlane = overlayGUI.front();
1067 if (!m_videoPlane.get())
1069 LOG(VB_GENERAL, LOG_ERR,
LOC +
"No video plane");
1073 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Selected Plane #%1 %2 for video")
1075 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Supported DRM video formats: %1")
1079 if (!m_guiPlane.get())
1081 LOG(VB_GENERAL, LOG_ERR,
LOC +
"No GUI plane");
1085 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