win32: pause cleanly during window size/move and menu loops

Second attempt at the problem 91920289 tried to solve, informed by its
revert (7fbc868499). Dragging the title bar, resizing by the border or
browsing a menu bar runs a modal loop inside DefWindowProc; with
non-threaded video the run loop is on that thread and stops. Before
this the client area went stale or black and the audio sink underran
and popped on release.

Now: on WM_ENTERSIZEMOVE / WM_ENTERMENULOOP the audio driver is stopped
(unless RUNLOOP_FLAG_PAUSED is set - a user who paused keeps it paused)
and a 16 ms timer re-presents the last frame at the current window
size. Each tick makes the two calls the run loop makes per frame and
nothing else: the driver's alive(), which is where win32_check_window()
consumes WIN32_CMN_FLAG_RESIZED and arms the swapchain resize, then
video_driver_cached_frame(). Nothing runs runloop_iterate(), the menu,
input or the task queue from inside the modal loop; content pauses for
the length of the drag, as in every other windowed game. Threaded video
returns early at enter - the window is on the video thread and the run
loop never blocked.

One timer for the process with a stored owner HWND, so the companion
and the main window cannot kill each other's; a depth counter for
nested loops (system menu opened while sizing); abort on
WM_CLOSE/WM_DESTROY/WM_QUIT with no audio restart. The companion's main
wndproc routes the same messages; its floating dock panes are not wired
in this change. No changes to runloop.c, ui_win32.c or the pace flags.

Checked: tools/mingw_syntax_check.sh on the three .c files and
--consumers-of win32_common.h (sdl3_gfx/uwp_egl_ctx fail identically
on master); tools/companion_win32_test.sh under Wine. NOT yet run on
Windows hardware: needs the row table (drag/resize/system menu/alt-tab/
companion menu bar/quit mid-drag, D3D11 and GL, vsync on/off, threaded
on/off, paused) green from Tatsuya79 and sonninnos before merge.
This commit is contained in:
libretroadmin committed 2026-09-10 00:23:14 +02:00
1 parent e58643e67d
commit 6c0cedc945
4 files changed
+197

No files matched your search

+147
View File
@@ -130,6 +130,7 @@ typedef char win32_dwm_timing_info_size_check[
#include "../../verbosity.h"
#include "../../paths.h"
#include "../../retroarch.h"
#include "../../audio/audio_driver.h"
#include "../../tasks/task_content.h"
#include "../../tasks/tasks_internal.h"
#include "../../core_info.h"
@@ -686,6 +687,111 @@ uint16_t win32_get_keyboard_mods(void)
return (uint16_t)retro_atomic_load_acquire_int(&win32_kb_mods);
}
#if !defined(_XBOX)
#ifndef WM_ENTERSIZEMOVE
#define WM_ENTERSIZEMOVE 0x0231
#endif
#ifndef WM_EXITSIZEMOVE
#define WM_EXITSIZEMOVE 0x0232
#endif
#ifndef WM_ENTERMENULOOP
#define WM_ENTERMENULOOP 0x0211
#endif
#ifndef WM_EXITMENULOOP
#define WM_EXITMENULOOP 0x0212
#endif
/* Title-bar drags, border resizes and menu bars run a modal loop inside
* DefWindowProc that does not return until the user lets go. With
* non-threaded video the run loop is on this thread, so content pauses
* for the duration - that is the norm for a windowed game and is not
* changed here. What is done: the audio driver is stopped so the sink
* does not underrun and pop, and a plain timer keeps the last frame on
* screen at the current window size. Nothing in here runs the run
* loop, the menu, input or the task queue; running those nested inside
* a captured-mouse modal loop is what 91920289 did and why it was
* reverted.
*
* Size/move and menu loops can nest (system menu opened while sizing):
* arm on the first entry, disarm on the last exit. One timer for the
* process; the window that armed it owns it, so the companion and the
* main window never kill each other's. */
#define WIN32_SIZEMOVE_TIMER_ID 0x5241
static uint8_t win32_sizemove_depth;
static bool win32_sizemove_stopped_audio;
static HWND win32_sizemove_timer_hwnd;
void win32_sizemove_enter(HWND hwnd)
{
if (win32_sizemove_depth++)
return;
/* The window lives on the video thread; the run loop is elsewhere
* and keeps going on its own. */
if (video_driver_is_threaded())
return;
/* A user who pressed P already stopped the driver and must not get
* it restarted on release. audio_driver_stop() returns false when
* the driver is not alive, so the latch is a real transition. */
win32_sizemove_stopped_audio = false;
if (!(runloop_state_get_ptr()->flags & RUNLOOP_FLAG_PAUSED))
win32_sizemove_stopped_audio = audio_driver_stop();
if (SetTimer(hwnd, WIN32_SIZEMOVE_TIMER_ID, 16, NULL))
win32_sizemove_timer_hwnd = hwnd;
}
void win32_sizemove_exit(HWND hwnd)
{
(void)hwnd;
if (!win32_sizemove_depth || --win32_sizemove_depth)
return;
if (video_driver_is_threaded())
return;
if (win32_sizemove_timer_hwnd)
{
KillTimer(win32_sizemove_timer_hwnd, WIN32_SIZEMOVE_TIMER_ID);
win32_sizemove_timer_hwnd = NULL;
}
/* A failed start clears AUDIO_FLAG_ACTIVE for the session; say so. */
if (win32_sizemove_stopped_audio && !audio_driver_start(false))
RARCH_WARN("[Win32] Audio did not restart after a window size/move.\n");
win32_sizemove_stopped_audio = false;
}
/* A routed window is going away mid-drag. No audio restart: the driver
* may already be gone, and audio_driver_start() failing mutes the
* session. */
void win32_sizemove_abort(void)
{
if (win32_sizemove_timer_hwnd)
KillTimer(win32_sizemove_timer_hwnd, WIN32_SIZEMOVE_TIMER_ID);
win32_sizemove_timer_hwnd = NULL;
win32_sizemove_depth = 0;
win32_sizemove_stopped_audio = false;
}
/* WM_TIMER with WIN32_SIZEMOVE_TIMER_ID, delivered on the thread that
* owns the driver. The same two calls the run loop makes per frame and
* nothing else: the driver's alive() is where win32_check_window()
* consumes WIN32_CMN_FLAG_RESIZED and arms the swapchain resize, and
* video_driver_cached_frame() then presents the last frame into the
* resized chain - the pause picture. current_video and data have
* independent lifetimes during teardown, hence both checks. */
void win32_sizemove_tick(void)
{
video_driver_state_t *video_st = video_state_get_ptr();
if (!win32_sizemove_depth || video_driver_is_threaded())
return;
if (video_st->current_video && video_st->data)
video_st->current_video->alive(video_st->data);
video_driver_cached_frame();
}
#endif
static LRESULT CALLBACK wnd_proc_common(
bool *quit, HWND hwnd, UINT message,
WPARAM wparam, LPARAM lparam)
@@ -731,12 +837,32 @@ static LRESULT CALLBACK wnd_proc_common(
case WM_CLOSE:
case WM_DESTROY:
case WM_QUIT:
#if !defined(_XBOX)
win32_sizemove_abort();
#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_sizemove_enter(hwnd);
break;
case WM_EXITSIZEMOVE:
case WM_EXITMENULOOP:
win32_sizemove_exit(hwnd);
break;
case WM_TIMER:
/* Someone else's timer falls through to DefWindowProc. */
if (wparam != WIN32_SIZEMOVE_TIMER_ID)
break;
win32_sizemove_tick();
*quit = true;
return 0;
#endif
case WM_SIZE:
/* Do not send resize message if we minimize. */
if ( wparam != SIZE_MAXHIDE
@@ -951,6 +1077,13 @@ static LRESULT CALLBACK wnd_proc_common_internal(HWND hwnd,
case WM_QUIT:
case WM_MOVE:
case WM_SIZE:
#if !defined(_XBOX)
case WM_ENTERSIZEMOVE:
case WM_EXITSIZEMOVE:
case WM_ENTERMENULOOP:
case WM_EXITMENULOOP:
case WM_TIMER:
#endif
case WM_GETMINMAXINFO:
case WM_COMMAND:
#ifdef HAVE_THREADS
@@ -1036,6 +1169,13 @@ static LRESULT CALLBACK wnd_proc_winraw_common_internal(HWND hwnd,
case WM_QUIT:
case WM_MOVE:
case WM_SIZE:
#if !defined(_XBOX)
case WM_ENTERSIZEMOVE:
case WM_EXITSIZEMOVE:
case WM_ENTERMENULOOP:
case WM_EXITMENULOOP:
case WM_TIMER:
#endif
case WM_GETMINMAXINFO:
case WM_COMMAND:
#ifdef HAVE_THREADS
@@ -1247,6 +1387,13 @@ static LRESULT CALLBACK wnd_proc_common_dinput_internal(HWND hwnd,
case WM_QUIT:
case WM_MOVE:
case WM_SIZE:
#if !defined(_XBOX)
case WM_ENTERSIZEMOVE:
case WM_EXITSIZEMOVE:
case WM_ENTERMENULOOP:
case WM_EXITMENULOOP:
case WM_TIMER:
#endif
case WM_GETMINMAXINFO:
case WM_COMMAND:
#ifdef HAVE_THREADS
+11
View File
@@ -132,6 +132,17 @@ retro_time_t win32_dwm_last_vblank_time(void);
void win32_clip_window(bool grab);
#endif
#if !defined(_XBOX)
/* Size/move and menu-loop handling for any window whose wndproc runs on
* the run loop's thread: content pauses, audio is stopped cleanly and a
* timer re-presents the last frame. See win32_common.c. */
#define WIN32_SIZEMOVE_TIMER_ID 0x5241
void win32_sizemove_enter(HWND hwnd);
void win32_sizemove_exit(HWND hwnd);
void win32_sizemove_tick(void);
void win32_sizemove_abort(void);
#endif
void win32_check_window(void *data,
bool *quit,
bool *resize, unsigned *width, unsigned *height);
+4
View File
@@ -53,6 +53,10 @@
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_sizemove_enter(HWND h) { (void)h; }
void win32_sizemove_exit(HWND h) { (void)h; }
void win32_sizemove_tick(void) { }
void win32_sizemove_abort(void) { }
extern settings_t test_settings;
extern runloop_state_t test_runloop;
+35
View File
@@ -137,6 +137,20 @@
/* Control / command IDs. Kept clear of the ID_M_* range in ui_win32.h. */
#include "ui_win32_companion_ids.h"
/* Pre-Win98 SDKs lack these; the messages themselves date from 95. */
#ifndef WM_ENTERSIZEMOVE
#define WM_ENTERSIZEMOVE 0x0231
#endif
#ifndef WM_EXITSIZEMOVE
#define WM_EXITSIZEMOVE 0x0232
#endif
#ifndef WM_ENTERMENULOOP
#define WM_ENTERMENULOOP 0x0211
#endif
#ifndef WM_EXITMENULOOP
#define WM_EXITMENULOOP 0x0212
#endif
#include "../companion/companion_dock.h"
typedef struct ui_companion_win32_wimp
@@ -3914,8 +3928,29 @@ static LRESULT CALLBACK cw_wndproc(HWND hwnd, UINT msg,
case WM_CLOSE:
/* Closing the companion never quits RetroArch. */
win32_sizemove_abort();
ShowWindow(hwnd, SW_HIDE);
return 0;
case WM_DESTROY:
win32_sizemove_abort();
break;
/* Dragging or sizing this window, or browsing its menu bar, runs
* a modal loop on the run loop's thread exactly as the main window
* does; same handling, no run loop access. */
case WM_ENTERSIZEMOVE:
case WM_ENTERMENULOOP:
win32_sizemove_enter(hwnd);
break;
case WM_EXITSIZEMOVE:
case WM_EXITMENULOOP:
win32_sizemove_exit(hwnd);
break;
case WM_TIMER:
if (wparam != WIN32_SIZEMOVE_TIMER_ID)
break;
win32_sizemove_tick();
return 0;
/* The docks: strips, tabs and gaps are the window's own surface. */
case WM_PAINT: