diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index 6f10e1e3..8bbf1c1e 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -106,7 +106,7 @@ void Session::clLogMessage(const char* format, ...) bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds, SDL_Window* window, int videoFormat, int width, int height, - int frameRate, IVideoDecoder*& chosenDecoder) + int frameRate, bool enableVsync, IVideoDecoder*& chosenDecoder) { #ifdef HAVE_SLVIDEO chosenDecoder = new SLVideoDecoder(); @@ -125,7 +125,7 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds, #ifdef HAVE_FFMPEG chosenDecoder = new FFmpegVideoDecoder(); - if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate)) { + if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate, enableVsync)) { SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "FFmpeg-based video decoder chosen"); return true; @@ -212,7 +212,7 @@ bool Session::isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelect return false; } - if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, decoder)) { + if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, decoder)) { SDL_DestroyWindow(window); SDL_QuitSubSystem(SDL_INIT_VIDEO); return false; @@ -823,6 +823,7 @@ void Session::exec() if (!chooseDecoder(m_Preferences.videoDecoderSelection, m_Window, m_ActiveVideoFormat, m_ActiveVideoWidth, m_ActiveVideoHeight, m_ActiveVideoFrameRate, + true, // TODO: User configuration for V-sync s_ActiveSession->m_VideoDecoder)) { SDL_AtomicUnlock(&m_DecoderLock); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, diff --git a/app/streaming/session.hpp b/app/streaming/session.hpp index eb9e0896..631a3531 100644 --- a/app/streaming/session.hpp +++ b/app/streaming/session.hpp @@ -54,7 +54,7 @@ private: static bool chooseDecoder(StreamingPreferences::VideoDecoderSelection vds, SDL_Window* window, int videoFormat, int width, int height, - int frameRate, IVideoDecoder*& chosenDecoder); + int frameRate, bool enableVsync, IVideoDecoder*& chosenDecoder); static void clStageStarting(int stage); diff --git a/app/streaming/video/decoder.h b/app/streaming/video/decoder.h index 2719557f..04f8b7be 100644 --- a/app/streaming/video/decoder.h +++ b/app/streaming/video/decoder.h @@ -16,7 +16,8 @@ public: int videoFormat, int width, int height, - int frameRate) = 0; + int frameRate, + bool enableVsync) = 0; virtual bool isHardwareAccelerated() = 0; virtual int submitDecodeUnit(PDECODE_UNIT du) = 0; virtual void renderFrame(SDL_UserEvent* event) = 0; diff --git a/app/streaming/video/ffmpeg-renderers/dxva2.cpp b/app/streaming/video/ffmpeg-renderers/dxva2.cpp index 3d6e0a3c..07c539c3 100644 --- a/app/streaming/video/ffmpeg-renderers/dxva2.cpp +++ b/app/streaming/video/ffmpeg-renderers/dxva2.cpp @@ -428,7 +428,7 @@ bool DXVA2Renderer::isDecoderBlacklisted() return result; } -bool DXVA2Renderer::initializeDevice(SDL_Window* window) +bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync) { SDL_SysWMinfo info; @@ -487,13 +487,22 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Windowed mode with DWM running"); } - else { - // Uncomposited desktop or full-screen exclusive mode + else if (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; SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "V-Sync enabled"); } + else { + // Uncomposited desktop or full-screen exclusive mode with V-sync disabled + // We will allowing tearing for lowest latency. + d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "V-Sync disabled in tearing mode"); + } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Windowed: %d | Present Interval: %x", @@ -540,7 +549,7 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window) return true; } -bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height, int) +bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height, int, bool enableVsync) { m_VideoFormat = videoFormat; m_VideoWidth = width; @@ -569,7 +578,7 @@ bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, i m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame; m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2'); - if (!initializeDevice(window)) { + if (!initializeDevice(window, enableVsync)) { return false; } diff --git a/app/streaming/video/ffmpeg-renderers/dxva2.h b/app/streaming/video/ffmpeg-renderers/dxva2.h index 458eac56..013461f2 100644 --- a/app/streaming/video/ffmpeg-renderers/dxva2.h +++ b/app/streaming/video/ffmpeg-renderers/dxva2.h @@ -19,7 +19,8 @@ public: int videoFormat, int width, int height, - int maxFps); + int maxFps, + bool enableVsync); virtual bool prepareDecoderContext(AVCodecContext* context); virtual void renderFrameAtVsync(AVFrame* frame); virtual bool needsTestFrame(); @@ -27,7 +28,7 @@ public: private: bool initializeDecoder(); bool initializeRenderer(); - bool initializeDevice(SDL_Window* window); + bool initializeDevice(SDL_Window* window, bool enableVsync); bool isDecoderBlacklisted(); static diff --git a/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp b/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp index cf0fac08..28b22a99 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp +++ b/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp @@ -116,9 +116,10 @@ RenderNextFrame: av_frame_free(&frame); } -bool Pacer::initialize(SDL_Window* window, int maxVideoFps) +bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enableVsync) { m_MaxVideoFps = maxVideoFps; + m_EnableVsync = enableVsync; int displayIndex = SDL_GetWindowDisplayIndex(window); if (displayIndex < 0) { @@ -136,21 +137,28 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps) m_DisplayFps = mode.refresh_rate; } - SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, - "Frame pacing: target %d Hz with %d FPS stream", - m_DisplayFps, m_MaxVideoFps); + if (m_EnableVsync) { + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Frame pacing in tear-free mode: target %d Hz with %d FPS stream", + m_DisplayFps, m_MaxVideoFps); -#if defined(Q_OS_DARWIN) - m_VsyncSource = new DisplayLinkVsyncSource(this); -#elif defined(Q_OS_WIN32) - m_VsyncSource = new DxVsyncSource(this); -#else - // Platforms without a VsyncSource will just render frames - // immediately like they used to. -#endif + #if defined(Q_OS_DARWIN) + m_VsyncSource = new DisplayLinkVsyncSource(this); + #elif defined(Q_OS_WIN32) + m_VsyncSource = new DxVsyncSource(this); + #else + // Platforms without a VsyncSource will just render frames + // immediately like they used to. + #endif - if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window, m_DisplayFps)) { - return false; + if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window, m_DisplayFps)) { + return false; + } + } + else { + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Minimal latency tearing mode: target %d Hz with %d FPS stream", + m_DisplayFps, m_MaxVideoFps); } return true; diff --git a/app/streaming/video/ffmpeg-renderers/pacer/pacer.h b/app/streaming/video/ffmpeg-renderers/pacer/pacer.h index afdf5acd..bcbec9e4 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/pacer.h +++ b/app/streaming/video/ffmpeg-renderers/pacer/pacer.h @@ -19,7 +19,7 @@ public: void submitFrame(AVFrame* frame); - bool initialize(SDL_Window* window, int maxVideoFps); + bool initialize(SDL_Window* window, int maxVideoFps, bool enableVsync); void vsyncCallback(int timeUntilNextVsyncMillis); @@ -34,4 +34,5 @@ private: IFFmpegRenderer* m_VsyncRenderer; int m_MaxVideoFps; int m_DisplayFps; + bool m_EnableVsync; }; diff --git a/app/streaming/video/ffmpeg-renderers/renderer.h b/app/streaming/video/ffmpeg-renderers/renderer.h index 79acfdf5..23304bb9 100644 --- a/app/streaming/video/ffmpeg-renderers/renderer.h +++ b/app/streaming/video/ffmpeg-renderers/renderer.h @@ -13,7 +13,8 @@ public: int videoFormat, int width, int height, - int maxFps) = 0; + int maxFps, + bool enableVsync) = 0; virtual bool prepareDecoderContext(AVCodecContext* context) = 0; virtual void renderFrameAtVsync(AVFrame* frame) = 0; virtual bool needsTestFrame() = 0; @@ -27,7 +28,8 @@ public: int videoFormat, int width, int height, - int maxFps); + int maxFps, + bool enableVsync); virtual bool prepareDecoderContext(AVCodecContext* context); virtual void renderFrameAtVsync(AVFrame* frame); virtual bool needsTestFrame(); diff --git a/app/streaming/video/ffmpeg-renderers/sdl.cpp b/app/streaming/video/ffmpeg-renderers/sdl.cpp index 92d4ab44..dee1901d 100644 --- a/app/streaming/video/ffmpeg-renderers/sdl.cpp +++ b/app/streaming/video/ffmpeg-renderers/sdl.cpp @@ -40,9 +40,21 @@ bool SdlRenderer::initialize(SDL_Window* window, int, int width, int height, - int) + int, + bool enableVsync) { - m_Renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + Uint32 rendererFlags = SDL_RENDERER_ACCELERATED; + + if ((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) { + // In full-screen exclusive mode, we enable V-sync if requested. For other modes, Windows and Mac + // have compositors that make rendering tear-free. Linux compositor varies by distro and user + // configuration but doesn't seem feasible to detect here. + if (enableVsync) { + rendererFlags |= SDL_RENDERER_PRESENTVSYNC; + } + } + + m_Renderer = SDL_CreateRenderer(window, -1, rendererFlags); if (!m_Renderer) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateRenderer() failed: %s", diff --git a/app/streaming/video/ffmpeg-renderers/vaapi.cpp b/app/streaming/video/ffmpeg-renderers/vaapi.cpp index 6b4e8afe..9793c5d7 100644 --- a/app/streaming/video/ffmpeg-renderers/vaapi.cpp +++ b/app/streaming/video/ffmpeg-renderers/vaapi.cpp @@ -27,7 +27,7 @@ VAAPIRenderer::~VAAPIRenderer() } bool -VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height, int) +VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height, int, bool) { int err; SDL_SysWMinfo info; diff --git a/app/streaming/video/ffmpeg-renderers/vaapi.h b/app/streaming/video/ffmpeg-renderers/vaapi.h index 13122c4c..5609e9cb 100644 --- a/app/streaming/video/ffmpeg-renderers/vaapi.h +++ b/app/streaming/video/ffmpeg-renderers/vaapi.h @@ -34,7 +34,8 @@ public: int videoFormat, int width, int height, - int maxFps); + int maxFps, + bool enableVsync); virtual bool prepareDecoderContext(AVCodecContext* context); virtual void renderFrameAtVsync(AVFrame* frame); virtual bool needsTestFrame(); diff --git a/app/streaming/video/ffmpeg-renderers/vdpau.cpp b/app/streaming/video/ffmpeg-renderers/vdpau.cpp index 996d62c2..da390b1e 100644 --- a/app/streaming/video/ffmpeg-renderers/vdpau.cpp +++ b/app/streaming/video/ffmpeg-renderers/vdpau.cpp @@ -54,7 +54,7 @@ VDPAURenderer::~VDPAURenderer() } } -bool VDPAURenderer::initialize(SDL_Window* window, int, int width, int height, int) +bool VDPAURenderer::initialize(SDL_Window* window, int, int width, int height, int, bool) { int err; VdpStatus status; diff --git a/app/streaming/video/ffmpeg-renderers/vdpau.h b/app/streaming/video/ffmpeg-renderers/vdpau.h index 56ead072..04282c6b 100644 --- a/app/streaming/video/ffmpeg-renderers/vdpau.h +++ b/app/streaming/video/ffmpeg-renderers/vdpau.h @@ -17,7 +17,8 @@ public: int videoFormat, int width, int height, - int maxFps); + int maxFps, + bool enableVsync); virtual bool prepareDecoderContext(AVCodecContext* context); virtual void renderFrameAtVsync(AVFrame* frame); virtual bool needsTestFrame(); diff --git a/app/streaming/video/ffmpeg-renderers/vt.mm b/app/streaming/video/ffmpeg-renderers/vt.mm index aa0e55e0..7962ebbf 100644 --- a/app/streaming/video/ffmpeg-renderers/vt.mm +++ b/app/streaming/video/ffmpeg-renderers/vt.mm @@ -101,7 +101,8 @@ public: int videoFormat, int, int, - int) override + int, + bool) override { int err; diff --git a/app/streaming/video/ffmpeg.cpp b/app/streaming/video/ffmpeg.cpp index 3e541ff3..018a3e10 100644 --- a/app/streaming/video/ffmpeg.cpp +++ b/app/streaming/video/ffmpeg.cpp @@ -108,10 +108,10 @@ void FFmpegVideoDecoder::reset() bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* window, int videoFormat, int width, int height, - int maxFps, bool testOnly) + int maxFps, bool enableVsync, bool testOnly) { m_Pacer = new Pacer(m_Renderer); - if (!m_Pacer->initialize(window, maxFps)) { + if (!m_Pacer->initialize(window, maxFps, enableVsync)) { return false; } @@ -229,7 +229,8 @@ bool FFmpegVideoDecoder::initialize( int videoFormat, int width, int height, - int maxFps) + int maxFps, + bool enableVsync) { AVCodec* decoder; @@ -264,8 +265,8 @@ bool FFmpegVideoDecoder::initialize( m_HwDecodeCfg = nullptr; m_Renderer = new SdlRenderer(); if (vds != StreamingPreferences::VDS_FORCE_HARDWARE && - m_Renderer->initialize(window, videoFormat, width, height, maxFps) && - completeInitialization(decoder, window, videoFormat, width, height, maxFps, false)) { + m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) && + completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableVsync, false)) { return true; } else { @@ -281,14 +282,14 @@ bool FFmpegVideoDecoder::initialize( m_HwDecodeCfg = config; // Initialize the hardware codec and submit a test frame if the renderer needs it - if (m_Renderer->initialize(window, videoFormat, width, height, maxFps) && - completeInitialization(decoder, window, videoFormat, width, height, maxFps, m_Renderer->needsTestFrame())) { + if (m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) && + completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableVsync, m_Renderer->needsTestFrame())) { if (m_Renderer->needsTestFrame()) { // The test worked, so now let's initialize it for real reset(); if ((m_Renderer = createAcceleratedRenderer(config)) != nullptr && - m_Renderer->initialize(window, videoFormat, width, height, maxFps) && - completeInitialization(decoder, window, videoFormat, width, height, maxFps, false)) { + m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) && + completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableVsync, false)) { return true; } else { diff --git a/app/streaming/video/ffmpeg.h b/app/streaming/video/ffmpeg.h index 009f91fb..dc692a3a 100644 --- a/app/streaming/video/ffmpeg.h +++ b/app/streaming/video/ffmpeg.h @@ -17,7 +17,8 @@ public: int videoFormat, int width, int height, - int maxFps) override; + int maxFps, + bool enableVsync) override; virtual bool isHardwareAccelerated() override; virtual int submitDecodeUnit(PDECODE_UNIT du) override; virtual void renderFrame(SDL_UserEvent* event) override; @@ -28,7 +29,7 @@ public: private: bool completeInitialization(AVCodec* decoder, SDL_Window* window, int videoFormat, int width, int height, - int maxFps, bool testOnly); + int maxFps, bool enableVsync, bool testOnly); IFFmpegRenderer* createAcceleratedRenderer(const AVCodecHWConfig* hwDecodeCfg); diff --git a/app/streaming/video/sl.cpp b/app/streaming/video/sl.cpp index c187e9dd..a7b051bb 100644 --- a/app/streaming/video/sl.cpp +++ b/app/streaming/video/sl.cpp @@ -28,7 +28,7 @@ SLVideoDecoder::isHardwareAccelerated() bool SLVideoDecoder::initialize(StreamingPreferences::VideoDecoderSelection vds, SDL_Window*, - int videoFormat, int, int, int frameRate) + int videoFormat, int, int, int frameRate, bool) { // SLVideo only supports hardware decoding if (vds == StreamingPreferences::VDS_FORCE_SOFTWARE) { diff --git a/app/streaming/video/sl.h b/app/streaming/video/sl.h index a1cc1af4..810f340b 100644 --- a/app/streaming/video/sl.h +++ b/app/streaming/video/sl.h @@ -14,7 +14,8 @@ public: int videoFormat, int width, int height, - int frameRate); + int frameRate, + bool enableVsync); virtual bool isHardwareAccelerated(); virtual int submitDecodeUnit(PDECODE_UNIT du);