From c9a7c15f98e92faab266010a8f6967cd2c72b087 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 8 Sep 2018 15:09:46 -0700 Subject: [PATCH] Improve unsupported FPS options and performance --- app/gui/SettingsView.qml | 16 +++++-- app/streaming/session.cpp | 41 ++++++++++++----- app/streaming/streamutils.cpp | 46 +++++++++++++++++++ app/streaming/streamutils.h | 3 ++ .../video/ffmpeg-renderers/dxva2.cpp | 28 ++++++----- app/streaming/video/ffmpeg-renderers/dxva2.h | 1 - .../video/ffmpeg-renderers/pacer/pacer.cpp | 40 +--------------- 7 files changed, 110 insertions(+), 65 deletions(-) diff --git a/app/gui/SettingsView.qml b/app/gui/SettingsView.qml index fce1e640..937e9859 100644 --- a/app/gui/SettingsView.qml +++ b/app/gui/SettingsView.qml @@ -178,17 +178,28 @@ ScrollView { fpsListModel.append({"text": "30 FPS", "video_fps": "30"}) fpsListModel.append({"text": "60 FPS", "video_fps": "60"}) + // Add unsupported FPS values that come before the display max FPS + if (prefs.unsupportedFps) { + if (max_fps > 90) { + fpsListModel.append({"text": "90 FPS (Unsupported)", "video_fps": "90"}) + } + if (max_fps > 120) { + fpsListModel.append({"text": "120 FPS (Unsupported)", "video_fps": "120"}) + } + } + // Use 64 as the cutoff for adding a separate option to // handle wonky displays that report just over 60 Hz. if (max_fps > 64) { fpsListModel.append({"text": max_fps+" FPS", "video_fps": ""+max_fps}) } + // Add unsupported FPS values that come after the display max FPS if (prefs.unsupportedFps) { - if (max_fps !== 90) { + if (max_fps < 90) { fpsListModel.append({"text": "90 FPS (Unsupported)", "video_fps": "90"}) } - if (max_fps !== 120) { + if (max_fps < 120) { fpsListModel.append({"text": "120 FPS (Unsupported)", "video_fps": "120"}) } } @@ -325,7 +336,6 @@ ScrollView { id: vsyncCheck text: "Enable V-Sync" font.pointSize: 12 - visible: prefs.windowMode === StreamingPreferences.WM_FULLSCREEN checked: prefs.enableVsync onCheckedChanged: { prefs.enableVsync = checked diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index 231a722d..4037afd4 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -417,6 +417,10 @@ bool Session::validateLaunch() if (m_Preferences.unsupportedFps && m_StreamConfig.fps > 60) { emitLaunchWarning("Using unsupported FPS options may cause stuttering or lag."); + + if (m_Preferences.enableVsync) { + emitLaunchWarning("V-sync will be disabled when streaming at a higher frame rate than the display."); + } } if (m_StreamConfig.supportsHevc) { @@ -1001,18 +1005,31 @@ void Session::exec(int displayOriginX, int displayOriginY) SDL_FlushEvent(SDL_RENDER_DEVICE_RESET); SDL_FlushEvent(SDL_RENDER_TARGETS_RESET); - // Choose a new decoder (hopefully the same one, but possibly - // not if a GPU was removed or something). - if (!chooseDecoder(m_Preferences.videoDecoderSelection, - m_Window, m_ActiveVideoFormat, m_ActiveVideoWidth, - m_ActiveVideoHeight, m_ActiveVideoFrameRate, - m_Preferences.enableVsync, - s_ActiveSession->m_VideoDecoder)) { - SDL_AtomicUnlock(&m_DecoderLock); - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "Failed to recreate decoder after reset"); - emit displayLaunchError("Unable to initialize video decoder. Please check your streaming settings and try again."); - goto DispatchDeferredCleanup; + { + // If the stream exceeds the display refresh rate (plus some slack), + // forcefully disable V-sync to allow the stream to render faster + // than the display. + int displayHz = StreamUtils::getDisplayRefreshRate(m_Window); + bool enableVsync = m_Preferences.enableVsync; + if (displayHz + 5 < m_StreamConfig.fps) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Disabling V-sync because refresh rate limit exceeded"); + enableVsync = false; + } + + // Choose a new decoder (hopefully the same one, but possibly + // not if a GPU was removed or something). + if (!chooseDecoder(m_Preferences.videoDecoderSelection, + m_Window, m_ActiveVideoFormat, m_ActiveVideoWidth, + m_ActiveVideoHeight, m_ActiveVideoFrameRate, + enableVsync, + s_ActiveSession->m_VideoDecoder)) { + SDL_AtomicUnlock(&m_DecoderLock); + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to recreate decoder after reset"); + emit displayLaunchError("Unable to initialize video decoder. Please check your streaming settings and try again."); + goto DispatchDeferredCleanup; + } } // Request an IDR frame to complete the reset diff --git a/app/streaming/streamutils.cpp b/app/streaming/streamutils.cpp index 1b776d6c..5b3ccf1d 100644 --- a/app/streaming/streamutils.cpp +++ b/app/streaming/streamutils.cpp @@ -25,6 +25,52 @@ void StreamUtils::scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst) } } +int StreamUtils::getDisplayRefreshRate(SDL_Window* window) +{ + int displayIndex = SDL_GetWindowDisplayIndex(window); + if (displayIndex < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to get current display: %s", + SDL_GetError()); + + // Assume display 0 if it fails + displayIndex = 0; + } + + SDL_DisplayMode mode; + if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) { + // Use the window display mode for full-screen exclusive mode + if (SDL_GetWindowDisplayMode(window, &mode) != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "SDL_GetWindowDisplayMode() failed: %s", + SDL_GetError()); + + // Assume 60 Hz + return 60; + } + } + else { + // Use the current display mode for windowed and borderless + if (SDL_GetCurrentDisplayMode(displayIndex, &mode) != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "SDL_GetCurrentDisplayMode() failed: %s", + SDL_GetError()); + + // Assume 60 Hz + return 60; + } + } + + // May be zero if undefined + if (mode.refresh_rate == 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Refresh rate unknown; assuming 60 Hz"); + mode.refresh_rate = 60; + } + + return mode.refresh_rate; +} + bool StreamUtils::getRealDesktopMode(int displayIndex, SDL_DisplayMode* mode) { #ifdef Q_OS_DARWIN diff --git a/app/streaming/streamutils.h b/app/streaming/streamutils.h index b037bb2c..ce6fc6f5 100644 --- a/app/streaming/streamutils.h +++ b/app/streaming/streamutils.h @@ -10,4 +10,7 @@ public: static bool getRealDesktopMode(int displayIndex, SDL_DisplayMode* mode); + + static + int getDisplayRefreshRate(SDL_Window* window); }; diff --git a/app/streaming/video/ffmpeg-renderers/dxva2.cpp b/app/streaming/video/ffmpeg-renderers/dxva2.cpp index 95d9566e..f41e8729 100644 --- a/app/streaming/video/ffmpeg-renderers/dxva2.cpp +++ b/app/streaming/video/ffmpeg-renderers/dxva2.cpp @@ -17,7 +17,6 @@ DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0x #define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); } DXVA2Renderer::DXVA2Renderer() : - m_ForceVsyncOn(false), m_DecService(nullptr), m_Decoder(nullptr), m_SurfacesUsed(0), @@ -467,7 +466,6 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync) D3DPRESENT_PARAMETERS d3dpp = {}; d3dpp.hDeviceWindow = info.info.win.window; d3dpp.Flags = D3DPRESENTFLAG_VIDEO; - d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; if ((windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) { d3dpp.Windowed = false; @@ -490,14 +488,20 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync) // to reduce latency by avoiding double v-syncing. d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; - // D3DSWAPEFFECT_FLIPEX requires at least 2 back buffers to allow us to - // continue while DWM is waiting to render the surface to the display. - d3dpp.SwapEffect = D3DSWAPEFFECT_FLIPEX; - d3dpp.BackBufferCount = 2; - - // We need our V-sync source enabled to synchronize with DWM composition - // and avoid micro-stuttering. - m_ForceVsyncOn = true; + // If V-sync is enabled (not rendering faster than display), + // we can use FlipEx for more efficient swapping. + if (enableVsync) { + // D3DSWAPEFFECT_FLIPEX requires at least 2 back buffers to allow us to + // continue while DWM is waiting to render the surface to the display. + d3dpp.SwapEffect = D3DSWAPEFFECT_FLIPEX; + d3dpp.BackBufferCount = 2; + } + else { + // With V-sync off, we won't use FlipEx because that will block while + // DWM is waiting to render our surface (effectively behaving like V-Sync). + d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; + d3dpp.BackBufferCount = 1; + } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Windowed mode with DWM running"); @@ -506,6 +510,7 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync) // Uncomposited desktop or full-screen exclusive mode with V-sync enabled // We will enable V-sync in this scenario to avoid tearing. d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; + d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferCount = 1; SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, @@ -515,6 +520,7 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync) // Uncomposited desktop or full-screen exclusive mode with V-sync disabled // We will allowing tearing for lowest latency. d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferCount = 1; SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, @@ -631,7 +637,7 @@ int DXVA2Renderer::getDecoderCapabilities() IFFmpegRenderer::VSyncConstraint DXVA2Renderer::getVsyncConstraint() { - return m_ForceVsyncOn ? VSYNC_FORCE_ON : VSYNC_ANY; + return VSYNC_ANY; } void DXVA2Renderer::renderFrameAtVsync(AVFrame *frame) diff --git a/app/streaming/video/ffmpeg-renderers/dxva2.h b/app/streaming/video/ffmpeg-renderers/dxva2.h index 068e49b9..5b0ae473 100644 --- a/app/streaming/video/ffmpeg-renderers/dxva2.h +++ b/app/streaming/video/ffmpeg-renderers/dxva2.h @@ -45,7 +45,6 @@ private: int m_VideoFormat; int m_VideoWidth; int m_VideoHeight; - bool m_ForceVsyncOn; int m_DisplayWidth; int m_DisplayHeight; diff --git a/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp b/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp index 540310c9..fbd8c702 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp +++ b/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp @@ -1,4 +1,5 @@ #include "pacer.h" +#include "streaming/streamutils.h" #include "nullthreadedvsyncsource.h" @@ -120,44 +121,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enableVsync) { m_MaxVideoFps = maxVideoFps; m_EnableVsync = enableVsync; - - int displayIndex = SDL_GetWindowDisplayIndex(window); - if (displayIndex < 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "Failed to get current display: %s", - SDL_GetError()); - - // Assume display 0 if it fails - displayIndex = 0; - } - - SDL_DisplayMode mode; - if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) { - // Use the window display mode for full-screen exclusive mode - if (SDL_GetWindowDisplayMode(window, &mode) != 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_GetWindowDisplayMode() failed: %s", - SDL_GetError()); - return false; - } - } - else { - // Use the current display mode for windowed and borderless - if (SDL_GetCurrentDisplayMode(displayIndex, &mode) != 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_GetCurrentDisplayMode() failed: %s", - SDL_GetError()); - return false; - } - } - - // May be zero if undefined - m_DisplayFps = mode.refresh_rate; - if (m_DisplayFps == 0) { - SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, - "Refresh rate unknown; assuming 60 Hz"); - m_DisplayFps = 60; - } + m_DisplayFps = StreamUtils::getDisplayRefreshRate(window); if (m_EnableVsync) { SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,