diff --git a/app/app.pro b/app/app.pro index 84b4a835..89466f6f 100644 --- a/app/app.pro +++ b/app/app.pro @@ -34,7 +34,7 @@ win32 { LIBS += -L$$PWD/../libs/windows/lib/x64 } - LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib dwmapi.lib + LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib } macx { INCLUDEPATH += $$PWD/../libs/mac/include $$PWD/../libs/mac/Frameworks/SDL2.framework/Versions/A/Headers diff --git a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp index 23ddc0fc..3f653721 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp +++ b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp @@ -1,10 +1,13 @@ #include "dxvsyncsource.h" -#include +// Useful references: +// https://bugs.chromium.org/p/chromium/issues/detail?id=467617 +// https://chromium.googlesource.com/chromium/src.git/+/c564f2fe339b2b2abb0c8773c90c83215670ea71/gpu/ipc/service/gpu_vsync_provider_win.cc DxVsyncSource::DxVsyncSource(Pacer* pacer) : m_Pacer(pacer), - m_Thread(nullptr) + m_Thread(nullptr), + m_Gdi32Handle(nullptr) { SDL_AtomicSet(&m_Stopping, 0); } @@ -15,10 +18,47 @@ DxVsyncSource::~DxVsyncSource() SDL_AtomicSet(&m_Stopping, 1); SDL_WaitThread(m_Thread, nullptr); } + + if (m_Gdi32Handle != nullptr) { + FreeLibrary(m_Gdi32Handle); + } } bool DxVsyncSource::initialize(SDL_Window* window) { + m_Gdi32Handle = LoadLibraryA("gdi32.dll"); + if (m_Gdi32Handle == nullptr) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to load gdi32.dll: %d", + GetLastError()); + return false; + } + + m_D3DKMTOpenAdapterFromHdc = (PFND3DKMTOPENADAPTERFROMHDC)GetProcAddress(m_Gdi32Handle, "D3DKMTOpenAdapterFromHdc"); + m_D3DKMTCloseAdapter = (PFND3DKMTCLOSEADAPTER)GetProcAddress(m_Gdi32Handle, "D3DKMTCloseAdapter"); + m_D3DKMTWaitForVerticalBlankEvent = (PFND3DKMTWAITFORVERTICALBLANKEVENT)GetProcAddress(m_Gdi32Handle, "D3DKMTWaitForVerticalBlankEvent"); + + if (m_D3DKMTOpenAdapterFromHdc == nullptr || + m_D3DKMTCloseAdapter == nullptr || + m_D3DKMTWaitForVerticalBlankEvent == nullptr) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Missing required function in gdi32.dll"); + return false; + } + + SDL_SysWMinfo info; + + SDL_VERSION(&info.version); + + if (!SDL_GetWindowWMInfo(window, &info)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "SDL_GetWindowWMInfo() failed: %s", + SDL_GetError()); + return false; + } + + m_Window = info.info.win.window; + m_Thread = SDL_CreateThread(vsyncThread, "DX Vsync Thread", this); if (m_Thread == nullptr) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, @@ -36,14 +76,95 @@ int DxVsyncSource::vsyncThread(void* context) SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); + D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {}; + HMONITOR lastMonitor = nullptr; + DEVMODEA monitorMode; + monitorMode.dmSize = sizeof(monitorMode); + while (SDL_AtomicGet(&me->m_Stopping) == 0) { - // FIXME: We should really use D3DKMTWaitForVerticalBlankEvent() instead! - // https://bugs.chromium.org/p/chromium/issues/detail?id=467617 - // https://chromium.googlesource.com/chromium/src.git/+/c564f2fe339b2b2abb0c8773c90c83215670ea71/gpu/ipc/service/gpu_vsync_provider_win.cc - DwmFlush(); + 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; + } + + waitForVblankEventParams.hAdapter = openAdapterParams.hAdapter; + waitForVblankEventParams.hDevice = 0; + waitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId; + + status = me->m_D3DKMTWaitForVerticalBlankEvent(&waitForVblankEventParams); + if (status != STATUS_SUCCESS) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "D3DKMTWaitForVerticalBlankEvent() failed: %x", + status); + SDL_Delay(10); + continue; + } + + // Try to delay to the middle of the V-sync period to give the frame + // from the host time to arrive. + SDL_Delay(500 / monitorMode.dmDisplayFrequency); me->m_Pacer->vsyncCallback(); } + if (openAdapterParams.hAdapter != 0) { + D3DKMT_CLOSEADAPTER closeAdapterParams = {}; + closeAdapterParams.hAdapter = openAdapterParams.hAdapter; + me->m_D3DKMTCloseAdapter(&closeAdapterParams); + } + return 0; } diff --git a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h index d3a93606..3c72f2f4 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h +++ b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h @@ -2,6 +2,32 @@ #include "pacer.h" +#include + +// from +typedef LONG NTSTATUS; +typedef UINT D3DKMT_HANDLE; +typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID; +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define STATUS_GRAPHICS_PRESENT_OCCLUDED ((NTSTATUS)0xC01E0006L) +typedef struct _D3DKMT_OPENADAPTERFROMHDC { + HDC hDc; + D3DKMT_HANDLE hAdapter; + LUID AdapterLuid; + D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId; +} D3DKMT_OPENADAPTERFROMHDC; +typedef struct _D3DKMT_CLOSEADAPTER { + D3DKMT_HANDLE hAdapter; +} D3DKMT_CLOSEADAPTER; +typedef struct _D3DKMT_WAITFORVERTICALBLANKEVENT { + D3DKMT_HANDLE hAdapter; + D3DKMT_HANDLE hDevice; + D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId; +} D3DKMT_WAITFORVERTICALBLANKEVENT; +typedef NTSTATUS(APIENTRY* PFND3DKMTOPENADAPTERFROMHDC)(D3DKMT_OPENADAPTERFROMHDC*); +typedef NTSTATUS(APIENTRY* PFND3DKMTCLOSEADAPTER)(D3DKMT_CLOSEADAPTER*); +typedef NTSTATUS(APIENTRY* PFND3DKMTWAITFORVERTICALBLANKEVENT)(D3DKMT_WAITFORVERTICALBLANKEVENT*); + class DxVsyncSource : public IVsyncSource { public: @@ -17,4 +43,10 @@ private: Pacer* m_Pacer; SDL_Thread* m_Thread; SDL_atomic_t m_Stopping; + HMODULE m_Gdi32Handle; + HWND m_Window; + + PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc; + PFND3DKMTCLOSEADAPTER m_D3DKMTCloseAdapter; + PFND3DKMTWAITFORVERTICALBLANKEVENT m_D3DKMTWaitForVerticalBlankEvent; };