Allow Pacer to wait for frames up until a few ms before v-sync for better smoothness and lower latency

This commit is contained in:
Cameron Gutman
2018-08-20 17:53:35 -07:00
parent d6e7173af0
commit 6b395c816f
8 changed files with 49 additions and 14 deletions
@@ -34,13 +34,15 @@ DisplayLinkVsyncSource::displayLinkOutputCallback(
// interval, even if many ms prior, we can safely use the current host time // interval, even if many ms prior, we can safely use the current host time
// and get a consistent callback for each v-sync. This reduces video latency // and get a consistent callback for each v-sync. This reduces video latency
// by at least 1 frame vs. rendering with the actual vsyncTime. // by at least 1 frame vs. rendering with the actual vsyncTime.
me->m_Pacer->vsyncCallback(); me->m_Pacer->vsyncCallback(500 / m_DisplayFps);
return kCVReturnSuccess; return kCVReturnSuccess;
} }
bool DisplayLinkVsyncSource::initialize(SDL_Window*) bool DisplayLinkVsyncSource::initialize(SDL_Window*, int displayFps)
{ {
m_DisplayFps = displayFps;
CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink); CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this); CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
CVDisplayLinkStart(m_DisplayLink); CVDisplayLinkStart(m_DisplayLink);
@@ -11,7 +11,7 @@ public:
virtual ~DisplayLinkVsyncSource(); virtual ~DisplayLinkVsyncSource();
virtual bool initialize(SDL_Window* window); virtual bool initialize(SDL_Window* window, int displayFps);
private: private:
static static
@@ -26,5 +26,6 @@ private:
Pacer* m_Pacer; Pacer* m_Pacer;
CVDisplayLinkRef m_DisplayLink; CVDisplayLinkRef m_DisplayLink;
int m_DisplayFps;
}; };
@@ -24,8 +24,10 @@ DxVsyncSource::~DxVsyncSource()
} }
} }
bool DxVsyncSource::initialize(SDL_Window* window) bool DxVsyncSource::initialize(SDL_Window* window, int displayFps)
{ {
m_DisplayFps = displayFps;
m_Gdi32Handle = LoadLibraryA("gdi32.dll"); m_Gdi32Handle = LoadLibraryA("gdi32.dll");
if (m_Gdi32Handle == nullptr) { if (m_Gdi32Handle == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@@ -144,8 +146,6 @@ int DxVsyncSource::vsyncThread(void* context)
waitForVblankEventParams.hDevice = 0; waitForVblankEventParams.hDevice = 0;
waitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId; waitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId;
me->m_Pacer->vsyncCallback();
status = me->m_D3DKMTWaitForVerticalBlankEvent(&waitForVblankEventParams); status = me->m_D3DKMTWaitForVerticalBlankEvent(&waitForVblankEventParams);
if (status != STATUS_SUCCESS) { if (status != STATUS_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@@ -154,6 +154,8 @@ int DxVsyncSource::vsyncThread(void* context)
SDL_Delay(10); SDL_Delay(10);
continue; continue;
} }
me->m_Pacer->vsyncCallback(1000 / me->m_DisplayFps);
} }
if (openAdapterParams.hAdapter != 0) { if (openAdapterParams.hAdapter != 0) {
@@ -35,7 +35,7 @@ public:
virtual ~DxVsyncSource(); virtual ~DxVsyncSource();
virtual bool initialize(SDL_Window* window); virtual bool initialize(SDL_Window* window, int displayFps);
private: private:
static int vsyncThread(void* context); static int vsyncThread(void* context);
@@ -45,6 +45,7 @@ private:
SDL_atomic_t m_Stopping; SDL_atomic_t m_Stopping;
HMODULE m_Gdi32Handle; HMODULE m_Gdi32Handle;
HWND m_Window; HWND m_Window;
int m_DisplayFps;
PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc; PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc;
PFND3DKMTCLOSEADAPTER m_D3DKMTCloseAdapter; PFND3DKMTCLOSEADAPTER m_D3DKMTCloseAdapter;
@@ -15,8 +15,9 @@ NullThreadedVsyncSource::~NullThreadedVsyncSource()
} }
} }
bool NullThreadedVsyncSource::initialize(SDL_Window*) bool NullThreadedVsyncSource::initialize(SDL_Window*, int displayFps)
{ {
m_DisplayFps = displayFps;
m_Thread = SDL_CreateThread(vsyncThread, "Null Vsync Thread", this); m_Thread = SDL_CreateThread(vsyncThread, "Null Vsync Thread", this);
if (m_Thread == nullptr) { if (m_Thread == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@@ -35,7 +36,7 @@ int NullThreadedVsyncSource::vsyncThread(void* context)
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
while (SDL_AtomicGet(&me->m_Stopping) == 0) { while (SDL_AtomicGet(&me->m_Stopping) == 0) {
me->m_Pacer->vsyncCallback(); me->m_Pacer->vsyncCallback(1000 / me->m_DisplayFps);
} }
return 0; return 0;
@@ -9,7 +9,7 @@ public:
virtual ~NullThreadedVsyncSource(); virtual ~NullThreadedVsyncSource();
virtual bool initialize(SDL_Window* window); virtual bool initialize(SDL_Window* window, int displayFps);
private: private:
static int vsyncThread(void* context); static int vsyncThread(void* context);
@@ -17,4 +17,5 @@ private:
Pacer* m_Pacer; Pacer* m_Pacer;
SDL_Thread* m_Thread; SDL_Thread* m_Thread;
SDL_atomic_t m_Stopping; SDL_atomic_t m_Stopping;
int m_DisplayFps;
}; };
@@ -1,5 +1,7 @@
#include "pacer.h" #include "pacer.h"
#include "nullthreadedvsyncsource.h"
#ifdef Q_OS_DARWIN #ifdef Q_OS_DARWIN
#include "displaylinkvsyncsource.h" #include "displaylinkvsyncsource.h"
#endif #endif
@@ -10,6 +12,13 @@
#define FRAME_HISTORY_ENTRIES 8 #define FRAME_HISTORY_ENTRIES 8
// We may be woken up slightly late so don't go all the way
// up to the next V-sync since we may accidentally step into
// the next V-sync period. It also takes some amount of time
// to do the render itself, so we can't render right before
// V-sync happens.
#define TIMER_SLACK_MS 3
Pacer::Pacer(IFFmpegRenderer* renderer) : Pacer::Pacer(IFFmpegRenderer* renderer) :
m_FrameQueueLock(0), m_FrameQueueLock(0),
m_VsyncSource(nullptr), m_VsyncSource(nullptr),
@@ -34,11 +43,15 @@ Pacer::~Pacer()
// Called in an arbitrary thread by the IVsyncSource on V-sync // Called in an arbitrary thread by the IVsyncSource on V-sync
// or an event synchronized with V-sync // or an event synchronized with V-sync
void Pacer::vsyncCallback() void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
{ {
// Make sure initialize() has been called // Make sure initialize() has been called
SDL_assert(m_MaxVideoFps != 0); SDL_assert(m_MaxVideoFps != 0);
SDL_assert(timeUntilNextVsyncMillis >= TIMER_SLACK_MS);
Uint32 vsyncCallbackStartTime = SDL_GetTicks();
SDL_AtomicLock(&m_FrameQueueLock); SDL_AtomicLock(&m_FrameQueueLock);
// If the queue length history entries are large, be strict // If the queue length history entries are large, be strict
@@ -71,13 +84,27 @@ void Pacer::vsyncCallback()
av_frame_free(&frame); av_frame_free(&frame);
} }
if (m_FrameQueue.isEmpty()) { if (m_FrameQueue.isEmpty()) {
SDL_AtomicUnlock(&m_FrameQueueLock); SDL_AtomicUnlock(&m_FrameQueueLock);
while (!SDL_TICKS_PASSED(SDL_GetTicks(),
vsyncCallbackStartTime + timeUntilNextVsyncMillis - TIMER_SLACK_MS)) {
SDL_Delay(1);
SDL_AtomicLock(&m_FrameQueueLock);
if (!m_FrameQueue.isEmpty()) {
// Don't release the lock
goto RenderNextFrame;
}
SDL_AtomicUnlock(&m_FrameQueueLock);
}
// Nothing to render at this time // Nothing to render at this time
return; return;
} }
RenderNextFrame:
// Grab the first frame // Grab the first frame
AVFrame* frame = m_FrameQueue.dequeue(); AVFrame* frame = m_FrameQueue.dequeue();
SDL_AtomicUnlock(&m_FrameQueueLock); SDL_AtomicUnlock(&m_FrameQueueLock);
@@ -122,7 +149,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps)
// immediately like they used to. // immediately like they used to.
#endif #endif
if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window)) { if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window, m_DisplayFps)) {
return false; return false;
} }
@@ -7,7 +7,7 @@
class IVsyncSource { class IVsyncSource {
public: public:
virtual ~IVsyncSource() {} virtual ~IVsyncSource() {}
virtual bool initialize(SDL_Window* window) = 0; virtual bool initialize(SDL_Window* window, int displayFps) = 0;
}; };
class Pacer class Pacer
@@ -21,7 +21,7 @@ public:
bool initialize(SDL_Window* window, int maxVideoFps); bool initialize(SDL_Window* window, int maxVideoFps);
void vsyncCallback(); void vsyncCallback(int timeUntilNextVsyncMillis);
bool isUsingFrameQueue(); bool isUsingFrameQueue();