| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| // MythTV | ||
| #include "mythdrmvrr.h" | ||
| #include "mythlogging.h" | ||
|
|
||
| #define LOC QString("FreeSync: ") | ||
|
|
||
| /*! \brief Force FreeSync on or off *before* the main app is started | ||
| */ | ||
| void MythDRMVRR::ForceFreeSync(MythDRMPtr Device, bool Enable) | ||
| { | ||
| if (!(Device && Device->Authenticated() && Device->GetCrtc())) | ||
| return; | ||
|
|
||
| auto freesync = CreateFreeSync(Device, {0,0,false}); | ||
| if (!freesync) | ||
| { | ||
| LOG(VB_GENERAL, LOG_INFO, LOC + "No FreeSync support detected - cannot force"); | ||
| return; | ||
| } | ||
|
|
||
| if (freesync->Enabled() == Enable) | ||
| { | ||
| LOG(VB_GENERAL, LOG_INFO, LOC + QString("FreeSync already %1abled") | ||
| .arg(Enable ? "en" : "dis")); | ||
| return; | ||
| } | ||
|
|
||
| if (!freesync->IsControllable()) | ||
| { | ||
| LOG(VB_GENERAL, LOG_INFO, LOC + QString("Cannot %1able FreeSync - do not have permission") | ||
| .arg(Enable ? "en" : "dis")); | ||
| return; | ||
| } | ||
|
|
||
| auto freesync2 = dynamic_cast<MythDRMVRR*>(freesync.get()); | ||
| if (!freesync2) | ||
| return; | ||
|
|
||
| // We have no Qt QPA plugin at this point, so no atomic modesetting etc etc. | ||
| // Just try and enable the property directly. | ||
| if (drmModeObjectSetProperty(Device->GetFD(), Device->GetCrtc()->m_id, | ||
| DRM_MODE_OBJECT_CRTC, freesync2->GetVRRProperty()->m_id, Enable ? 1 : 0) == 0) | ||
| { | ||
| // Release freesync now so that it doesn't reset the state on deletion | ||
| freesync = nullptr; | ||
| s_freeSyncDefaultValue = !Enable; | ||
| s_freeSyncResetOnExit = true; | ||
| LOG(VB_GENERAL, LOG_INFO, LOC + (Enable ? "Enabled" : "Disabled")); | ||
| } | ||
| else | ||
| { | ||
| LOG(VB_GENERAL, LOG_ERR, LOC + "Error setting FreeSync"); | ||
| } | ||
| } | ||
|
|
||
| MythVRRPtr MythDRMVRR::CreateFreeSync(MythDRMPtr Device, MythVRRRange Range) | ||
| { | ||
| if (!(Device && Device->GetConnector() && Device->GetCrtc())) | ||
| return nullptr; | ||
|
|
||
| auto connector = Device->GetConnector(); | ||
| auto capable = MythDRMProperty::GetProperty("VRR_CAPABLE", connector->m_properties); | ||
| if (!capable) | ||
| return nullptr; | ||
| auto * capableval = dynamic_cast<MythDRMRangeProperty*>(capable.get()); | ||
| if (!capableval || capableval->m_value < 1) | ||
| return nullptr; | ||
|
|
||
| auto crtc = Device->GetCrtc(); | ||
| auto enabled = MythDRMProperty::GetProperty("VRR_ENABLED", crtc->m_properties); | ||
| if (!enabled) | ||
| return nullptr; | ||
| auto * enabledval = dynamic_cast<MythDRMRangeProperty*>(enabled.get()); | ||
| if (!enabledval) | ||
| return nullptr; | ||
|
|
||
| // We have a valid device with VRR_CAPABLE property (connector) and VRR_ENABLED | ||
| // property (CRTC). Now check whether it is enabled and whether we can control it. | ||
| bool isenabled = enabledval->m_value > 0; | ||
| bool controllable = Device->Atomic() && Device->Authenticated(); | ||
| return std::shared_ptr<MythVRR>(new MythDRMVRR(Device, enabled, controllable, isenabled, Range)); | ||
| } | ||
|
|
||
| /*! \class MythDRMVRR | ||
| * \brief A wrapper around FreeSync/Adaptive-Sync support. | ||
| * | ||
| * FreeSync support on linux is currently limited to AMD when using a Display Port | ||
| * connection. FreeSync must be either enabled before starting MythTV (e.g xorg.conf) | ||
| * or the command line option -/--vrr can be used to try and force it on or off | ||
| * at startup. Note however that if running under X11 or Wayland, MythTV will not | ||
| * have the correct permissions to set FreeSync; it will only work via DRM (and | ||
| * typically using the eglfs QPA plugin) - although its current state can still | ||
| * be detected (and hence unnecessary mode switches avoided etc). | ||
| * | ||
| * \sa MythGSync | ||
| * \sa MythVRR | ||
| */ | ||
| MythDRMVRR::MythDRMVRR(MythDRMPtr Device, DRMProp VRRProp, bool Controllable, | ||
| bool Enabled, MythVRRRange Range) | ||
| : MythVRR(Controllable, FreeSync, Enabled, Range), | ||
| m_device(Device), | ||
| m_vrrProp(VRRProp) | ||
| { | ||
| } | ||
|
|
||
| MythDRMVRR::~MythDRMVRR() | ||
| { | ||
| if (s_freeSyncResetOnExit) | ||
| { | ||
| LOG(VB_GENERAL, LOG_INFO, LOC + "Resetting FreeSync to desktop default"); | ||
| MythDRMVRR::SetEnabled(s_freeSyncDefaultValue); | ||
| s_freeSyncResetOnExit = false; | ||
| } | ||
| } | ||
|
|
||
| void MythDRMVRR::SetEnabled(bool Enable) | ||
| { | ||
| if (m_device && m_vrrProp && m_device->GetCrtc() && | ||
| m_device->QueueAtomics({{ m_device->GetCrtc()->m_id, m_vrrProp->m_id, Enable ? 1 : 0 }})) | ||
| { | ||
| m_enabled = Enable; | ||
| LOG(VB_GENERAL, LOG_INFO, LOC + (Enable ? "Enabled" : "Disabled")); | ||
| } | ||
| } | ||
|
|
||
| DRMProp MythDRMVRR::GetVRRProperty() | ||
| { | ||
| return m_vrrProp; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| #ifndef MYTHDRMVRR_H | ||
| #define MYTHDRMVRR_H | ||
|
|
||
| // MythTV | ||
| #include "mythvrr.h" | ||
| #include "platforms/mythdrmdevice.h" | ||
|
|
||
| class MythDRMVRR : public MythVRR | ||
| { | ||
| public: | ||
| static inline bool s_freeSyncResetOnExit = false; | ||
| static inline bool s_freeSyncDefaultValue = false; | ||
|
|
||
| static void ForceFreeSync (MythDRMPtr Device, bool Enable); | ||
| static MythVRRPtr CreateFreeSync (MythDRMPtr Device, MythVRRRange Range); | ||
| ~MythDRMVRR() override; | ||
|
|
||
| void SetEnabled(bool Enable = true) override; | ||
| DRMProp GetVRRProperty(); | ||
|
|
||
| protected: | ||
| MythDRMVRR(MythDRMPtr Device, DRMProp VRRProp, bool Controllable, | ||
| bool Enabled, MythVRRRange Range); | ||
|
|
||
| private: | ||
| MythDRMPtr m_device { nullptr }; | ||
| DRMProp m_vrrProp { nullptr }; | ||
| }; | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,218 @@ | ||
| // MythTV | ||
| #include "mythnvcontrol.h" | ||
| #include "mythlogging.h" | ||
|
|
||
| #define LOC QString("NVCtrl: ") | ||
|
|
||
| #define NV_CTRL_TARGET_TYPE_X_SCREEN 0 | ||
| #define NV_CTRL_TARGET_TYPE_DISPLAY 8 | ||
| #define NV_CTRL_BINARY_DATA_DISPLAYS_ENABLED_ON_XSCREEN 17 | ||
| #define NV_CTRL_VRR_ALLOWED 408 | ||
| #define NV_CTRL_DISPLAY_VRR_MODE 429 | ||
| #define NV_CTRL_DISPLAY_VRR_MODE_NONE 0 | ||
| #define NV_CTRL_DISPLAY_VRR_MODE_GSYNC 1 | ||
| #define NV_CTRL_DISPLAY_VRR_MODE_GSYNC_COMPATIBLE 2 | ||
| #define NV_CTRL_DISPLAY_VRR_MODE_GSYNC_COMPATIBLE_UNVALIDATED 3 | ||
| #define NV_CTRL_DISPLAY_VRR_ENABLED 431 | ||
| #define NV_CTRL_DISPLAY_VRR_MIN_REFRESH_RATE 430 | ||
|
|
||
| /*! \brief Enable or disable GSync *before* the main window is created. | ||
| * | ||
| * If the state has been changed, s_gsyncResetOnExit will be set to true and | ||
| * s_gsyncDefaultValue will be set to the default/desktop value so that the MythDisplay | ||
| * instance can reset GSync on exit. | ||
| */ | ||
| void MythGSync::ForceGSync(bool Enable) | ||
| { | ||
| if (auto nvcontrol = MythNVControl::Create(); nvcontrol) | ||
| { | ||
| auto gsync = CreateGSync(nvcontrol, {0,0,false}); | ||
| if (!gsync) | ||
| { | ||
| LOG(VB_GENERAL, LOG_INFO, LOC + "No GSync support detected - cannot force"); | ||
| return; | ||
| } | ||
|
|
||
| if (gsync->Enabled() == Enable) | ||
| { | ||
| LOG(VB_GENERAL, LOG_INFO, LOC + QString("GSync already %1abled") | ||
| .arg(Enable ? "en" : "dis")); | ||
| return; | ||
| } | ||
|
|
||
| gsync->SetEnabled(Enable); | ||
| // Release GSync to ensure the state is not reset when it is deleted | ||
| gsync = nullptr; | ||
| s_gsyncDefaultValue = !Enable; | ||
| s_gsyncResetOnExit = true; | ||
| LOG(VB_GENERAL, LOG_INFO, LOC + (Enable ? "Enabled" : "Disabled")); | ||
| } | ||
| } | ||
|
|
||
| MythVRRPtr MythGSync::CreateGSync(NVControl Device, MythVRRRange Range) | ||
| { | ||
| if (!Device) | ||
| return nullptr; | ||
|
|
||
| auto displayid = Device->GetDisplayID(); | ||
| if (displayid < 0) | ||
| return nullptr; | ||
|
|
||
| auto display = Device->m_display->GetDisplay(); | ||
| int enabled = 0; | ||
| if (!Device->m_queryTarget(display, NV_CTRL_TARGET_TYPE_DISPLAY, displayid, | ||
| 0, NV_CTRL_DISPLAY_VRR_ENABLED, &enabled) || !enabled) | ||
| { | ||
| return nullptr; | ||
| } | ||
|
|
||
| // We have a a valid device that has GSync/VRR available | ||
| int type = 0; | ||
| int minrate = 0; | ||
| int allowed = 0; | ||
| Device->m_queryTarget(display, NV_CTRL_TARGET_TYPE_DISPLAY, displayid, 0, | ||
| NV_CTRL_DISPLAY_VRR_MODE, &type); | ||
| Device->m_queryTarget(display, NV_CTRL_TARGET_TYPE_DISPLAY, displayid, 0, | ||
| NV_CTRL_DISPLAY_VRR_MIN_REFRESH_RATE, &minrate); | ||
| Device->m_queryScreen(display, Device->m_display->GetScreen(), 0, NV_CTRL_VRR_ALLOWED, &allowed); | ||
|
|
||
| if (minrate > 0) | ||
| { | ||
| std::get<0>(Range) = minrate; | ||
| std::get<2>(Range) = true; | ||
| } | ||
|
|
||
| auto vrrtype = type == NV_CTRL_DISPLAY_VRR_MODE_GSYNC ? GSync : GSyncCompat; | ||
| return std::shared_ptr<MythVRR>(new MythGSync(Device, vrrtype, allowed > 0, Range)); | ||
| } | ||
|
|
||
| /*! \class MythGSync | ||
| * \brief A wrapper around NVidia GSync support (when using X11 and a Display Port connection). | ||
| * | ||
| * If libXNVCtrl support is available through the MythNVControl class, then we | ||
| * can check for GSync capabilities and state. Note however that any changes requested | ||
| * to the GSync state will have no effect while the current GUI is running; so if | ||
| * we want to enable/disable GSync it must be done before we create the main window | ||
| * (and we can revert that state on exit). | ||
| * | ||
| * \sa MythVRR | ||
| * \sa MythNVControl | ||
| */ | ||
| MythGSync::MythGSync(NVControl Control, VRRType Type, bool Enabled, MythVRRRange Range) | ||
| : MythVRR(true, Type, Enabled, Range), | ||
| m_nvControl(Control) | ||
| { | ||
| } | ||
|
|
||
| MythGSync::~MythGSync() | ||
| { | ||
| if (s_gsyncResetOnExit) | ||
| { | ||
| LOG(VB_GENERAL, LOG_INFO, LOC + "Resetting GSync to desktop default"); | ||
| MythGSync::SetEnabled(s_gsyncDefaultValue); | ||
| s_gsyncResetOnExit = false; | ||
| } | ||
| } | ||
|
|
||
| void MythGSync::SetEnabled(bool Enable) | ||
| { | ||
| if (!m_nvControl || !m_nvControl->m_display) | ||
| return; | ||
| int enable = Enable ? 1 : 0; | ||
| auto display = m_nvControl->m_display; | ||
| m_nvControl->m_setAttrib(display->GetDisplay(), display->GetScreen(), 0, NV_CTRL_VRR_ALLOWED, enable); | ||
| } | ||
|
|
||
| /*! \brief Create a valid instance of MythNVControl. | ||
| * | ||
| * If libXNVCtrl is not found at the first attempt, this function will always | ||
| * return null. If libXNVCtrl is found but the current display does not support | ||
| * the extension, further attempts will be made in case the screen changes (to one | ||
| * which does support the extension). | ||
| */ | ||
| NVControl MythNVControl::Create() | ||
| { | ||
| static const QStringList s_paths = { "libXNVCtrl", "libXNVCtrl.so.0" }; | ||
| static bool s_available = false; | ||
| static bool s_checked = false; | ||
| if (s_checked && !s_available) | ||
| return nullptr; | ||
| s_checked = true; | ||
|
|
||
| // Is libxnvctrl available? | ||
| for (const auto & path : s_paths) | ||
| { | ||
| if (QLibrary lib(path); lib.load()) | ||
| { | ||
| s_available = true; | ||
| auto isnvscreen = reinterpret_cast<bool(*)(Display*,int)>(lib.resolve("XNVCTRLIsNvScreen")); | ||
| auto queryversion = reinterpret_cast<bool(*)(Display*,int,int)>(lib.resolve("XNVCTRLQueryVersion")); | ||
| if (isnvscreen && queryversion) | ||
| { | ||
| if (auto xdisplay = MythXDisplay::OpenMythXDisplay(false); xdisplay && xdisplay->GetDisplay()) | ||
| { | ||
| int major = 0; | ||
| int minor = 0; | ||
| if (isnvscreen(xdisplay->GetDisplay(), xdisplay->GetScreen()) && | ||
| queryversion(xdisplay->GetDisplay(), major, minor)) | ||
| { | ||
| if (auto res = std::shared_ptr<MythNVControl>(new MythNVControl(path, xdisplay)); | ||
| res->m_queryBinary && res->m_queryScreen && res->m_queryTarget && res->m_setAttrib) | ||
| { | ||
| return res; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| lib.unload(); | ||
| } | ||
| } | ||
| return nullptr; | ||
| } | ||
|
|
||
| /*! \class MythNVControl | ||
| * \brief A simple wrapper around libXNVCtrl - which is dynamically loaded on demand. | ||
| */ | ||
| MythNVControl::MythNVControl(const QString &Path, MythXDisplay* _Display) | ||
| : m_lib(Path), | ||
| m_display(_Display), | ||
| m_queryBinary(reinterpret_cast<QueryTargetBinary>(m_lib.resolve("XNVCTRLQueryTargetBinaryData"))), | ||
| m_queryScreen(reinterpret_cast<QueryScreenAttrib>(m_lib.resolve("XNVCTRLQueryAttribute"))), | ||
| m_queryTarget(reinterpret_cast<QueryTargetAttrib>(m_lib.resolve("XNVCTRLQueryTargetAttribute"))), | ||
| m_setAttrib(reinterpret_cast<SetAttribute>(m_lib.resolve("XNVCTRLSetAttribute"))) | ||
| { | ||
| } | ||
|
|
||
| MythNVControl::~MythNVControl() | ||
| { | ||
| if (m_display) | ||
| delete m_display; | ||
| m_lib.unload(); | ||
| } | ||
|
|
||
| int MythNVControl::GetDisplayID() | ||
| { | ||
| auto display = m_display->GetDisplay(); | ||
| auto screen = m_display->GetScreen(); | ||
| uint32_t * data = nullptr; | ||
| int size = 0; | ||
| if (!m_queryBinary(display, NV_CTRL_TARGET_TYPE_X_SCREEN, screen, 0, | ||
| NV_CTRL_BINARY_DATA_DISPLAYS_ENABLED_ON_XSCREEN, | ||
| reinterpret_cast<unsigned char **>(&data), &size)) | ||
| { | ||
| LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to retrieve display id for screen"); | ||
| return -1; | ||
| } | ||
|
|
||
| // Minimum result size is 4bytes for number of ids and 4bytes for each id | ||
| if (size < 8) | ||
| return -1; | ||
|
|
||
| if (size > 8) | ||
| { | ||
| LOG(VB_GENERAL, LOG_INFO, LOC + QString("%1 display id's returned - using first") | ||
| .arg((size - 4) / 4)); | ||
| } | ||
|
|
||
| return static_cast<int>(data[1]); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| #ifndef MYTHNVCONTROL_H | ||
| #define MYTHNVCONTROL_H | ||
|
|
||
| // MythTV | ||
| #include "mythvrr.h" | ||
|
|
||
| // Qt | ||
| #include <QLibrary> | ||
|
|
||
| // Std | ||
| #include <tuple> | ||
| #include <memory> | ||
|
|
||
| // X headers always last | ||
| #include "platforms/mythxdisplay.h" | ||
|
|
||
| using NVControl = std::shared_ptr<class MythNVControl>; | ||
| using QueryTargetBinary = bool(*)(Display*,int,int,unsigned int,unsigned int, unsigned char**,int*); | ||
| using QueryScreenAttrib = bool(*)(Display*,int,unsigned int,unsigned int,int*); | ||
| using QueryTargetAttrib = bool(*)(Display*,int,int,unsigned int,unsigned int,int*); | ||
| using SetAttribute = void(*)(Display*,int,unsigned int,unsigned int,int); | ||
|
|
||
| class MythGSync : public MythVRR | ||
| { | ||
| public: | ||
| static inline bool s_gsyncResetOnExit = false; | ||
| static inline bool s_gsyncDefaultValue = false; | ||
| static void ForceGSync(bool Enable); | ||
| static MythVRRPtr CreateGSync(NVControl Device, MythVRRRange Range); | ||
| ~MythGSync() override; | ||
| void SetEnabled(bool Enable = true) override; | ||
|
|
||
| protected: | ||
| MythGSync(NVControl Device, VRRType Type, bool Enabled, MythVRRRange Range); | ||
| NVControl m_nvControl { nullptr }; | ||
| }; | ||
|
|
||
| class MythNVControl | ||
| { | ||
| public: | ||
| static NVControl Create(); | ||
| ~MythNVControl(); | ||
|
|
||
| int GetDisplayID(); | ||
|
|
||
| protected: | ||
| MythNVControl(const QString& Path, MythXDisplay* _Display); | ||
| QLibrary m_lib; | ||
|
|
||
| public: | ||
| MythXDisplay* m_display { nullptr }; | ||
| QueryTargetBinary m_queryBinary { nullptr }; | ||
| QueryScreenAttrib m_queryScreen { nullptr }; | ||
| QueryTargetAttrib m_queryTarget { nullptr }; | ||
| SetAttribute m_setAttrib { nullptr }; | ||
| }; | ||
| #endif |