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 : qAsConst(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 
637 {
638  static const DRMModes empty;
640  return m_connector->m_modes;
641  return empty;
642 }
643 
650 bool MythDRMDevice::SwitchMode(int ModeIndex)
651 {
652  if (!(m_authenticated && m_atomic && m_connector.get() && m_crtc.get()))
653  return false;
654 
655  auto index = static_cast<size_t>(ModeIndex);
656 
657  if (ModeIndex < 0 || index >= m_connector->m_modes.size())
658  return false;
659 
660  bool result = false;
661 #ifdef USING_QTPRIVATEHEADERS
662  auto crtcid = MythDRMProperty::GetProperty("crtc_id", m_connector->m_properties);
663  auto modeid = MythDRMProperty::GetProperty("mode_id", m_crtc->m_properties);
664  if (crtcid.get() && modeid.get())
665  {
666  uint32_t blobid = 0;
667  // Presumably blobid does not need to be released? Can't find any documentation but
668  // there is the matching drmModeDestroyPropertyBlob...
669  if (drmModeCreatePropertyBlob(m_fd, &m_connector->m_modes[index], sizeof(drmModeModeInfo), &blobid) == 0)
670  {
671  QueueAtomics( {{ m_connector->m_id, crtcid->m_id, m_crtc->m_id },
672  { m_crtc->m_id, modeid->m_id, blobid }} );
673  m_adjustedRefreshRate = m_connector->m_modes[index]->m_rate;
674  result = true;
675  }
676  else
677  {
678  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create mode blob");
679  }
680  }
681 #endif
682  return result;
683 }
684 
692 {
693  if (!m_fd || m_authenticated)
694  return;
695 
696  int ret = drmSetMaster(m_fd);
697  m_authenticated = ret >= 0;
698 
699  if (!m_authenticated)
700  {
701  drm_magic_t magic = 0;
702  m_authenticated = drmGetMagic(m_fd, &magic) == 0 && drmAuthMagic(m_fd, magic) == 0;
703  }
704 
705  if (m_authenticated)
706  {
707  const auto * extra = m_atomic ? "" : " but atomic operations required for mode switching";
708  LOG(VB_GENERAL, m_verbose, LOC + "Authenticated" + extra);
709  }
710  else
711  {
712  LOG(VB_GENERAL, m_verbose, LOC + "Not authenticated - mode switching not available");
713  }
714 }
715 
717 {
721 }
722 
724 {
725  if (!m_fd)
726  return false;
727 
728  // Find the serial number of the display we are connected to
729  auto serial = m_screen ? m_screen->serialNumber() : "";
730  if (m_screen && serial.isEmpty())
731  {
732  // No serial number either means an older version of Qt or the EDID
733  // is not available for some reason - in which case there is no point
734  // in trying to use it anyway.
735  LOG(VB_GENERAL, m_verbose, LOC + "QScreen has no serial number.");
736  LOG(VB_GENERAL, m_verbose, LOC + "Will use first suitable connected device");
737  }
738 
739  // Retrieve full details for the device
740  Load();
741 
742  // Find connector
743  for (const auto & connector : m_connectors)
744  {
745  if (connector->m_state == DRM_MODE_CONNECTED)
746  {
747  if (serial.isEmpty())
748  {
749  m_connector = connector;
750  break;
751  }
752 
753  // Does the connected display have the serial number we are looking for?
754  if (const auto edidprop = MythDRMProperty::GetProperty("EDID", connector->m_properties); edidprop.get())
755  {
756  MythEDID edid;
757  if (auto * blob = dynamic_cast<MythDRMBlobProperty*>(edidprop.get()); blob)
758  edid = MythEDID(blob->m_blob);
759 
760  if (edid.Valid() && edid.SerialNumbers().contains(serial))
761  {
762  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Matched connector with serial '%1'")
763  .arg(serial));
764  m_connector = connector;
765  m_physicalSize = QSize(static_cast<int>(connector->m_mmWidth),
766  static_cast<int>(connector->m_mmHeight));
767  m_serialNumber = serial;
768  m_edid = edid;
769  break;
770  }
771 
772  if (!edid.Valid())
773  LOG(VB_GENERAL, m_verbose, LOC + "Connected device has invalid EDID");
774 
775  if (m_connector && !m_serialNumber.isEmpty())
776  break;
777  }
778  else
779  {
780  LOG(VB_GENERAL, m_verbose, LOC + "Connected device has no EDID");
781  }
782  }
783  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Ignoring disconnected connector %1")
784  .arg(connector->m_name));
785  }
786 
787  if (!m_connector.get())
788  {
789  LOG(VB_GENERAL, LOG_DEBUG, LOC + "No connected connectors");
790  return false;
791  }
792 
793  LOG(VB_GENERAL, m_verbose, LOC + QString("Selected connector %1").arg(m_connector->m_name));
794 
795  // Find the encoder for the connector
796  auto encoder = MythDRMEncoder::GetEncoder(m_encoders, m_connector->m_encoderId);
797  if (!encoder)
798  {
799  LOG(VB_GENERAL, m_verbose, LOC + QString("Failed to find encoder for %1").arg(m_connector->m_name));
800  return false;
801  }
802 
803  // Find the CRTC for the encoder
804  m_crtc = MythDRMCrtc::GetCrtc(m_crtcs, encoder->m_crtcId);
805  if (!m_crtc)
806  {
807  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Failed to find crtc for encoder");
808  return false;
809  }
810 
811  m_resolution = QSize(static_cast<int>(m_crtc->m_width), static_cast<int>(m_crtc->m_height));
812  if (m_crtc->m_mode.get())
813  m_refreshRate = m_crtc->m_mode->m_rate;
814 
815  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Initialised");
816  return true;
817 }
818 
820 {
821  if (!m_screen)
822  return {};
823 
824  auto [root, devices] = GetDeviceList();
825  if (devices.isEmpty())
826  return {};
827 
828  // Only one device - return it
829  if (devices.size() == 1)
830  return root + devices.first();
831 
832  // Use the serial number from the current QScreen to select a suitable device
833  auto serial = m_screen->serialNumber();
834  if (serial.isEmpty())
835  {
836  LOG(VB_GENERAL, m_verbose, LOC + "No serial number to search for");
837  return {};
838  }
839 
840  for (const auto& dev : qAsConst(devices))
841  {
842  QString device = root + dev;
843  if (!ConfirmDevice(device))
844  {
845  LOG(VB_GENERAL, m_verbose, LOC + "Failed to confirm device");
846  continue;
847  }
848  MythDRMDevice drmdevice(m_screen, device);
849  if (drmdevice.GetSerialNumber() == serial)
850  return device;
851  }
852  return {};
853 }
854 
856 {
857  bool result = false;
858  int fd = open(Device.toLocal8Bit().constData(), O_RDWR);
859  if (fd < 0)
860  return result;
861  drmVersionPtr version = drmGetVersion(fd);
862  if (version)
863  {
864  drmFreeVersion(version);
865  result = true;
866  }
867  close(fd);
868  return result;
869 }
870 
872 {
873  return m_crtc;
874 }
875 
877 {
878  return m_connector;
879 }
880 
881 #if defined (USING_QTPRIVATEHEADERS)
882 void MythDRMDevice::MainWindowReady()
883 {
884  // This is causing issues - disabled for now
885  //DisableVideoPlane();
886 
887  // Temporarily disabled - this is informational only
888  /*
889  // Confirm GUI plane format now that Qt is setup
890  if (m_guiPlane.get())
891  {
892  // TODO Add methods to retrieve up to date property values rather than
893  // create new objects
894  if (auto plane = MythDRMPlane::Create(m_fd, m_guiPlane->m_id, 0); plane)
895  {
896  if (auto guifb = MythDRMFramebuffer::Create(m_fd, plane->m_fbId); guifb)
897  {
898  if (MythDRMPlane::HasOverlayFormat({ guifb->m_format }))
899  {
900  LOG(VB_GENERAL, LOG_INFO, LOC + QString("GUI alpha format confirmed (%1)")
901  .arg(MythDRMPlane::FormatToString(guifb->m_format)));
902  }
903  else
904  {
905  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("GUI plane has no alpha (%1)")
906  .arg(MythDRMPlane::FormatToString(guifb->m_format)));
907  }
908  }
909  }
910  }
911  */
912 }
913 
914 bool MythDRMDevice::QueueAtomics(const MythAtomics& Atomics) const
915 {
916  auto * app = dynamic_cast<QGuiApplication *>(QCoreApplication::instance());
917  if (!(m_atomic && m_authenticated && app))
918  return false;
919 
920  auto * pni = QGuiApplication::platformNativeInterface();
921  if (auto * dri = pni->nativeResourceForIntegration("dri_atomic_request"); dri)
922  {
923  if (auto * request = reinterpret_cast<drmModeAtomicReq*>(dri); request != nullptr)
924  {
925  for (const auto & a : Atomics)
926  drmModeAtomicAddProperty(request, std::get<0>(a), std::get<1>(a), std::get<2>(a));
927  return true;
928  }
929  }
930  return false;
931 }
932 
933 void MythDRMDevice::DisableVideoPlane()
934 {
935  if (m_videoPlane.get())
936  {
937  LOG(VB_GENERAL, LOG_INFO, LOC + "Disabling video plane");
938  QueueAtomics( {{ m_videoPlane->m_id, m_videoPlane->m_fbIdProp->m_id, 0 },
939  { m_videoPlane->m_id, m_videoPlane->m_crtcIdProp->m_id, 0 }} );
940  }
941 }
942 
943 DRMPlane MythDRMDevice::GetVideoPlane() const
944 {
945  return m_videoPlane;
946 }
947 
948 DRMPlane MythDRMDevice::GetGUIPlane() const
949 {
950  return m_guiPlane;
951 }
952 
972 void MythDRMDevice::AnalysePlanes()
973 {
974  if (!m_fd || !m_crtc || m_crtc->m_index <= -1)
975  return;
976 
977  // Find our planes
978  auto allplanes = MythDRMPlane::GetPlanes(m_fd);
980 
981  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found %1 planes; %2 for this CRTC")
982  .arg(allplanes.size()).arg(m_planes.size()));
983 
984  DRMPlanes primaryVideo;
985  DRMPlanes overlayVideo;
986  DRMPlanes primaryGUI;
987  DRMPlanes overlayGUI;
988 
989  for (const auto & plane : m_planes)
990  {
991  if (plane->m_type == DRM_PLANE_TYPE_PRIMARY)
992  {
993  if (!plane->m_videoFormats.empty())
994  primaryVideo.emplace_back(plane);
995  if (MythDRMPlane::HasOverlayFormat(plane->m_formats))
996  primaryGUI.emplace_back(plane);
997  }
998  else if (plane->m_type == DRM_PLANE_TYPE_OVERLAY)
999  {
1000  if (!plane->m_videoFormats.empty())
1001  overlayVideo.emplace_back(plane);
1002  if (MythDRMPlane::HasOverlayFormat(plane->m_formats))
1003  overlayGUI.emplace_back(plane);
1004  }
1005 
1006  if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_INFO))
1007  LOG(VB_PLAYBACK, LOG_INFO, LOC + plane->Description());
1008  }
1009 
1010  // This *should not happen*
1011  if (primaryGUI.empty() && overlayGUI.empty())
1012  return;
1013 
1014  // Neither should this really...
1015  if (primaryVideo.empty() && overlayVideo.empty())
1016  {
1017  LOG(VB_GENERAL, LOG_WARNING, LOC + "Found no planes with video support");
1018  return;
1019  }
1020 
1021  // Need to ensure we don't pick the same plane for video and GUI
1022  auto nodupe = [](const auto & Planes, const auto & Plane)
1023  {
1024  for (const auto & plane : Planes)
1025  if (plane->m_id != Plane->m_id)
1026  return plane;
1027  return DRMPlane { nullptr };
1028  };
1029 
1030  // Note: If video is an overlay or both planes are of the same type then
1031  // video composition will likely fail if there is no zpos support. Oddly
1032  // clang-tidy-16 thinks the "if" and "else" clauses are the same.
1033  // NOLINTNEXTLINE(bugprone-branch-clone)
1034  if (primaryVideo.empty())
1035  {
1036  m_videoPlane = overlayVideo.front();
1037  if (overlayGUI.empty())
1038  m_guiPlane = primaryGUI.front();
1039  else
1040  m_guiPlane = nodupe(overlayGUI, m_videoPlane);
1041  }
1042  else
1043  {
1044  m_videoPlane = primaryVideo.front();
1045  if (overlayGUI.empty())
1046  m_guiPlane = nodupe(primaryGUI, m_videoPlane);
1047  else
1048  m_guiPlane = overlayGUI.front(); // Simple primary video and overlay GUI
1049  }
1050 
1051  if (!m_videoPlane.get())
1052  {
1053  LOG(VB_GENERAL, LOG_ERR, LOC + "No video plane");
1054  }
1055  else
1056  {
1057  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Selected Plane #%1 %2 for video")
1058  .arg(m_videoPlane->m_id).arg(MythDRMPlane::PlaneTypeToString(m_videoPlane->m_type)));
1059  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Supported DRM video formats: %1")
1060  .arg(MythDRMPlane::FormatsToString(m_videoPlane->m_videoFormats)));
1061  }
1062 
1063  if (!m_guiPlane.get())
1064  {
1065  LOG(VB_GENERAL, LOG_ERR, LOC + "No GUI plane");
1066  }
1067  else
1068  {
1069  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Selected Plane #%1 %2 for GUI")
1070  .arg(m_guiPlane->m_id).arg(MythDRMPlane::PlaneTypeToString(m_guiPlane->m_type)));
1071  }
1072 }
1073 #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:691
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:650
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:723
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:636
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:876
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:2251
MythDRMDevice::FindBestDevice
QString FindBestDevice()
Definition: mythdrmdevice.cpp:819
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:871
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:2359
mythedid.h
MythCommandLineParser::toBool
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
Definition: mythcommandlineparser.cpp:2202
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:716
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:855