MythTV master
mythdisplay.cpp
Go to the documentation of this file.
1// Std
2#include <algorithm>
3
4//Qt
5#include <QTimer>
6#include <QThread>
7#include <QApplication>
8#include <QElapsedTimer>
9#include <QWindow>
10
11#include "config.h"
12
13#if CONFIG_QTWEBENGINE
14#include <QQuickWindow>
15#endif
16
17// MythTV
18#include "libmythbase/compat.h"
21#include "mythuihelper.h"
22#include "mythdisplay.h"
23#include "opengl/mythegl.h"
24#include "mythmainwindow.h"
25
26#if CONFIG_QTDBUS
28#endif
29#if CONFIG_WAYLANDEXTRAS
31#endif
32#ifdef Q_OS_ANDROID
34#endif
35#ifdef Q_OS_DARWIN
37#endif
38#if CONFIG_X11
42#endif
43#if CONFIG_DRM
46#endif
47#ifdef Q_OS_WIN
49#endif
50#if CONFIG_MMAL
52#endif
53
54#define LOC QString("Display: ")
55
90MythDisplay* MythDisplay::Create([[maybe_unused]] MythMainWindow* MainWindow)
91{
92 MythDisplay* result = nullptr;
93#if CONFIG_X11
95 result = new MythDisplayX11();
96#endif
97#if CONFIG_QTDBUS
98 // Disabled for now as org.gnome.Mutter.DisplayConfig.ApplyConfiguration does
99 // not seem to be actually implemented by anyone.
100#if CONFIG_WAYLANDEXTRAS
101 //if (MythWaylandDevice::IsAvailable())
102#endif
103 //{
104 // if (!result)
105 // result = MythDisplayMutter::Create();
106 //}
107#endif
108#if CONFIG_DRM
109 if (!result)
110 {
111 result = new MythDisplayDRM(MainWindow);
112 // On the Pi, use MythDisplayRPI if mode switching is not available via DRM
113#if CONFIG_MMAL
114 if (!result->VideoModesAvailable())
115 {
116 delete result;
117 result = nullptr;
118 }
119#endif
120 }
121#endif
122#if CONFIG_MMAL
123 if (!result)
124 result = new MythDisplayRPI();
125#endif
126#ifdef Q_OS_DARWIN
127 if (!result)
128 result = new MythDisplayOSX();
129#endif
130#ifdef Q_OS_ANDROID
131 if (!result)
132 result = new MythDisplayAndroid();
133#endif
134#ifdef Q_OS_WIN
135 if (!result)
136 result = new MythDisplayWindows();
137#endif
138 if (!result)
139 result = new MythDisplay();
140 return result;
141}
142
144{
145 QStringList result;
146 bool spanall = false;
147 int screencount = MythDisplay::GetScreenCount();
148 if (MythDisplay::SpanAllScreens() && screencount > 1)
149 {
150 spanall = true;
151 result.append(tr("Spanning %1 screens").arg(screencount));
152 result.append(tr("Total bounds") + QString("\t: %1x%2")
153 .arg(GetScreenBounds().width()).arg(GetScreenBounds().height()));
154 result.append("");
155 }
156
157 if (m_hdrState)
158 {
159 auto types = m_hdrState->m_supportedTypes;
160 auto hdr = m_hdrState->TypesToString();
161 result.append(tr("Supported HDR formats\t: %1").arg(hdr.join(",")));
162 if (types && !m_hdrState->IsControllable())
163 result.append(tr("HDR mode switching is not available"));
164 if (auto brightness = m_hdrState->GetMaxLuminance(); brightness > 1.0)
165 result.append(tr("Max display brightness\t: %1 nits").arg(static_cast<int>(brightness)));
166 }
167
168 if (m_vrrState)
169 {
170 result.append(tr("Variable refresh rate '%1': %2 %3")
171 .arg(m_vrrState->TypeToString(),
172 m_vrrState->Enabled() ? tr("Enabled") : tr("Disabled"),
173 m_vrrState->RangeDescription()));
174 }
175
176 auto * current = GetCurrentScreen();
177 const auto screens = QGuiApplication::screens();
178 bool first = true;
179 for (auto *screen : std::as_const(screens))
180 {
181 if (!first)
182 result.append("");
183 first = false;
184 auto id = QString("(%1)").arg(screen->manufacturer());
185 if (screen == current && !spanall)
186 result.append(tr("Current screen\t: %1 %2").arg(screen->name(), id));
187 else
188 result.append(tr("Screen\t\t: %1 %2").arg(screen->name(), id));
189 result.append(tr("Size") + QString("\t\t: %1mmx%2mm")
190 .arg(screen->physicalSize().width()).arg(screen->physicalSize().height()));
191 if (screen == current)
192 {
193 QString source;
194 auto aspect = GetAspectRatio(source);
195 result.append(tr("Aspect ratio") + QString("\t: %1 (%2)")
196 .arg(aspect, 0, 'f', 3).arg(source));
197 if (!spanall)
198 {
199 result.append(tr("Current mode") + QString("\t: %1x%2@%3Hz")
200 .arg(GetResolution().width()).arg(GetResolution().height())
201 .arg(GetRefreshRate(), 0, 'f', 2));
202 const auto & modes = GetVideoModes();
203 if (!modes.empty())
204 {
205 result.append(tr("Available modes:"));
206 for (auto it = modes.crbegin(); it != modes.crend(); ++it)
207 result.append(" " + it->ToString());
208 }
209 }
210 }
211 }
212
213 return result;
214}
215
217 : m_screen(GetDesiredScreen())
218{
219 DebugScreen(m_screen, "Using");
220 if (m_screen)
221 {
222 connect(m_screen, &QScreen::geometryChanged, this, &MythDisplay::GeometryChanged);
223 connect(m_screen, &QScreen::physicalDotsPerInchChanged, this, &MythDisplay::PhysicalDPIChanged);
224 }
225
226 auto *guiapp = qobject_cast<QGuiApplication *>(QCoreApplication::instance());
227 if (guiapp == nullptr)
228 return;
229
230 connect(guiapp, &QGuiApplication::screenRemoved, this, &MythDisplay::ScreenRemoved);
231 connect(guiapp, &QGuiApplication::screenAdded, this, &MythDisplay::ScreenAdded);
232 connect(guiapp, &QGuiApplication::primaryScreenChanged, this, &MythDisplay::PrimaryScreenChanged);
233}
234
236{
237 LOG(VB_GENERAL, LOG_INFO, LOC + "Deleting");
238}
239
253void MythDisplay::SetWidget(QWidget *MainWindow)
254{
255 QWidget* oldwidget = m_widget;
256 m_widget = MainWindow;
257 if (!m_modeComplete)
259
260 QWindow* oldwindow = m_window;
261 if (m_widget)
262 m_window = m_widget->windowHandle();
263 else
264 m_window = nullptr;
265
266 if (m_widget && (m_widget != oldwidget))
267 LOG(VB_GENERAL, LOG_INFO, LOC + "Have main widget");
268
269 if (m_window && (m_window != oldwindow))
270 {
271 LOG(VB_GENERAL, LOG_INFO, LOC + "Have main window");
272
273 connect(m_window, &QWindow::screenChanged, this, &MythDisplay::ScreenChanged, Qt::UniqueConnection);
274 QScreen *desired = GetDesiredScreen();
275 // If we have changed the video mode for the old screen then reset
276 // it to the default/desktop mode
277 if (oldwindow)
279 // Ensure we completely re-initialise when the new screen is set
280 m_initialised = false;
281 if (desired != m_screen)
282 DebugScreen(desired, "Moving to");
283 m_window->setScreen(desired);
284 // WaitForNewScreen doesn't work as intended. It successfully filters
285 // out unwanted screenChanged signals after moving screens - but always
286 //times out. This just delays startup by 500ms - so ignore on startup as it isn't needed.
289 m_firstScreenChange = false;
291 return;
292 }
293}
294
296{
297 return QGuiApplication::screens().size();
298}
299
301{
302 if (m_physicalSize.isEmpty() || m_resolution.isEmpty())
303 return 1.0;
304
305 // HD-Ready or better displays always have square pixels
306 if (m_resolution.height() >= 720)
307 return 1.0;
308
309 return (m_physicalSize.width() / static_cast<double>(m_resolution.width())) /
310 (m_physicalSize.height() / static_cast<double>(m_resolution.height()));
311}
312
314{
315 return m_guiMode.Resolution();
316}
317
319{
320 return m_screenBounds;
321}
322
336{
337 return m_screen;
338}
339
341{
342 QScreen* newscreen = nullptr;
343
344 // If geometry is overriden at the command line level, try and determine
345 // which screen that applies to (if any).
346 // N.B. So many potential issues here e.g. should the geometry override be
347 // ignored after first use? (as it will continue to override the screen
348 // regardless of changes to screen preference).
350 {
351 // this matches the check in MythMainWindow
352 bool windowed = GetMythDB()->GetBoolSetting("RunFrontendInWindow", false) &&
354 QRect override = MythMainWindow::GetGeometryOverride();
355 // When windowed, we use topleft as a best guess as to which screen we belong in.
356 // When fullscreen, Qt appears to use the reverse - though this may be
357 // the window manager rather than Qt. So could be wrong.
358 QPoint point = windowed ? override.topLeft() : override.bottomRight();
359 QList screens = QGuiApplication::screens();
360 for (QScreen *screen : std::as_const(screens))
361 {
362 if (screen->geometry().contains(point))
363 {
364 newscreen = screen;
365 LOG(VB_GENERAL, LOG_INFO, LOC + QString(
366 "Geometry override places window in screen '%1'").arg(newscreen->name()));
367 break;
368 }
369 }
370 }
371
372 // If spanning all screens, then always use the primary display
373 if (!newscreen && MythDisplay::SpanAllScreens())
374 {
375 LOG(VB_GENERAL, LOG_INFO, LOC + "Using primary screen for multiscreen");
376 newscreen = QGuiApplication::primaryScreen();
377 }
378
379 QString name = gCoreContext->GetSetting("XineramaScreen", nullptr);
380 // Lookup by name
381 if (!newscreen)
382 {
383 QList screens = QGuiApplication::screens();
384 for (QScreen *screen : std::as_const(screens))
385 {
386 if (!name.isEmpty() && name == screen->name())
387 {
388 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found screen '%1'").arg(name));
389 newscreen = screen;
390 }
391 }
392 }
393
394 // No name match. These were previously numbers.
395 if (!newscreen)
396 {
397 bool ok = false;
398 int screen_num = name.toInt(&ok);
399 QList<QScreen *>screens = QGuiApplication::screens();
400 if (ok && (screen_num >= 0) && (screen_num < screens.size()))
401 {
402 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Found screen number %1 (%2)")
403 .arg(name, screens[screen_num]->name()));
404 newscreen = screens[screen_num];
405 }
406 }
407
408 // For anything else, return the primary screen.
409 if (!newscreen)
410 {
411 QScreen *primary = QGuiApplication::primaryScreen();
412 if (name.isEmpty() && primary)
413 {
414 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Defaulting to primary screen (%1)")
415 .arg(primary->name()));
416 }
417 else if (name != "-1" && primary)
418 {
419 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen '%1' not found, defaulting to primary screen (%2)")
420 .arg(name, primary->name()));
421 }
422 newscreen = primary;
423 }
424
425 return newscreen;
426}
427
430void MythDisplay::ScreenChanged(QScreen *qScreen)
431{
432 if (qScreen == m_screen)
433 return;
434 if (m_screen)
435 disconnect(m_screen, nullptr, this, nullptr);
436 DebugScreen(qScreen, "Changed to");
437 m_screen = qScreen;
438 connect(m_screen, &QScreen::geometryChanged, this, &MythDisplay::GeometryChanged);
439 connect(m_screen, &QScreen::physicalDotsPerInchChanged, this, &MythDisplay::PhysicalDPIChanged);
440 Initialise();
441 emit CurrentScreenChanged(qScreen);
442}
443
445{
446 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Qt screen pixel ratio changed to %1")
447 .arg(DPI, 2, 'f', 2, '0'));
448 emit CurrentDPIChanged(DPI);
449}
450
452{
453 DebugScreen(qScreen, "New primary");
454}
455
456void MythDisplay::ScreenAdded(QScreen* qScreen)
457{
458 DebugScreen(qScreen, "New");
459 emit ScreenCountChanged(QGuiApplication::screens().size());
460}
461
462void MythDisplay::ScreenRemoved(QScreen* qScreen)
463{
464 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen '%1' removed").arg(qScreen->name()));
465 emit ScreenCountChanged(QGuiApplication::screens().size());
466}
467
468void MythDisplay::GeometryChanged(const QRect Geo)
469{
470 LOG(VB_GENERAL, LOG_INFO, LOC + QString("New screen geometry: %1x%2+%3+%4")
471 .arg(Geo.width()).arg(Geo.height()).arg(Geo.left()).arg(Geo.top()));
472}
473
481{
482 // Certain platform implementations do not have a window to access at startup
483 // and hence use this implementation. Flag the status as incomplete to ensure
484 // we try to retrieve the full details at the first opportunity.
485 m_modeComplete = false;
486 m_edid = MythEDID();
487 QScreen *screen = GetCurrentScreen();
488 if (!screen)
489 {
490 m_refreshRate = 60.0;
491 m_physicalSize = QSize(0, 0);
492 m_resolution = QSize(1920, 1080);
493 return;
494 }
495 m_refreshRate = screen->refreshRate();
496 m_resolution = screen->size();
497 m_physicalSize = QSize(static_cast<int>(screen->physicalSize().width()),
498 static_cast<int>(screen->physicalSize().height()));
499}
500
503{
504 return gCoreContext->GetSetting("XineramaScreen", nullptr) == "-1";
505}
506
507QString MythDisplay::GetExtraScreenInfo(QScreen *qScreen)
508{
509 QString mfg = qScreen->manufacturer();
510 if (mfg.isEmpty())
511 mfg = "Unknown";
512 QString model = qScreen->model();
513 if (model.isEmpty())
514 model = "Unknown";
515 return QString("(Make: %1 Model: %2)").arg(mfg, model);
516}
517
518void MythDisplay::DebugScreen(QScreen *qScreen, const QString &Message)
519{
520 if (!qScreen)
521 return;
522
523 auto geom = qScreen->geometry();
524 LOG(VB_GENERAL, LOG_INFO, LOC + QString("%1 screen '%2' %3")
525 .arg(Message, qScreen->name(), GetExtraScreenInfo(qScreen)));
526 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Qt screen pixel ratio: %1")
527 .arg(qScreen->devicePixelRatio(), 2, 'f', 2, '0'));
528 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Geometry: %1x%2+%3+%4 Size(Qt): %5mmx%6mm")
529 .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top())
530 .arg(qScreen->physicalSize().width()).arg(qScreen->physicalSize().height()));
531
532 if (qScreen->virtualGeometry() != geom)
533 {
534 geom = qScreen->virtualGeometry();
535 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Total virtual geometry: %1x%2+%3+%4")
536 .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top()));
537 }
538}
539
541{
542 m_videoModes.clear();
543 m_overrideVideoModes.clear();
545 // Note: The EDID is retrieved in UpdateCurrentMode and we need the EDID to
546 // check for refresh rate range support.
549
550 // Set the desktop mode - which is the mode at startup. We must always return
551 // the screen to this mode.
552 if (!m_initialised)
553 {
554 // Only ever set this once or after a screen change
555 m_initialised = true;
557 LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Desktop video mode: %1x%2 %3Hz")
558 .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate, 0, 'f', 3));
559 if (m_edid.Valid())
560 {
561 if (m_edid.IsSRGB())
562 LOG(VB_GENERAL, LOG_NOTICE, LOC + "Display is using sRGB colourspace");
563 else
564 LOG(VB_GENERAL, LOG_NOTICE, LOC + "Display has custom colourspace");
565
566 InitHDR();
567 }
568 }
569
570 // Set the gui mode from database settings
571 int pixelwidth = m_resolution.width();
572 int pixelheight = m_resolution.height();
573 int mmwidth = m_physicalSize.width();
574 int mmheight = m_physicalSize.height();
575 double refreshrate = m_refreshRate;
576 double aspectratio = 0.0;
577 GetMythDB()->GetResolutionSetting("GuiVidMode", pixelwidth, pixelheight, aspectratio, refreshrate);
578 GetMythDB()->GetResolutionSetting("DisplaySize", mmwidth, mmheight);
579 m_guiMode = MythDisplayMode(pixelwidth, pixelheight, mmwidth, mmheight, -1.0, refreshrate);
580
581 // Set default video mode
582 pixelwidth = pixelheight = 0;
583 GetMythDB()->GetResolutionSetting("TVVidMode", pixelwidth, pixelheight, aspectratio, refreshrate);
584 m_videoMode = MythDisplayMode(pixelwidth, pixelheight, mmwidth, mmheight, aspectratio, refreshrate);
585
586 // Initialise video override modes
587 for (int i = 0; true; ++i)
588 {
589 int iw = 0;
590 int ih = 0;
591 int ow = 0;
592 int oh = 0;
593 double iaspect = 0.0;
594 double oaspect = 0.0;
595 double irate = 0.0;
596 double orate = 0.0;
597
598 GetMythDB()->GetResolutionSetting("VidMode", iw, ih, iaspect, irate, i);
599 GetMythDB()->GetResolutionSetting("TVVidMode", ow, oh, oaspect, orate, i);
600
601 if ((!iw && !ih && qFuzzyIsNull(irate)) || !(ih && ow && oh))
602 break;
603
604 uint64_t key = MythDisplayMode::CalcKey(QSize(iw, ih), irate);
605 MythDisplayMode scr(QSize(ow, oh), QSize(mmwidth, mmheight), oaspect, orate);
606 m_overrideVideoModes[key] = scr;
607 }
608}
609
610
619{
620 const auto screens = QGuiApplication::screens();
621 for (auto * screen : std::as_const(screens))
622 {
623 auto dim = screen->geometry();
624 auto extra = MythDisplay::GetExtraScreenInfo(screen);
625 LOG(VB_GUI, LOG_INFO, LOC + QString("Screen %1: %2x%3 %4")
626 .arg(screen->name()).arg(dim.width()).arg(dim.height()).arg(extra));
627 }
628
629 const auto * primary = QGuiApplication::primaryScreen();
630 if (!primary)
631 {
632 if (!screens.empty())
633 primary = screens.front();
634 if (!primary)
635 {
636 LOG(VB_GENERAL, LOG_ERR, LOC + "Qt has no screens!");
637 return;
638 }
639 }
640
641 LOG(VB_GUI, LOG_INFO, LOC +QString("Primary screen: %1.").arg(primary->name()));
642
643 auto numScreens = MythDisplay::GetScreenCount();
644 auto dim = primary->virtualSize();
645 LOG(VB_GUI, LOG_INFO, LOC + QString("Total desktop dim: %1x%2, over %3 screen[s].")
646 .arg(dim.width()).arg(dim.height()).arg(numScreens));
647
649 {
650 LOG(VB_GUI, LOG_INFO, LOC + QString("Using entire desktop."));
651 m_screenBounds = primary->virtualGeometry();
652 return;
653 }
654
655 if (!GetMythDB()->GetBoolSetting("ForceFullScreen", false) &&
656 GetMythDB()->GetBoolSetting("RunFrontendInWindow", false))
657 {
658 LOG(VB_GUI, LOG_INFO, LOC + "Running in a window");
659 // This doesn't include the area occupied by the
660 // Windows taskbar, or the Mac OS X menu bar and Dock
661 m_screenBounds = m_screen->availableGeometry();
662 }
663 else
664 {
665 m_screenBounds = m_screen->geometry();
666 }
667
668 LOG(VB_GUI, LOG_INFO, LOC + QString("Using screen %1: %2x%3 at %4+%5")
669 .arg(m_screen->name()).arg(m_screenBounds.width()).arg(m_screenBounds.height())
670 .arg(m_screenBounds.left()).arg(m_screenBounds.top()));
671}
672
680{
681 return Size.width() > m_resolution.width() || Size.height() > m_resolution.height();
682}
683
689{
691 if (current == m_desktopMode)
692 return;
694}
695
699bool MythDisplay::SwitchToVideo(QSize Size, double Rate)
700{
701 if (!m_modeComplete)
703
706 double targetrate = 0.0;
707 double aspectoverride = 0.0;
708
709 // try to find video override mode
711 Size.width(), Size.height(), Rate);
712
713 if (key != 0)
714 {
715 next = m_overrideVideoModes[key];
716 if (next.AspectRatio() > 0.0)
717 aspectoverride = next.AspectRatio();
718 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Found custom screen override %1x%2 Aspect %3")
719 .arg(next.Width()).arg(next.Height()).arg(aspectoverride));
720 }
721
722 // If requested refresh rate is 0, attempt to match video fps
723 if (qFuzzyIsNull(next.RefreshRate()))
724 {
725 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Trying to match best refresh rate %1Hz")
726 .arg(Rate, 0, 'f', 3));
727 next.SetRefreshRate(Rate);
728 }
729
730 // need to change video mode?
731 (void)MythDisplayMode::FindBestMatch(GetVideoModes(), next, targetrate);
732
733 // If GSync or FreeSync are enabled, ignore refresh rate only changes.
734 // N.B. This check is not used when switching to GUI (which already ignores
735 // rate only changes) or switching back to the desktop (where we must reset
736 // the display to the original state).
737 if (m_vrrState && m_vrrState->Enabled())
738 {
739 if (next.Resolution() == current.Resolution())
740 {
741 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Ignoring mode switch to %1Hz - VRR enabled")
742 .arg(Rate, 0, 'f', 3));
743 return true;
744 }
745 LOG(VB_GENERAL, LOG_INFO, LOC + "Allowing mode switch with VRR enabled for new resolution");
746 }
747
748 // No need for change
749 if ((next == current) && (MythDisplayMode::CompareRates(current.RefreshRate(), targetrate)))
750 {
751 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using current mode %1x%2@%3Hz")
752 .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate));
753 return true;
754 }
755
756 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Trying mode %1x%2@%3Hz")
757 .arg(next.Width()).arg(next.Height()).arg(next.RefreshRate(), 0, 'f', 3));
758
759 if (!SwitchToVideoMode(next.Resolution(), targetrate))
760 {
761 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to change mode to %1x%2@%3Hz")
762 .arg(next.Width()).arg(next.Height()).arg(next.RefreshRate(), 0, 'f', 3));
763 return false;
764 }
765
766 if (next.Resolution() != m_resolution)
768
769 // N.B. We used a computed aspect ratio unless overridden
770 m_aspectRatioOverride = aspectoverride > 0.0 ? aspectoverride : 0.0;
772 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switched to %1x%2@%3Hz for video %4x%5")
773 .arg(m_resolution.width()).arg(m_resolution.height())
774 .arg(m_refreshRate, 0, 'f', 3).arg(Size.width()).arg(Size.height()));
776 return true;
777}
778
782{
783 if (!m_modeComplete)
785
786 // If the current resolution is the same as the GUI resolution then do nothing
787 // as refresh rate should not be critical for the GUI.
789 {
790 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using %1x%2@%3Hz for GUI")
791 .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate));
792 return true;
793 }
794
796 {
797 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to change mode to %1x%2@%3Hz")
798 .arg(m_guiMode.Width()).arg(m_guiMode.Height()).arg(m_guiMode.RefreshRate(), 0, 'f', 3));
799 return false;
800 }
801
802 if (Wait && (m_resolution != m_guiMode.Resolution()))
804
807 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Switched to %1x%2@%3Hz")
808 .arg(m_resolution.width()).arg(m_resolution.height()).arg(m_refreshRate, 0, 'f', 3));
809 return true;
810}
811
813{
814 return m_refreshRate;
815}
816
817std::chrono::microseconds MythDisplay::GetRefreshInterval(std::chrono::microseconds Fallback) const
818{
819 // If FreeSync or GSync are enabled, return the maximum refresh rate.
820 // N.B. This may need more work as the max may not be well defined - especially
821 // if the resolution is changing. Displays should however support at least 60Hz
822 // at all resolutions which should be fine in the vast majority of cases (as the
823 // only place the refresh interval is functionally important is in checking
824 // for double rate deinterlacing support).
825 if (m_vrrState && m_vrrState->Enabled())
826 {
827 const auto range = m_vrrState->GetRange();
828 auto max = std::get<1>(range) > 60 ? std::get<1>(range) : 60;
829 return microsecondsFromFloat(1000000.0 / max);
830 }
831
832 if (m_refreshRate > 20.0 && m_refreshRate < 200.0)
833 return microsecondsFromFloat(1000000.0 / m_refreshRate);
834 if (Fallback > 33ms) // ~30Hz
835 Fallback /= 2;
836 return Fallback;
837}
838
840{
841 auto targetrate = static_cast<double>(NAN);
842 const MythDisplayMode mode(Size, QSize(0, 0), -1.0, 0.0);
843 const auto & modes = GetVideoModes();
844 int match = MythDisplayMode::FindBestMatch(modes, mode, targetrate);
845 if (match < 0)
846 return {};
847 return modes[static_cast<size_t>(match)].RefreshRates();
848}
849
850bool MythDisplay::SwitchToVideoMode(QSize /*Size*/, double /*Framerate*/)
851{
852 return false;
853}
854
856{
857 return m_videoModes;
858}
859
878double MythDisplay::GetAspectRatio(QString &Source, bool IgnoreModeOverride)
879{
880 auto valid = [](double Aspect) { return (Aspect > 0.1 && Aspect < 10.0); };
881
882 // Override for this video mode
883 // Is this behaviour still needed?
884 if (!IgnoreModeOverride && valid(m_aspectRatioOverride))
885 {
886 Source = tr("Video mode override");
888 }
889
890 // General override for invalid/misleading EDIDs or multiscreen setups
891 // New default of -1.0 equates to square pixels for modern displays
892 bool multiscreen = MythDisplay::SpanAllScreens() && GetScreenCount() > 1;
893 double override = gCoreContext->GetFloatSettingOnHost("XineramaMonitorAspectRatio",
894 gCoreContext->GetHostName(), -1.0);
895
896 // Zero (not valid) indicates auto
897 if (valid(override))
898 {
899 Source = tr("Override");
900 return override;
901 }
902
903 // Auto for multiscreen is a best guess
904 if (multiscreen)
905 {
906 double aspect = EstimateVirtualAspectRatio();
907 if (valid(aspect))
908 {
909 Source = tr("Multiscreen estimate");
910 return aspect;
911 }
912 }
913
914 double calculated = m_resolution.isEmpty() ? 0.0 :
915 static_cast<double>(m_resolution.width()) / m_resolution.height();
916 double detected = m_physicalSize.isEmpty() ? 0.0 :
917 static_cast<double>(m_physicalSize.width()) / m_physicalSize.height();
918
919 // Assume pixel aspect ratio is 1 (square pixels)
920 if (valid(calculated))
921 {
922 if ((override < 0.0) || !valid(detected))
923 {
924 Source = tr("Square pixels");
925 return calculated;
926 }
927 }
928
929 // Based on actual physical size if available
930 if (valid(detected))
931 {
932 Source = tr("Detected");
933 return detected;
934 }
935
936 // the aspect ratio of last resort
937 Source = tr("Guessed");
938 return 16.0 / 9.0;
939}
940
942{
943 return m_edid;
944}
945
947{
948 return m_hdrState;
949}
950
952{
953 if (m_edid.Valid())
954 {
955 auto hdrdesc = m_edid.GetHDRSupport();
956 m_hdrState = MythHDR::Create(this, hdrdesc);
957 LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Supported HDR formats: %1")
958 .arg(m_hdrState->TypesToString().join(",")));
959 if (auto brightness = m_hdrState->GetMaxLuminance(); brightness > 1.0)
960 {
961 LOG(VB_GENERAL, LOG_NOTICE, LOC + QString("Display reports max brightness of %1 nits")
962 .arg(static_cast<int>(brightness)));
963 }
964 }
965}
966
977{
978 auto sortscreens = [](const QScreen* First, const QScreen* Second)
979 {
980 if (First->geometry().left() < Second->geometry().left())
981 return true;
982 if (First->geometry().top() < Second->geometry().top())
983 return true;
984 return false;
985 };
986
987 // default
988 double result = 16.0 / 9.0;
989
990 QList<QScreen*> screens;
991 if (m_screen)
992 screens = m_screen->virtualSiblings();
993 if (screens.empty())
994 return result;
995
996 // N.B. This sorting may not be needed
997 std::sort(screens.begin(), screens.end(), sortscreens);
998 QList<double> aspectratios;
999 QSize totalresolution;
1000 int lasttop = 0;
1001 int lastleft = 0;
1002 int rows = 1;
1003 int columns = 1;
1004 for (auto it = screens.constBegin() ; it != screens.constEnd(); ++it)
1005 {
1006 QRect geom = (*it)->geometry();
1007 totalresolution += geom.size();
1008 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("%1x%2+%3+%4 %5")
1009 .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top())
1010 .arg((*it)->physicalSize().width() / (*it)->physicalSize().height()));
1011 if (lastleft < geom.left())
1012 {
1013 columns++;
1014 lastleft = geom.left();
1015 }
1016 if (lasttop < geom.top())
1017 {
1018 rows++;
1019 lasttop = geom.top();
1020 lastleft = 0;
1021 }
1022 aspectratios << (*it)->physicalSize().width() / (*it)->physicalSize().height();
1023 }
1024
1025 // If all else fails, use the total resolution and assume pixel aspect ratio
1026 // equals display aspect ratio
1027 if (!totalresolution.isEmpty())
1028 result = static_cast<double>(totalresolution.width()) / totalresolution.height();
1029
1030 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Screen layout: %1x%2").arg(rows).arg(columns));
1031 if (rows == columns)
1032 {
1033 LOG(VB_GENERAL, LOG_DEBUG, LOC + "Grid layout");
1034 }
1035 else if (rows == 1 && columns > 1)
1036 {
1037 LOG(VB_GENERAL, LOG_DEBUG, LOC + "Horizontal layout");
1038 }
1039 else if (columns == 1 && rows > 1)
1040 {
1041 LOG(VB_GENERAL, LOG_DEBUG, LOC + "Vertical layout");
1042 }
1043 else
1044 {
1045 LOG(VB_GENERAL, LOG_INFO,
1046 LOC + QString("Unsupported layout - defaulting to %1 (%2/%3)")
1047 .arg(result).arg(totalresolution.width()).arg(totalresolution.height()));
1048 return result;
1049 }
1050
1051 // validate aspect ratios - with a little fuzzyness
1052 double aspectratio = 0.0;
1053 double average = 0.0;
1054 int count = 1;
1055 for (auto it2 = aspectratios.constBegin() ; it2 != aspectratios.constEnd(); ++it2, ++count)
1056 {
1057 aspectratio += *it2;
1058 average = aspectratio / count;
1059 if (qAbs(*it2 - average) > 0.1)
1060 {
1061 LOG(VB_GENERAL, LOG_INFO, LOC +
1062 QString("Inconsistent aspect ratios - defaulting to %1 (%2/%3)")
1063 .arg(result).arg(totalresolution.width()).arg(totalresolution.height()));
1064 return result;
1065 }
1066 }
1067
1068 aspectratio = (average * columns) / rows;
1069 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Estimated aspect ratio: %1")
1070 .arg(aspectratio));
1071 return aspectratio;
1072}
1073
1075{
1076 return m_resolution;
1077}
1078
1080{
1081 return m_physicalSize;
1082}
1083
1085{
1086 // Some implementations may have their own mechanism for ensuring the mode
1087 // is updated before continuing
1089 return;
1090
1091 LOG(VB_GENERAL, LOG_INFO, LOC + "Waiting for resolution change");
1092 QEventLoop loop;
1093 QTimer timer;
1094 timer.setSingleShot(true);
1095 connect(&timer, &QTimer::timeout, [](){ LOG(VB_GENERAL, LOG_WARNING, LOC + "Timed out wating for screen change"); });
1096 QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1097 QObject::connect(m_screen, &QScreen::geometryChanged, &loop, &QEventLoop::quit);
1098 // 500ms maximum wait
1099 timer.start(500ms);
1100 loop.exec();
1101}
1102
1104{
1105 // N.B. This isn't working as intended as it always times out rather than
1106 // exiting deliberately. It does however somehow filter out unwanted screenChanged
1107 // events that otherwise often put the widget in the wrong screen.
1108 // Needs more investigation - but for now it works:)
1109 if (!m_widget || !m_widget->windowHandle())
1110 return;
1111 LOG(VB_GENERAL, LOG_INFO, LOC + "Waiting for new screen");
1112 QEventLoop loop;
1113 QTimer timer;
1114 timer.setSingleShot(true);
1115 connect(&timer, &QTimer::timeout, [](){ LOG(VB_GENERAL, LOG_WARNING, LOC + "Timed out waiting for new screen"); });
1116 QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1117 QObject::connect(m_widget->windowHandle(), &QWindow::screenChanged, &loop, &QEventLoop::quit);
1118 // 500ms maximum wait
1119 timer.start(500ms);
1120 loop.exec();
1121}
1122
1124{
1125 int pauselengthinms = gCoreContext->GetNumSetting("VideoModeChangePauseMS", 0);
1126 if (pauselengthinms)
1127 {
1128 LOG(VB_GENERAL, LOG_INFO, LOC +
1129 QString("Pausing %1ms for video mode switch").arg(pauselengthinms));
1130 QEventLoop loop;
1131 QTimer timer;
1132 timer.setSingleShot(true);
1133 QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1134 // 500ms maximum wait
1135 timer.start(pauselengthinms);
1136 loop.exec();
1137 }
1138}
1139
1141{
1142 // This is intentionally formatted to match the output of xrandr for comparison
1143 if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_INFO))
1144 {
1145 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Available modes:");
1146 for (auto it = m_videoModes.crbegin(); it != m_videoModes.crend(); ++it)
1147 {
1148 auto rates = (*it).RefreshRates();
1149 QStringList rateslist;
1150 for (auto it2 = rates.crbegin(); it2 != rates.crend(); ++it2)
1151 rateslist.append(QString("%1").arg(*it2, 2, 'f', 2, '0'));
1152 if (rateslist.empty())
1153 rateslist.append("Variable rate?");
1154 LOG(VB_PLAYBACK, LOG_INFO, QString("%1x%2\t%3")
1155 .arg((*it).Width()).arg((*it).Height()).arg(rateslist.join("\t")));
1156 }
1157 }
1158}
1159
1165void MythDisplay::ConfigureQtGUI(int SwapInterval, const MythCommandLineParser& CmdLine)
1166{
1167 auto forcevrr = CmdLine.toBool("vrr");
1168 bool gsyncchanged = false;
1169 bool freesyncchanged = false;
1170
1171#if CONFIG_QTWEBENGINE
1172 QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
1173 QQuickWindow::setSceneGraphBackend("software");
1174 LOG(VB_GENERAL, LOG_INFO, LOC + "Using shared OpenGL Contexts");
1175#endif
1176
1177 // Set the default surface format. Explicitly required on some platforms.
1178 QSurfaceFormat format;
1179 // Allow overriding the default depth - use with caution as Qt will likely
1180 // crash if it cannot find a matching visual.
1181 if (qEnvironmentVariableIsSet("MYTHTV_DEPTH"))
1182 {
1183 // Note: Don't set depth and stencil to give Qt as much flexibility as possible
1184 int depth = std::clamp(qEnvironmentVariableIntValue("MYTHTV_DEPTH"), 6, 16);
1185 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Trying to force depth to '%1'").arg(depth));
1186 format.setRedBufferSize(depth);
1187 }
1188 else
1189 {
1190 format.setDepthBufferSize(0);
1191 format.setStencilBufferSize(0);
1192 }
1193 format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
1194 format.setProfile(QSurfaceFormat::CompatibilityProfile);
1195 format.setSwapInterval(SwapInterval);
1196 QSurfaceFormat::setDefaultFormat(format);
1197
1198#ifdef Q_OS_DARWIN
1199 // Without this, we can't set focus to any of the CheckBoxSetting, and most
1200 // of the MythPushButton widgets, and they don't use the themed background.
1201 QApplication::setDesktopSettingsAware(false);
1202#endif
1203
1204#if CONFIG_DRM && CONFIG_QTPRIVATEHEADERS
1205 // Avoid trying to setup DRM if we are definitely not going to use it.
1206#if CONFIG_X11
1208#endif
1209 {
1210#if CONFIG_WAYLANDEXTRAS
1211 // When vt switching this still detects wayland servers, so disabled for now
1212 //if (!MythWaylandDevice::IsAvailable())
1213#endif
1214 {
1215 MythDRMDevice::SetupDRM(CmdLine);
1216 freesyncchanged = MythDRMVRR::s_freeSyncResetOnExit;
1217 }
1218 }
1219#endif
1220
1221#if defined (Q_OS_LINUX) && CONFIG_EGL && CONFIG_X11
1222 // We want to use EGL for VAAPI/MMAL/DRMPRIME rendering to ensure we
1223 // can use zero copy video buffers for the best performance.
1224 // To force Qt to use EGL we must set 'QT_XCB_GL_INTEGRATION' to 'xcb_egl'
1225 // and this must be done before any GUI is created. If the platform plugin is
1226 // not xcb then this should have no effect.
1227 // This does however break when using NVIDIA drivers - which do not support
1228 // EGL like other drivers so we try to check the EGL vendor - and we currently
1229 // have no need for EGL with NVIDIA (that may change however).
1230 // NOTE force using EGL by setting MYTHTV_FORCE_EGL
1231 // NOTE disable using EGL by setting MYTHTV_NO_EGL
1232 // NOTE We have no Qt platform information, window/surface or logging when this is called.
1233 QString soft = qgetenv("LIBGL_ALWAYS_SOFTWARE");
1234 bool ignore = soft == "1" || soft.compare("true", Qt::CaseInsensitive) == 0;
1235 bool allow = qEnvironmentVariableIsEmpty("MYTHTV_NO_EGL") && !ignore;
1236 bool force = !qEnvironmentVariableIsEmpty("MYTHTV_FORCE_EGL");
1237 if ((force || allow) && MythDisplayX11::IsAvailable())
1238 {
1239 // N.B. By default, ignore EGL if vendor string is not returned
1240 QString vendor = MythEGL::GetEGLVendor();
1241 if (vendor.contains("nvidia", Qt::CaseInsensitive) && !force)
1242 {
1243 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Not requesting EGL for vendor '%1'").arg(vendor));
1244 }
1245 else if (!vendor.isEmpty() || force)
1246 {
1247 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Requesting EGL for vendor '%1'").arg(vendor));
1248 setenv("QT_XCB_GL_INTEGRATION", "xcb_egl", 0);
1249 }
1250 }
1251#endif
1252
1253#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1254 // Ignore desktop scaling
1255 QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
1256#endif
1257
1258#if CONFIG_X11
1259 if (auto display = CmdLine.toString("display"); !display.isEmpty())
1261 // GSync support via libXNVCtrl
1262 // Note: FreeSync support is checked in MythDRMDevice::SetupDRM
1263 if (forcevrr)
1264 {
1265 MythGSync::ForceGSync(CmdLine.toUInt("vrr") > 0);
1266 gsyncchanged = MythGSync::s_gsyncResetOnExit;
1267 }
1268#endif
1269
1270 if (forcevrr && !(gsyncchanged || freesyncchanged))
1271 LOG(VB_GENERAL, LOG_INFO, LOC + "Variable refresh rate not adjusted");
1272}
Parent class for defining application command line parsers.
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
uint toUInt(const QString &key) const
Returns stored QVariant as an unsigned integer, falling to default if not provided.
QString GetHostName(void)
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
double GetFloatSettingOnHost(const QString &key, const QString &host, double defaultval=0.0)
static bool s_freeSyncResetOnExit
Definition: mythdrmvrr.h:11
int Height() const
static bool CompareRates(double First, double Second, double Precision=0.01)
Determine whether two rates are considered equal with the given precision.
static int FindBestMatch(const MythDisplayModes &Modes, const MythDisplayMode &Mode, double &TargetRate)
void SetRefreshRate(double Rate)
double AspectRatio() const
double RefreshRate() const
static uint64_t FindBestScreen(const DisplayModeMap &Map, int Width, int Height, double Rate)
static uint64_t CalcKey(QSize Size, double Rate)
QSize Resolution() const
static bool IsAvailable()
MythEDID & GetEDID()
QWidget * m_widget
Definition: mythdisplay.h:95
double GetPixelAspectRatio()
virtual bool SwitchToVideoMode(QSize Size, double Framerate)
QSize m_resolution
Definition: mythdisplay.h:92
void InitScreenBounds()
Get screen size from Qt while respecting the user's multiscreen settings.
MythHDRPtr GetHDRState()
QStringList GetDescription()
static void PauseForModeSwitch()
double EstimateVirtualAspectRatio()
Estimate the overall display aspect ratio for multi screen setups.
double GetAspectRatio(QString &Source, bool IgnoreModeOverride=false)
Returns current screen aspect ratio.
void WaitForNewScreen()
void SwitchToDesktop()
Return the screen to the original desktop video mode.
virtual void UpdateCurrentMode()
Retrieve screen details.
static void GeometryChanged(QRect Geometry)
MythDisplayRates GetRefreshRates(QSize Size)
static void ConfigureQtGUI(int SwapInterval, const MythCommandLineParser &CmdLine)
Shared static initialisation code for all MythTV GUI applications.
QSize GetPhysicalSize()
MythDisplayMode m_guiMode
Definition: mythdisplay.h:110
QWindow * m_window
Definition: mythdisplay.h:96
bool m_firstScreenChange
Definition: mythdisplay.h:107
double GetRefreshRate() const
bool m_initialised
Definition: mythdisplay.h:106
bool SwitchToVideo(QSize Size, double Rate=0.0)
Switches to the resolution and refresh rate defined in the database for the specified video resolutio...
QSize m_physicalSize
Definition: mythdisplay.h:93
void PhysicalDPIChanged(qreal DPI)
static QScreen * GetDesiredScreen()
QSize GetGUIResolution()
static void DebugScreen(QScreen *qScreen, const QString &Message)
virtual bool VideoModesAvailable()
Definition: mythdisplay.h:29
static MythDisplay * Create(MythMainWindow *MainWindow)
Create a MythDisplay object appropriate for the current platform.
Definition: mythdisplay.cpp:90
bool SwitchToGUI(bool Wait=false)
Switches to the GUI resolution.
void Initialise()
double m_refreshRate
Definition: mythdisplay.h:90
virtual void ScreenChanged(QScreen *qScreen)
The actual screen in use has changed. We must use it.
MythDisplayModes m_videoModes
Definition: mythdisplay.h:98
bool m_waitForModeChanges
Definition: mythdisplay.h:88
void ScreenRemoved(QScreen *qScreen)
MythDisplayMode m_desktopMode
Definition: mythdisplay.h:109
QScreen * GetCurrentScreen()
Return a pointer to the screen to use.
static void PrimaryScreenChanged(QScreen *qScreen)
QRect GetScreenBounds()
~MythDisplay() override
void ScreenCountChanged(int Screens)
void ScreenAdded(QScreen *qScreen)
DisplayModeMap m_overrideVideoModes
Definition: mythdisplay.h:112
MythHDRPtr m_hdrState
Definition: mythdisplay.h:99
double m_aspectRatioOverride
Definition: mythdisplay.h:91
void CurrentScreenChanged(QScreen *qScreen)
QScreen * m_screen
Definition: mythdisplay.h:97
MythEDID m_edid
Definition: mythdisplay.h:94
void WaitForScreenChange()
virtual const MythDisplayModes & GetVideoModes()
static QString GetExtraScreenInfo(QScreen *qScreen)
void SetWidget(QWidget *MainWindow)
Set the QWidget and QWindow in use.
void InitHDR()
MythVRRPtr m_vrrState
Definition: mythdisplay.h:100
QRect m_screenBounds
Definition: mythdisplay.h:108
std::chrono::microseconds GetRefreshInterval(std::chrono::microseconds Fallback) const
MythDisplayMode m_videoMode
Definition: mythdisplay.h:111
QSize GetResolution()
bool NextModeIsLarger(QSize Size)
Check whether the next mode is larger in size than the current mode.
bool m_modeComplete
Definition: mythdisplay.h:89
static bool SpanAllScreens()
Return true if the MythTV windows should span all screens.
static int GetScreenCount()
void DebugModes() const
void CurrentDPIChanged(qreal DPI)
MythHDRDesc GetHDRSupport() const
Definition: mythedid.cpp:99
bool IsSRGB() const
Definition: mythedid.cpp:74
bool Valid() const
Definition: mythedid.cpp:39
static QString GetEGLVendor(void)
Definition: mythegl.cpp:83
static bool s_gsyncResetOnExit
Definition: mythnvcontrol.h:26
static void ForceGSync(bool Enable)
Enable or disable GSync before the main window is created.
static MythHDRPtr Create(class MythDisplay *MDisplay, const MythHDRDesc &Desc)
Definition: mythhdr.cpp:31
static QRect GetGeometryOverride()
static bool WindowIsAlwaysFullscreen()
Return true if the current platform only supports fullscreen windows.
static bool GeometryIsOverridden()
static MythVRRPtr Create(class MythDisplay *MDisplay)
Create a concrete implementation of MythVRR suitable for the given Display.
Definition: mythvrr.cpp:57
static void SetQtX11Display(const QString &DisplayStr)
#define setenv(x, y, z)
Definition: compat.h:76
static const struct wl_interface * types[]
@ quit
Definition: lirc_client.h:30
std::enable_if_t< std::is_floating_point_v< T >, std::chrono::microseconds > microsecondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
Definition: mythchrono.h:102
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythDB * GetMythDB(void)
Definition: mythdb.cpp:51
#define LOC
Definition: mythdisplay.cpp:54
std::vector< MythDisplayMode > MythDisplayModes
std::vector< double > MythDisplayRates
std::shared_ptr< class MythHDR > MythHDRPtr
Definition: mythhdr.h:30
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:206