129 changes: 129 additions & 0 deletions mythtv/libs/libmythui/platforms/drm/mythdrmvrr.cpp
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;
}
30 changes: 30 additions & 0 deletions mythtv/libs/libmythui/platforms/drm/mythdrmvrr.h
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
5 changes: 5 additions & 0 deletions mythtv/libs/libmythui/platforms/mythdrmdevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

// MythTV
#include "mythedid.h"
#include "platforms/drm/mythdrmvrr.h"
#include "platforms/drm/mythdrmencoder.h"
#include "platforms/drm/mythdrmframebuffer.h"
#include "platforms/mythdrmdevice.h"
Expand Down Expand Up @@ -138,6 +139,10 @@ MythDRMPtr MythDRMDevice::FindDevice(bool NeedPlanes)

void MythDRMDevice::SetupDRM(const MythCommandLineParser& CmdLine)
{
// Try and enable/disable FreeSync if requested by the user
if (CmdLine.toBool("vrr"))
MythDRMVRR::ForceFreeSync(FindDevice(false), CmdLine.toUInt("vrr") > 0);

#if QT_VERSION >= QT_VERSION_CHECK(5,12,0)
// Return early if eglfs is not *explicitly* requested via the command line or environment.
// Note: On some setups it is not necessary to explicitly request eglfs for Qt to use it.
Expand Down
218 changes: 218 additions & 0 deletions mythtv/libs/libmythui/platforms/mythnvcontrol.cpp
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]);
}
57 changes: 57 additions & 0 deletions mythtv/libs/libmythui/platforms/mythnvcontrol.h
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
2 changes: 2 additions & 0 deletions mythtv/programs/mythavtest/commandlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,7 @@ void MythAVTestCommandLineParser::LoadArguments(void)
"The number of seconds to run the test (default 5).", "")
->SetGroup("Video Performance Testing")
->SetChildOf("test");
add(QStringList{"-vrr", "--vrr"}, "vrr", 0U,
"Try to enable (1) or disable (0) variable refresh rate (FreeSync or GSync)","");
}

3 changes: 3 additions & 0 deletions mythtv/programs/mythfrontend/commandlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ void MythFrontendCommandLineParser::LoadArguments(void)
" the primary database schema. Use mythtv-setup\n"
" or restart your primary backend to have it\n"
" perform the task automatically.", "0.25");

add(QStringList{"-vrr", "--vrr"}, "vrr", 0U,
"Try to enable (1) or disable (0) variable refresh rate (FreeSync or GSync)","");
}

QString MythFrontendCommandLineParser::GetHelpHeader(void) const
Expand Down