MythTV  master
mythdrmdevice.cpp
Go to the documentation of this file.
1 // Qt
2 #include <QDir>
3 #include <QMutex>
4 #include <QtGlobal>
5 #include <QScreen>
6 #include <QGuiApplication>
7 
8 #ifdef USING_QTPRIVATEHEADERS
9 #include <qpa/qplatformnativeinterface.h>
10 #endif
11 
12 // MythTV
13 #include "mythedid.h"
18 
19 // Std
20 #include <unistd.h>
21 #include <fcntl.h>
22 
23 // libdrm
24 extern "C" {
25 #include <drm_fourcc.h>
26 }
27 
28 #define LOC (QString("%1: ").arg(m_deviceName))
29 
30 /* A DRM based display is only useable if neither X nor Wayland are running; as
31  * X or Wayland will hold the master/privileged DRM connection and without it
32  * there is little we can do (no video mode setting etc) - and both X and Wayland
33  * provide their own relevant, higher level API's.
34  *
35  * If X or Wayland aren't running we may have privileged access *if* MythTV was
36  * compiled with Qt private header support; this is the only way to retrieve
37  * the master DRM file descriptor and use DRM atomic operations.
38  *
39  * Having master privileges allows us to:-
40  *
41  * 1. Set the video mode
42  * 2. Improve performance on SoCs by rendering YUV video directly to the framebuffer
43  * 3. Implement HDR support and/or setup 10bit output
44  * 4. Enable/disable FreeSync
45  *
46  * There are a variety of use cases, depending on hardware, user preferences and
47  * compile time support (and assuming neither X or Wayland are running):-
48  *
49  * 1. No master DRM privileges. Use DRM as a last resort and for information only.
50  * 2. Have master privileges but only need video mode switching (judder free) and maybe
51  * HDR support. This is likely with more modern graphics, and e.g. VAAPI decoding
52  * support, where we do not need the extra performance of rendering YUV frames
53  * directly, it is potentially unstable and the additional complexity of
54  * setting up Qt is not needed.
55  * 3. We require direct YUV rendering to video planes for performance. This requires
56  * us to configure Qt in various ways to ensure we can use the correct plane
57  * for video and Qt uses an appropriate plane for OpenGL/Vulkan, correctly
58  * configured to ensure alpha blending works.
59  * 4. Option 3 plus potential optimisation of 4K rendering (Qt allows us to configure
60  * our display with a 1080P GUI framebuffer but a 4K video framebuffer) and/or
61  * forcing of full video mode switching (we are currently limited to switching
62  * the refresh rate only but if we force the first modeswitch to the maximum
63  * (e.g. 4K) we could then manipulate windowing to allow smaller modes).
64  *
65  * Options 1 and 2 require no additional configuration; if we have a privileged
66  * connection then we can use mode switching, if not we have a 'dumb' connection.
67  *
68  * Option 4 is not yet implemented.
69  *
70  * Option 3 requires us to 'configure' Qt by way of environment variables and a
71  * configuration file *before* we start the QGuiApplication instance *and* before
72  * we have a database connection. Given the complexity of this setup and the
73  * possibility for it to go horribly wrong, this option must be explicitly enabled
74  * via an environment variable.
75  *
76  * Typically, DRM drivers provide 3 types of plane; primary, overlay and cursor.
77  * A typical implementation provides one of each for each CRTC; with the primary
78  * plane usually providing video support. Different vendors do however approach
79  * setup differently; so there may be multiple primary planes, multiple overlay
80  * planes, no cursor planes (not relevant to us) and video support may only be
81  * provided in the overlay plane(s) - in which case we need 'zpos' support so
82  * that we can manipulate the rendering order...
83  *
84  * Qt's eglfs_kms implementation will typically grab the first suitable primary plane
85  * for its own use but that is often the only plane with YUV format support (which we
86  * need for video playback support) and we want to overlay the UI on top of the video.
87  *
88  * Fortunately QKmsDevice respects the QT_QPA_EGLFS_KMS_PLANE_INDEX environment
89  * variable (since Qt 5.9) which allows us to tell QKmsDevice to use the overlay
90  * plane for its OpenGL implementation; hence allowing us to grab the primary plane later.
91  * With Qt5.15 and later, we can also use QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS which
92  * is much more flexible. We set both here and if Qt supports QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS
93  * it will override the older version and be ignored otherwise.
94  *
95  * So that hopefully gets the video and UI in the correct planes but...
96  *
97  * Furthermore, we must tell Qt to use a transparent format for the overlay plane
98  * so that it does not obscure the video plane *and* ensure that we clear our
99  * OpenGL/Vulkan overlay framebuffer with an alpha of zero. This is however
100  * handled not by the Qt environment variables but by the KMS json configuration
101  * file which is pointed to by QT_QPA_EGLFS_KMS_CONFIG. So we create or modifiy
102  * that file here and direct Qt to it.
103  *
104  * Finally, if the video and GUI planes are of the same type, we need to tell Qt
105  * to set the zpos for the GUI plane to ensure it is on top of the video.
106  *
107  * So we need to set 4 environment variables and create one config file...
108  *
109  * \note If *any* of the 4 environment variables have been set by the user then
110  * we assume the user has a custom solution and do nothing.
111  *
112  * \note This is called immediately after application startup; all we have for
113  * reference is the MythCommandLineParsers instance and any environment variables.
114 */
115 #ifdef USING_QTPRIVATEHEADERS
116 MythDRMPtr MythDRMDevice::FindDevice(bool NeedPlanes)
117 {
118  // Retrieve possible devices and analyse them.
119  // We are only interested in authenticated devices with a connected connector.
120  // We can only use one device, so if there are multiple devices (RPI4 only?) then
121  // take the first with a connected connector (which on the RPI4 at least is
122  // usually the better choice anyway).
123  auto [root, devices] = GetDeviceList();
124 
125  // Allow the user to specify the device
126  if (!s_mythDRMDevice.isEmpty())
127  {
128  LOG(VB_GENERAL, LOG_INFO, QString("Forcing '%1' as DRM device").arg(s_mythDRMDevice));
129  root.clear();
130  devices.clear();
131  devices.append(s_mythDRMDevice);
132  }
133 
134  for (const auto & dev : devices)
135  if (auto device = MythDRMDevice::Create(nullptr, root + dev, NeedPlanes); device && device->Authenticated())
136  return device;
137 
138  return nullptr;
139 }
140 
141 void MythDRMDevice::SetupDRM(const MythCommandLineParser& CmdLine)
142 {
143  // Try and enable/disable FreeSync if requested by the user
144  if (CmdLine.toBool("vrr"))
145  MythDRMVRR::ForceFreeSync(FindDevice(false), CmdLine.toUInt("vrr") > 0);
146 
147 #if QT_VERSION >= QT_VERSION_CHECK(5,12,0)
148  // Return early if eglfs is not *explicitly* requested via the command line or environment.
149  // Note: On some setups it is not necessary to explicitly request eglfs for Qt to use it.
150  // Note: Not sure which takes precedent in Qt or what happens if they are different.
151  auto platform = CmdLine.toString("platform");
152  if (platform.isEmpty())
153 #if QT_VERSION < QT_VERSION_CHECK(5,10,0)
154  platform = QString(qgetenv("QT_QPA_PLATFORM"));
155 #else
156  platform = qEnvironmentVariable("QT_QPA_PLATFORM");
157 #endif
158  if (!platform.contains("eglfs", Qt::CaseInsensitive))
159  {
160  // Log something just in case it reminds someone to enable eglfs
161  LOG(VB_GENERAL, LOG_INFO, "'eglfs' not explicitly requested. Not configuring DRM.");
162  return;
163  }
164 
165  // Qt environment variables
166  static const char * s_kmsPlaneIndex = "QT_QPA_EGLFS_KMS_PLANE_INDEX"; // Qt 5.9
167  static const char * s_kmsPlaneCRTCS = "QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS"; // Qt 5.15
168  static const char * s_kmsPlaneZpos = "QT_QPA_EGLFS_KMS_ZPOS"; // Qt 5.12
169  static const char * s_kmsConfigFile = "QT_QPA_EGLFS_KMS_CONFIG";
170  static const char * s_kmsAtomic = "QT_QPA_EGLFS_KMS_ATOMIC"; // Qt 5.12
171  static const char * s_kmsSetMode = "QT_QPA_EGLFS_ALWAYS_SET_MODE";
172 
173  // The following 2 environment variables are forced regardless of any existing
174  // environment settings etc. They are just needed and should have no adverse
175  // impacts
176 
177  // If we are using eglfs_kms we want atomic operations. No effect on other plugins.
178  LOG(VB_GENERAL, LOG_INFO, QString("Exporting '%1=1'").arg(s_kmsAtomic));
179  setenv(s_kmsAtomic, "1", 0);
180 
181  // Seems to fix occasional issues. Again no impact on other plugins.
182  LOG(VB_GENERAL, LOG_INFO, QString("Exporting '%1=1'").arg(s_kmsSetMode));
183  setenv(s_kmsSetMode, "1", 0);
184 
185  bool plane = qEnvironmentVariableIsSet(s_kmsPlaneIndex) ||
186  qEnvironmentVariableIsSet(s_kmsPlaneCRTCS);
187  bool config = qEnvironmentVariableIsSet(s_kmsConfigFile);
188  bool zpos = qEnvironmentVariableIsSet(s_kmsPlaneZpos);
189  bool custom = plane || config || zpos;
190 
191  // Don't attempt to override any custom user configuration
192  if (custom)
193  {
194  LOG(VB_GENERAL, LOG_INFO, "QT_QPA_EGLFS_KMS user overrides detected");
195 
196  if (!s_mythDRMVideo)
197  {
198  // It is likely the user is customising planar video; so warn if planar
199  // video has not been enabled
200  LOG(VB_GENERAL, LOG_WARNING, "Qt eglfs_kms custom plane settings detected"
201  " but planar support not requested.");
202  }
203  else
204  {
205  // Planar support requested so we must signal to our future self
206  s_planarRequested = true;
207 
208  // We don't know whether zpos support is required at this point
209  if (!zpos)
210  {
211  LOG(VB_GENERAL, LOG_WARNING, QString("%1 not detected - assuming not required")
212  .arg(s_kmsPlaneZpos));
213  }
214 
215  // Warn if we do no see all of the known required config
216  if (!(plane && config))
217  {
218  LOG(VB_GENERAL, LOG_WARNING, "Warning: DRM planar support requested but "
219  "it looks like not all environment variables have been set.");
220  LOG(VB_GENERAL, LOG_INFO,
221  QString("Minimum required: %1 and/or %2 for plane index and %3 for alpha blending")
222  .arg(s_kmsPlaneIndex).arg(s_kmsPlaneCRTCS).arg(s_kmsConfigFile));
223  }
224  else
225  {
226  LOG(VB_GENERAL, LOG_INFO, "DRM planar support enabled for custom user settings");
227  }
228  }
229  return;
230  }
231 
232  if (!s_mythDRMVideo)
233  {
234  LOG(VB_GENERAL, LOG_INFO, "Qt eglfs_kms planar video not requested");
235  return;
236  }
237 
238  MythDRMPtr device = FindDevice();
239  if (!device)
240  {
241  LOG(VB_GENERAL, LOG_WARNING, "Failed to open any suitable DRM devices with privileges");
242  return;
243  }
244 
245  if (!(device->m_guiPlane.get() && device->m_guiPlane->m_id &&
246  device->m_videoPlane.get() && device->m_videoPlane->m_id))
247  {
248  LOG(VB_GENERAL, LOG_WARNING, QString("Failed to deduce correct planes for device '%1'")
249  .arg(drmGetDeviceNameFromFd2(device->GetFD())));
250  return;
251  }
252 
253  // We have a valid, authenticated device with a connected display and validated planes
254  auto guiplane = device->m_guiPlane;
255  auto format = MythDRMPlane::GetAlphaFormat(guiplane->m_formats);
256  if (format == DRM_FORMAT_INVALID)
257  {
258  LOG(VB_GENERAL, LOG_WARNING, "Failed to find alpha format for GUI. Quitting DRM setup.");
259  return;
260  }
261 
262  // N.B. No MythDirs setup yet so mimic the conf dir setup
263 #if QT_VERSION < QT_VERSION_CHECK(5,10,0)
264  QString confdir = QString(qgetenv("MYTHCONFDIR"));
265 #else
266  QString confdir = qEnvironmentVariable("MYTHCONFDIR");
267 #endif
268  if (confdir.isEmpty())
269  confdir = QDir::homePath() + "/.mythtv";
270 
271  auto filename = confdir + "/eglfs_kms_config.json";
272  QFile file(filename);
273  if (!file.open(QIODevice::WriteOnly))
274  {
275  LOG(VB_GENERAL, LOG_WARNING, QString("Failed to open '%1' for writing. Quitting DRM setup.")
276  .arg(filename));
277  return;
278  }
279 
280  static const QString s_json =
281  "{\n"
282  " \"device\": \"%1\",\n"
283  " \"outputs\": [ { \"name\": \"%2\", \"format\": \"%3\", \"mode\": \"%4\" } ]\n"
284  "}\n";
285 
286  // Note: mode is not sanitised
287  auto wrote = qPrintable(s_json.arg(drmGetDeviceNameFromFd2(device->GetFD()))
288  .arg(device->m_connector->m_name).arg(MythDRMPlane::FormatToString(format).toLower())
289  .arg(s_mythDRMVideoMode.isEmpty() ? "current" : s_mythDRMVideoMode));
290 
291  if (file.write(wrote))
292  {
293  LOG(VB_GENERAL, LOG_INFO, QString("Wrote %1:\r\n%2").arg(filename).arg(wrote));
294  LOG(VB_GENERAL, LOG_INFO, QString("Exporting '%1=%2'").arg(s_kmsConfigFile).arg(filename));
295  setenv(s_kmsConfigFile, qPrintable(filename), 1);
296  }
297  file.close();
298 
299  auto planeindex = QString::number(guiplane->m_index);
300  auto crtcplane = QString("%1,%2").arg(device->m_crtc->m_id).arg(guiplane->m_id);
301  LOG(VB_GENERAL, LOG_INFO, QString("Exporting '%1=%2'").arg(s_kmsPlaneIndex).arg(planeindex));
302  LOG(VB_GENERAL, LOG_INFO, QString("Exporting '%1=%2'").arg(s_kmsPlaneCRTCS).arg(crtcplane));
303  setenv(s_kmsPlaneIndex, qPrintable(planeindex), 1);
304  setenv(s_kmsPlaneCRTCS, qPrintable(crtcplane), 1);
305 
306  // Set the zpos if supported
307  if (auto zposp = MythDRMProperty::GetProperty("zpos", guiplane->m_properties); zposp.get())
308  {
309  if (auto range = dynamic_cast<MythDRMRangeProperty*>(zposp.get()); range)
310  {
311  auto val = QString::number(std::min(range->m_min + 1, range->m_max));
312  LOG(VB_GENERAL, LOG_INFO, QString("Exporting '%1=%2'").arg(s_kmsPlaneZpos).arg(val));
313  setenv(s_kmsPlaneZpos, qPrintable(val), 1);
314  }
315  }
316 
317  // Signal to our future self that we did request some Qt DRM configuration
318  s_planarRequested = true;
319 #endif
320 }
321 #endif
322 
326 MythDRMPtr MythDRMDevice::Create(QScreen *qScreen, const QString &Device, bool NeedPlanes)
327 {
328 #ifdef USING_QTPRIVATEHEADERS
329  if (qScreen && qGuiApp && qGuiApp->platformName().contains("eglfs", Qt::CaseInsensitive))
330  {
331  int fd = 0;
332  uint32_t crtc = 0;
333  uint32_t connector = 0;
334  bool useatomic = false;
335  if (auto * drifd = qGuiApp->platformNativeInterface()->nativeResourceForIntegration("dri_fd"); drifd)
336  fd = static_cast<int>(reinterpret_cast<qintptr>(drifd));
337  if (auto * crtcid = qGuiApp->platformNativeInterface()->nativeResourceForScreen("dri_crtcid", qScreen); crtcid)
338  crtc = static_cast<uint32_t>(reinterpret_cast<qintptr>(crtcid));
339  if (auto * connid = qGuiApp->platformNativeInterface()->nativeResourceForScreen("dri_connectorid", qScreen); connid)
340  connector = static_cast<uint32_t>(reinterpret_cast<qintptr>(connid));
341  if (auto * atomic = qGuiApp->platformNativeInterface()->nativeResourceForIntegration("dri_atomic_request"); atomic)
342  if (auto * request = reinterpret_cast<drmModeAtomicReq*>(atomic); request != nullptr)
343  useatomic = true;
344 
345  LOG(VB_GENERAL, LOG_INFO, QString("%1 Qt EGLFS/KMS Fd:%2 Crtc id:%3 Connector id:%4 Atomic: %5")
346  .arg(drmGetDeviceNameFromFd2(fd)).arg(fd).arg(crtc).arg(connector).arg(useatomic));
347 
348  // We have all the details we need from Qt
349  if (fd && crtc && connector)
350  {
351  if (auto result = std::shared_ptr<MythDRMDevice>(new MythDRMDevice(fd, crtc, connector, useatomic));
352  result.get() && result->m_valid)
353  {
354  return result;
355  }
356  }
357  }
358 #endif
359 
360  if (qScreen)
361  {
362  if (auto result = std::shared_ptr<MythDRMDevice>(new MythDRMDevice(qScreen, Device));
363  result.get() && result->m_valid)
364  {
365  return result;
366  }
367  // N.B. Don't fall through here.
368  return nullptr;
369  }
370 
371 #ifdef USING_QTPRIVATEHEADERS
372  if (auto result = std::shared_ptr<MythDRMDevice>(new MythDRMDevice(Device, NeedPlanes)); result && result->m_valid)
373  return result;
374 #else
375  (void)NeedPlanes;
376 #endif
377  return nullptr;
378 }
379 
380 std::tuple<QString, QStringList> MythDRMDevice::GetDeviceList()
381 {
382  // Iterate over /dev/dri/card*
383  const QString root(QString(DRM_DIR_NAME) + "/");
384  QDir dir(root);
385  QStringList namefilters;
386 #ifdef __OpenBSD__
387  namefilters.append("drm*");
388 #else
389  namefilters.append("card*");
390 #endif
391  return { root, dir.entryList(namefilters, QDir::Files | QDir::System) };
392 }
393 
400 MythDRMDevice::MythDRMDevice(QScreen* qScreen, const QString& Device)
401  : m_screen(qScreen),
402  m_deviceName(Device),
403  m_verbose(Device.isEmpty() ? LOG_INFO : LOG_DEBUG)
404 {
405  // This is hackish workaround to suppress logging when it isn't required
406  if (m_deviceName == DRM_QUIET)
407  {
408  m_deviceName.clear();
409  m_verbose = LOG_DEBUG;
410  }
411 
412  if (!Open())
413  {
414  LOG(VB_GENERAL, m_verbose, LOC + "Failed to open");
415  return;
416  }
417 
418  if (!Initialise())
419  return;
420 
421  m_valid = true;
422 
423  // Will almost certainly fail
424  Authenticate();
425 }
426 
427 #if defined (USING_QTPRIVATEHEADERS)
428 
436 MythDRMDevice::MythDRMDevice(int Fd, uint32_t CrtcId, uint32_t ConnectorId, bool Atomic)
437  : m_openedDevice(false),
438  m_fd(Fd),
439  m_atomic(Atomic)
440 {
441  if (m_fd < 1)
442  return;
443 
444  // Get the device name for debugging
445  m_deviceName = drmGetDeviceNameFromFd2(m_fd);
446 
447  // This should always succeed here...
448  Authenticate();
449 
450  // Retrieve all objects
451  Load();
452 
453  // Get correct connector and Crtc
456  m_valid = m_connector.get() && m_crtc.get();
457 
458  if (m_valid)
459  {
460  // Get physical size
461  m_physicalSize = QSize(static_cast<int>(m_connector->m_mmWidth),
462  static_cast<int>(m_connector->m_mmHeight));
463  // Get EDID
464  auto prop = MythDRMProperty::GetProperty("EDID", m_connector->m_properties);
465  if (auto blob = dynamic_cast<MythDRMBlobProperty*>(prop.get()); blob)
466  {
467  MythEDID edid(blob->m_blob);
468  if (edid.Valid())
469  m_edid = edid;
470  }
471 
472  // Get resolution and rate
473  m_resolution = QSize(static_cast<int>(m_crtc->m_width), static_cast<int>(m_crtc->m_height));
474  if (m_crtc->m_mode.get())
475  m_refreshRate = m_crtc->m_mode->m_rate;
476 
477  // Only setup video and gui planes if requested
478  if (s_planarRequested)
479  {
480  AnalysePlanes();
481  if (m_videoPlane.get() && m_guiPlane.get() && m_videoPlane->m_id && m_guiPlane->m_id)
482  s_planarSetup = true;
483  }
484  LOG(VB_GENERAL, LOG_INFO, LOC + "DRM device retrieved from Qt");
485  }
486  else
487  {
488  LOG(VB_GENERAL, LOG_ERR, LOC + "Device setup failed");
489  }
490 
491  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Multi-plane setup: Requested: %1 Setup: %2")
492  .arg(s_planarRequested).arg(s_planarSetup));
493 }
494 
501 MythDRMDevice::MythDRMDevice(const QString& Device, bool NeedPlanes)
502  : m_deviceName(Device),
503  m_atomic(true), // Just squashes some logging
504  m_verbose(LOG_INFO)
505 {
506  if (!Open())
507  return;
508  Authenticate();
509  if (!m_authenticated)
510  return;
511  m_valid = drmSetClientCap(m_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) == 0;
512  if (!m_valid)
513  {
514  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to request universal planes");
515  return;
516  }
517  Load();
518  m_valid = false;
519 
520  // Find a user suggested connector or the first connected
521  if (!s_mythDRMConnector.isEmpty())
522  {
524  }
525  else
526  {
527  for (const auto & connector : m_connectors)
528  {
529  if (connector->m_state == DRM_MODE_CONNECTED)
530  {
531  m_connector = connector;
532  break;
533  }
534  }
535  }
536 
537  if (!m_connector)
538  {
539  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to find connector");
540  return;
541  }
542 
543  auto encoder = MythDRMEncoder::GetEncoder(m_encoders, m_connector->m_encoderId);
544  if (!encoder.get())
545  return;
546 
547  m_crtc = MythDRMCrtc::GetCrtc(m_crtcs, encoder->m_crtcId);
548  if (!m_crtc)
549  return;
550 
551  if (NeedPlanes)
552  {
553  AnalysePlanes();
554  m_valid = m_videoPlane.get() && m_guiPlane.get();
555  }
556  else
557  {
558  m_valid = true;
559  }
560 }
561 #endif
562 
564 {
565  if (m_fd && m_openedDevice)
566  {
567  close(m_fd);
568  LOG(VB_GENERAL, m_verbose, LOC + "Closed");
569  }
570 }
571 
573 {
574  if (m_deviceName.isEmpty())
576  if (m_deviceName.isEmpty())
577  return false;
578  m_fd = open(m_deviceName.toLocal8Bit().constData(), O_RDWR);
579  return m_fd > 0;
580 }
581 
583 {
584  return m_valid && m_authenticated;
585 }
586 
588 {
589  return m_atomic;
590 }
591 
593 {
594  return m_fd;
595 }
596 
598 {
599  return m_serialNumber;
600 }
601 
602 QScreen* MythDRMDevice::GetScreen() const
603 {
604  return m_screen;
605 }
606 
608 {
609  return m_resolution;
610 }
611 
613 {
614  return m_physicalSize;
615 }
616 
618 {
619  return m_edid;
620 }
621 
633 {
634  if (m_adjustedRefreshRate > 1.0)
635  return m_adjustedRefreshRate;
636  return m_refreshRate;
637 }
638 
640 {
641  return m_valid && m_authenticated && m_atomic;
642 }
643 
645 {
646  static const DRMModes empty;
648  return m_connector->m_modes;
649  return empty;
650 }
651 
658 bool MythDRMDevice::SwitchMode(int ModeIndex)
659 {
660  if (!(m_authenticated && m_atomic && m_connector.get() && m_crtc.get()))
661  return false;
662 
663  auto index = static_cast<size_t>(ModeIndex);
664 
665  if (ModeIndex < 0 || index >= m_connector->m_modes.size())
666  return false;
667 
668  bool result = false;
669 #ifdef USING_QTPRIVATEHEADERS
670  auto crtcid = MythDRMProperty::GetProperty("crtc_id", m_connector->m_properties);
671  auto modeid = MythDRMProperty::GetProperty("mode_id", m_crtc->m_properties);
672  if (crtcid.get() && modeid.get())
673  {
674  uint32_t blobid = 0;
675  // Presumably blobid does not need to be released? Can't find any documentation but
676  // there is the matching drmModeDestroyPropertyBlob...
677  if (drmModeCreatePropertyBlob(m_fd, &m_connector->m_modes[index], sizeof(drmModeModeInfo), &blobid) == 0)
678  {
679  QueueAtomics( {{ m_connector->m_id, crtcid->m_id, m_crtc->m_id },
680  { m_crtc->m_id, modeid->m_id, blobid }} );
681  m_adjustedRefreshRate = m_connector->m_modes[index]->m_rate;
682  result = true;
683  }
684  else
685  {
686  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create mode blob");
687  }
688  }
689 #endif
690  return result;
691 }
692 
700 {
701  if (!m_fd || m_authenticated)
702  return;
703 
704  int ret = drmSetMaster(m_fd);
705  m_authenticated = ret >= 0;
706 
707  if (!m_authenticated)
708  {
709  drm_magic_t magic = 0;
710  m_authenticated = drmGetMagic(m_fd, &magic) == 0 && drmAuthMagic(m_fd, magic) == 0;
711  }
712 
713  if (m_authenticated)
714  {
715  const auto * extra = m_atomic ? "" : " but atomic operations required for mode switching";
716  LOG(VB_GENERAL, m_verbose, LOC + "Authenticated" + extra);
717  }
718  else
719  {
720  LOG(VB_GENERAL, m_verbose, LOC + "Not authenticated - mode switching not available");
721  }
722 }
723 
725 {
729 }
730 
732 {
733  if (!m_fd)
734  return false;
735 
736  // Find the serial number of the display we are connected to
737  auto serial = m_screen ? m_screen->serialNumber() : "";
738  if (m_screen && serial.isEmpty())
739  {
740  // No serial number either means an older version of Qt or the EDID
741  // is not available for some reason - in which case there is no point
742  // in trying to use it anyway.
743  LOG(VB_GENERAL, m_verbose, LOC + "QScreen has no serial number.");
744  LOG(VB_GENERAL, m_verbose, LOC + "Will use first suitable connected device");
745  }
746 
747  // Retrieve full details for the device
748  Load();
749 
750  // Find connector
751  for (const auto & connector : m_connectors)
752  {
753  if (connector->m_state == DRM_MODE_CONNECTED)
754  {
755  if (serial.isEmpty())
756  {
757  m_connector = connector;
758  break;
759  }
760 
761  // Does the connected display have the serial number we are looking for?
762  if (const auto edidprop = MythDRMProperty::GetProperty("EDID", connector->m_properties); edidprop.get())
763  {
764  MythEDID edid;
765  if (auto * blob = dynamic_cast<MythDRMBlobProperty*>(edidprop.get()); blob)
766  edid = MythEDID(blob->m_blob);
767 
768  if (edid.Valid() && edid.SerialNumbers().contains(serial))
769  {
770  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Matched connector with serial '%1'")
771  .arg(serial));
772  m_connector = connector;
773  m_physicalSize = QSize(static_cast<int>(connector->m_mmWidth),
774  static_cast<int>(connector->m_mmHeight));
775  m_serialNumber = serial;
776  m_edid = edid;
777  break;
778  }
779 
780  if (!edid.Valid())
781  LOG(VB_GENERAL, m_verbose, LOC + "Connected device has invalid EDID");
782 
783  if (m_connector && !m_serialNumber.isEmpty())
784  break;
785  }
786  else
787  {
788  LOG(VB_GENERAL, m_verbose, LOC + "Connected device has no EDID");
789  }
790  }
791  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Ignoring disconnected connector %1")
792  .arg(connector->m_name));
793  }
794 
795  if (!m_connector.get())
796  {
797  LOG(VB_GENERAL, LOG_DEBUG, LOC + "No connected connectors");
798  return false;
799  }
800 
801  LOG(VB_GENERAL, m_verbose, LOC + QString("Selected connector %1").arg(m_connector->m_name));
802 
803  // Find the encoder for the connector
804  auto encoder = MythDRMEncoder::GetEncoder(m_encoders, m_connector->m_encoderId);
805  if (!encoder)
806  {
807  LOG(VB_GENERAL, m_verbose, LOC + QString("Failed to find encoder for %1").arg(m_connector->m_name));
808  return false;
809  }
810 
811  // Find the CRTC for the encoder
812  m_crtc = MythDRMCrtc::GetCrtc(m_crtcs, encoder->m_crtcId);
813  if (!m_crtc)
814  {
815  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Failed to find crtc for encoder");
816  return false;
817  }
818 
819  m_resolution = QSize(static_cast<int>(m_crtc->m_width), static_cast<int>(m_crtc->m_height));
820  if (m_crtc->m_mode.get())
821  m_refreshRate = m_crtc->m_mode->m_rate;
822 
823  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Initialised");
824  return true;
825 }
826 
828 {
829  if (!m_screen)
830  return QString();
831 
832  auto [root, devices] = GetDeviceList();
833  if (devices.isEmpty())
834  return {};
835 
836  // Only one device - return it
837  if (devices.size() == 1)
838  return root + devices.first();
839 
840  // Use the serial number from the current QScreen to select a suitable device
841  auto serial = m_screen->serialNumber();
842  if (serial.isEmpty())
843  {
844  LOG(VB_GENERAL, m_verbose, LOC + "No serial number to search for");
845  return QString();
846  }
847 
848  for (const auto& dev : qAsConst(devices))
849  {
850  QString device = root + dev;
851  if (!ConfirmDevice(device))
852  {
853  LOG(VB_GENERAL, m_verbose, LOC + "Failed to confirm device");
854  continue;
855  }
856  MythDRMDevice drmdevice(m_screen, device);
857  if (drmdevice.GetSerialNumber() == serial)
858  return device;
859  }
860  return QString();
861 }
862 
864 {
865  bool result = false;
866  int fd = open(Device.toLocal8Bit().constData(), O_RDWR);
867  if (fd < 0)
868  return result;
869  drmVersionPtr version = drmGetVersion(fd);
870  if (version)
871  {
872  drmFreeVersion(version);
873  result = true;
874  }
875  close(fd);
876  return result;
877 }
878 
880 {
881  return m_crtc;
882 }
883 
885 {
886  return m_connector;
887 }
888 
889 #if defined (USING_QTPRIVATEHEADERS)
890 void MythDRMDevice::MainWindowReady()
891 {
892  // This is causing issues - disabled for now
893  //DisableVideoPlane();
894 
895  // Temporarily disabled - this is informational only
896  /*
897  // Confirm GUI plane format now that Qt is setup
898  if (m_guiPlane.get())
899  {
900  // TODO Add methods to retrieve up to date property values rather than
901  // create new objects
902  if (auto plane = MythDRMPlane::Create(m_fd, m_guiPlane->m_id, 0); plane)
903  {
904  if (auto guifb = MythDRMFramebuffer::Create(m_fd, plane->m_fbId); guifb)
905  {
906  if (MythDRMPlane::HasOverlayFormat({ guifb->m_format }))
907  {
908  LOG(VB_GENERAL, LOG_INFO, LOC + QString("GUI alpha format confirmed (%1)")
909  .arg(MythDRMPlane::FormatToString(guifb->m_format)));
910  }
911  else
912  {
913  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("GUI plane has no alpha (%1)")
914  .arg(MythDRMPlane::FormatToString(guifb->m_format)));
915  }
916  }
917  }
918  }
919  */
920 }
921 
922 bool MythDRMDevice::QueueAtomics(const MythAtomics& Atomics)
923 {
924  if (!(m_atomic && m_authenticated && qGuiApp))
925  return false;
926 
927  if (auto * dri = qGuiApp->platformNativeInterface()->nativeResourceForIntegration("dri_atomic_request"); dri)
928  {
929  if (auto * request = reinterpret_cast<drmModeAtomicReq*>(dri); request != nullptr)
930  {
931  for (const auto & a : Atomics)
932  drmModeAtomicAddProperty(request, std::get<0>(a), std::get<1>(a), std::get<2>(a));
933  return true;
934  }
935  }
936  return false;
937 }
938 
939 void MythDRMDevice::DisableVideoPlane()
940 {
941  if (m_videoPlane.get())
942  {
943  LOG(VB_GENERAL, LOG_INFO, LOC + "Disabling video plane");
944  QueueAtomics( {{ m_videoPlane->m_id, m_videoPlane->m_fbIdProp->m_id, 0 },
945  { m_videoPlane->m_id, m_videoPlane->m_crtcIdProp->m_id, 0 }} );
946  }
947 }
948 
949 DRMPlane MythDRMDevice::GetVideoPlane() const
950 {
951  return m_videoPlane;
952 }
953 
954 DRMPlane MythDRMDevice::GetGUIPlane() const
955 {
956  return m_guiPlane;
957 }
958 
978 void MythDRMDevice::AnalysePlanes()
979 {
980  if (!(m_fd && m_crtc && m_crtc->m_index > -1))
981  return;
982 
983  // Find our planes
984  auto allplanes = MythDRMPlane::GetPlanes(m_fd);
986 
987  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found %1 planes; %2 for this CRTC")
988  .arg(allplanes.size()).arg(m_planes.size()));
989 
990  DRMPlanes primaryVideo;
991  DRMPlanes overlayVideo;
992  DRMPlanes primaryGUI;
993  DRMPlanes overlayGUI;
994 
995  for (const auto & plane : m_planes)
996  {
997  if (plane->m_type == DRM_PLANE_TYPE_PRIMARY)
998  {
999  if (!plane->m_videoFormats.empty())
1000  primaryVideo.emplace_back(plane);
1001  if (MythDRMPlane::HasOverlayFormat(plane->m_formats))
1002  primaryGUI.emplace_back(plane);
1003  }
1004  else if (plane->m_type == DRM_PLANE_TYPE_OVERLAY)
1005  {
1006  if (!plane->m_videoFormats.empty())
1007  overlayVideo.emplace_back(plane);
1008  if (MythDRMPlane::HasOverlayFormat(plane->m_formats))
1009  overlayGUI.emplace_back(plane);
1010  }
1011 
1012  if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_INFO))
1013  LOG(VB_PLAYBACK, LOG_INFO, LOC + plane->Description());
1014  }
1015 
1016  // This *should not happen*
1017  if (primaryGUI.empty() && overlayGUI.empty())
1018  return;
1019 
1020  // Neither should this really...
1021  if (primaryVideo.empty() && overlayVideo.empty())
1022  {
1023  LOG(VB_GENERAL, LOG_WARNING, LOC + "Found no planes with video support");
1024  return;
1025  }
1026 
1027  // Need to ensure we don't pick the same plane for video and GUI
1028  auto nodupe = [](const auto & Planes, const auto & Plane)
1029  {
1030  for (const auto & plane : Planes)
1031  if (plane->m_id != Plane->m_id)
1032  return plane;
1033  return DRMPlane { nullptr };
1034  };
1035 
1036  // Note: If video is an overlay or both planes are of the same type then
1037  // video composition will likely fail if there is no zpos support
1038  if (primaryVideo.empty())
1039  {
1040  m_videoPlane = overlayVideo.front();
1041  if (overlayGUI.empty())
1042  m_guiPlane = primaryGUI.front();
1043  else
1044  m_guiPlane = nodupe(overlayGUI, m_videoPlane);
1045  }
1046  else
1047  {
1048  m_videoPlane = primaryVideo.front();
1049  if (overlayGUI.empty())
1050  m_guiPlane = nodupe(primaryGUI, m_videoPlane);
1051  else
1052  m_guiPlane = overlayGUI.front(); // Simple primary video and overlay GUI
1053  }
1054 
1055  if (!m_videoPlane.get())
1056  {
1057  LOG(VB_GENERAL, LOG_ERR, LOC + "No video plane");
1058  }
1059  else
1060  {
1061  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Selected Plane #%1 %2 for video")
1062  .arg(m_videoPlane->m_id).arg(MythDRMPlane::PlaneTypeToString(m_videoPlane->m_type)));
1063  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Supported DRM video formats: %1")
1064  .arg(MythDRMPlane::FormatsToString(m_videoPlane->m_videoFormats)));
1065  }
1066 
1067  if (!m_guiPlane.get())
1068  {
1069  LOG(VB_GENERAL, LOG_ERR, LOC + "No GUI plane");
1070  }
1071  else
1072  {
1073  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Selected Plane #%1 %2 for GUI")
1074  .arg(m_guiPlane->m_id).arg(MythDRMPlane::PlaneTypeToString(m_guiPlane->m_type)));
1075  }
1076 }
1077 #endif
MythDRMVRR::ForceFreeSync
static void ForceFreeSync(const MythDRMPtr &Device, bool Enable)
Force FreeSync on or off before the main app is started.
Definition: mythdrmvrr.cpp:9
MythDRMDevice::m_authenticated
bool m_authenticated
Definition: mythdrmdevice.h:95
MythDRMDevice::m_adjustedRefreshRate
double m_adjustedRefreshRate
Definition: mythdrmdevice.h:105
MythDRMConnector::GetConnectors
static DRMConns GetConnectors(int FD)
Definition: mythdrmconnector.cpp:27
MythDRMDevice::Authenticate
void Authenticate()
Attempt to acquire privileged DRM access.
Definition: mythdrmdevice.cpp:699
MythDRMDevice::m_screen
QScreen * m_screen
Definition: mythdrmdevice.h:90
MythDRMDevice::GetEDID
MythEDID GetEDID() const
Definition: mythdrmdevice.cpp:617
MythCommandLineParser
Parent class for defining application command line parsers.
Definition: mythcommandlineparser.h:116
MythDRMDevice::GetResolution
QSize GetResolution() const
Definition: mythdrmdevice.cpp:607
MythDRMDevice::GetScreen
QScreen * GetScreen() const
Definition: mythdrmdevice.cpp:602
false
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:85
MythDRMCrtc::GetCrtcs
static DRMCrtcs GetCrtcs(int FD)
Definition: mythdrmcrtc.cpp:27
MythDRMDevice::CanSwitchModes
bool CanSwitchModes() const
Definition: mythdrmdevice.cpp:639
MythDRMPlane::FormatToString
static QString FormatToString(uint32_t Format)
Definition: mythdrmplane.cpp:124
MythDRMDevice::SwitchMode
bool SwitchMode(int ModeIndex)
Set the required video mode.
Definition: mythdrmdevice.cpp:658
MythDRMDevice::Create
static MythDRMPtr Create(QScreen *qScreen, const QString &Device=QString(), bool NeedPlanes=true)
Create a MythDRMDevice instance.
Definition: mythdrmdevice.cpp:326
MythDRMDevice::~MythDRMDevice
~MythDRMDevice()
Definition: mythdrmdevice.cpp:563
setenv
#define setenv(x, y, z)
Definition: compat.h:157
MythDRMDevice::GetSerialNumber
QString GetSerialNumber() const
Definition: mythdrmdevice.cpp:597
MythDRMCrtc::GetCrtc
static DRMCrtc GetCrtc(const DRMCrtcs &Crtcs, uint32_t Id)
Definition: mythdrmcrtc.cpp:19
MythDRMPlane::PlaneTypeToString
static QString PlaneTypeToString(uint64_t Type)
Definition: mythdrmplane.cpp:12
MythDRMDevice::m_resolution
QSize m_resolution
Definition: mythdrmdevice.h:102
confdir
static QString confdir
Definition: mythdirs.cpp:20
MythDRMDevice::m_serialNumber
QString m_serialNumber
Definition: mythdrmdevice.h:106
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
DRM_QUIET
#define DRM_QUIET
Definition: mythdrmdevice.h:22
MythDRMPlane::HasOverlayFormat
static bool HasOverlayFormat(const FOURCCVec &Formats)
Enusure list of supplied formats contains a format that is suitable for OpenGL/Vulkan.
Definition: mythdrmplane.cpp:181
Device
A device containing images (ie. USB stick, CD, storage group etc)
Definition: imagemanager.cpp:33
build_compdb.file
file
Definition: build_compdb.py:55
MythDRMPlane::FormatsToString
static QString FormatsToString(const FOURCCVec &Formats)
Definition: mythdrmplane.cpp:151
DRMCrtc
std::shared_ptr< class MythDRMCrtc > DRMCrtc
Definition: mythdrmcrtc.h:8
MythDRMDevice::Initialise
bool Initialise()
Definition: mythdrmdevice.cpp:731
MythDRMDevice::m_valid
bool m_valid
Definition: mythdrmdevice.h:89
true
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:91
MythDRMDevice::m_connector
DRMConn m_connector
Definition: mythdrmdevice.h:100
MythDRMDevice::m_crtcs
DRMCrtcs m_crtcs
Definition: mythdrmdevice.h:98
close
#define close
Definition: compat.h:17
MythDRMEncoder::GetEncoders
static DRMEncs GetEncoders(int FD)
Definition: mythdrmencoder.cpp:27
DRM_FORMAT_INVALID
#define DRM_FORMAT_INVALID
Definition: mythdrmplane.h:13
MythDRMProperty::GetProperty
static DRMProp GetProperty(const QString &Name, const DRMProps &Properties)
Definition: mythdrmproperty.cpp:55
MythDRMPlane::GetAlphaFormat
static uint32_t GetAlphaFormat(const FOURCCVec &Formats)
Definition: mythdrmplane.cpp:199
MythDRMDevice::GetPhysicalSize
QSize GetPhysicalSize() const
Definition: mythdrmdevice.cpp:612
MythDRMDevice::GetModes
const DRMModes & GetModes() const
Definition: mythdrmdevice.cpp:644
MythEDID::SerialNumbers
QStringList SerialNumbers() const
Definition: mythedid.cpp:44
MythDRMDevice::m_verbose
LogLevel_t m_verbose
Definition: mythdrmdevice.h:107
MythDRMRangeProperty
Definition: mythdrmproperty.h:42
MythDRMDevice::MythDRMDevice
MythDRMDevice(QScreen *qScreen, const QString &Device=QString())
Constructor used when we have no DRM handles from Qt.
Definition: mythdrmdevice.cpp:400
MythDRMPtr
std::shared_ptr< class MythDRMDevice > MythDRMPtr
Definition: mythdrmdevice.h:18
MythDRMDevice::GetConnector
DRMConn GetConnector() const
Definition: mythdrmdevice.cpp:884
MythDRMDevice::Atomic
bool Atomic() const
Definition: mythdrmdevice.cpp:587
MythDRMDevice
Definition: mythdrmdevice.h:24
MythEDID::Valid
bool Valid() const
Definition: mythedid.cpp:39
MythDRMDevice::m_openedDevice
bool m_openedDevice
Definition: mythdrmdevice.h:92
MythDRMDevice::Authenticated
bool Authenticated() const
Definition: mythdrmdevice.cpp:582
MythCommandLineParser::toUInt
uint toUInt(const QString &key) const
Returns stored QVariant as an unsigned integer, falling to default if not provided.
Definition: mythcommandlineparser.cpp:2144
MythDRMDevice::FindBestDevice
QString FindBestDevice()
Definition: mythdrmdevice.cpp:827
DRMPlanes
std::vector< DRMPlane > DRMPlanes
Definition: mythdrmplane.h:51
MythDRMDevice::m_encoders
DRMEncs m_encoders
Definition: mythdrmdevice.h:97
MythDRMDevice::m_crtc
DRMCrtc m_crtc
Definition: mythdrmdevice.h:101
DRMConn
std::shared_ptr< class MythDRMConnector > DRMConn
Definition: mythdrmconnector.h:10
MythDRMDevice::GetCrtc
DRMCrtc GetCrtc() const
Definition: mythdrmdevice.cpp:879
MythDRMDevice::GetFD
int GetFD() const
Definition: mythdrmdevice.cpp:592
MythDRMDevice::m_edid
MythEDID m_edid
Definition: mythdrmdevice.h:108
MythDRMDevice::m_connectors
DRMConns m_connectors
Definition: mythdrmdevice.h:96
mythdrmframebuffer.h
DRMModes
std::vector< DRMMode > DRMModes
Definition: mythdrmmode.h:8
MythDRMConnector::GetConnectorByName
static DRMConn GetConnectorByName(const DRMConns &Connectors, const QString &Name)
Definition: mythdrmconnector.cpp:75
MythDRMDevice::m_deviceName
QString m_deviceName
Definition: mythdrmdevice.h:91
VERBOSE_LEVEL_CHECK
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:14
LOC
#define LOC
Definition: mythdrmdevice.cpp:28
MythDRMDevice::m_refreshRate
double m_refreshRate
Definition: mythdrmdevice.h:104
MythCommandLineParser::toString
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
Definition: mythcommandlineparser.cpp:2252
mythedid.h
MythCommandLineParser::toBool
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
Definition: mythcommandlineparser.cpp:2095
MythDRMDevice::m_planes
DRMPlanes m_planes
Definition: mythdrmdevice.h:99
MythDRMBlobProperty
Definition: mythdrmproperty.h:77
mythdrmvrr.h
MythDRMEncoder::GetEncoder
static DRMEnc GetEncoder(const DRMEncs &Encoders, uint32_t Id)
Definition: mythdrmencoder.cpp:19
DRMPlane
std::shared_ptr< class MythDRMPlane > DRMPlane
Definition: mythdrmplane.h:50
MythDRMDevice::GetRefreshRate
double GetRefreshRate() const
Return the refresh rate we think is in use.
Definition: mythdrmdevice.cpp:632
MythDRMConnector::GetConnector
static DRMConn GetConnector(const DRMConns &Connectors, uint32_t Id)
Definition: mythdrmconnector.cpp:19
mythdrmdevice.h
MythAtomics
std::vector< MythAtomic > MythAtomics
Definition: mythdrmdevice.h:20
MythDRMDevice::GetDeviceList
static std::tuple< QString, QStringList > GetDeviceList()
Definition: mythdrmdevice.cpp:380
MythDRMPlane::GetPlanes
static DRMPlanes GetPlanes(int FD, int CRTCFilter=-1)
Definition: mythdrmplane.cpp:80
MythDRMDevice::Load
void Load()
Definition: mythdrmdevice.cpp:724
build_compdb.filename
filename
Definition: build_compdb.py:21
MythDRMDevice::m_physicalSize
QSize m_physicalSize
Definition: mythdrmdevice.h:103
MythDRMDevice::m_fd
int m_fd
Definition: mythdrmdevice.h:93
MythDRMDevice::m_atomic
bool m_atomic
Definition: mythdrmdevice.h:94
mythdrmencoder.h
nv_python_libs.bbciplayer.bbciplayer_api.version
string version
Definition: bbciplayer_api.py:81
MythDRMDevice::Open
bool Open()
Definition: mythdrmdevice.cpp:572
MythEDID
Definition: mythedid.h:21
MythDRMDevice::ConfirmDevice
static bool ConfirmDevice(const QString &Device)
Definition: mythdrmdevice.cpp:863