mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-26 22:10:56 +00:00
Improve robustness and error handling of DXVsyncSource
This commit is contained in:
1 parent
e4db5c2c18
commit
9af0e9f539
4 files changed
+78
-48
No files matched your search
@@ -10,6 +10,8 @@ DxVsyncSource::DxVsyncSource(Pacer* pacer) :
|
|||||||
m_LastMonitor(nullptr)
|
m_LastMonitor(nullptr)
|
||||||
{
|
{
|
||||||
SDL_zero(m_WaitForVblankEventParams);
|
SDL_zero(m_WaitForVblankEventParams);
|
||||||
|
m_WaitForVblankEventParams.VidPnSourceId = D3DDDI_ID_UNINITIALIZED;
|
||||||
|
SDL_zero(m_AdapterLuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
DxVsyncSource::~DxVsyncSource()
|
DxVsyncSource::~DxVsyncSource()
|
||||||
@@ -72,41 +74,43 @@ bool DxVsyncSource::isAsync()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DxVsyncSource::waitForVsync()
|
bool DxVsyncSource::waitForVsync()
|
||||||
{
|
{
|
||||||
NTSTATUS status;
|
NTSTATUS status;
|
||||||
|
|
||||||
// If the monitor has changed from last time, open the new adapter
|
// If the monitor has changed from last time, open the new adapter
|
||||||
HMONITOR currentMonitor = MonitorFromWindow(m_Window, MONITOR_DEFAULTTONEAREST);
|
HMONITOR currentMonitor = MonitorFromWindow(m_Window, MONITOR_DEFAULTTONEAREST);
|
||||||
if (currentMonitor != m_LastMonitor) {
|
if (!currentMonitor || currentMonitor != m_LastMonitor) {
|
||||||
|
// Close the old adapter
|
||||||
|
if (m_WaitForVblankEventParams.hAdapter != 0) {
|
||||||
|
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
|
||||||
|
closeAdapterParams.hAdapter = m_WaitForVblankEventParams.hAdapter;
|
||||||
|
m_D3DKMTCloseAdapter(&closeAdapterParams);
|
||||||
|
m_WaitForVblankEventParams.hAdapter = 0;
|
||||||
|
}
|
||||||
|
m_LastMonitor = nullptr;
|
||||||
|
|
||||||
|
// We can't wait if we're not on any monitor
|
||||||
|
if (!currentMonitor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
MONITORINFOEXA monitorInfo = {};
|
MONITORINFOEXA monitorInfo = {};
|
||||||
monitorInfo.cbSize = sizeof(monitorInfo);
|
monitorInfo.cbSize = sizeof(monitorInfo);
|
||||||
if (!GetMonitorInfoA(currentMonitor, &monitorInfo)) {
|
if (!GetMonitorInfoA(currentMonitor, &monitorInfo)) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"GetMonitorInfo() failed: %d",
|
"GetMonitorInfo() failed: %d",
|
||||||
GetLastError());
|
GetLastError());
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DEVMODEA monitorMode;
|
DEVMODEA monitorMode = {};
|
||||||
monitorMode.dmSize = sizeof(monitorMode);
|
monitorMode.dmSize = sizeof(monitorMode);
|
||||||
if (!EnumDisplaySettingsA(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &monitorMode)) {
|
if (!EnumDisplaySettingsA(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &monitorMode)) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"EnumDisplaySettings() failed: %d",
|
"EnumDisplaySettings() failed: %d",
|
||||||
GetLastError());
|
GetLastError());
|
||||||
return;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
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 = {};
|
D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {};
|
||||||
@@ -115,7 +119,7 @@ void DxVsyncSource::waitForVsync()
|
|||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"CreateDC() failed: %d",
|
"CreateDC() failed: %d",
|
||||||
GetLastError());
|
GetLastError());
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the new adapter
|
// Open the new adapter
|
||||||
@@ -126,21 +130,38 @@ void DxVsyncSource::waitForVsync()
|
|||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"D3DKMTOpenAdapterFromHdc() failed: %x",
|
"D3DKMTOpenAdapterFromHdc() failed: %x",
|
||||||
status);
|
status);
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VidPnSourceId is scoped to the adapter, so we must also check the adapter LUID
|
||||||
|
if (m_AdapterLuid.LowPart != openAdapterParams.AdapterLuid.LowPart ||
|
||||||
|
m_AdapterLuid.HighPart != openAdapterParams.AdapterLuid.HighPart ||
|
||||||
|
m_WaitForVblankEventParams.VidPnSourceId != openAdapterParams.VidPnSourceId) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Monitor changed: %s %d Hz",
|
||||||
|
monitorInfo.szDevice,
|
||||||
|
monitorMode.dmDisplayFrequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_WaitForVblankEventParams.hAdapter = openAdapterParams.hAdapter;
|
m_WaitForVblankEventParams.hAdapter = openAdapterParams.hAdapter;
|
||||||
m_WaitForVblankEventParams.hDevice = 0;
|
m_WaitForVblankEventParams.hDevice = 0;
|
||||||
m_WaitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId;
|
m_WaitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId;
|
||||||
|
|
||||||
|
m_AdapterLuid = openAdapterParams.AdapterLuid;
|
||||||
m_LastMonitor = currentMonitor;
|
m_LastMonitor = currentMonitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
status = m_D3DKMTWaitForVerticalBlankEvent(&m_WaitForVblankEventParams);
|
status = m_D3DKMTWaitForVerticalBlankEvent(&m_WaitForVblankEventParams);
|
||||||
if (status != STATUS_SUCCESS) {
|
if (status != STATUS_SUCCESS) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
if (status != STATUS_GRAPHICS_PRESENT_OCCLUDED) {
|
||||||
"D3DKMTWaitForVerticalBlankEvent() failed: %x",
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
status);
|
"D3DKMTWaitForVerticalBlankEvent() failed: %x",
|
||||||
return;
|
status);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_LastMonitor = nullptr;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,7 @@ typedef UINT D3DKMT_HANDLE;
|
|||||||
typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID;
|
typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID;
|
||||||
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
|
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
|
||||||
#define STATUS_GRAPHICS_PRESENT_OCCLUDED ((NTSTATUS)0xC01E0006L)
|
#define STATUS_GRAPHICS_PRESENT_OCCLUDED ((NTSTATUS)0xC01E0006L)
|
||||||
|
#define D3DDDI_ID_UNINITIALIZED (UINT)(~0)
|
||||||
typedef struct _D3DKMT_OPENADAPTERFROMHDC {
|
typedef struct _D3DKMT_OPENADAPTERFROMHDC {
|
||||||
HDC hDc;
|
HDC hDc;
|
||||||
D3DKMT_HANDLE hAdapter;
|
D3DKMT_HANDLE hAdapter;
|
||||||
@@ -39,13 +40,14 @@ public:
|
|||||||
|
|
||||||
virtual bool isAsync() override;
|
virtual bool isAsync() override;
|
||||||
|
|
||||||
virtual void waitForVsync() override;
|
virtual bool waitForVsync() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Pacer* m_Pacer;
|
Pacer* m_Pacer;
|
||||||
HMODULE m_Gdi32Handle;
|
HMODULE m_Gdi32Handle;
|
||||||
HWND m_Window;
|
HWND m_Window;
|
||||||
HMONITOR m_LastMonitor;
|
HMONITOR m_LastMonitor;
|
||||||
|
LUID m_AdapterLuid;
|
||||||
D3DKMT_WAITFORVERTICALBLANKEVENT m_WaitForVblankEventParams;
|
D3DKMT_WAITFORVERTICALBLANKEVENT m_WaitForVblankEventParams;
|
||||||
|
|
||||||
PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc;
|
PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc;
|
||||||
|
|||||||
@@ -115,13 +115,20 @@ int Pacer::vsyncThread(void *context)
|
|||||||
while (!me->m_Stopping) {
|
while (!me->m_Stopping) {
|
||||||
if (async) {
|
if (async) {
|
||||||
// Wait for the VSync source to invoke signalVsync() or 100ms to elapse
|
// Wait for the VSync source to invoke signalVsync() or 100ms to elapse
|
||||||
me->m_FrameQueueLock.lock();
|
QMutexLocker locker(&me->m_FrameQueueLock);
|
||||||
me->m_VsyncSignalled.wait(&me->m_FrameQueueLock, 100);
|
me->m_VsyncSignalled.wait(&me->m_FrameQueueLock, 100);
|
||||||
me->m_FrameQueueLock.unlock();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Let the VSync source wait in the context of our thread
|
// Let the VSync source wait in the context of our thread
|
||||||
me->m_VsyncSource->waitForVsync();
|
if (!me->m_VsyncSource->waitForVsync()) {
|
||||||
|
// Fall back to a fixed delay if the VSync source fails
|
||||||
|
//
|
||||||
|
// NB: We don't actually expect signalVsync() to be called, since
|
||||||
|
// this is a synchronous VSync source, but we wait on m_VsyncSignalled
|
||||||
|
// anyway so that the destructor can immediately wake us on shutdown.
|
||||||
|
QMutexLocker locker(&me->m_FrameQueueLock);
|
||||||
|
me->m_VsyncSignalled.wait(&me->m_FrameQueueLock, 1000 / me->m_DisplayFps);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (me->m_Stopping) {
|
if (me->m_Stopping) {
|
||||||
@@ -148,24 +155,25 @@ int Pacer::renderThread(void* context)
|
|||||||
// Wait for the renderer to be ready for the next frame
|
// Wait for the renderer to be ready for the next frame
|
||||||
me->m_VsyncRenderer->waitToRender();
|
me->m_VsyncRenderer->waitToRender();
|
||||||
|
|
||||||
// Acquire the frame queue lock to protect the queue and
|
AVFrame* frame;
|
||||||
// the not empty condition
|
{
|
||||||
me->m_FrameQueueLock.lock();
|
// Acquire the frame queue lock to protect the queue and
|
||||||
|
// the not empty condition
|
||||||
|
QMutexLocker locker(&me->m_FrameQueueLock);
|
||||||
|
|
||||||
// Wait for a frame to be ready to render
|
// Wait for a frame to be ready to render
|
||||||
while (!me->m_Stopping && me->m_RenderQueue.isEmpty()) {
|
while (!me->m_Stopping && me->m_RenderQueue.isEmpty()) {
|
||||||
me->m_RenderQueueNotEmpty.wait(&me->m_FrameQueueLock);
|
me->m_RenderQueueNotEmpty.wait(&me->m_FrameQueueLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (me->m_Stopping) {
|
||||||
|
// Exit this thread
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame = me->m_RenderQueue.dequeue();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (me->m_Stopping) {
|
|
||||||
// Exit this thread
|
|
||||||
me->m_FrameQueueLock.unlock();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVFrame* frame = me->m_RenderQueue.dequeue();
|
|
||||||
me->m_FrameQueueLock.unlock();
|
|
||||||
|
|
||||||
me->renderFrame(frame);
|
me->renderFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,7 +357,7 @@ void Pacer::renderFrame(AVFrame* frame)
|
|||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
|
|
||||||
// Drop frames if we have too many queued up for a while
|
// Drop frames if we have too many queued up for a while
|
||||||
m_FrameQueueLock.lock();
|
QMutexLocker frameQueueLocker(&m_FrameQueueLock);
|
||||||
|
|
||||||
int frameDropTarget;
|
int frameDropTarget;
|
||||||
|
|
||||||
@@ -382,13 +390,11 @@ void Pacer::renderFrame(AVFrame* frame)
|
|||||||
AVFrame* frame = m_RenderQueue.dequeue();
|
AVFrame* frame = m_RenderQueue.dequeue();
|
||||||
|
|
||||||
// Drop the lock while we call av_frame_free()
|
// Drop the lock while we call av_frame_free()
|
||||||
m_FrameQueueLock.unlock();
|
frameQueueLocker.unlock();
|
||||||
m_VideoStats->pacerDroppedFrames++;
|
m_VideoStats->pacerDroppedFrames++;
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
m_FrameQueueLock.lock();
|
frameQueueLocker.relock();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_FrameQueueLock.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Pacer::dropFrameForEnqueue(QQueue<AVFrame*>& queue)
|
void Pacer::dropFrameForEnqueue(QQueue<AVFrame*>& queue)
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ public:
|
|||||||
// sources require calls to waitForVsync().
|
// sources require calls to waitForVsync().
|
||||||
virtual bool isAsync() = 0;
|
virtual bool isAsync() = 0;
|
||||||
|
|
||||||
virtual void waitForVsync() {
|
virtual bool waitForVsync() {
|
||||||
// Synchronous sources must implement waitForVsync()!
|
// Synchronous sources must implement waitForVsync()!
|
||||||
SDL_assert(false);
|
SDL_assert(false);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user