vk/windows: Try to keep msq thread from ever stopping

- NVIDIA drivers hook into the msq before our nativeEvent handler. This means NV is aware of events before rpcs3 is aware of them and sometimes stops until a new event is triggered.
  If rpcs3 is inside a driver call at this time, the system will deadlock since the driver waits for msq which waits for the renderer which waits for the driver.
- Use explicit hook management to control window events
- Add fence timeout to attempt detection of surface loss events
This commit is contained in:
kd-11 2019-01-07 01:03:56 +03:00 committed by kd-11
parent 987b607cb0
commit 3bfa564ef8
7 changed files with 420 additions and 314 deletions

View file

@ -27,13 +27,162 @@
constexpr auto qstr = QString::fromStdString;
#ifdef _WIN32
namespace win32
{
HHOOK _hook = NULL;
bool _in_sizing_event = false;
bool _interactive_resize = false;
bool _user_interaction_active = false;
bool _minimized = false;
void _ReleaseHook();
LRESULT CALLBACK __HookCallback(INT nCode, WPARAM wParam, LPARAM lParam)
{
std::shared_ptr<GSRender> renderer;
if (Emu.IsStopped())
{
// Stop receiving events
_ReleaseHook();
}
else
{
renderer = fxm::get<GSRender>();
}
if (nCode >= 0 && renderer)
{
auto frame_wnd = renderer->get_frame();
auto msg = (CWPSTRUCT*)lParam;
switch ((msg->hwnd == frame_wnd->handle()) ? msg->message : 0)
{
case WM_WINDOWPOSCHANGING:
{
const auto flags = reinterpret_cast<LPWINDOWPOS>(msg->lParam)->flags & SWP_NOSIZE;
if (_in_sizing_event || flags != 0)
break;
// About to resize
_in_sizing_event = true;
_interactive_resize = false;
frame_wnd->push_wm_event(wm_event::geometry_change_notice);
break;
}
case WM_WINDOWPOSCHANGED:
{
if (_user_interaction_active)
{
// Window dragged or resized by user causing position to change, but user is not yet done
frame_wnd->push_wm_event(wm_event::geometry_change_in_progress);
break;
}
const auto flags = reinterpret_cast<LPWINDOWPOS>(msg->lParam)->flags & (SWP_NOSIZE | SWP_NOMOVE);
if (!_in_sizing_event || flags == (SWP_NOSIZE | SWP_NOMOVE))
break;
_in_sizing_event = false;
if (flags & SWP_NOSIZE)
{
frame_wnd->push_wm_event(wm_event::window_moved);
}
else
{
LPWINDOWPOS wpos = reinterpret_cast<LPWINDOWPOS>(msg->lParam);
if (wpos->cx <= GetSystemMetrics(SM_CXMINIMIZED) || wpos->cy <= GetSystemMetrics(SM_CYMINIMIZED))
{
// Minimize event
_minimized = true;
frame_wnd->push_wm_event(wm_event::window_minimized);
}
else if (_minimized)
{
_minimized = false;
frame_wnd->push_wm_event(wm_event::window_restored);
}
else
{
// Handle the resize in WM_SIZE message
_in_sizing_event = true;
}
}
break;
}
case WM_ENTERSIZEMOVE:
_user_interaction_active = true;
break;
case WM_EXITSIZEMOVE:
_user_interaction_active = false;
if (_in_sizing_event && !_user_interaction_active)
{
// Just finished resizing using manual interaction. The corresponding WM_SIZE is consumed before this event fires
frame_wnd->push_wm_event(_interactive_resize ? wm_event::window_resized : wm_event::window_moved);
_in_sizing_event = false;
}
break;
case WM_SIZE:
{
if (_user_interaction_active)
{
// Interaction is a resize not a move
_interactive_resize = true;
frame_wnd->push_wm_event(wm_event::geometry_change_in_progress);
}
else if (_in_sizing_event)
{
// Any other unexpected resize mode will give an unconsumed WM_SIZE event
frame_wnd->push_wm_event(wm_event::window_resized);
_in_sizing_event = false;
}
break;
}
}
}
return CallNextHookEx(_hook, nCode, wParam, lParam);
}
void _InstallHook()
{
if (_hook)
{
_ReleaseHook();
}
Emu.CallAfter([&]()
{
if (_hook = SetWindowsHookEx(WH_CALLWNDPROC, __HookCallback, NULL, GetCurrentThreadId()); !_hook)
{
LOG_ERROR(RSX, "Failed to install window hook!");
}
});
}
void _ReleaseHook()
{
if (_hook)
{
UnhookWindowsHookEx(_hook);
_hook = NULL;
}
}
}
#endif
gs_frame::gs_frame(const QString& title, const QRect& geometry, const QIcon& appIcon, const std::shared_ptr<gui_settings>& gui_settings)
: QWindow(), m_windowTitle(title), m_gui_settings(gui_settings)
{
m_disable_mouse = gui_settings->GetValue(gui::gs_disableMouse).toBool();
// Workaround for a Qt bug affecting 5.11.1 binaries
m_use_5_11_1_workaround = QLibraryInfo::version() == QVersionNumber(5, 11, 1);
//m_use_5_11_1_workaround = QLibraryInfo::version() == QVersionNumber(5, 11, 1);
//Get version by substringing VersionNumber-buildnumber-commithash to get just the part before the dash
std::string version = rpcs3::version.to_string();
@ -85,6 +234,8 @@ gs_frame::gs_frame(const QString& title, const QRect& geometry, const QIcon& app
m_tb_progress = m_tb_button->progress();
m_tb_progress->setRange(0, m_gauge_max);
m_tb_progress->setVisible(false);
win32::_ReleaseHook();
#elif HAVE_QTDBUS
UpdateProgress(0);
m_progress_value = 0;
@ -126,54 +277,60 @@ void gs_frame::showEvent(QShowEvent *event)
void gs_frame::keyPressEvent(QKeyEvent *keyEvent)
{
auto l_handleKeyEvent = [this ,keyEvent]()
switch (keyEvent->key())
{
switch (keyEvent->key())
case Qt::Key_L:
if (keyEvent->modifiers() == Qt::AltModifier) { static int count = 0; LOG_SUCCESS(GENERAL, "Made forced mark %d in log", ++count); }
break;
case Qt::Key_Return:
if (keyEvent->modifiers() == Qt::AltModifier) { toggle_fullscreen(); return; }
break;
case Qt::Key_Escape:
if (visibility() == FullScreen) { toggle_fullscreen(); return; }
break;
case Qt::Key_P:
if (keyEvent->modifiers() == Qt::ControlModifier && Emu.IsRunning()) { Emu.Pause(); return; }
break;
case Qt::Key_S:
if (keyEvent->modifiers() == Qt::ControlModifier && (!Emu.IsStopped())) { Emu.Stop(); return; }
break;
case Qt::Key_R:
if (keyEvent->modifiers() == Qt::ControlModifier && (!Emu.GetBoot().empty())) { Emu.Restart(); return; }
break;
case Qt::Key_E:
if (keyEvent->modifiers() == Qt::ControlModifier)
{
case Qt::Key_L:
if (keyEvent->modifiers() == Qt::AltModifier) { static int count = 0; LOG_SUCCESS(GENERAL, "Made forced mark %d in log", ++count); }
break;
case Qt::Key_Return:
if (keyEvent->modifiers() == Qt::AltModifier) { OnFullScreen(); return; }
break;
case Qt::Key_Escape:
if (visibility() == FullScreen) { setVisibility(Windowed); return; }
break;
case Qt::Key_P:
if (keyEvent->modifiers() == Qt::ControlModifier && Emu.IsRunning()) { Emu.Pause(); return; }
break;
case Qt::Key_S:
if (keyEvent->modifiers() == Qt::ControlModifier && (!Emu.IsStopped())) { Emu.Stop(); return; }
break;
case Qt::Key_R:
if (keyEvent->modifiers() == Qt::ControlModifier && (!Emu.GetBoot().empty())) { Emu.Restart(); return; }
break;
case Qt::Key_E:
if (keyEvent->modifiers() == Qt::ControlModifier)
{
if (Emu.IsReady()) { Emu.Run(); return; }
else if (Emu.IsPaused()) { Emu.Resume(); return; }
}
break;
if (Emu.IsReady()) { Emu.Run(); return; }
else if (Emu.IsPaused()) { Emu.Resume(); return; }
}
};
Emu.CallAfter(l_handleKeyEvent);
break;
}
}
void gs_frame::OnFullScreen()
void gs_frame::toggle_fullscreen()
{
auto l_setFullScreenVis = [=]()
if (wm_allow_fullscreen)
{
if (visibility() == FullScreen)
auto l_setFullScreenVis = [&]()
{
setVisibility(Windowed);
}
else
{
setVisibility(FullScreen);
}
};
Emu.CallAfter(l_setFullScreenVis);
if (visibility() == FullScreen)
{
setVisibility(Windowed);
}
else
{
setVisibility(FullScreen);
}
};
Emu.CallAfter(l_setFullScreenVis);
}
else
{
// Forward the request to the backend
push_wm_event(wm_event::toggle_fullscreen);
std::this_thread::sleep_for(1s);
}
}
void gs_frame::close()
@ -248,6 +405,10 @@ draw_context_t gs_frame::make_context()
void gs_frame::set_current(draw_context_t ctx)
{
Q_UNUSED(ctx);
#ifdef _WIN32
win32::_InstallHook();
#endif
}
void gs_frame::delete_context(draw_context_t ctx)
@ -310,7 +471,7 @@ void gs_frame::mouseDoubleClickEvent(QMouseEvent* ev)
if (ev->button() == Qt::LeftButton)
{
OnFullScreen();
toggle_fullscreen();
}
}
@ -351,127 +512,6 @@ bool gs_frame::event(QEvent* ev)
return QWindow::event(ev);
}
bool gs_frame::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
#ifdef _WIN32
if (wm_event_queue_enabled.load(std::memory_order_consume) && !Emu.IsStopped())
{
//Wait for consumer to clear notification pending flag
while (wm_event_raised.load(std::memory_order_consume) && !Emu.IsStopped());
{
MSG* msg;
if (m_use_5_11_1_workaround)
{
// https://bugreports.qt.io/browse/QTBUG-69074?focusedCommentId=409797&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-409797
msg = *reinterpret_cast<MSG**>(message);
}
else
{
msg = reinterpret_cast<MSG*>(message);
}
std::lock_guard lock(wm_event_lock);
switch (msg->message)
{
case WM_WINDOWPOSCHANGING:
{
const auto flags = reinterpret_cast<LPWINDOWPOS>(msg->lParam)->flags & SWP_NOSIZE;
if (m_in_sizing_event || flags != 0)
break;
//About to resize
m_in_sizing_event = true;
m_interactive_resize = false;
m_raised_event = wm_event::geometry_change_notice;
wm_event_raised.store(true);
break;
}
case WM_WINDOWPOSCHANGED:
{
const auto flags = reinterpret_cast<LPWINDOWPOS>(msg->lParam)->flags & (SWP_NOSIZE | SWP_NOMOVE);
if (!m_in_sizing_event || m_user_interaction_active || flags == (SWP_NOSIZE | SWP_NOMOVE))
break;
m_in_sizing_event = false;
if (flags & SWP_NOSIZE)
{
m_raised_event = wm_event::window_moved;
}
else
{
LPWINDOWPOS wpos = reinterpret_cast<LPWINDOWPOS>(msg->lParam);
if (wpos->cx <= GetSystemMetrics(SM_CXMINIMIZED) || wpos->cy <= GetSystemMetrics(SM_CYMINIMIZED))
{
//Minimize event
m_minimized = true;
m_raised_event = wm_event::window_minimized;
}
else if (m_minimized)
{
m_minimized = false;
m_raised_event = wm_event::window_restored;
}
else
{
//Handle the resize in WM_SIZE message
m_in_sizing_event = true;
break;
}
}
//Possibly finished resizing using maximize or SWP
wm_event_raised.store(true);
break;
}
case WM_ENTERSIZEMOVE:
m_user_interaction_active = true;
break;
case WM_EXITSIZEMOVE:
m_user_interaction_active = false;
if (m_in_sizing_event && !m_user_interaction_active)
{
//Just finished resizing using manual interaction. The corresponding WM_SIZE is consumed before this event fires
m_raised_event = m_interactive_resize ? wm_event::window_resized : wm_event::window_moved;
m_in_sizing_event = false;
wm_event_raised.store(true);
}
break;
case WM_SIZE:
{
if (m_user_interaction_active)
{
//Interaction is a resize not a move
m_interactive_resize = true;
}
else if (m_in_sizing_event)
{
//Any other unexpected resize mode will give an unconsumed WM_SIZE event
m_raised_event = wm_event::window_resized;
m_in_sizing_event = false;
wm_event_raised.store(true);
}
break;
}
}
}
//Do not enter DefWndProc until the consumer has consumed the message
while (wm_event_raised.load(std::memory_order_consume) && !Emu.IsStopped());
}
#endif
//Let the default handler deal with this. Only the notification is required
return false;
}
wm_event gs_frame::get_default_wm_event() const
{
return (m_user_interaction_active) ? wm_event::geometry_change_in_progress : wm_event::none;
}
void gs_frame::progress_reset(bool reset_limit)
{
#ifdef _WIN32