diff --git a/app/streaming/video/ffmpeg-renderers/d3d11va.cpp b/app/streaming/video/ffmpeg-renderers/d3d11va.cpp index a37b4c2a..45aeed0f 100644 --- a/app/streaming/video/ffmpeg-renderers/d3d11va.cpp +++ b/app/streaming/video/ffmpeg-renderers/d3d11va.cpp @@ -581,8 +581,8 @@ void D3D11VARenderer::renderFrame(AVFrame* frame) } else { // Otherwise, we'll submit as fast as possible and DWM will discard excess - // frames for us. If frame pacing is also enabled, our Vsync source will keep - // us in sync with VBlank. + // frames for us. If frame pacing is also enabled or we're in full-screen, + // our Vsync source will keep us in sync with VBlank. flags = 0; } @@ -993,8 +993,19 @@ bool D3D11VARenderer::checkDecoderSupport(IDXGIAdapter* adapter) int D3D11VARenderer::getRendererAttributes() { + int attributes = 0; + // This renderer supports HDR - return RENDERER_ATTRIBUTE_HDR_SUPPORT; + attributes |= RENDERER_ATTRIBUTE_HDR_SUPPORT; + + // This renderer requires frame pacing to synchronize with VBlank when we're + // in full-screen. In windowed mode, we will render as fast we can and DWM + // will grab whatever is latest at the time unless the user opts for pacing. + if (SDL_GetWindowFlags(m_DecoderParams.window) & SDL_WINDOW_FULLSCREEN) { + attributes |= RENDERER_ATTRIBUTE_FORCE_PACING; + } + + return attributes; } bool D3D11VARenderer::needsTestFrame() diff --git a/app/streaming/video/ffmpeg-renderers/drm.cpp b/app/streaming/video/ffmpeg-renderers/drm.cpp index 5100d0e8..f4b0e688 100644 --- a/app/streaming/video/ffmpeg-renderers/drm.cpp +++ b/app/streaming/video/ffmpeg-renderers/drm.cpp @@ -443,6 +443,9 @@ int DrmRenderer::getRendererAttributes() // This renderer supports HDR attributes |= RENDERER_ATTRIBUTE_HDR_SUPPORT; + // This renderer does not buffer any frames in the graphics pipeline + attributes |= RENDERER_ATTRIBUTE_NO_BUFFERING; + return attributes; } diff --git a/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp b/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp index 84b247bd..721d8293 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp +++ b/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp @@ -208,6 +208,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing) { m_MaxVideoFps = maxVideoFps; m_DisplayFps = StreamUtils::getDisplayRefreshRate(window); + m_RendererAttributes = m_VsyncRenderer->getRendererAttributes(); if (enablePacing) { SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, @@ -225,6 +226,8 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing) // immediately like they used to. #endif + SDL_assert(m_VsyncSource != nullptr || !(m_RendererAttributes & RENDERER_ATTRIBUTE_FORCE_PACING)); + if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window, m_DisplayFps)) { return false; } @@ -259,22 +262,31 @@ void Pacer::renderFrame(AVFrame* frame) // Drop frames if we have too many queued up for a while m_FrameQueueLock.lock(); - int frameDropTarget = 0; - for (int queueHistoryEntry : m_RenderQueueHistory) { - if (queueHistoryEntry == 0) { - // Be lenient as long as the queue length - // resolves before the end of frame history - frameDropTarget = 2; - break; + int frameDropTarget; + + if (m_RendererAttributes & RENDERER_ATTRIBUTE_NO_BUFFERING) { + // Renderers that don't buffer any frames but don't support waitToRender() need us to buffer + // an extra frame to ensure they don't starve while waiting to present. + frameDropTarget = 1; + } + else { + frameDropTarget = 0; + for (int queueHistoryEntry : m_RenderQueueHistory) { + if (queueHistoryEntry == 0) { + // Be lenient as long as the queue length + // resolves before the end of frame history + frameDropTarget = 2; + break; + } } - } - // Keep a rolling 500 ms window of render queue history - if (m_RenderQueueHistory.count() == m_MaxVideoFps / 2) { - m_RenderQueueHistory.dequeue(); - } + // Keep a rolling 500 ms window of render queue history + if (m_RenderQueueHistory.count() == m_MaxVideoFps / 2) { + m_RenderQueueHistory.dequeue(); + } - m_RenderQueueHistory.enqueue(m_RenderQueue.count()); + m_RenderQueueHistory.enqueue(m_RenderQueue.count()); + } // Catch up if we're several frames ahead while (m_RenderQueue.count() > frameDropTarget) { diff --git a/app/streaming/video/ffmpeg-renderers/pacer/pacer.h b/app/streaming/video/ffmpeg-renderers/pacer/pacer.h index ed1c4a39..013cfe5e 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/pacer.h +++ b/app/streaming/video/ffmpeg-renderers/pacer/pacer.h @@ -52,4 +52,5 @@ private: int m_MaxVideoFps; int m_DisplayFps; PVIDEO_STATS m_VideoStats; + int m_RendererAttributes; }; diff --git a/app/streaming/video/ffmpeg-renderers/renderer.h b/app/streaming/video/ffmpeg-renderers/renderer.h index 244496b4..cfae0b0d 100644 --- a/app/streaming/video/ffmpeg-renderers/renderer.h +++ b/app/streaming/video/ffmpeg-renderers/renderer.h @@ -110,7 +110,8 @@ private: #define RENDERER_ATTRIBUTE_FULLSCREEN_ONLY 0x01 #define RENDERER_ATTRIBUTE_1080P_MAX 0x02 #define RENDERER_ATTRIBUTE_HDR_SUPPORT 0x04 -#define RENDERER_ATTRIBUTE_SELF_PACING 0x08 +#define RENDERER_ATTRIBUTE_NO_BUFFERING 0x08 +#define RENDERER_ATTRIBUTE_FORCE_PACING 0x10 class IFFmpegRenderer : public Overlay::IOverlayRenderer { public: diff --git a/app/streaming/video/ffmpeg.cpp b/app/streaming/video/ffmpeg.cpp index 82d1ef55..f28bb403 100644 --- a/app/streaming/video/ffmpeg.cpp +++ b/app/streaming/video/ffmpeg.cpp @@ -290,7 +290,8 @@ bool FFmpegVideoDecoder::completeInitialization(const AVCodec* decoder, PDECODER // Don't bother initializing Pacer if we're not actually going to render if (!testFrame) { m_Pacer = new Pacer(m_FrontendRenderer, &m_ActiveWndVideoStats); - if (!m_Pacer->initialize(params->window, params->frameRate, params->enableFramePacing)) { + if (!m_Pacer->initialize(params->window, params->frameRate, + params->enableFramePacing || (params->enableVsync && (m_FrontendRenderer->getRendererAttributes() & RENDERER_ATTRIBUTE_FORCE_PACING)))) { return false; } }