Skip to content

Commit

Permalink
Refactor Pacer to handle both blocking and non-blocking VsyncSources
Browse files Browse the repository at this point in the history
  • Loading branch information
cgutman committed Nov 12, 2022
1 parent 8e3e19a commit 6ae6218
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 131 deletions.
178 changes: 76 additions & 102 deletions app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,27 @@

DxVsyncSource::DxVsyncSource(Pacer* pacer) :
m_Pacer(pacer),
m_Thread(nullptr),
m_Gdi32Handle(nullptr)
m_Gdi32Handle(nullptr),
m_LastMonitor(nullptr)
{
SDL_AtomicSet(&m_Stopping, 0);
SDL_zero(m_WaitForVblankEventParams);
}

DxVsyncSource::~DxVsyncSource()
{
if (m_Thread != nullptr) {
SDL_AtomicSet(&m_Stopping, 1);
SDL_WaitThread(m_Thread, nullptr);
if (m_WaitForVblankEventParams.hAdapter != 0) {
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
closeAdapterParams.hAdapter = m_WaitForVblankEventParams.hAdapter;
m_D3DKMTCloseAdapter(&closeAdapterParams);
}

if (m_Gdi32Handle != nullptr) {
FreeLibrary(m_Gdi32Handle);
}
}

bool DxVsyncSource::initialize(SDL_Window* window, int displayFps)
bool DxVsyncSource::initialize(SDL_Window* window, int)
{
m_DisplayFps = displayFps;

m_Gdi32Handle = LoadLibraryA("gdi32.dll");
if (m_Gdi32Handle == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
Expand Down Expand Up @@ -59,114 +58,89 @@ bool DxVsyncSource::initialize(SDL_Window* window, int displayFps)
return false;
}

m_Window = info.info.win.window;
// Pacer should only create us on Win32
SDL_assert(info.subsystem == SDL_SYSWM_WINDOWS);

m_Thread = SDL_CreateThread(vsyncThread, "DXVsync", this);
if (m_Thread == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Unable to create DX V-sync thread: %s",
SDL_GetError());
return false;
}
m_Window = info.info.win.window;

return true;
}

int DxVsyncSource::vsyncThread(void* context)
bool DxVsyncSource::isAsync()
{
// We wait in the context of the Pacer thread
return false;
}

void DxVsyncSource::waitForVsync()
{
DxVsyncSource* me = reinterpret_cast<DxVsyncSource*>(context);

#if SDL_VERSION_ATLEAST(2, 0, 9)
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL);
#else
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
#endif

D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {};
HMONITOR lastMonitor = nullptr;
DEVMODEA monitorMode;
monitorMode.dmSize = sizeof(monitorMode);

while (SDL_AtomicGet(&me->m_Stopping) == 0) {
D3DKMT_WAITFORVERTICALBLANKEVENT waitForVblankEventParams;
NTSTATUS status;

// If the monitor has changed from last time, open the new adapter
HMONITOR currentMonitor = MonitorFromWindow(me->m_Window, MONITOR_DEFAULTTONEAREST);
if (currentMonitor != lastMonitor) {
MONITORINFOEXA monitorInfo = {};
monitorInfo.cbSize = sizeof(monitorInfo);
if (!GetMonitorInfoA(currentMonitor, &monitorInfo)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"GetMonitorInfo() failed: %d",
GetLastError());
SDL_Delay(10);
continue;
}

if (!EnumDisplaySettingsA(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &monitorMode)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"EnumDisplaySettings() failed: %d",
GetLastError());
SDL_Delay(10);
continue;
}

SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Monitor changed: %s %d Hz",
monitorInfo.szDevice,
monitorMode.dmDisplayFrequency);

if (openAdapterParams.hAdapter != 0) {
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
closeAdapterParams.hAdapter = openAdapterParams.hAdapter;
me->m_D3DKMTCloseAdapter(&closeAdapterParams);
}

openAdapterParams.hDc = CreateDCA(nullptr, monitorInfo.szDevice, nullptr, nullptr);
if (!openAdapterParams.hDc) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"CreateDC() failed: %d",
GetLastError());
SDL_Delay(10);
continue;
}

status = me->m_D3DKMTOpenAdapterFromHdc(&openAdapterParams);
DeleteDC(openAdapterParams.hDc);

if (status != STATUS_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"D3DKMTOpenAdapterFromHdc() failed: %x",
status);
SDL_Delay(10);
continue;
}

lastMonitor = currentMonitor;
NTSTATUS status;

// If the monitor has changed from last time, open the new adapter
HMONITOR currentMonitor = MonitorFromWindow(m_Window, MONITOR_DEFAULTTONEAREST);
if (currentMonitor != m_LastMonitor) {
MONITORINFOEXA monitorInfo = {};
monitorInfo.cbSize = sizeof(monitorInfo);
if (!GetMonitorInfoA(currentMonitor, &monitorInfo)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"GetMonitorInfo() failed: %d",
GetLastError());
return;
}

waitForVblankEventParams.hAdapter = openAdapterParams.hAdapter;
waitForVblankEventParams.hDevice = 0;
waitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId;
DEVMODEA monitorMode;
monitorMode.dmSize = sizeof(monitorMode);
if (!EnumDisplaySettingsA(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &monitorMode)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"EnumDisplaySettings() failed: %d",
GetLastError());
return;
}

SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Monitor changed: %s %d Hz",
monitorInfo.szDevice,
monitorMode.dmDisplayFrequency);

// Close the old adapter
if (m_WaitForVblankEventParams.hAdapter != 0) {
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
closeAdapterParams.hAdapter = m_WaitForVblankEventParams.hAdapter;
m_D3DKMTCloseAdapter(&closeAdapterParams);
}

D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {};
openAdapterParams.hDc = CreateDCA(nullptr, monitorInfo.szDevice, nullptr, nullptr);
if (!openAdapterParams.hDc) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"CreateDC() failed: %d",
GetLastError());
return;
}

// Open the new adapter
status = m_D3DKMTOpenAdapterFromHdc(&openAdapterParams);
DeleteDC(openAdapterParams.hDc);

status = me->m_D3DKMTWaitForVerticalBlankEvent(&waitForVblankEventParams);
if (status != STATUS_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"D3DKMTWaitForVerticalBlankEvent() failed: %x",
"D3DKMTOpenAdapterFromHdc() failed: %x",
status);
SDL_Delay(10);
continue;
return;
}

me->m_Pacer->vsyncCallback(1000 / me->m_DisplayFps);
}
m_WaitForVblankEventParams.hAdapter = openAdapterParams.hAdapter;
m_WaitForVblankEventParams.hDevice = 0;
m_WaitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId;

if (openAdapterParams.hAdapter != 0) {
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
closeAdapterParams.hAdapter = openAdapterParams.hAdapter;
me->m_D3DKMTCloseAdapter(&closeAdapterParams);
m_LastMonitor = currentMonitor;
}

return 0;
status = m_D3DKMTWaitForVerticalBlankEvent(&m_WaitForVblankEventParams);
if (status != STATUS_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"D3DKMTWaitForVerticalBlankEvent() failed: %x",
status);
return;
}
}
13 changes: 7 additions & 6 deletions app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,18 @@ class DxVsyncSource : public IVsyncSource

virtual ~DxVsyncSource();

virtual bool initialize(SDL_Window* window, int displayFps);
virtual bool initialize(SDL_Window* window, int) override;

private:
static int vsyncThread(void* context);
virtual bool isAsync() override;

virtual void waitForVsync() override;

private:
Pacer* m_Pacer;
SDL_Thread* m_Thread;
SDL_atomic_t m_Stopping;
HMODULE m_Gdi32Handle;
HWND m_Window;
int m_DisplayFps;
HMONITOR m_LastMonitor;
D3DKMT_WAITFORVERTICALBLANKEVENT m_WaitForVblankEventParams;

PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc;
PFND3DKMTCLOSEADAPTER m_D3DKMTCloseAdapter;
Expand Down
61 changes: 57 additions & 4 deletions app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

Pacer::Pacer(IFFmpegRenderer* renderer, PVIDEO_STATS videoStats) :
m_RenderThread(nullptr),
m_VsyncThread(nullptr),
m_Stopping(false),
m_VsyncSource(nullptr),
m_VsyncRenderer(renderer),
Expand All @@ -42,12 +43,19 @@ Pacer::Pacer(IFFmpegRenderer* renderer, PVIDEO_STATS videoStats) :

Pacer::~Pacer()
{
m_Stopping = true;

// Stop the V-sync thread
if (m_VsyncThread != nullptr) {
m_VsyncSignalled.wakeAll();
SDL_WaitThread(m_VsyncThread, nullptr);
}

// Stop V-sync callbacks
delete m_VsyncSource;
m_VsyncSource = nullptr;

// Stop the render thread
m_Stopping = true;
if (m_RenderThread != nullptr) {
m_RenderQueueNotEmpty.wakeAll();
SDL_WaitThread(m_RenderThread, nullptr);
Expand Down Expand Up @@ -89,6 +97,39 @@ void Pacer::renderOnMainThread()
}
}

int Pacer::vsyncThread(void *context)
{
Pacer* me = reinterpret_cast<Pacer*>(context);

#if SDL_VERSION_ATLEAST(2, 0, 9)
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL);
#else
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
#endif

bool async = me->m_VsyncSource->isAsync();
while (!me->m_Stopping) {
if (async) {
// Wait for the VSync source to invoke signalVsync() or 100ms to elapse
me->m_FrameQueueLock.lock();
me->m_VsyncSignalled.wait(&me->m_FrameQueueLock, 100);
me->m_FrameQueueLock.unlock();
}
else {
// Let the VSync source wait in the context of our thread
me->m_VsyncSource->waitForVsync();
}

if (me->m_Stopping) {
break;
}

me->handleVsync(1000 / me->m_DisplayFps);
}

return 0;
}

int Pacer::renderThread(void* context)
{
Pacer* me = reinterpret_cast<Pacer*>(context);
Expand Down Expand Up @@ -153,7 +194,7 @@ void Pacer::enqueueFrameForRenderingAndUnlock(AVFrame *frame)

// Called in an arbitrary thread by the IVsyncSource on V-sync
// or an event synchronized with V-sync
void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
void Pacer::handleVsync(int timeUntilNextVsyncMillis)
{
// Make sure initialize() has been called
SDL_assert(m_MaxVideoFps != 0);
Expand Down Expand Up @@ -217,7 +258,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)

if (enablePacing) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Frame pacing active: target %d Hz with %d FPS stream",
"Frame pacing: target %d Hz with %d FPS stream",
m_DisplayFps, m_MaxVideoFps);

SDL_SysWMinfo info;
Expand Down Expand Up @@ -255,7 +296,10 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
SDL_assert(m_VsyncSource != nullptr || !(m_RendererAttributes & RENDERER_ATTRIBUTE_FORCE_PACING));

if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window, m_DisplayFps)) {
return false;
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Vsync source failed to initialize. Frame pacing will not be available!");
delete m_VsyncSource;
m_VsyncSource = nullptr;
}
}
else {
Expand All @@ -264,13 +308,22 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
m_DisplayFps, m_MaxVideoFps);
}

if (m_VsyncSource != nullptr) {
m_VsyncThread = SDL_CreateThread(Pacer::vsyncThread, "PacerVsync", this);
}

if (m_VsyncRenderer->isRenderThreadSupported()) {
m_RenderThread = SDL_CreateThread(Pacer::renderThread, "PacerRender", this);
}

return true;
}

void Pacer::signalVsync()
{
m_VsyncSignalled.wakeOne();
}

void Pacer::renderFrame(AVFrame* frame)
{
// Count time spent in Pacer's queues
Expand Down
Loading

0 comments on commit 6ae6218

Please sign in to comment.