mirror of
https://github.com/moonlight-stream/moonlight-qt
synced 2024-12-16 22:23:07 +00:00
Refactor Pacer to handle both blocking and non-blocking VsyncSources
This commit is contained in:
parent
8e3e19a7f7
commit
6ae6218043
6 changed files with 170 additions and 129 deletions
|
@ -6,17 +6,18 @@
|
|||
|
||||
DxVsyncSource::DxVsyncSource(Pacer* pacer) :
|
||||
m_Pacer(pacer),
|
||||
m_Thread(nullptr),
|
||||
m_Gdi32Handle(nullptr)
|
||||
m_Gdi32Handle(nullptr),
|
||||
m_LastMonitor(nullptr)
|
||||
{
|
||||
SDL_AtomicSet(&m_Stopping, 0);
|
||||
SDL_zero(m_WaitForVblankEventParams);
|
||||
}
|
||||
|
||||
DxVsyncSource::~DxVsyncSource()
|
||||
{
|
||||
if (m_Thread != nullptr) {
|
||||
SDL_AtomicSet(&m_Stopping, 1);
|
||||
SDL_WaitThread(m_Thread, nullptr);
|
||||
if (m_WaitForVblankEventParams.hAdapter != 0) {
|
||||
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
|
||||
closeAdapterParams.hAdapter = m_WaitForVblankEventParams.hAdapter;
|
||||
m_D3DKMTCloseAdapter(&closeAdapterParams);
|
||||
}
|
||||
|
||||
if (m_Gdi32Handle != nullptr) {
|
||||
|
@ -24,10 +25,8 @@ DxVsyncSource::~DxVsyncSource()
|
|||
}
|
||||
}
|
||||
|
||||
bool DxVsyncSource::initialize(SDL_Window* window, int displayFps)
|
||||
bool DxVsyncSource::initialize(SDL_Window* window, int)
|
||||
{
|
||||
m_DisplayFps = displayFps;
|
||||
|
||||
m_Gdi32Handle = LoadLibraryA("gdi32.dll");
|
||||
if (m_Gdi32Handle == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
|
@ -59,57 +58,43 @@ bool DxVsyncSource::initialize(SDL_Window* window, int displayFps)
|
|||
return false;
|
||||
}
|
||||
|
||||
m_Window = info.info.win.window;
|
||||
// Pacer should only create us on Win32
|
||||
SDL_assert(info.subsystem == SDL_SYSWM_WINDOWS);
|
||||
|
||||
m_Thread = SDL_CreateThread(vsyncThread, "DXVsync", this);
|
||||
if (m_Thread == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to create DX V-sync thread: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
m_Window = info.info.win.window;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int DxVsyncSource::vsyncThread(void* context)
|
||||
bool DxVsyncSource::isAsync()
|
||||
{
|
||||
DxVsyncSource* me = reinterpret_cast<DxVsyncSource*>(context);
|
||||
// We wait in the context of the Pacer thread
|
||||
return false;
|
||||
}
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL);
|
||||
#else
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||
#endif
|
||||
|
||||
D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {};
|
||||
HMONITOR lastMonitor = nullptr;
|
||||
DEVMODEA monitorMode;
|
||||
monitorMode.dmSize = sizeof(monitorMode);
|
||||
|
||||
while (SDL_AtomicGet(&me->m_Stopping) == 0) {
|
||||
D3DKMT_WAITFORVERTICALBLANKEVENT waitForVblankEventParams;
|
||||
void DxVsyncSource::waitForVsync()
|
||||
{
|
||||
NTSTATUS status;
|
||||
|
||||
// If the monitor has changed from last time, open the new adapter
|
||||
HMONITOR currentMonitor = MonitorFromWindow(me->m_Window, MONITOR_DEFAULTTONEAREST);
|
||||
if (currentMonitor != lastMonitor) {
|
||||
HMONITOR currentMonitor = MonitorFromWindow(m_Window, MONITOR_DEFAULTTONEAREST);
|
||||
if (currentMonitor != m_LastMonitor) {
|
||||
MONITORINFOEXA monitorInfo = {};
|
||||
monitorInfo.cbSize = sizeof(monitorInfo);
|
||||
if (!GetMonitorInfoA(currentMonitor, &monitorInfo)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GetMonitorInfo() failed: %d",
|
||||
GetLastError());
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
DEVMODEA monitorMode;
|
||||
monitorMode.dmSize = sizeof(monitorMode);
|
||||
if (!EnumDisplaySettingsA(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &monitorMode)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"EnumDisplaySettings() failed: %d",
|
||||
GetLastError());
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
|
@ -117,56 +102,45 @@ int DxVsyncSource::vsyncThread(void* context)
|
|||
monitorInfo.szDevice,
|
||||
monitorMode.dmDisplayFrequency);
|
||||
|
||||
if (openAdapterParams.hAdapter != 0) {
|
||||
// Close the old adapter
|
||||
if (m_WaitForVblankEventParams.hAdapter != 0) {
|
||||
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
|
||||
closeAdapterParams.hAdapter = openAdapterParams.hAdapter;
|
||||
me->m_D3DKMTCloseAdapter(&closeAdapterParams);
|
||||
closeAdapterParams.hAdapter = m_WaitForVblankEventParams.hAdapter;
|
||||
m_D3DKMTCloseAdapter(&closeAdapterParams);
|
||||
}
|
||||
|
||||
D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {};
|
||||
openAdapterParams.hDc = CreateDCA(nullptr, monitorInfo.szDevice, nullptr, nullptr);
|
||||
if (!openAdapterParams.hDc) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CreateDC() failed: %d",
|
||||
GetLastError());
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
status = me->m_D3DKMTOpenAdapterFromHdc(&openAdapterParams);
|
||||
// Open the new adapter
|
||||
status = m_D3DKMTOpenAdapterFromHdc(&openAdapterParams);
|
||||
DeleteDC(openAdapterParams.hDc);
|
||||
|
||||
if (status != STATUS_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"D3DKMTOpenAdapterFromHdc() failed: %x",
|
||||
status);
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
lastMonitor = currentMonitor;
|
||||
m_WaitForVblankEventParams.hAdapter = openAdapterParams.hAdapter;
|
||||
m_WaitForVblankEventParams.hDevice = 0;
|
||||
m_WaitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId;
|
||||
|
||||
m_LastMonitor = currentMonitor;
|
||||
}
|
||||
|
||||
waitForVblankEventParams.hAdapter = openAdapterParams.hAdapter;
|
||||
waitForVblankEventParams.hDevice = 0;
|
||||
waitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId;
|
||||
|
||||
status = me->m_D3DKMTWaitForVerticalBlankEvent(&waitForVblankEventParams);
|
||||
status = m_D3DKMTWaitForVerticalBlankEvent(&m_WaitForVblankEventParams);
|
||||
if (status != STATUS_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"D3DKMTWaitForVerticalBlankEvent() failed: %x",
|
||||
status);
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
me->m_Pacer->vsyncCallback(1000 / me->m_DisplayFps);
|
||||
}
|
||||
|
||||
if (openAdapterParams.hAdapter != 0) {
|
||||
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
|
||||
closeAdapterParams.hAdapter = openAdapterParams.hAdapter;
|
||||
me->m_D3DKMTCloseAdapter(&closeAdapterParams);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -35,17 +35,18 @@ public:
|
|||
|
||||
virtual ~DxVsyncSource();
|
||||
|
||||
virtual bool initialize(SDL_Window* window, int displayFps);
|
||||
virtual bool initialize(SDL_Window* window, int) override;
|
||||
|
||||
virtual bool isAsync() override;
|
||||
|
||||
virtual void waitForVsync() override;
|
||||
|
||||
private:
|
||||
static int vsyncThread(void* context);
|
||||
|
||||
Pacer* m_Pacer;
|
||||
SDL_Thread* m_Thread;
|
||||
SDL_atomic_t m_Stopping;
|
||||
HMODULE m_Gdi32Handle;
|
||||
HWND m_Window;
|
||||
int m_DisplayFps;
|
||||
HMONITOR m_LastMonitor;
|
||||
D3DKMT_WAITFORVERTICALBLANKEVENT m_WaitForVblankEventParams;
|
||||
|
||||
PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc;
|
||||
PFND3DKMTCLOSEADAPTER m_D3DKMTCloseAdapter;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
Pacer::Pacer(IFFmpegRenderer* renderer, PVIDEO_STATS videoStats) :
|
||||
m_RenderThread(nullptr),
|
||||
m_VsyncThread(nullptr),
|
||||
m_Stopping(false),
|
||||
m_VsyncSource(nullptr),
|
||||
m_VsyncRenderer(renderer),
|
||||
|
@ -42,12 +43,19 @@ Pacer::Pacer(IFFmpegRenderer* renderer, PVIDEO_STATS videoStats) :
|
|||
|
||||
Pacer::~Pacer()
|
||||
{
|
||||
m_Stopping = true;
|
||||
|
||||
// Stop the V-sync thread
|
||||
if (m_VsyncThread != nullptr) {
|
||||
m_VsyncSignalled.wakeAll();
|
||||
SDL_WaitThread(m_VsyncThread, nullptr);
|
||||
}
|
||||
|
||||
// Stop V-sync callbacks
|
||||
delete m_VsyncSource;
|
||||
m_VsyncSource = nullptr;
|
||||
|
||||
// Stop the render thread
|
||||
m_Stopping = true;
|
||||
if (m_RenderThread != nullptr) {
|
||||
m_RenderQueueNotEmpty.wakeAll();
|
||||
SDL_WaitThread(m_RenderThread, nullptr);
|
||||
|
@ -89,6 +97,39 @@ void Pacer::renderOnMainThread()
|
|||
}
|
||||
}
|
||||
|
||||
int Pacer::vsyncThread(void *context)
|
||||
{
|
||||
Pacer* me = reinterpret_cast<Pacer*>(context);
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL);
|
||||
#else
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||
#endif
|
||||
|
||||
bool async = me->m_VsyncSource->isAsync();
|
||||
while (!me->m_Stopping) {
|
||||
if (async) {
|
||||
// Wait for the VSync source to invoke signalVsync() or 100ms to elapse
|
||||
me->m_FrameQueueLock.lock();
|
||||
me->m_VsyncSignalled.wait(&me->m_FrameQueueLock, 100);
|
||||
me->m_FrameQueueLock.unlock();
|
||||
}
|
||||
else {
|
||||
// Let the VSync source wait in the context of our thread
|
||||
me->m_VsyncSource->waitForVsync();
|
||||
}
|
||||
|
||||
if (me->m_Stopping) {
|
||||
break;
|
||||
}
|
||||
|
||||
me->handleVsync(1000 / me->m_DisplayFps);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Pacer::renderThread(void* context)
|
||||
{
|
||||
Pacer* me = reinterpret_cast<Pacer*>(context);
|
||||
|
@ -153,7 +194,7 @@ void Pacer::enqueueFrameForRenderingAndUnlock(AVFrame *frame)
|
|||
|
||||
// Called in an arbitrary thread by the IVsyncSource on V-sync
|
||||
// or an event synchronized with V-sync
|
||||
void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
|
||||
void Pacer::handleVsync(int timeUntilNextVsyncMillis)
|
||||
{
|
||||
// Make sure initialize() has been called
|
||||
SDL_assert(m_MaxVideoFps != 0);
|
||||
|
@ -217,7 +258,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
|||
|
||||
if (enablePacing) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Frame pacing active: target %d Hz with %d FPS stream",
|
||||
"Frame pacing: target %d Hz with %d FPS stream",
|
||||
m_DisplayFps, m_MaxVideoFps);
|
||||
|
||||
SDL_SysWMinfo info;
|
||||
|
@ -255,7 +296,10 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
|||
SDL_assert(m_VsyncSource != nullptr || !(m_RendererAttributes & RENDERER_ATTRIBUTE_FORCE_PACING));
|
||||
|
||||
if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window, m_DisplayFps)) {
|
||||
return false;
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Vsync source failed to initialize. Frame pacing will not be available!");
|
||||
delete m_VsyncSource;
|
||||
m_VsyncSource = nullptr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -264,6 +308,10 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
|||
m_DisplayFps, m_MaxVideoFps);
|
||||
}
|
||||
|
||||
if (m_VsyncSource != nullptr) {
|
||||
m_VsyncThread = SDL_CreateThread(Pacer::vsyncThread, "PacerVsync", this);
|
||||
}
|
||||
|
||||
if (m_VsyncRenderer->isRenderThreadSupported()) {
|
||||
m_RenderThread = SDL_CreateThread(Pacer::renderThread, "PacerRender", this);
|
||||
}
|
||||
|
@ -271,6 +319,11 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
|||
return true;
|
||||
}
|
||||
|
||||
void Pacer::signalVsync()
|
||||
{
|
||||
m_VsyncSignalled.wakeOne();
|
||||
}
|
||||
|
||||
void Pacer::renderFrame(AVFrame* frame)
|
||||
{
|
||||
// Count time spent in Pacer's queues
|
||||
|
|
|
@ -11,6 +11,15 @@ class IVsyncSource {
|
|||
public:
|
||||
virtual ~IVsyncSource() {}
|
||||
virtual bool initialize(SDL_Window* window, int displayFps) = 0;
|
||||
|
||||
// Asynchronous sources produce callbacks on their own, while synchronous
|
||||
// sources require calls to waitForVsync().
|
||||
virtual bool isAsync() = 0;
|
||||
|
||||
virtual void waitForVsync() {
|
||||
// Synchronous sources must implement waitForVsync()!
|
||||
SDL_assert(false);
|
||||
}
|
||||
};
|
||||
|
||||
class Pacer
|
||||
|
@ -24,13 +33,17 @@ public:
|
|||
|
||||
bool initialize(SDL_Window* window, int maxVideoFps, bool enablePacing);
|
||||
|
||||
void vsyncCallback(int timeUntilNextVsyncMillis);
|
||||
void signalVsync();
|
||||
|
||||
void renderOnMainThread();
|
||||
|
||||
private:
|
||||
static int vsyncThread(void* context);
|
||||
|
||||
static int renderThread(void* context);
|
||||
|
||||
void handleVsync(int timeUntilNextVsyncMillis);
|
||||
|
||||
void enqueueFrameForRenderingAndUnlock(AVFrame* frame);
|
||||
|
||||
void renderFrame(AVFrame* frame);
|
||||
|
@ -44,7 +57,9 @@ private:
|
|||
QMutex m_FrameQueueLock;
|
||||
QWaitCondition m_RenderQueueNotEmpty;
|
||||
QWaitCondition m_PacingQueueNotEmpty;
|
||||
QWaitCondition m_VsyncSignalled;
|
||||
SDL_Thread* m_RenderThread;
|
||||
SDL_Thread* m_VsyncThread;
|
||||
bool m_Stopping;
|
||||
|
||||
IVsyncSource* m_VsyncSource;
|
||||
|
|
|
@ -10,8 +10,7 @@ WaylandVsyncSource::WaylandVsyncSource(Pacer* pacer)
|
|||
: m_Pacer(pacer),
|
||||
m_Display(nullptr),
|
||||
m_Surface(nullptr),
|
||||
m_Callback(nullptr),
|
||||
m_LastFrameTime(0)
|
||||
m_Callback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -24,7 +23,7 @@ WaylandVsyncSource::~WaylandVsyncSource()
|
|||
}
|
||||
}
|
||||
|
||||
bool WaylandVsyncSource::initialize(SDL_Window* window, int displayFps)
|
||||
bool WaylandVsyncSource::initialize(SDL_Window* window, int)
|
||||
{
|
||||
SDL_SysWMinfo info;
|
||||
|
||||
|
@ -40,7 +39,6 @@ bool WaylandVsyncSource::initialize(SDL_Window* window, int displayFps)
|
|||
// Pacer shoud not create us for non-Wayland windows
|
||||
SDL_assert(info.subsystem == SDL_SYSWM_WAYLAND);
|
||||
|
||||
m_DisplayFps = displayFps;
|
||||
m_Display = info.info.wl.display;
|
||||
m_Surface = info.info.wl.surface;
|
||||
|
||||
|
@ -52,7 +50,13 @@ bool WaylandVsyncSource::initialize(SDL_Window* window, int displayFps)
|
|||
return true;
|
||||
}
|
||||
|
||||
void WaylandVsyncSource::frameDone(void* data, struct wl_callback* oldCb, uint32_t time)
|
||||
bool WaylandVsyncSource::isAsync()
|
||||
{
|
||||
// Wayland frame callbacks are asynchronous
|
||||
return true;
|
||||
}
|
||||
|
||||
void WaylandVsyncSource::frameDone(void* data, struct wl_callback* oldCb, uint32_t)
|
||||
{
|
||||
auto me = (WaylandVsyncSource*)data;
|
||||
|
||||
|
@ -60,18 +64,12 @@ void WaylandVsyncSource::frameDone(void* data, struct wl_callback* oldCb, uint32
|
|||
SDL_assert(oldCb == me->m_Callback);
|
||||
wl_callback_destroy(oldCb);
|
||||
|
||||
// Register for another callback before invoking Pacer to ensure we don't miss
|
||||
// a frame callback if Pacer takes too long.
|
||||
// Wake the Pacer Vsync thread
|
||||
me->m_Pacer->signalVsync();
|
||||
|
||||
// Register for another callback
|
||||
me->m_Callback = wl_surface_frame(me->m_Surface);
|
||||
wl_callback_add_listener(me->m_Callback, &s_FrameListener, data);
|
||||
wl_surface_commit(me->m_Surface);
|
||||
wl_display_flush(me->m_Display);
|
||||
|
||||
if (me->m_LastFrameTime != 0) {
|
||||
// Assuming that the time until the next V-Sync will usually be equal to the
|
||||
// time from last callback to this one but cap it at 2x the expected frame period.
|
||||
me->m_Pacer->vsyncCallback(SDL_min(time - me->m_LastFrameTime, 2000 / me->m_DisplayFps));
|
||||
}
|
||||
|
||||
me->m_LastFrameTime = time;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,9 @@ public:
|
|||
|
||||
virtual ~WaylandVsyncSource();
|
||||
|
||||
virtual bool initialize(SDL_Window* window, int displayFps);
|
||||
virtual bool initialize(SDL_Window* window, int displayFps) override;
|
||||
|
||||
virtual bool isAsync() override;
|
||||
|
||||
private:
|
||||
static void frameDone(void* data, struct wl_callback* oldCb, uint32_t time);
|
||||
|
@ -20,10 +22,8 @@ private:
|
|||
static const struct wl_callback_listener s_FrameListener;
|
||||
|
||||
Pacer* m_Pacer;
|
||||
int m_DisplayFps;
|
||||
wl_display* m_Display;
|
||||
wl_surface* m_Surface;
|
||||
wl_callback* m_Callback;
|
||||
uint32_t m_LastFrameTime;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue