rust-windowing / winit

Window handling library in pure Rust
https://docs.rs/winit/
Apache License 2.0
4.87k stars 910 forks source link

Using WM_PAINT for controlling the event loop is way too slow for real-time rendering #2782

Open fredizzimo opened 1 year ago

fredizzimo commented 1 year ago

Winit is currently flooding the Windows event queue with WM_PAINT messages. Furthermore the main loop is always blocking with GetMessage, waiting for either WM_PAINT or another message to appear.

This can cause long gaps between frames, event when ControlFlow::Poll is used. For example take a look at the following capture

image

Here there was 3.89 ms between two calls with MainEventsCleared, almost half the frame budget when rendering at 120 FPS, causing a missed vsync here. Note that my loop is called two times in between with some other event, so this is not even a worst case, if there are multiple events, then the delay could be even longer.

The main loop should never block, normally you use PeekMessage in a non-blocking loop on Windows, or completely decouple the rendering from the event loop.

Due to this I'm forced to take the second appoach and use a separate loop for the rendering, but because the events takes a timeline it's quite invonvenient https://github.com/rust-windowing/winit/issues/1387 to pass the events to my rendering thread.

You could perhaps use some use event to singal a new pass of the loops, but polling with PeekMessage should be preferred. WM_PAINT indirectly though RedrawWindow is really bad, because it has a very special meaning to the operating system, and probably forces a lot of interna stuff related to the window to be updated, in addition to posting the even.

Note there are a few issues I found releated to this, but not exactly the same https://github.com/rust-windowing/winit/issues/2698 - Not directly related, but decoupling the loop from WM_PAINT should fix the issue https://github.com/rust-windowing/winit/issues/2367 - This is mostly the same issue, the flooding of WM_PAINT, which is increadible slow causes other events to be delayed https://github.com/rust-windowing/winit/issues/2287 - However, my timings on Windows 11 are much, much worse than what's reported there

The TLDR; for my suggestion is:

fredizzimo commented 1 year ago

When debugging another issue I saw the following calls stack

enum2$<core::result::Result<std::sync::mutex::MutexGuard<winit::platform_impl::platform::keyboard_layout::LayoutCache>,std::sync::poison::PoisonError<std::sync::mutex::MutexGuard<winit::platform_impl::platform::keyboard_layout::LayoutCache> > > > std::sync::mutex::Mutex<winit::platform_impl::platform::keyboard_layout::LayoutCache>::lock<winit::platform_impl::platform::keyboard_layout::LayoutCache>() (@std::sync::mutex::Mutex<T>::lock:13)
static struct alloc::vec::Vec<winit::platform_impl::platform::keyboard::MessageAsKeyEvent,alloc::alloc::Global> winit::platform_impl::platform::keyboard::KeyEventBuilder::synthesize_kbd_state(winit::event::ElementState, unsigned char[256] *) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\keyboard.rs:336)
static union enum2$<winit::platform_impl::platform::keyboard::impl$1::process_message::MatchResult> winit::platform_impl::platform::keyboard::impl$1::process_message::closure$0(struct winit::platform_impl::platform::keyboard::impl$1::process_message::closure_env$0 *) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\keyboard.rs:115)
struct alloc::vec::Vec<winit::platform_impl::platform::keyboard::MessageAsKeyEvent,alloc::alloc::Global> winit::platform_impl::platform::keyboard::KeyEventBuilder::process_message(__int64, unsigned int, unsigned __int64, __int64, union enum2$<winit::platform_impl::platform::event_loop::ProcResult> *) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\keyboard.rs:319)
void winit::platform_impl::platform::event_loop::public_window_callback_inner::closure$2<enum2$<neovide::window::UserEvent> >(struct winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > *) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\event_loop.rs:1016)
void core::ops::function::FnOnce::call_once<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> >,tuple$<> >(struct winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> >) (@core::ops::function::FnOnce::call_once:8)
void core::panic::unwind_safe::impl$23::call_once<tuple$<>,winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >(struct core::panic::unwind_safe::AssertUnwindSafe<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >) (@<core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once:10)
static void std::panicking::try::do_call<core::panic::unwind_safe::AssertUnwindSafe<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >,tuple$<> >(unsigned char *) (@7ff7e12729e4..7ff7e1272a8e:3)
140014993 (@7ff7e1274993..7ff7e1274a01:3)
union enum2$<core::result::Result<tuple$<>,alloc::boxed::Box<dyn$<core::any::Any,core::marker::Send>,alloc::alloc::Global> > > std::panicking::try<tuple$<>,core::panic::unwind_safe::AssertUnwindSafe<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > > >(struct core::panic::unwind_safe::AssertUnwindSafe<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >) (@std::panicking::try:18)
union enum2$<core::result::Result<tuple$<>,alloc::boxed::Box<dyn$<core::any::Any,core::marker::Send>,alloc::alloc::Global> > > std::panic::catch_unwind<core::panic::unwind_safe::AssertUnwindSafe<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >,tuple$<> >(struct core::panic::unwind_safe::AssertUnwindSafe<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >) (@std::panic::catch_unwind:5)
union enum2$<core::option::Option<tuple$<> > > winit::platform_impl::platform::event_loop::runner::EventLoopRunner<enum2$<neovide::window::UserEvent> >::catch_unwind<enum2$<neovide::window::UserEvent>,tuple$<>,winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >(struct winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> >) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\event_loop\runner.rs:157)
static __int64 winit::platform_impl::platform::event_loop::public_window_callback_inner<enum2$<neovide::window::UserEvent> >(__int64, unsigned int, unsigned __int64, __int64, struct winit::platform_impl::platform::event_loop::WindowData<enum2$<neovide::window::UserEvent> > *) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\event_loop.rs:1030)
__int64 winit::platform_impl::platform::event_loop::public_window_callback<enum2$<neovide::window::UserEvent> >(__int64, unsigned int, unsigned __int64, __int64) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\event_loop.rs:968)
CallWindowProcW (@CallWindowProcW:248)
CallWindowProcW (@CallWindowProcW:39)
wglSwapBuffers (@wglSwapBuffers:129)
CallWindowProcW (@CallWindowProcW:248)
EnumChildWindows (@EnumChildWindows:78)
IsIconic (@IsIconic:108)
KiUserCallbackDispatcher (@KiUserCallbackDispatcher:10)
NtUserPeekMessage (@NtUserPeekMessage:8)
PeekMessageW (@PeekMessageW:127)
PeekMessageW (@PeekMessageW:80)
static union enum2$<core::option::Option<windows_sys::Windows::Win32::UI::WindowsAndMessaging::MSG> > winit::platform_impl::platform::keyboard::next_kbd_msg(__int64) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\keyboard.rs:899)
static union enum2$<winit::platform_impl::platform::keyboard::impl$1::process_message::MatchResult> winit::platform_impl::platform::keyboard::impl$1::process_message::closure$0(struct winit::platform_impl::platform::keyboard::impl$1::process_message::closure_env$0 *) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\keyboard.rs:292)
struct alloc::vec::Vec<winit::platform_impl::platform::keyboard::MessageAsKeyEvent,alloc::alloc::Global> winit::platform_impl::platform::keyboard::KeyEventBuilder::process_message(__int64, unsigned int, unsigned __int64, __int64, union enum2$<winit::platform_impl::platform::event_loop::ProcResult> *) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\keyboard.rs:319)
void winit::platform_impl::platform::event_loop::public_window_callback_inner::closure$2<enum2$<neovide::window::UserEvent> >(struct winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > *) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\event_loop.rs:1016)
void core::ops::function::FnOnce::call_once<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> >,tuple$<> >(struct winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> >) (@core::ops::function::FnOnce::call_once:8)
void core::panic::unwind_safe::impl$23::call_once<tuple$<>,winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >(struct core::panic::unwind_safe::AssertUnwindSafe<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >) (@<core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once:10)
static void std::panicking::try::do_call<core::panic::unwind_safe::AssertUnwindSafe<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >,tuple$<> >(unsigned char *) (@7ff7e12729e4..7ff7e1272a8e:3)
140014993 (@7ff7e1274993..7ff7e1274a01:3)
union enum2$<core::result::Result<tuple$<>,alloc::boxed::Box<dyn$<core::any::Any,core::marker::Send>,alloc::alloc::Global> > > std::panicking::try<tuple$<>,core::panic::unwind_safe::AssertUnwindSafe<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > > >(struct core::panic::unwind_safe::AssertUnwindSafe<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >) (@std::panicking::try:18)
union enum2$<core::result::Result<tuple$<>,alloc::boxed::Box<dyn$<core::any::Any,core::marker::Send>,alloc::alloc::Global> > > std::panic::catch_unwind<core::panic::unwind_safe::AssertUnwindSafe<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >,tuple$<> >(struct core::panic::unwind_safe::AssertUnwindSafe<winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >) (@std::panic::catch_unwind:5)
union enum2$<core::option::Option<tuple$<> > > winit::platform_impl::platform::event_loop::runner::EventLoopRunner<enum2$<neovide::window::UserEvent> >::catch_unwind<enum2$<neovide::window::UserEvent>,tuple$<>,winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> > >(struct winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$2<enum2$<neovide::window::UserEvent> >) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\event_loop\runner.rs:157)
static __int64 winit::platform_impl::platform::event_loop::public_window_callback_inner<enum2$<neovide::window::UserEvent> >(__int64, unsigned int, unsigned __int64, __int64, struct winit::platform_impl::platform::event_loop::WindowData<enum2$<neovide::window::UserEvent> > *) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\event_loop.rs:1030)
__int64 winit::platform_impl::platform::event_loop::public_window_callback<enum2$<neovide::window::UserEvent> >(__int64, unsigned int, unsigned __int64, __int64) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\event_loop.rs:968)
CallWindowProcW (@CallWindowProcW:248)
CallWindowProcW (@CallWindowProcW:39)
wglSwapBuffers (@wglSwapBuffers:129)
CallWindowProcW (@CallWindowProcW:248)
DispatchMessageW (@DispatchMessageW:172)
static int winit::platform_impl::platform::event_loop::EventLoop<enum2$<neovide::window::UserEvent> >::run_return<enum2$<neovide::window::UserEvent>,neovide::window::create_window::closure_env$1>(struct neovide::window::create_window::closure_env$1) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\event_loop.rs:292)
void winit::platform_impl::platform::event_loop::EventLoop<enum2$<neovide::window::UserEvent> >::run<enum2$<neovide::window::UserEvent>,neovide::window::create_window::closure_env$1>(struct winit::platform_impl::platform::event_loop::EventLoop<enum2$<neovide::window::UserEvent> >, struct neovide::window::create_window::closure_env$1) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\platform_impl\windows\event_loop.rs:255)
void winit::event_loop::EventLoop<enum2$<neovide::window::UserEvent> >::run<enum2$<neovide::window::UserEvent>,neovide::window::create_window::closure_env$1>(struct winit::event_loop::EventLoop<enum2$<neovide::window::UserEvent> >, struct neovide::window::create_window::closure_env$1) (c:\Users\fred.sundvik\.cargo\git\checkouts\winit-58efae9df3f5264a\4d8d82f\src\event_loop.rs:305)
void neovide::window::create_window() (f:\neovide\src\window\mod.rs:618)
static void neovide::protected_main() (f:\neovide\src\main.rs:172)
static void neovide::main() (f:\neovide\src\main.rs:75)
void core::ops::function::FnOnce::call_once<void (*)(),tuple$<> >( *) (@core::ops::function::FnOnce::call_once:6)
void std::sys_common::backtrace::__rust_begin_short_backtrace<void (*)(),tuple$<> >( *) (@std::sys_common::backtrace::__rust_begin_short_backtrace:6)
int std::rt::lang_start::closure$0<tuple$<> >(struct std::rt::lang_start::closure_env$0<tuple$<> > *) (@std::rt::lang_start::{{closure}}:7)
void std::rt::lang_start_internal() (@std::rt::lang_start_internal:45)
__int64 std::rt::lang_start<tuple$<> >( *, __int64, unsigned char * *, unsigned char) (@std::rt::lang_start:16)
main (@main:9)
static int __scrt_common_main_seh() (@7ff7e1ecf16c..7ff7e1ecf1d3:3)
BaseThreadInitThunk (@BaseThreadInitThunk:8)
RtlUserThreadStart (@RtlUserThreadStart:13)

That is probably caused by these extra WM_PAINT messages and could explain why a few frames still are dropped on Windows with https://github.com/neovide/neovide/pull/1870.

rib commented 1 year ago

Hi, it could be worth taking a look at https://github.com/rust-windowing/winit/pull/2767 where I've overhauled the structure of the event loops for each platform. Overall I'd say that the Windows backend was the one that changed the most, including enabling the use of PeekMessage when we want to check for messages without blocking.

fredizzimo commented 1 year ago

Thank you @rib. That does indeed look promising. I will take a closer look, maybe during the weekend.