win32: remove the modal size/move run loop pump

Reverts the effect of 91920289e3. Running runloop_iterate() from a
tick posted into DefWindowProc's size/move and menu loops re-enters
the menu and input drivers while the modal loop has the mouse
captured: window drag stalls, focus sticks, the menu bar stops taking
clicks after the first, and menu hover sounds keep firing on a hidden
window. Bisected by Tatsuya79 to Sept 4..5; sonninnos confirmed the
symptoms go away once the pump is disabled.

The win32 side is removed outright: the clock thread, the WM_TIMER
fallback, the tick handler, the message routing in every wndproc and
the companion window, and the mouse-message filtering in the ui
driver's pump. RUNLOOP_PACE_EXTERNAL stays in runloop.c since the
pacing rework in e0acbe2639 is built on it; nothing sets it now.
This commit is contained in:
libretroadmin committed 2026-09-08 18:16:56 +02:00
1 parent 300b32b5ee
commit 7fbc868499
5 files changed
+1 -407

No files matched your search

-293
View File
@@ -617,255 +617,6 @@ uint16_t win32_get_keyboard_mods(void)
return (uint16_t)retro_atomic_load_acquire_int(&win32_kb_mods);
}
#if !defined(_XBOX)
/* A title bar drag, a border resize, or an open menu enters a modal
* loop inside DefWindowProc that does not return until the user lets
* go. The run loop lives on this thread, so for that whole time no
* frame runs, no audio is written, and the frame limiter falls behind
* by the length of the drag.
*
* The loop does dispatch posted messages, so for its duration a helper
* thread sleeps at the content rate on the high resolution timer and
* posts a tick; the tick runs one iteration with RUNLOOP_PACE_EXTERNAL
* set, which makes the iteration return as soon as the frame is out
* instead of sleeping until the next one. Content and audio keep their
* rate, and the loop has the mouse back within a frame's work, not a
* frame's period. Where there is no thread to be had the fallback is
* WM_TIMER, whose coarse cadence still beats a freeze.
*
* Every API here dates from Windows 95; the constants an older SDK
* lacks are defined below. */
#ifndef USER_TIMER_MINIMUM
#define USER_TIMER_MINIMUM 0x0000000A
#endif
#ifndef WM_ENTERMENULOOP
#define WM_ENTERMENULOOP 0x0211
#endif
#ifndef WM_EXITMENULOOP
#define WM_EXITMENULOOP 0x0212
#endif
#ifndef WM_ENTERSIZEMOVE
#define WM_ENTERSIZEMOVE 0x0231
#endif
#ifndef WM_EXITSIZEMOVE
#define WM_EXITSIZEMOVE 0x0232
#endif
typedef struct
{
#ifdef HAVE_THREADS
sthread_t *thread;
/* Signalled to start a clock session; the thread parks on it
* between sessions rather than being created per drag, which
* would also leave retro_sleep()'s per-thread timer handle behind
* each time. Both events auto-reset. */
HANDLE wake;
HANDLE parked;
#endif
HWND hwnd;
/* Set once a tick is posted and cleared when it is consumed, so a
* main thread that falls behind gets one message, not a backlog. */
retro_atomic_int_t pending;
retro_atomic_int_t stop;
retro_atomic_int_t quit;
/* Size/move and menu loops can nest (a menu opened from the system
* menu of a window being sized is the one case), so arm on the
* first entry and disarm on the last exit. */
uint8_t depth;
} win32_modal_t;
static win32_modal_t win32_modal;
#ifdef HAVE_THREADS
static void win32_modal_clock(void *data)
{
win32_modal_t *m = (win32_modal_t*)data;
for (;;)
{
retro_time_t next;
WaitForSingleObject(m->wake, INFINITE);
if (retro_atomic_load_acquire_int(&m->quit))
break;
next = cpu_features_get_time_usec();
while (!retro_atomic_load_acquire_int(&m->stop))
{
/* core_hz is written on the main thread and only read here;
* a torn read costs one oddly timed tick, nothing worse. */
retro_time_t period = runloop_content_frame_time_us(
video_state_get_ptr()->core_hz);
retro_time_t now = cpu_features_get_time_usec();
retro_time_t wait;
next += period;
wait = next - now;
if (wait > 0)
retro_sleep_us((unsigned)wait);
else
next = now; /* fell behind: no burst, just resume */
if (retro_atomic_cas_int(&m->pending, 0, 1))
PostMessage(m->hwnd, WM_RA_MODAL_TICK, 0, 0);
}
SetEvent(m->parked);
}
}
/* One thread for the life of the process, created on first use. NULL
* on any failure, in which case the WM_TIMER fallback is used. */
static sthread_t *win32_modal_thread_get(win32_modal_t *m)
{
if (m->thread)
return m->thread;
if (!m->wake && !(m->wake = CreateEvent(NULL, FALSE, FALSE, NULL)))
return NULL;
if (!m->parked && !(m->parked = CreateEvent(NULL, FALSE, FALSE, NULL)))
return NULL;
retro_atomic_store_release_int(&m->quit, 0);
return (m->thread = sthread_create(win32_modal_clock, m));
}
#endif
void win32_modal_enter(HWND hwnd)
{
win32_modal_t *m = &win32_modal;
if (m->depth++)
return;
/* With threaded video this window, and so this loop, belong to the
* video thread; the run loop is elsewhere and is not blocked. */
if (video_driver_is_threaded())
return;
m->hwnd = hwnd;
retro_atomic_store_release_int(&m->pending, 0);
retro_atomic_store_release_int(&m->stop, 0);
runloop_state_get_ptr()->pace_external = true;
g_win32_flags |= WIN32_CMN_FLAG_MODAL_TIMER;
#ifdef HAVE_THREADS
if (win32_modal_thread_get(m))
{
SetEvent(m->wake);
return;
}
#endif
SetTimer(hwnd, WIN32_MODAL_TIMER_ID, USER_TIMER_MINIMUM, NULL);
}
/* Stops the clock whatever the nesting depth: the last exit message,
* a quit from inside a tick, and a window going away mid-drag all
* end here. */
static void win32_modal_stop(HWND hwnd)
{
win32_modal_t *m = &win32_modal;
m->depth = 0;
if (!(g_win32_flags & WIN32_CMN_FLAG_MODAL_TIMER))
return;
#ifdef HAVE_THREADS
if (m->thread)
{
/* The clock is inside a sleep of at most one frame; once it has
* parked it cannot post again until the next wake. */
retro_atomic_store_release_int(&m->stop, 1);
WaitForSingleObject(m->parked, INFINITE);
}
else
#endif
KillTimer(hwnd, WIN32_MODAL_TIMER_ID);
/* A tick that was posted before the stop is still in the queue;
* the flag clear below makes it a no-op when it arrives. */
g_win32_flags &= ~WIN32_CMN_FLAG_MODAL_TIMER;
runloop_state_get_ptr()->pace_external = false;
}
void win32_modal_exit(HWND hwnd)
{
win32_modal_t *m = &win32_modal;
if (!m->depth || --m->depth)
return;
win32_modal_stop(hwnd);
}
/* The window is going away. Any session is ended and the thread, if
* one was ever started, is joined. */
void win32_modal_window_destroyed(HWND hwnd)
{
win32_modal_t *m = &win32_modal;
/* Only the window whose loop is being clocked ends the session;
* another window closing during a drag is none of our business. */
if (m->hwnd == hwnd)
win32_modal_stop(hwnd);
}
static void win32_modal_deinit(HWND hwnd)
{
win32_modal_t *m = &win32_modal;
win32_modal_stop(hwnd);
#ifdef HAVE_THREADS
if (m->thread)
{
retro_atomic_store_release_int(&m->quit, 1);
SetEvent(m->wake);
sthread_join(m->thread);
m->thread = NULL;
}
if (m->wake)
{
CloseHandle(m->wake);
m->wake = NULL;
}
if (m->parked)
{
CloseHandle(m->parked);
m->parked = NULL;
}
#endif
}
void win32_modal_tick(HWND hwnd)
{
win32_modal_t *m = &win32_modal;
int ret;
retro_atomic_store_release_int(&m->pending, 0);
if (!(g_win32_flags & WIN32_CMN_FLAG_MODAL_TIMER))
return;
/* The iteration pumps this thread's queue, and a tick pulled out
* there would land here again, inside the iteration it belongs to. */
if (g_win32_flags & WIN32_CMN_FLAG_MODAL_TICK)
return;
/* A quit that was already accepted is reported by the main loop's
* own next iteration; repeating it here would run the shutdown
* work once per tick until the loop lets go. */
if (runloop_state_get_ptr()->flags & RUNLOOP_FLAG_SHUTDOWN_INITIATED)
return;
g_win32_flags |= WIN32_CMN_FLAG_MODAL_TICK;
ret = runloop_iterate();
task_queue_check();
g_win32_flags &= ~WIN32_CMN_FLAG_MODAL_TICK;
if (ret == -1)
win32_modal_stop(hwnd);
}
#endif /* !defined(_XBOX) */
static LRESULT CALLBACK wnd_proc_common(
bool *quit, HWND hwnd, UINT message,
WPARAM wparam, LPARAM lparam)
@@ -911,32 +662,12 @@ static LRESULT CALLBACK wnd_proc_common(
case WM_CLOSE:
case WM_DESTROY:
case WM_QUIT:
#if !defined(_XBOX)
win32_modal_deinit(hwnd);
#endif
g_win32_flags |= WIN32_CMN_FLAG_QUIT;
*quit = true;
/* fall-through */
case WM_MOVE:
win32_save_position();
break;
#if !defined(_XBOX)
case WM_ENTERSIZEMOVE:
case WM_ENTERMENULOOP:
win32_modal_enter(hwnd);
break;
case WM_EXITSIZEMOVE:
case WM_EXITMENULOOP:
win32_modal_exit(hwnd);
break;
case WM_RA_MODAL_TICK:
win32_modal_tick(hwnd);
break;
case WM_TIMER:
if (wparam == WIN32_MODAL_TIMER_ID)
win32_modal_tick(hwnd);
break;
#endif
case WM_SIZE:
/* Do not send resize message if we minimize. */
if ( wparam != SIZE_MAXHIDE
@@ -1153,14 +884,6 @@ static LRESULT CALLBACK wnd_proc_common_internal(HWND hwnd,
case WM_SIZE:
case WM_GETMINMAXINFO:
case WM_COMMAND:
#if !defined(_XBOX)
case WM_ENTERSIZEMOVE:
case WM_EXITSIZEMOVE:
case WM_ENTERMENULOOP:
case WM_EXITMENULOOP:
case WM_TIMER:
case WM_RA_MODAL_TICK:
#endif
#ifdef HAVE_THREADS
case WM_BROWSER_OPEN_RESULT:
case WM_BROWSER_CANCELLED:
@@ -1246,14 +969,6 @@ static LRESULT CALLBACK wnd_proc_winraw_common_internal(HWND hwnd,
case WM_SIZE:
case WM_GETMINMAXINFO:
case WM_COMMAND:
#if !defined(_XBOX)
case WM_ENTERSIZEMOVE:
case WM_EXITSIZEMOVE:
case WM_ENTERMENULOOP:
case WM_EXITMENULOOP:
case WM_TIMER:
case WM_RA_MODAL_TICK:
#endif
#ifdef HAVE_THREADS
case WM_BROWSER_OPEN_RESULT:
case WM_BROWSER_CANCELLED:
@@ -1465,14 +1180,6 @@ static LRESULT CALLBACK wnd_proc_common_dinput_internal(HWND hwnd,
case WM_SIZE:
case WM_GETMINMAXINFO:
case WM_COMMAND:
#if !defined(_XBOX)
case WM_ENTERSIZEMOVE:
case WM_EXITSIZEMOVE:
case WM_ENTERMENULOOP:
case WM_EXITMENULOOP:
case WM_TIMER:
case WM_RA_MODAL_TICK:
#endif
#ifdef HAVE_THREADS
case WM_BROWSER_OPEN_RESULT:
case WM_BROWSER_CANCELLED:
+1 -30
View File
@@ -70,40 +70,11 @@ enum win32_common_flags
WIN32_CMN_FLAG_TASKBAR_CREATED = (1 << 2),
WIN32_CMN_FLAG_RESTORE_DESKTOP = (1 << 3),
WIN32_CMN_FLAG_INITED = (1 << 4),
WIN32_CMN_FLAG_SWAP_MOUSE_BTNS = (1 << 5),
/* The window is inside a modal size/move or menu loop owned by
* DefWindowProc, and a timer is driving the run loop from within
* it. */
WIN32_CMN_FLAG_MODAL_TIMER = (1 << 6),
/* A run loop iteration started from that timer is in progress. */
WIN32_CMN_FLAG_MODAL_TICK = (1 << 7)
WIN32_CMN_FLAG_SWAP_MOUSE_BTNS = (1 << 5)
};
extern uint8_t g_win32_flags;
#if !defined(_XBOX)
/* Modal size/move and menu loops (DefWindowProc runs them on this
* thread and returns only when they end) are clocked so the run loop
* keeps iterating: WM_ENTERSIZEMOVE / WM_ENTERMENULOOP -> _enter,
* WM_EXITSIZEMOVE / WM_EXITMENULOOP -> _exit, and the ticks arrive as
* WM_RA_MODAL_TICK or as WM_TIMER with WIN32_MODAL_TIMER_ID -> _tick.
* Any top-level window on the main thread - the companion window
* included - must route these, or dragging it freezes RetroArch.
* Chosen not to collide with anything else that might use timers or
* WM_APP messages on those windows. */
#ifndef WM_APP
#define WM_APP 0x8000
#endif
#define WIN32_MODAL_TIMER_ID 0x5241
#define WM_RA_MODAL_TICK (WM_APP + 0x52)
void win32_modal_enter(HWND hwnd);
void win32_modal_exit(HWND hwnd);
void win32_modal_tick(HWND hwnd);
/* Ends the session if @hwnd is the window being clocked. */
void win32_modal_window_destroyed(HWND hwnd);
#endif
#if !defined(_XBOX)
extern unsigned g_win32_resize_width;
extern unsigned g_win32_resize_height;
-4
View File
@@ -51,10 +51,6 @@
ui_window_win32_t main_window;
static int stub_menu_loop_calls;
LRESULT win32_menu_loop(HWND hwnd, WPARAM wparam) { (void)hwnd; (void)wparam; stub_menu_loop_calls++; return 0; }
void win32_modal_enter(HWND h) { (void)h; }
void win32_modal_exit(HWND h) { (void)h; }
void win32_modal_tick(HWND h) { (void)h; }
void win32_modal_window_destroyed(HWND h) { (void)h; }
extern settings_t test_settings;
extern runloop_state_t test_runloop;
-18
View File
@@ -100,24 +100,6 @@ static void ui_application_win32_process_events(void)
{
MSG msg;
/* Called from a run loop iteration that a modal size/move or menu
* loop is driving from its timer (win32_common.c). That loop has
* the mouse captured and reads its own movement and button-up from
* the queue; taking those here would leave it tracking nothing.
* Everything else - keyboard, raw input, WM_TIMER, posted commands
* - is dispatched as usual. Ranges are numeric so that they do not
* move with the SDK's idea of WM_MOUSELAST. */
if (g_win32_flags & WIN32_CMN_FLAG_MODAL_TICK)
{
while (PeekMessage(&msg, 0, 0x0000, 0x009F, PM_REMOVE))
ui_application_win32_dispatch(&msg);
while (PeekMessage(&msg, 0, 0x00B0, 0x01FF, PM_REMOVE))
ui_application_win32_dispatch(&msg);
while (PeekMessage(&msg, 0, 0x0210, 0xFFFF, PM_REMOVE))
ui_application_win32_dispatch(&msg);
return;
}
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
ui_application_win32_dispatch(&msg);
}
-62
View File
@@ -130,20 +130,6 @@
* cut, so appends stay O(line) instead of the control's O(text). */
#define COMPANION_WIN32_LOG_MAX (256 * 1024)
/* Pre-Win98 SDKs lack these; the messages themselves date from 95. */
#ifndef WM_ENTERMENULOOP
#define WM_ENTERMENULOOP 0x0211
#endif
#ifndef WM_EXITMENULOOP
#define WM_EXITMENULOOP 0x0212
#endif
#ifndef WM_ENTERSIZEMOVE
#define WM_ENTERSIZEMOVE 0x0231
#endif
#ifndef WM_EXITSIZEMOVE
#define WM_EXITSIZEMOVE 0x0232
#endif
/* Control / command IDs. Kept clear of the ID_M_* range in ui_win32.h. */
#include "ui_win32_companion_ids.h"
@@ -2779,27 +2765,6 @@ static LRESULT CALLBACK cw_cores_wndproc(HWND hwnd, UINT msg,
case WM_CLOSE:
ShowWindow(hwnd, SW_HIDE);
return 0;
case WM_DESTROY:
win32_modal_window_destroyed(hwnd);
break;
case WM_ENTERSIZEMOVE:
case WM_ENTERMENULOOP:
win32_modal_enter(hwnd);
break;
case WM_EXITSIZEMOVE:
case WM_EXITMENULOOP:
win32_modal_exit(hwnd);
break;
case WM_RA_MODAL_TICK:
win32_modal_tick(hwnd);
return 0;
case WM_TIMER:
if (wparam == WIN32_MODAL_TIMER_ID)
{
win32_modal_tick(hwnd);
return 0;
}
break;
case WM_COMMAND:
if (!w)
break;
@@ -3079,33 +3044,6 @@ static LRESULT CALLBACK cw_wndproc(HWND hwnd, UINT msg,
ShowWindow(hwnd, SW_HIDE);
return 0;
case WM_DESTROY:
win32_modal_window_destroyed(hwnd);
break;
/* Dragging or sizing this window, or browsing its menu bar, runs a
* modal loop inside DefWindowProc on the main thread. Clock the
* run loop through it exactly as the main window does, or
* RetroArch's video stops for the duration. */
case WM_ENTERSIZEMOVE:
case WM_ENTERMENULOOP:
win32_modal_enter(hwnd);
break;
case WM_EXITSIZEMOVE:
case WM_EXITMENULOOP:
win32_modal_exit(hwnd);
break;
case WM_RA_MODAL_TICK:
win32_modal_tick(hwnd);
return 0;
case WM_TIMER:
if (wparam == WIN32_MODAL_TIMER_ID)
{
win32_modal_tick(hwnd);
return 0;
}
break;
/* Splitter */
case WM_SETCURSOR:
if (w && (HWND)wparam == hwnd && LOWORD(lparam) == HTCLIENT)