From 6ae62180435a31251881402abd89c1a38e56a0d6 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 12 Nov 2022 15:08:50 -0600 Subject: [PATCH] Refactor Pacer to handle both blocking and non-blocking VsyncSources --- .../ffmpeg-renderers/pacer/dxvsyncsource.cpp | 174 ++++++++---------- .../ffmpeg-renderers/pacer/dxvsyncsource.h | 13 +- .../video/ffmpeg-renderers/pacer/pacer.cpp | 61 +++++- .../video/ffmpeg-renderers/pacer/pacer.h | 17 +- .../pacer/waylandvsyncsource.cpp | 28 ++- .../pacer/waylandvsyncsource.h | 6 +- 6 files changed, 170 insertions(+), 129 deletions(-) diff --git a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp index 8ebcc440..2d30aadb 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp +++ b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp @@ -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,114 +58,89 @@ 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(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 +void DxVsyncSource::waitForVsync() +{ + NTSTATUS status; - D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {}; - HMONITOR lastMonitor = nullptr; - DEVMODEA monitorMode; - monitorMode.dmSize = sizeof(monitorMode); - - while (SDL_AtomicGet(&me->m_Stopping) == 0) { - D3DKMT_WAITFORVERTICALBLANKEVENT waitForVblankEventParams; - 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) { - MONITORINFOEXA monitorInfo = {}; - monitorInfo.cbSize = sizeof(monitorInfo); - if (!GetMonitorInfoA(currentMonitor, &monitorInfo)) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "GetMonitorInfo() failed: %d", - GetLastError()); - SDL_Delay(10); - continue; - } - - if (!EnumDisplaySettingsA(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &monitorMode)) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "EnumDisplaySettings() failed: %d", - GetLastError()); - SDL_Delay(10); - continue; - } - - SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, - "Monitor changed: %s %d Hz", - monitorInfo.szDevice, - monitorMode.dmDisplayFrequency); - - if (openAdapterParams.hAdapter != 0) { - D3DKMT_CLOSEADAPTER closeAdapterParams = {}; - closeAdapterParams.hAdapter = openAdapterParams.hAdapter; - me->m_D3DKMTCloseAdapter(&closeAdapterParams); - } - - 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; - } - - status = me->m_D3DKMTOpenAdapterFromHdc(&openAdapterParams); - DeleteDC(openAdapterParams.hDc); - - if (status != STATUS_SUCCESS) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "D3DKMTOpenAdapterFromHdc() failed: %x", - status); - SDL_Delay(10); - continue; - } - - lastMonitor = currentMonitor; + // If the monitor has changed from last time, open the new adapter + 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()); + return; } - waitForVblankEventParams.hAdapter = openAdapterParams.hAdapter; - waitForVblankEventParams.hDevice = 0; - waitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId; + DEVMODEA monitorMode; + monitorMode.dmSize = sizeof(monitorMode); + if (!EnumDisplaySettingsA(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &monitorMode)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "EnumDisplaySettings() failed: %d", + GetLastError()); + return; + } + + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Monitor changed: %s %d Hz", + monitorInfo.szDevice, + monitorMode.dmDisplayFrequency); + + // Close the old adapter + if (m_WaitForVblankEventParams.hAdapter != 0) { + D3DKMT_CLOSEADAPTER 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()); + return; + } + + // Open the new adapter + status = m_D3DKMTOpenAdapterFromHdc(&openAdapterParams); + DeleteDC(openAdapterParams.hDc); - status = me->m_D3DKMTWaitForVerticalBlankEvent(&waitForVblankEventParams); if (status != STATUS_SUCCESS) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "D3DKMTWaitForVerticalBlankEvent() failed: %x", + "D3DKMTOpenAdapterFromHdc() failed: %x", status); - SDL_Delay(10); - continue; + return; } - me->m_Pacer->vsyncCallback(1000 / me->m_DisplayFps); + m_WaitForVblankEventParams.hAdapter = openAdapterParams.hAdapter; + m_WaitForVblankEventParams.hDevice = 0; + m_WaitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId; + + m_LastMonitor = currentMonitor; } - if (openAdapterParams.hAdapter != 0) { - D3DKMT_CLOSEADAPTER closeAdapterParams = {}; - closeAdapterParams.hAdapter = openAdapterParams.hAdapter; - me->m_D3DKMTCloseAdapter(&closeAdapterParams); + status = m_D3DKMTWaitForVerticalBlankEvent(&m_WaitForVblankEventParams); + if (status != STATUS_SUCCESS) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "D3DKMTWaitForVerticalBlankEvent() failed: %x", + status); + return; } - - return 0; } diff --git a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h index 86920ea4..f126df15 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h +++ b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h @@ -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; diff --git a/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp b/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp index da907d90..13ea90d7 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp +++ b/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp @@ -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(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(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 diff --git a/app/streaming/video/ffmpeg-renderers/pacer/pacer.h b/app/streaming/video/ffmpeg-renderers/pacer/pacer.h index 013cfe5e..11963a5f 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/pacer.h +++ b/app/streaming/video/ffmpeg-renderers/pacer/pacer.h @@ -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; diff --git a/app/streaming/video/ffmpeg-renderers/pacer/waylandvsyncsource.cpp b/app/streaming/video/ffmpeg-renderers/pacer/waylandvsyncsource.cpp index 9887a5c9..85851dab 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/waylandvsyncsource.cpp +++ b/app/streaming/video/ffmpeg-renderers/pacer/waylandvsyncsource.cpp @@ -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; } diff --git a/app/streaming/video/ffmpeg-renderers/pacer/waylandvsyncsource.h b/app/streaming/video/ffmpeg-renderers/pacer/waylandvsyncsource.h index 23056fe3..c3a3a607 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/waylandvsyncsource.h +++ b/app/streaming/video/ffmpeg-renderers/pacer/waylandvsyncsource.h @@ -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; };