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 : std::as_const(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  // Return early if eglfs is not *explicitly* requested via the command line or environment.
148  // Note: On some setups it is not necessary to explicitly request eglfs for Qt to use it.
149  // Note: Not sure which takes precedent in Qt or what happens if they are different.
150  auto platform = CmdLine.toString("platform");
151  if (platform.isEmpty())
152  platform = qEnvironmentVariable("QT_QPA_PLATFORM");
153  if (!platform.contains("eglfs", Qt::CaseInsensitive))
154  {
155  // Log something just in case it reminds someone to enable eglfs
156  LOG(VB_GENERAL, LOG_INFO, "'eglfs' not explicitly requested. Not configuring DRM.");
157  return;
158  }
159 
160  // Qt environment variables
161  static const char * s_kmsPlaneIndex = "QT_QPA_EGLFS_KMS_PLANE_INDEX"; // Qt 5.9
162  static const char * s_kmsPlaneCRTCS = "QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS"; // Qt 5.15
163  static const char * s_kmsPlaneZpos = "QT_QPA_EGLFS_KMS_ZPOS"; // Qt 5.12
164  static const char * s_kmsConfigFile = "QT_QPA_EGLFS_KMS_CONFIG";
165  static const char * s_kmsAtomic = "QT_QPA_EGLFS_KMS_ATOMIC"; // Qt 5.12
166  static const char * s_kmsSetMode = "QT_QPA_EGLFS_ALWAYS_SET_MODE";
167 
168  // The following 2 environment variables are forced regardless of any existing
169  // environment settings etc. They are just needed and should have no adverse
170  // impacts
171 
172  // If we are using eglfs_kms we want atomic operations. No effect on other plugins.
173  LOG(VB_GENERAL, LOG_INFO, QString("Exporting '%1=1'").arg(s_kmsAtomic));
174  setenv(s_kmsAtomic, "1", 0);
175 
176  // Seems to fix occasional issues. Again no impact on other plugins.
177  LOG(VB_GENERAL, LOG_INFO, QString("Exporting '%1=1'").arg(s_kmsSetMode));
178  setenv(s_kmsSetMode, "1", 0);
179 
180  bool plane = qEnvironmentVariableIsSet(s_kmsPlaneIndex) ||
181  qEnvironmentVariableIsSet(s_kmsPlaneCRTCS);
182  bool config = qEnvironmentVariableIsSet(s_kmsConfigFile);
183  bool zpos = qEnvironmentVariableIsSet(s_kmsPlaneZpos);
184  bool custom = plane || config || zpos;
185 
186  // Don't attempt to override any custom user configuration
187  if (custom)
188  {
189  LOG(VB_GENERAL, LOG_INFO, "QT_QPA_EGLFS_KMS user overrides detected");
190 
191  if (!s_mythDRMVideo)
192  {
193  // It is likely the user is customising planar video; so warn if planar
194  // video has not been enabled
195  LOG(VB_GENERAL, LOG_WARNING, "Qt eglfs_kms custom plane settings detected"
196  " but planar support not requested.");
197  }
198  else
199  {
200  // Planar support requested so we must signal to our future self
201  s_planarRequested = true;
202 
203  // We don't know whether zpos support is required at this point
204  if (!zpos)
205  {
206  LOG(VB_GENERAL, LOG_WARNING, QString("%1 not detected - assuming not required")
207  .arg(s_kmsPlaneZpos));
208  }
209 
210  // Warn if we do no see all of the known required config
211  if (!(plane && config))
212  {
213  LOG(VB_GENERAL, LOG_WARNING, "Warning: DRM planar support requested but "
214  "it looks like not all environment variables have been set.");
215  LOG(VB_GENERAL, LOG_INFO,
216  QString("Minimum required: %1 and/or %2 for plane index and %3 for alpha blending")
217  .arg(s_kmsPlaneIndex, s_kmsPlaneCRTCS, s_kmsConfigFile));
218  }
219  else
220  {
221  LOG(VB_GENERAL, LOG_INFO, "DRM planar support enabled for custom user settings");
222  }
223  }
224  return;
225  }
226 
227  if (!s_mythDRMVideo)
228  {
229  LOG(VB_GENERAL, LOG_INFO, "Qt eglfs_kms planar video not requested");
230  return;
231  }
232 
233  MythDRMPtr device = FindDevice();
234  if (!device)
235  {
236  LOG(VB_GENERAL, LOG_WARNING, "Failed to open any suitable DRM devices with privileges");
237  return;
238  }
239 
240  if (!(device->m_guiPlane.get() && device->m_guiPlane->m_id &&
241  device->m_videoPlane.get() && device->m_videoPlane->m_id))
242  {
243  LOG(VB_GENERAL, LOG_WARNING, QString("Failed to deduce correct planes for device '%1'")
244  .arg(drmGetDeviceNameFromFd2(device->GetFD())));
245  return;
246  }
247 
248  // We have a valid, authenticated device with a connected display and validated planes
249  auto guiplane = device->m_guiPlane;
250  auto format = MythDRMPlane::GetAlphaFormat(guiplane->m_formats);
251  if (format == DRM_FORMAT_INVALID)
252  {
253  LOG(VB_GENERAL, LOG_WARNING, "Failed to find alpha format for GUI. Quitting DRM setup.");
254  return;
255  }
256 
257  // N.B. No MythDirs setup yet so mimic the conf dir setup
258  QString confdir = qEnvironmentVariable("MYTHCONFDIR");
259  if (confdir.isEmpty())
260  confdir = QDir::homePath() + "/.mythtv";
261 
262  auto filename = confdir + "/eglfs_kms_config.json";
263  QFile file(filename);
264  if (!file.open(QIODevice::WriteOnly))
265  {
266  LOG(VB_GENERAL, LOG_WARNING, QString("Failed to open '%1' for writing. Quitting DRM setup.")
267  .arg(filename));
268  return;
269  }
270 
271  static const QString s_json =
272  "{\n"
273  " \"device\": \"%1\",\n"
274  " \"outputs\": [ { \"name\": \"%2\", \"format\": \"%3\", \"mode\": \"%4\" } ]\n"
275  "}\n";
276 
277  // Note: mode is not sanitised
278  QString wrote = s_json.arg(drmGetDeviceNameFromFd2(device->GetFD()),
279  device->m_connector->m_name, MythDRMPlane::FormatToString(format).toLower(),
280  s_mythDRMVideoMode.isEmpty() ? "current" : s_mythDRMVideoMode);
281 
282  if (file.write(qPrintable(wrote)))
283  {
284  LOG(VB_GENERAL, LOG_INFO, QString("Wrote %1:\r\n%2").arg(filename, wrote));
285  LOG(VB_GENERAL, LOG_INFO, QString("Exporting '%1=%2'").arg(s_kmsConfigFile, filename));
286  setenv(s_kmsConfigFile, qPrintable(filename), 1);
287  }
288  file.close();
289 
290  auto planeindex = QString::number(guiplane->m_index);
291  auto crtcplane = QString("%1,%2").arg(device->m_crtc->m_id).arg(guiplane->m_id);
292  LOG(VB_GENERAL, LOG_INFO, QString("Exporting '%1=%2'").arg(s_kmsPlaneIndex, planeindex));
293  LOG(VB_GENERAL, LOG_INFO, QString("Exporting '%1=%2'").arg(s_kmsPlaneCRTCS, crtcplane));
294  setenv(s_kmsPlaneIndex, qPrintable(planeindex), 1);
295  setenv(s_kmsPlaneCRTCS, qPrintable(crtcplane), 1);
296 
297  // Set the zpos if supported
298  if (auto zposp = MythDRMProperty::GetProperty("zpos", guiplane->m_properties); zposp.get())
299  {
300  if (auto *range = dynamic_cast<MythDRMRangeProperty*>(zposp.get()); range)
301  {
302  auto val = QString::number(std::min(range->m_min + 1, range->m_max));
303  LOG(VB_GENERAL, LOG_INFO, QString("Exporting '%1=%2'").arg(s_kmsPlaneZpos, val));
304  setenv(s_kmsPlaneZpos, qPrintable(val), 1);
305  }
306  }
307 
308  // Signal to our future self that we did request some Qt DRM configuration
309  s_planarRequested = true;
310 }
311 #endif
312 
316 MythDRMPtr MythDRMDevice::Create(QScreen *qScreen, const QString &Device,
317  [[maybe_unused]] bool NeedPlanes)
318 {
319 #ifdef USING_QTPRIVATEHEADERS
320  auto * app = dynamic_cast<QGuiApplication *>(QCoreApplication::instance());
321  if (qScreen && app && QGuiApplication::platformName().contains("eglfs", Qt::CaseInsensitive))
322  {
323  int fd = 0;
324  uint32_t crtc = 0;
325  uint32_t connector = 0;
326  bool useatomic = false;
327  auto * pni = QGuiApplication::platformNativeInterface();
328  if (auto * drifd = pni->nativeResourceForIntegration("dri_fd"); drifd)
329  fd = static_cast<int>(reinterpret_cast<qintptr>(drifd));
330  if (auto * crtcid = pni->nativeResourceForScreen("dri_crtcid", qScreen); crtcid)
331  crtc = static_cast<uint32_t>(reinterpret_cast<qintptr>(crtcid));
332  if (auto * connid = pni->nativeResourceForScreen("dri_connectorid", qScreen); connid)
333  connector = static_cast<uint32_t>(reinterpret_cast<qintptr>(connid));
334  if (auto * atomic = pni->nativeResourceForIntegration("dri_atomic_request"); atomic)
335  if (auto * request = reinterpret_cast<drmModeAtomicReq*>(atomic); request != nullptr)
336  useatomic = true;
337 
338  LOG(VB_GENERAL, LOG_INFO, QString("%1 Qt EGLFS/KMS Fd:%2 Crtc id:%3 Connector id:%4 Atomic: %5")
339  .arg(drmGetDeviceNameFromFd2(fd)).arg(fd).arg(crtc).arg(connector).arg(useatomic));
340 
341  // We have all the details we need from Qt
342  if (fd && crtc && connector)
343  {
344  if (auto result = std::shared_ptr<MythDRMDevice>(new MythDRMDevice(fd, crtc, connector, useatomic));
345  result.get() && result->m_valid)
346  {
347  return result;
348  }
349  }
350  }
351 #endif
352 
353  if (qScreen)
354  {
355  if (auto result = std::shared_ptr<MythDRMDevice>(new MythDRMDevice(qScreen, Device));
356  result.get() && result->m_valid)
357  {
358  return result;
359  }
360  // N.B. Don't fall through here.
361  return nullptr;
362  }
363 
364 #ifdef USING_QTPRIVATEHEADERS
365  if (auto result = std::shared_ptr<MythDRMDevice>(new MythDRMDevice(Device, NeedPlanes)); result && result->m_valid)
366  return result;
367 #endif
368  return nullptr;
369 }
370 
371 std::tuple<QString, QStringList> MythDRMDevice::GetDeviceList()
372 {
373  // Iterate over /dev/dri/card*
374  const QString root(QString(DRM_DIR_NAME) + "/");
375  QDir dir(root);
376  QStringList namefilters;
377 #ifdef __OpenBSD__
378  namefilters.append("drm*");
379 #else
380  namefilters.append("card*");
381 #endif
382  return { root, dir.entryList(namefilters, QDir::Files | QDir::System) };
383 }
384 
391 MythDRMDevice::MythDRMDevice(QScreen* qScreen, const QString& Device)
392  : m_screen(qScreen),
393  m_deviceName(Device),
394  m_verbose(Device.isEmpty() ? LOG_INFO : LOG_DEBUG)
395 {
396  // This is hackish workaround to suppress logging when it isn't required
397  if (m_deviceName == DRM_QUIET)
398  {
399  m_deviceName.clear();
400  m_verbose = LOG_DEBUG;
401  }
402 
403  if (!Open())
404  {
405  LOG(VB_GENERAL, m_verbose, LOC + "Failed to open");
406  return;
407  }
408 
409  if (!Initialise())
410  return;
411 
412  m_valid = true;
413 
414  // Will almost certainly fail
415  Authenticate();
416 }
417 
418 #if defined (USING_QTPRIVATEHEADERS)
419 
427 MythDRMDevice::MythDRMDevice(int Fd, uint32_t CrtcId, uint32_t ConnectorId, bool Atomic)
428  : m_openedDevice(false),
429  m_fd(Fd),
430  m_atomic(Atomic)
431 {
432  if (m_fd < 1)
433  return;
434 
435  // Get the device name for debugging
436  m_deviceName = drmGetDeviceNameFromFd2(m_fd);
437 
438  // This should always succeed here...
439  Authenticate();
440 
441  // Retrieve all objects
442  Load();
443 
444  // Get correct connector and Crtc
447  m_valid = m_connector.get() && m_crtc.get();
448 
449  if (m_valid)
450  {
451  // Get physical size
452  m_physicalSize = QSize(static_cast<int>(m_connector->m_mmWidth),
453  static_cast<int>(m_connector->m_mmHeight));
454  // Get EDID
455  auto prop = MythDRMProperty::GetProperty("EDID", m_connector->m_properties);
456  if (auto *blob = dynamic_cast<MythDRMBlobProperty*>(prop.get()); blob)
457  {
458  MythEDID edid(blob->m_blob);
459  if (edid.Valid())
460  m_edid = edid;
461  }
462 
463  // Get resolution and rate
464  m_resolution = QSize(static_cast<int>(m_crtc->m_width), static_cast<int>(m_crtc->m_height));
465  if (m_crtc->m_mode.get())
466  m_refreshRate = m_crtc->m_mode->m_rate;
467 
468  // Only setup video and gui planes if requested
469  if (s_planarRequested)
470  {
471  AnalysePlanes();
472  if (m_videoPlane.get() && m_guiPlane.get() && m_videoPlane->m_id && m_guiPlane->m_id)
473  s_planarSetup = true;
474  }
475  LOG(VB_GENERAL, LOG_INFO, LOC + "DRM device retrieved from Qt");
476  }
477  else
478  {
479  LOG(VB_GENERAL, LOG_ERR, LOC + "Device setup failed");
480  }
481 
482  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Multi-plane setup: Requested: %1 Setup: %2")
483  .arg(s_planarRequested).arg(s_planarSetup));
484 }
485 
492 MythDRMDevice::MythDRMDevice(QString Device, bool NeedPlanes)
493  : m_deviceName(std::move(Device)),
494  m_atomic(true) // Just squashes some logging
495 {
496  if (!Open())
497  return;
498  Authenticate();
499  if (!m_authenticated)
500  return;
501  m_valid = drmSetClientCap(m_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) == 0;
502  if (!m_valid)
503  {
504  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to request universal planes");
505  return;
506  }
507  Load();
508  m_valid = false;
509 
510  // Find a user suggested connector or the first connected. Oddly
511  // clang-tidy-16 thinks the "if" and "else" clauses are the same.
512  // NOLINTNEXTLINE(bugprone-branch-clone)
513  if (!s_mythDRMConnector.isEmpty())
514  {
516  }
517  else
518  {
519  for (const auto & connector : m_connectors)
520  {
521  if (connector->m_state == DRM_MODE_CONNECTED)
522  {
523  m_connector = connector;
524  break;
525  }
526  }
527  }
528 
529  if (!m_connector)
530  {
531  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to find connector");
532  return;
533  }
534 
535  auto encoder = MythDRMEncoder::GetEncoder(m_encoders, m_connector->m_encoderId);
536  if (!encoder.get())
537  return;
538 
539  m_crtc = MythDRMCrtc::GetCrtc(m_crtcs, encoder->m_crtcId);
540  if (!m_crtc)
541  return;
542 
543  if (NeedPlanes)
544  {
545  AnalysePlanes();
546  m_valid = m_videoPlane.get() && m_guiPlane.get();
547  }
548  else
549  {
550  m_valid = true;
551  }
552 }
553 #endif
554 
556 {
557  if (m_fd && m_openedDevice)
558  {
559  close(m_fd);
560  LOG(VB_GENERAL, m_verbose, LOC + "Closed");
561  }
562 }
563 
565 {
566  if (m_deviceName.isEmpty())
568  if (m_deviceName.isEmpty())
569  return false;
570  m_fd = open(m_deviceName.toLocal8Bit().constData(), O_RDWR);
571  return m_fd > 0;
572 }
573 
575 {
576  return m_valid && m_authenticated;
577 }
578 
580 {
581  return m_atomic;
582 }
583 
585 {
586  return m_fd;
587 }
588 
590 {
591  return m_serialNumber;
592 }
593 
594 QScreen* MythDRMDevice::GetScreen() const
595 {
596  return m_screen;
597 }
598 
600 {
601  return m_resolution;
602 }
603 
605 {
606  return m_physicalSize;
607 }
608 
610 {
611  return m_edid;
612 }
613 
625 {
626  if (m_adjustedRefreshRate > 1.0)
627  return m_adjustedRefreshRate;
628  return m_refreshRate;
629 }
630 
632 {
633  return m_valid && m_authenticated && m_atomic;
634 }
635 
636 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
638 {
639  static const DRMModes empty;
641  return m_connector->m_modes;
642  return empty;
643 }
644 
651 bool MythDRMDevice::SwitchMode(int ModeIndex)
652 {
653  if (!(m_authenticated && m_atomic && m_connector.get() && m_crtc.get()))
654  return false;
655 
656  auto index = static_cast<size_t>(ModeIndex);
657 
658  if (ModeIndex < 0 || index >= m_connector->m_modes.size())
659  return false;
660 
661  bool result = false;
662 #ifdef USING_QTPRIVATEHEADERS
663  auto crtcid = MythDRMProperty::GetProperty("crtc_id", m_connector->m_properties);
664  auto modeid = MythDRMProperty::GetProperty("mode_id", m_crtc->m_properties);
665  if (crtcid.get() && modeid.get())
666  {
667  uint32_t blobid = 0;
668  // Presumably blobid does not need to be released? Can't find any documentation but
669  // there is the matching drmModeDestroyPropertyBlob...
670  if (drmModeCreatePropertyBlob(m_fd, &m_connector->m_modes[index], sizeof(drmModeModeInfo), &blobid) == 0)
671  {
672  QueueAtomics( {{ m_connector->m_id, crtcid->m_id, m_crtc->m_id },
673  { m_crtc->m_id, modeid->m_id, blobid }} );
674  m_adjustedRefreshRate = m_connector->m_modes[index]->m_rate;
675  result = true;
676  }
677  else
678  {
679  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create mode blob");
680  }
681  }
682 #endif
683  return result;
684 }
685 
693 {
694  if (!m_fd || m_authenticated)
695  return;
696 
697  int ret = drmSetMaster(m_fd);
698  m_authenticated = ret >= 0;
699 
700  if (!m_authenticated)
701  {
702  drm_magic_t magic = 0;
703  m_authenticated = drmGetMagic(m_fd, &magic) == 0 && drmAuthMagic(m_fd, magic) == 0;
704  }
705 
706  if (m_authenticated)
707  {
708  const auto * extra = m_atomic ? "" : " but atomic operations required for mode switching";
709  LOG(VB_GENERAL, m_verbose, LOC + "Authenticated" + extra);
710  }
711  else
712  {
713  LOG(VB_GENERAL, m_verbose, LOC + "Not authenticated - mode switching not available");
714  }
715 }
716 
718 {
722 }
723 
725 {
726  if (!m_fd)
727  return false;
728 
729  // Find the serial number of the display we are connected to
730  auto serial = m_screen ? m_screen->serialNumber() : "";
731  if (m_screen && serial.isEmpty())
732  {
733  // No serial number either means an older version of Qt or the EDID
734  // is not available for some reason - in which case there is no point
735  // in trying to use it anyway.
736  LOG(VB_GENERAL, m_verbose, LOC + "QScreen has no serial number.");
737  LOG(VB_GENERAL, m_verbose, LOC + "Will use first suitable connected device");
738  }
739 
740  // Retrieve full details for the device
741  Load();
742 
743  // Find connector
744  for (const auto & connector : m_connectors)
745  {
746  if (connector->m_state == DRM_MODE_CONNECTED)
747  {
748  if (serial.isEmpty())
749  {
750  m_connector = connector;
751  break;
752  }
753 
754  // Does the connected display have the serial number we are looking for?
755  if (const auto edidprop = MythDRMProperty::GetProperty("EDID", connector->m_properties); edidprop.get())
756  {
757  MythEDID edid;
758  if (auto * blob = dynamic_cast<MythDRMBlobProperty*>(edidprop.get()); blob)
759  edid = MythEDID(blob->m_blob);
760 
761  if (edid.Valid() && edid.SerialNumbers().contains(serial))
762  {
763  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Matched connector with serial '%1'")
764  .arg(serial));
765  m_connector = connector;
766  m_physicalSize = QSize(static_cast<int>(connector->m_mmWidth),
767  static_cast<int>(connector->m_mmHeight));
768  m_serialNumber = serial;
769  m_edid = edid;
770  break;
771  }
772 
773  if (!edid.Valid())
774  LOG(VB_GENERAL, m_verbose, LOC + "Connected device has invalid EDID");
775 
776  if (m_connector && !m_serialNumber.isEmpty())
777  break;
778  }
779  else
780  {
781  LOG(VB_GENERAL, m_verbose, LOC + "Connected device has no EDID");
782  }
783  }
784  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Ignoring disconnected connector %1")
785  .arg(connector->m_name));
786  }
787 
788  if (!m_connector.get())
789  {
790  LOG(VB_GENERAL, LOG_DEBUG, LOC + "No connected connectors");
791  return false;
792  }
793 
794  LOG(VB_GENERAL, m_verbose, LOC + QString("Selected connector %1").arg(m_connector->m_name));
795 
796  // Find the encoder for the connector
797  auto encoder = MythDRMEncoder::GetEncoder(m_encoders, m_connector->m_encoderId);
798  if (!encoder)
799  {
800  LOG(VB_GENERAL, m_verbose, LOC + QString("Failed to find encoder for %1").arg(m_connector->m_name));
801  return false;
802  }
803 
804  // Find the CRTC for the encoder
805  m_crtc = MythDRMCrtc::GetCrtc(m_crtcs, encoder->m_crtcId);
806  if (!m_crtc)
807  {
808  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Failed to find crtc for encoder");
809  return false;
810  }
811 
812  m_resolution = QSize(static_cast<int>(m_crtc->m_width), static_cast<int>(m_crtc->m_height));
813  if (m_crtc->m_mode.get())
814  m_refreshRate = m_crtc->m_mode->m_rate;
815 
816  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Initialised");
817  return true;
818 }
819 
821 {
822  if (!m_screen)
823  return {};
824 
825  auto [root, devices] = GetDeviceList();
826  if (devices.isEmpty())
827  return {};
828 
829  // Only one device - return it
830  if (devices.size() == 1)
831  return root + devices.first();
832 
833  // Use the serial number from the current QScreen to select a suitable device
834  auto serial = m_screen->serialNumber();
835  if (serial.isEmpty())
836  {
837  LOG(VB_GENERAL, m_verbose, LOC + "No serial number to search for");
838  return {};
839  }
840 
841  for (const auto& dev : std::as_const(devices))
842  {
843  QString device = root + dev;
844  if (!ConfirmDevice(device))
845  {
846  LOG(VB_GENERAL, m_verbose, LOC + "Failed to confirm device");
847  continue;
848  }
849  MythDRMDevice drmdevice(m_screen, device);
850  if (drmdevice.GetSerialNumber() == serial)
851  return device;
852  }
853  return {};
854 }
855 
857 {
858  bool result = false;
859  int fd = open(Device.toLocal8Bit().constData(), O_RDWR);
860  if (fd < 0)
861  return result;
862  drmVersionPtr version = drmGetVersion(fd);
863  if (version)
864  {
865  drmFreeVersion(version);
866  result = true;
867  }
868  close(fd);
869  return result;
870 }
871 
873 {
874  return m_crtc;
875 }
876 
878 {
879  return m_connector;
880 }
881 
882 #if defined (USING_QTPRIVATEHEADERS)
883 void MythDRMDevice::MainWindowReady()
884 {
885  // This is causing issues - disabled for now
886  //DisableVideoPlane();
887 
888  // Temporarily disabled - this is informational only
889  /*
890  // Confirm GUI plane format now that Qt is setup
891  if (m_guiPlane.get())
892  {
893  // TODO Add methods to retrieve up to date property values rather than
894  // create new objects
895  if (auto plane = MythDRMPlane::Create(m_fd, m_guiPlane->m_id, 0); plane)
896  {
897  if (auto guifb = MythDRMFramebuffer::Create(m_fd, plane->m_fbId); guifb)
898  {
899  if (MythDRMPlane::HasOverlayFormat({ guifb->m_format }))
900  {
901  LOG(VB_GENERAL, LOG_INFO, LOC + QString("GUI alpha format confirmed (%1)")
902  .arg(MythDRMPlane::FormatToString(guifb->m_format)));
903  }
904  else
905  {
906  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("GUI plane has no alpha (%1)")
907  .arg(MythDRMPlane::FormatToString(guifb->m_format)));
908  }
909  }
910  }
911  }
912  */
913 }
914 
915 bool MythDRMDevice::QueueAtomics(const MythAtomics& Atomics) const
916 {
917  auto * app = dynamic_cast<QGuiApplication *>(QCoreApplication::instance());
918  if (!(m_atomic && m_authenticated && app))
919  return false;
920 
921  auto * pni = QGuiApplication::platformNativeInterface();
922  if (auto * dri = pni->nativeResourceForIntegration("dri_atomic_request"); dri)
923  {
924  if (auto * request = reinterpret_cast<drmModeAtomicReq*>(dri); request != nullptr)
925  {
926  for (const auto & a : Atomics)
927  drmModeAtomicAddProperty(request, std::get<0>(a), std::get<1>(a), std::get<2>(a));
928  return true;
929  }
930  }
931  return false;
932 }
933 
934 void MythDRMDevice::DisableVideoPlane()
935 {
936  if (m_videoPlane.get())
937  {
938  LOG(VB_GENERAL, LOG_INFO, LOC + "Disabling video plane");
939  QueueAtomics( {{ m_videoPlane->m_id, m_videoPlane->m_fbIdProp->m_id, 0 },
940  { m_videoPlane->m_id, m_videoPlane->m_crtcIdProp->m_id, 0 }} );
941  }
942 }
943 
944 DRMPlane MythDRMDevice::GetVideoPlane() const
945 {
946  return m_videoPlane;
947 }
948 
949 DRMPlane MythDRMDevice::GetGUIPlane() const
950 {
951  return m_guiPlane;
952 }
953 
973 void MythDRMDevice::AnalysePlanes()
974 {
975  if (!m_fd || !m_crtc || m_crtc->m_index <= -1)
976  return;
977 
978  // Find our planes
979  auto allplanes = MythDRMPlane::GetPlanes(m_fd);
981 
982  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found %1 planes; %2 for this CRTC")
983  .arg(allplanes.size()).arg(m_planes.size()));
984 
985  // NOLINTBEGIN(cppcoreguidelines-init-variables)
986  DRMPlanes primaryVideo;
987  DRMPlanes overlayVideo;
988  DRMPlanes primaryGUI;
989  DRMPlanes overlayGUI;
990  // NOLINTEND(cppcoreguidelines-init-variables)
991 
992  for (const auto & plane : m_planes)
993  {
994  if (plane->m_type == DRM_PLANE_TYPE_PRIMARY)
995  {
996  if (!plane->m_videoFormats.empty())
997  primaryVideo.emplace_back(plane);
998  if (MythDRMPlane::HasOverlayFormat(plane->m_formats))
999  primaryGUI.emplace_back(plane);
1000  }
1001  else if (plane->m_type == DRM_PLANE_TYPE_OVERLAY)
1002  {
1003  if (!plane->m_videoFormats.empty())
1004  overlayVideo.emplace_back(plane);
1005  if (MythDRMPlane::HasOverlayFormat(plane->m_formats))
1006  overlayGUI.emplace_back(plane);
1007  }
1008 
1009  if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_INFO))
1010  LOG(VB_PLAYBACK, LOG_INFO, LOC + plane->Description());
1011  }
1012 
1013  // This *should not happen*
1014  if (primaryGUI.empty() && overlayGUI.empty())
1015  return;
1016 
1017  // Neither should this really...
1018  if (primaryVideo.empty() && overlayVideo.empty())
1019  {
1020  LOG(VB_GENERAL, LOG_WARNING, LOC + "Found no planes with video support");
1021  return;
1022  }
1023 
1024  // Need to ensure we don't pick the same plane for video and GUI
1025  auto nodupe = [](const auto & Planes, const auto & Plane)
1026  {
1027  for (const auto & plane : Planes)
1028  if (plane->m_id != Plane->m_id)
1029  return plane;
1030  return DRMPlane { nullptr };
1031  };
1032 
1033  // Note: If video is an overlay or both planes are of the same type then
1034  // video composition will likely fail if there is no zpos support. Oddly
1035  // clang-tidy-16 thinks the "if" and "else" clauses are the same.
1036  // NOLINTNEXTLINE(bugprone-branch-clone)
1037  if (primaryVideo.empty())
1038  {
1039  m_videoPlane = overlayVideo.front();
1040  if (overlayGUI.empty())
1041  m_guiPlane = primaryGUI.front();
1042  else
1043  m_guiPlane = nodupe(overlayGUI, m_videoPlane);
1044  }
1045  else
1046  {
1047  m_videoPlane = primaryVideo.front();
1048  if (overlayGUI.empty())
1049  m_guiPlane = nodupe(primaryGUI, m_videoPlane);
1050  else
1051  m_guiPlane = overlayGUI.front(); // Simple primary video and overlay GUI
1052  }
1053 
1054  if (!m_videoPlane.get())
1055  {
1056  LOG(VB_GENERAL, LOG_ERR, LOC + "No video plane");
1057  }
1058  else
1059  {
1060  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Selected Plane #%1 %2 for video")
1061  .arg(m_videoPlane->m_id).arg(MythDRMPlane::PlaneTypeToString(m_videoPlane->m_type)));
1062  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Supported DRM video formats: %1")
1063  .arg(MythDRMPlane::FormatsToString(m_videoPlane->m_videoFormats)));
1064  }
1065 
1066  if (!m_guiPlane.get())
1067  {
1068  LOG(VB_GENERAL, LOG_ERR, LOC + "No GUI plane");
1069  }
1070  else
1071  {
1072  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Selected Plane #%1 %2 for GUI")
1073  .arg(m_guiPlane->m_id).arg(MythDRMPlane::PlaneTypeToString(m_guiPlane->m_type)));
1074  }
1075 }
1076 #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:89
MythDRMDevice::m_adjustedRefreshRate
double m_adjustedRefreshRate
Definition: mythdrmdevice.h:99
MythDRMConnector::GetConnectors
static DRMConns GetConnectors(int FD)
Definition: mythdrmconnector.cpp:28
MythDRMDevice::Authenticate
void Authenticate()
Attempt to acquire privileged DRM access.
Definition: mythdrmdevice.cpp:692
MythDRMDevice::m_screen
QScreen * m_screen
Definition: mythdrmdevice.h:84
MythDRMDevice::GetEDID
MythEDID GetEDID() const
Definition: mythdrmdevice.cpp:609
MythCommandLineParser
Parent class for defining application command line parsers.
Definition: mythcommandlineparser.h:116
MythDRMDevice::GetResolution
QSize GetResolution() const
Definition: mythdrmdevice.cpp:599
MythDRMDevice::GetScreen
QScreen * GetScreen() const
Definition: mythdrmdevice.cpp:594
false
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:89
MythDRMCrtc::GetCrtcs
static DRMCrtcs GetCrtcs(int FD)
Definition: mythdrmcrtc.cpp:27
MythDRMDevice::CanSwitchModes
bool CanSwitchModes() const
Definition: mythdrmdevice.cpp:631
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:651
MythDRMDevice::Create
static MythDRMPtr Create(QScreen *qScreen, const QString &Device=QString(), bool NeedPlanes=true)
Create a MythDRMDevice instance.
Definition: mythdrmdevice.cpp:316
MythDRMDevice::~MythDRMDevice
~MythDRMDevice()
Definition: mythdrmdevice.cpp:555
VERBOSE_LEVEL_CHECK
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
setenv
#define setenv(x, y, z)
Definition: compat.h:89
MythDRMDevice::GetSerialNumber
QString GetSerialNumber() const
Definition: mythdrmdevice.cpp:589
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:96
confdir
static QString confdir
Definition: mythdirs.cpp:22
MythDRMDevice::m_serialNumber
QString m_serialNumber
Definition: mythdrmdevice.h:100
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
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:35
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:724
MythDRMDevice::m_valid
bool m_valid
Definition: mythdrmdevice.h:83
true
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:95
MythDRMDevice::m_connector
DRMConn m_connector
Definition: mythdrmdevice.h:94
MythDRMDevice::m_crtcs
DRMCrtcs m_crtcs
Definition: mythdrmdevice.h:92
close
#define close
Definition: compat.h:43
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:604
MythDRMDevice::GetModes
const DRMModes & GetModes() const
Definition: mythdrmdevice.cpp:637
MythEDID::SerialNumbers
QStringList SerialNumbers() const
Definition: mythedid.cpp:44
MythDRMDevice::m_verbose
LogLevel_t m_verbose
Definition: mythdrmdevice.h:101
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:391
MythDRMPtr
std::shared_ptr< class MythDRMDevice > MythDRMPtr
Definition: mythdrmdevice.h:18
MythDRMDevice::GetConnector
DRMConn GetConnector() const
Definition: mythdrmdevice.cpp:877
MythDRMDevice::Atomic
bool Atomic() const
Definition: mythdrmdevice.cpp:579
MythDRMDevice
Definition: mythdrmdevice.h:24
MythEDID::Valid
bool Valid() const
Definition: mythedid.cpp:39
MythDRMDevice::m_openedDevice
bool m_openedDevice
Definition: mythdrmdevice.h:86
MythDRMDevice::Authenticated
bool Authenticated() const
Definition: mythdrmdevice.cpp:574
MythCommandLineParser::toUInt
uint toUInt(const QString &key) const
Returns stored QVariant as an unsigned integer, falling to default if not provided.
Definition: mythcommandlineparser.cpp:2236
MythDRMDevice::FindBestDevice
QString FindBestDevice()
Definition: mythdrmdevice.cpp:820
DRMPlanes
std::vector< DRMPlane > DRMPlanes
Definition: mythdrmplane.h:51
MythDRMDevice::m_encoders
DRMEncs m_encoders
Definition: mythdrmdevice.h:91
MythDRMDevice::m_crtc
DRMCrtc m_crtc
Definition: mythdrmdevice.h:95
DRMConn
std::shared_ptr< class MythDRMConnector > DRMConn
Definition: mythdrmconnector.h:10
MythDRMDevice::GetCrtc
DRMCrtc GetCrtc() const
Definition: mythdrmdevice.cpp:872
MythDRMDevice::GetFD
int GetFD() const
Definition: mythdrmdevice.cpp:584
MythDRMDevice::m_edid
MythEDID m_edid
Definition: mythdrmdevice.h:102
MythDRMDevice::m_connectors
DRMConns m_connectors
Definition: mythdrmdevice.h:90
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:76
MythDRMDevice::m_deviceName
QString m_deviceName
Definition: mythdrmdevice.h:85
DRM_QUIET
static constexpr const char * DRM_QUIET
Definition: mythdrmdevice.h:22
LOC
#define LOC
Definition: mythdrmdevice.cpp:28
MythDRMDevice::m_refreshRate
double m_refreshRate
Definition: mythdrmdevice.h:98
MythCommandLineParser::toString
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
Definition: mythcommandlineparser.cpp:2344
mythedid.h
MythCommandLineParser::toBool
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
Definition: mythcommandlineparser.cpp:2187
MythDRMDevice::m_planes
DRMPlanes m_planes
Definition: mythdrmdevice.h:93
std
Definition: mythchrono.h:23
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:624
MythDRMConnector::GetConnector
static DRMConn GetConnector(const DRMConns &Connectors, uint32_t Id)
Definition: mythdrmconnector.cpp:20
mythdrmdevice.h
MythAtomics
std::vector< MythAtomic > MythAtomics
Definition: mythdrmdevice.h:20
MythDRMDevice::GetDeviceList
static std::tuple< QString, QStringList > GetDeviceList()
Definition: mythdrmdevice.cpp:371
MythDRMPlane::GetPlanes
static DRMPlanes GetPlanes(int FD, int CRTCFilter=-1)
Definition: mythdrmplane.cpp:80
MythDRMDevice::Load
void Load()
Definition: mythdrmdevice.cpp:717
build_compdb.filename
filename
Definition: build_compdb.py:21
MythDRMDevice::m_physicalSize
QSize m_physicalSize
Definition: mythdrmdevice.h:97
MythDRMDevice::m_fd
int m_fd
Definition: mythdrmdevice.h:87
MythDRMDevice::m_atomic
bool m_atomic
Definition: mythdrmdevice.h:88
mythdrmencoder.h
nv_python_libs.bbciplayer.bbciplayer_api.version
string version
Definition: bbciplayer_api.py:77
MythDRMDevice::Open
bool Open()
Definition: mythdrmdevice.cpp:564
MythEDID
Definition: mythedid.h:21
MythDRMDevice::ConfirmDevice
static bool ConfirmDevice(const QString &Device)
Definition: mythdrmdevice.cpp:856