diff --git a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp index 2d30aadb..dbfb0a39 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp +++ b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp @@ -10,6 +10,8 @@ DxVsyncSource::DxVsyncSource(Pacer* pacer) : m_LastMonitor(nullptr) { SDL_zero(m_WaitForVblankEventParams); + m_WaitForVblankEventParams.VidPnSourceId = D3DDDI_ID_UNINITIALIZED; + SDL_zero(m_AdapterLuid); } DxVsyncSource::~DxVsyncSource() @@ -72,41 +74,43 @@ bool DxVsyncSource::isAsync() return false; } -void DxVsyncSource::waitForVsync() +bool DxVsyncSource::waitForVsync() { NTSTATUS status; // If the monitor has changed from last time, open the new adapter HMONITOR currentMonitor = MonitorFromWindow(m_Window, MONITOR_DEFAULTTONEAREST); - if (currentMonitor != m_LastMonitor) { + if (!currentMonitor || currentMonitor != m_LastMonitor) { + // Close the old adapter + if (m_WaitForVblankEventParams.hAdapter != 0) { + D3DKMT_CLOSEADAPTER closeAdapterParams = {}; + closeAdapterParams.hAdapter = m_WaitForVblankEventParams.hAdapter; + m_D3DKMTCloseAdapter(&closeAdapterParams); + m_WaitForVblankEventParams.hAdapter = 0; + } + m_LastMonitor = nullptr; + + // We can't wait if we're not on any monitor + if (!currentMonitor) { + return false; + } + MONITORINFOEXA monitorInfo = {}; monitorInfo.cbSize = sizeof(monitorInfo); if (!GetMonitorInfoA(currentMonitor, &monitorInfo)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GetMonitorInfo() failed: %d", GetLastError()); - return; + return false; } - DEVMODEA monitorMode; + 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); + return false; } D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {}; @@ -115,7 +119,7 @@ void DxVsyncSource::waitForVsync() SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "CreateDC() failed: %d", GetLastError()); - return; + return false; } // Open the new adapter @@ -126,21 +130,38 @@ void DxVsyncSource::waitForVsync() SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "D3DKMTOpenAdapterFromHdc() failed: %x", status); - return; + return false; + } + + // VidPnSourceId is scoped to the adapter, so we must also check the adapter LUID + if (m_AdapterLuid.LowPart != openAdapterParams.AdapterLuid.LowPart || + m_AdapterLuid.HighPart != openAdapterParams.AdapterLuid.HighPart || + m_WaitForVblankEventParams.VidPnSourceId != openAdapterParams.VidPnSourceId) { + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Monitor changed: %s %d Hz", + monitorInfo.szDevice, + monitorMode.dmDisplayFrequency); } m_WaitForVblankEventParams.hAdapter = openAdapterParams.hAdapter; m_WaitForVblankEventParams.hDevice = 0; m_WaitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId; + m_AdapterLuid = openAdapterParams.AdapterLuid; m_LastMonitor = currentMonitor; } status = m_D3DKMTWaitForVerticalBlankEvent(&m_WaitForVblankEventParams); if (status != STATUS_SUCCESS) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "D3DKMTWaitForVerticalBlankEvent() failed: %x", - status); - return; + if (status != STATUS_GRAPHICS_PRESENT_OCCLUDED) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "D3DKMTWaitForVerticalBlankEvent() failed: %x", + status); + } + + m_LastMonitor = nullptr; + return false; } + + return true; } diff --git a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h index f126df15..836b8da9 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h +++ b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h @@ -10,6 +10,7 @@ typedef UINT D3DKMT_HANDLE; typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID; #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) #define STATUS_GRAPHICS_PRESENT_OCCLUDED ((NTSTATUS)0xC01E0006L) +#define D3DDDI_ID_UNINITIALIZED (UINT)(~0) typedef struct _D3DKMT_OPENADAPTERFROMHDC { HDC hDc; D3DKMT_HANDLE hAdapter; @@ -39,13 +40,14 @@ public: virtual bool isAsync() override; - virtual void waitForVsync() override; + virtual bool waitForVsync() override; private: Pacer* m_Pacer; HMODULE m_Gdi32Handle; HWND m_Window; HMONITOR m_LastMonitor; + LUID m_AdapterLuid; D3DKMT_WAITFORVERTICALBLANKEVENT m_WaitForVblankEventParams; PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc; diff --git a/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp b/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp index 4ad14778..2985df82 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp +++ b/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp @@ -115,13 +115,20 @@ int Pacer::vsyncThread(void *context) while (!me->m_Stopping) { if (async) { // Wait for the VSync source to invoke signalVsync() or 100ms to elapse - me->m_FrameQueueLock.lock(); + QMutexLocker locker(&me->m_FrameQueueLock); 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_VsyncSource->waitForVsync()) { + // Fall back to a fixed delay if the VSync source fails + // + // NB: We don't actually expect signalVsync() to be called, since + // this is a synchronous VSync source, but we wait on m_VsyncSignalled + // anyway so that the destructor can immediately wake us on shutdown. + QMutexLocker locker(&me->m_FrameQueueLock); + me->m_VsyncSignalled.wait(&me->m_FrameQueueLock, 1000 / me->m_DisplayFps); + } } if (me->m_Stopping) { @@ -148,24 +155,25 @@ int Pacer::renderThread(void* context) // Wait for the renderer to be ready for the next frame me->m_VsyncRenderer->waitToRender(); - // Acquire the frame queue lock to protect the queue and - // the not empty condition - me->m_FrameQueueLock.lock(); + AVFrame* frame; + { + // Acquire the frame queue lock to protect the queue and + // the not empty condition + QMutexLocker locker(&me->m_FrameQueueLock); - // Wait for a frame to be ready to render - while (!me->m_Stopping && me->m_RenderQueue.isEmpty()) { - me->m_RenderQueueNotEmpty.wait(&me->m_FrameQueueLock); + // Wait for a frame to be ready to render + while (!me->m_Stopping && me->m_RenderQueue.isEmpty()) { + me->m_RenderQueueNotEmpty.wait(&me->m_FrameQueueLock); + } + + if (me->m_Stopping) { + // Exit this thread + break; + } + + frame = me->m_RenderQueue.dequeue(); } - if (me->m_Stopping) { - // Exit this thread - me->m_FrameQueueLock.unlock(); - break; - } - - AVFrame* frame = me->m_RenderQueue.dequeue(); - me->m_FrameQueueLock.unlock(); - me->renderFrame(frame); } @@ -349,7 +357,7 @@ void Pacer::renderFrame(AVFrame* frame) av_frame_free(&frame); // Drop frames if we have too many queued up for a while - m_FrameQueueLock.lock(); + QMutexLocker frameQueueLocker(&m_FrameQueueLock); int frameDropTarget; @@ -382,13 +390,11 @@ void Pacer::renderFrame(AVFrame* frame) AVFrame* frame = m_RenderQueue.dequeue(); // Drop the lock while we call av_frame_free() - m_FrameQueueLock.unlock(); + frameQueueLocker.unlock(); m_VideoStats->pacerDroppedFrames++; av_frame_free(&frame); - m_FrameQueueLock.lock(); + frameQueueLocker.relock(); } - - m_FrameQueueLock.unlock(); } void Pacer::dropFrameForEnqueue(QQueue& queue) diff --git a/app/streaming/video/ffmpeg-renderers/pacer/pacer.h b/app/streaming/video/ffmpeg-renderers/pacer/pacer.h index a1bb9eab..e55fe4bb 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/pacer.h +++ b/app/streaming/video/ffmpeg-renderers/pacer/pacer.h @@ -22,9 +22,10 @@ public: // sources require calls to waitForVsync(). virtual bool isAsync() = 0; - virtual void waitForVsync() { + virtual bool waitForVsync() { // Synchronous sources must implement waitForVsync()! SDL_assert(false); + return false; } };