Use a separate render thread even with a V-Sync source

This commit is contained in:
Cameron Gutman 2019-02-15 22:09:50 -08:00
parent 81c6202582
commit f03d12b677
2 changed files with 49 additions and 29 deletions

View File

@ -8,10 +8,13 @@
#endif #endif
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <VersionHelpers.h>
#include "dxvsyncsource.h" #include "dxvsyncsource.h"
#endif #endif
#define FRAME_HISTORY_ENTRIES 8 #define PACING_HISTORY_ENTRIES 8
#define RENDER_TIME_HISTORY_ENTRIES 20 #define RENDER_TIME_HISTORY_ENTRIES 20
// We may be woken up slightly late so don't go all the way // We may be woken up slightly late so don't go all the way
@ -42,13 +45,17 @@ Pacer::~Pacer()
// Stop the render thread // Stop the render thread
m_Stopping = true; m_Stopping = true;
if (m_RenderThread != nullptr) { if (m_RenderThread != nullptr) {
m_FrameQueueNotEmpty.wakeAll(); m_RenderQueueNotEmpty.wakeAll();
SDL_WaitThread(m_RenderThread, nullptr); SDL_WaitThread(m_RenderThread, nullptr);
} }
// Delete any remaining unconsumed frames // Delete any remaining unconsumed frames
while (!m_FrameQueue.isEmpty()) { while (!m_RenderQueue.isEmpty()) {
AVFrame* frame = m_FrameQueue.dequeue(); AVFrame* frame = m_RenderQueue.dequeue();
av_frame_free(&frame);
}
while (!m_PacingQueue.isEmpty()) {
AVFrame* frame = m_PacingQueue.dequeue();
av_frame_free(&frame); av_frame_free(&frame);
} }
} }
@ -63,8 +70,8 @@ int Pacer::renderThread(void* context)
me->m_FrameQueueLock.lock(); me->m_FrameQueueLock.lock();
// Wait for a frame to be ready to render // Wait for a frame to be ready to render
while (!me->m_Stopping && me->m_FrameQueue.isEmpty()) { while (!me->m_Stopping && me->m_RenderQueue.isEmpty()) {
me->m_FrameQueueNotEmpty.wait(&me->m_FrameQueueLock); me->m_RenderQueueNotEmpty.wait(&me->m_FrameQueueLock);
} }
if (me->m_Stopping) { if (me->m_Stopping) {
@ -76,7 +83,7 @@ int Pacer::renderThread(void* context)
// Dequeue the most recent frame for rendering and free the others. // Dequeue the most recent frame for rendering and free the others.
// NB: m_FrameQueueLock still held here! // NB: m_FrameQueueLock still held here!
AVFrame* lastFrame = nullptr; AVFrame* lastFrame = nullptr;
while (!me->m_FrameQueue.isEmpty()) { while (!me->m_RenderQueue.isEmpty()) {
if (lastFrame != nullptr) { if (lastFrame != nullptr) {
// Don't hold the frame queue lock across av_frame_free(), // Don't hold the frame queue lock across av_frame_free(),
// since it could need to talk to the GPU driver. This is safe // since it could need to talk to the GPU driver. This is safe
@ -88,7 +95,7 @@ int Pacer::renderThread(void* context)
me->m_FrameQueueLock.lock(); me->m_FrameQueueLock.lock();
} }
lastFrame = me->m_FrameQueue.dequeue(); lastFrame = me->m_RenderQueue.dequeue();
} }
// Release the frame queue lock before rendering // Release the frame queue lock before rendering
@ -120,7 +127,7 @@ void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
// frame history to drop frames only if consistently above the // frame history to drop frames only if consistently above the
// one queued frame mark. // one queued frame mark.
if (m_MaxVideoFps >= m_DisplayFps) { if (m_MaxVideoFps >= m_DisplayFps) {
for (int queueHistoryEntry : m_FrameQueueHistory) { for (int queueHistoryEntry : m_PacingQueueHistory) {
if (queueHistoryEntry <= 1) { if (queueHistoryEntry <= 1) {
// Be lenient as long as the queue length // Be lenient as long as the queue length
// resolves before the end of frame history // resolves before the end of frame history
@ -129,16 +136,16 @@ void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
} }
} }
if (m_FrameQueueHistory.count() == FRAME_HISTORY_ENTRIES) { if (m_PacingQueueHistory.count() == PACING_HISTORY_ENTRIES) {
m_FrameQueueHistory.dequeue(); m_PacingQueueHistory.dequeue();
} }
m_FrameQueueHistory.enqueue(m_FrameQueue.count()); m_PacingQueueHistory.enqueue(m_PacingQueue.count());
} }
// Catch up if we're several frames ahead // Catch up if we're several frames ahead
while (m_FrameQueue.count() > frameDropTarget) { while (m_PacingQueue.count() > frameDropTarget) {
AVFrame* frame = m_FrameQueue.dequeue(); AVFrame* frame = m_PacingQueue.dequeue();
// Drop the lock while we call av_frame_free() // Drop the lock while we call av_frame_free()
m_FrameQueueLock.unlock(); m_FrameQueueLock.unlock();
@ -147,9 +154,9 @@ void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
m_FrameQueueLock.lock(); m_FrameQueueLock.lock();
} }
if (m_FrameQueue.isEmpty()) { if (m_PacingQueue.isEmpty()) {
// Wait for a frame to arrive or our V-sync timeout to expire // Wait for a frame to arrive or our V-sync timeout to expire
if (!m_FrameQueueNotEmpty.wait(&m_FrameQueueLock, timeUntilNextVsyncMillis - TIMER_SLACK_MS)) { if (!m_PacingQueueNotEmpty.wait(&m_FrameQueueLock, timeUntilNextVsyncMillis - TIMER_SLACK_MS)) {
// Wait timed out - unlock and bail // Wait timed out - unlock and bail
m_FrameQueueLock.unlock(); m_FrameQueueLock.unlock();
@ -159,11 +166,10 @@ void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
} }
} }
// Grab the first frame // Place the first frame on the render queue
AVFrame* frame = m_FrameQueue.dequeue(); m_RenderQueue.enqueue(m_PacingQueue.dequeue());
m_FrameQueueLock.unlock(); m_FrameQueueLock.unlock();
m_RenderQueueNotEmpty.wakeOne();
renderFrame(frame);
} }
bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing) bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
@ -179,7 +185,11 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
#if defined(Q_OS_DARWIN) #if defined(Q_OS_DARWIN)
m_VsyncSource = DisplayLinkVsyncSourceFactory::createVsyncSource(this); m_VsyncSource = DisplayLinkVsyncSourceFactory::createVsyncSource(this);
#elif defined(Q_OS_WIN32) #elif defined(Q_OS_WIN32)
// Don't use D3DKMTWaitForVerticalBlankEvent() on Windows 7, because
// it blocks during other concurrent DX operations (like actually rendering).
if (IsWindows8OrGreater()) {
m_VsyncSource = new DxVsyncSource(this); m_VsyncSource = new DxVsyncSource(this);
}
#else #else
// Platforms without a VsyncSource will just render frames // Platforms without a VsyncSource will just render frames
// immediately like they used to. // immediately like they used to.
@ -195,9 +205,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
m_DisplayFps, m_MaxVideoFps); m_DisplayFps, m_MaxVideoFps);
} }
if (m_VsyncSource == nullptr) {
m_RenderThread = SDL_CreateThread(Pacer::renderThread, "Pacer Render Thread", this); m_RenderThread = SDL_CreateThread(Pacer::renderThread, "Pacer Render Thread", this);
}
return true; return true;
} }
@ -261,7 +269,17 @@ void Pacer::submitFrame(AVFrame* frame)
// Queue the frame and possibly wake up the render thread // Queue the frame and possibly wake up the render thread
m_FrameQueueLock.lock(); m_FrameQueueLock.lock();
m_FrameQueue.enqueue(frame); if (m_VsyncSource != nullptr) {
m_FrameQueueLock.unlock(); m_PacingQueue.enqueue(frame);
m_FrameQueueNotEmpty.wakeOne(); }
else {
m_RenderQueue.enqueue(frame);
}
m_FrameQueueLock.unlock();
if (m_VsyncSource != nullptr) {
m_PacingQueueNotEmpty.wakeOne();
}
else {
m_RenderQueueNotEmpty.wakeOne();
}
} }

View File

@ -33,11 +33,13 @@ private:
void renderFrame(AVFrame* frame); void renderFrame(AVFrame* frame);
QQueue<AVFrame*> m_FrameQueue; QQueue<AVFrame*> m_RenderQueue;
QQueue<int> m_FrameQueueHistory; QQueue<AVFrame*> m_PacingQueue;
QQueue<int> m_PacingQueueHistory;
QQueue<int> m_RenderTimeHistory; QQueue<int> m_RenderTimeHistory;
QMutex m_FrameQueueLock; QMutex m_FrameQueueLock;
QWaitCondition m_FrameQueueNotEmpty; QWaitCondition m_RenderQueueNotEmpty;
QWaitCondition m_PacingQueueNotEmpty;
SDL_Thread* m_RenderThread; SDL_Thread* m_RenderThread;
bool m_Stopping; bool m_Stopping;