From 31250d38a73bc3f222f2b603105f53ebb45d8b57 Mon Sep 17 00:00:00 2001 From: Fred Sundvik Date: Fri, 4 Nov 2022 12:56:07 +0200 Subject: [PATCH] Add a render thread The default event loop on winit, at least on Windows is too slow. See https://github.com/rust-windowing/winit/issues/2782 --- src/renderer/cursor_renderer/mod.rs | 3 +- src/renderer/mod.rs | 3 +- src/window/keyboard_manager.rs | 4 +- src/window/mod.rs | 138 ++++++++++++++++++++++++---- src/window/mouse_manager.rs | 4 +- src/window/update_loop.rs | 51 +++++----- src/window/window_wrapper.rs | 26 +++--- 7 files changed, 171 insertions(+), 58 deletions(-) diff --git a/src/renderer/cursor_renderer/mod.rs b/src/renderer/cursor_renderer/mod.rs index e82ec2ad0f..990027cc2e 100644 --- a/src/renderer/cursor_renderer/mod.rs +++ b/src/renderer/cursor_renderer/mod.rs @@ -13,6 +13,7 @@ use crate::{ renderer::animation_utils::*, renderer::{GridRenderer, RenderedWindow}, settings::{ParseFromValue, SETTINGS}, + window::UserEvent, }; use blink::*; @@ -198,7 +199,7 @@ impl CursorRenderer { renderer } - pub fn handle_event(&mut self, event: &Event<()>) { + pub fn handle_event(&mut self, event: &Event) { if let Event::WindowEvent { event: WindowEvent::Focused(is_focused), .. diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index e2c843a993..65117e9015 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -23,6 +23,7 @@ use crate::{ event_aggregator::EVENT_AGGREGATOR, profiling::tracy_zone, settings::*, + window::UserEvent, WindowSettings, }; @@ -132,7 +133,7 @@ impl Renderer { } } - pub fn handle_event(&mut self, event: &Event<()>) { + pub fn handle_event(&mut self, event: &Event) { self.cursor_renderer.handle_event(event); } diff --git a/src/window/keyboard_manager.rs b/src/window/keyboard_manager.rs index b38be1fb7b..97cb400a4c 100644 --- a/src/window/keyboard_manager.rs +++ b/src/window/keyboard_manager.rs @@ -2,6 +2,8 @@ use crate::{ bridge::{SerialCommand, UiCommand}, event_aggregator::EVENT_AGGREGATOR, }; + +use crate::window::UserEvent; #[cfg(target_os = "macos")] use crate::{settings::SETTINGS, window::KeyboardSettings}; #[allow(unused_imports)] @@ -24,7 +26,7 @@ impl KeyboardManager { } } - pub fn handle_event(&mut self, event: &Event<()>) { + pub fn handle_event(&mut self, event: &Event) { match event { Event::WindowEvent { event: diff --git a/src/window/mod.rs b/src/window/mod.rs index ffb68ca1a6..9a09cd20e3 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -12,7 +12,7 @@ mod draw_background; use std::env; use winit::{ - event_loop::EventLoop, + event_loop::EventLoopBuilder, window::{Icon, WindowBuilder}, }; @@ -27,6 +27,17 @@ use winit::platform::wayland::WindowBuilderExtWayland; #[cfg(target_os = "linux")] use winit::platform::x11::WindowBuilderExtX11; +#[cfg(target_os = "windows")] +use std::{ + sync::mpsc::{channel, RecvTimeoutError, TryRecvError}, + thread, +}; +#[cfg(target_os = "windows")] +use winit::{ + event::{Event, WindowEvent}, + event_loop::ControlFlow, +}; + use image::{load_from_memory, GenericImageView, Pixel}; use keyboard_manager::KeyboardManager; use mouse_manager::MouseManager; @@ -37,9 +48,9 @@ use window_wrapper::WinitWindowWrapper; use crate::{ cmd_line::CmdLineSettings, frame::Frame, - profiling::tracy_create_gpu_context, renderer::build_window, - settings::{load_last_window_settings, PersistentWindowSettings, SETTINGS}, + running_tracker::*, + settings::{load_last_window_settings, save_window_size, PersistentWindowSettings, SETTINGS}, }; pub use settings::{KeyboardSettings, WindowSettings}; @@ -52,6 +63,13 @@ pub enum WindowCommand { ListAvailableFonts, } + +#[allow(dead_code)] +#[derive(Clone, Debug)] +pub enum UserEvent { + ScaleFactorChanged(f64), +} + pub fn create_window() { let icon = { let icon = load_from_memory(ICON).expect("Failed to parse icon data"); @@ -63,7 +81,7 @@ pub fn create_window() { Icon::from_rgba(rgba, width, height).expect("Failed to create icon object") }; - let event_loop = EventLoop::new(); + let event_loop = EventLoopBuilder::::with_user_event().build(); let cmd_line_settings = SETTINGS.get::(); @@ -131,19 +149,107 @@ pub fn create_window() { let winit_window_builder = winit_window_builder.with_accepts_first_mouse(false); let (window, config) = build_window(winit_window_builder, &event_loop); - let mut window_wrapper = WinitWindowWrapper::new( - window, - config, - &cmd_line_settings, - previous_position, - maximized, - ); - tracy_create_gpu_context("main_render_context"); + // Use a render thread on Windows to work around performance issues with Winit + // see: https://github.com/rust-windowing/winit/issues/2782 + #[cfg(target_os = "windows")] + { + let (txtemp, rx) = channel::>(); + let mut tx = Some(txtemp); + let mut render_thread_handle = Some(thread::spawn(move || { + let mut window_wrapper = WinitWindowWrapper::new( + window, + config, + &cmd_line_settings, + previous_position, + maximized, + ); + let mut update_loop = UpdateLoop::new(cmd_line_settings.idle); + #[allow(unused_assignments)] + loop { + let (wait_duration, _) = update_loop.get_event_wait_time(); + let event = if wait_duration.as_nanos() == 0 { + rx.try_recv() + .map_err(|e| matches!(e, TryRecvError::Disconnected)) + } else { + rx.recv_timeout(wait_duration) + .map_err(|e| matches!(e, RecvTimeoutError::Disconnected)) + }; + + if update_loop.step(&mut window_wrapper, event).is_err() { + break; + } + } + let window = window_wrapper.windowed_context.window(); + save_window_size( + window.is_maximized(), + window.inner_size(), + window.outer_position().ok(), + ); + std::process::exit(RUNNING_TRACKER.exit_code()); + })); + + event_loop.run(move |e, _window_target, control_flow| { + let e = match e { + Event::WindowEvent { + event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, + .. + } => { + // It's really unfortunate that we have to do this, but + // https://github.com/rust-windowing/winit/issues/1387 + Some(Event::UserEvent(UserEvent::ScaleFactorChanged( + scale_factor, + ))) + } + Event::MainEventsCleared => None, + _ => { + // With the current Winit version, all events, except ScaleFactorChanged are static + Some(e.to_static().expect("Unexpected event received")) + } + }; + if let Some(e) = e { + let _ = tx.as_ref().unwrap().send(e); + } - let mut update_loop = UpdateLoop::new(cmd_line_settings.idle); + if !RUNNING_TRACKER.is_running() { + let tx = tx.take().unwrap(); + drop(tx); + let handle = render_thread_handle.take().unwrap(); + handle.join().unwrap(); + } + // We need to wake up regularly to check the running tracker, so that we can exit + *control_flow = ControlFlow::WaitUntil( + std::time::Instant::now() + std::time::Duration::from_millis(100), + ); + }); + } - event_loop.run(move |e, _window_target, control_flow| { - *control_flow = update_loop.step(&mut window_wrapper, e); - }); + #[cfg(not(target_os = "windows"))] + { + let mut window_wrapper = WinitWindowWrapper::new( + window, + config, + &cmd_line_settings, + previous_position, + maximized, + ); + + let mut update_loop = UpdateLoop::new(cmd_line_settings.idle); + + event_loop.run(move |e, _window_target, control_flow| { + *control_flow = update_loop.step(&mut window_wrapper, Ok(e)).unwrap(); + + if !RUNNING_TRACKER.is_running() { + let window = window_wrapper.windowed_context.window(); + save_window_size( + window.is_maximized(), + window.inner_size(), + window.outer_position().ok(), + ); + + std::process::exit(RUNNING_TRACKER.exit_code()); + } + + }); + } } diff --git a/src/window/mouse_manager.rs b/src/window/mouse_manager.rs index ed4b9564d6..3e6b4904b0 100644 --- a/src/window/mouse_manager.rs +++ b/src/window/mouse_manager.rs @@ -20,7 +20,7 @@ use crate::{ renderer::{Renderer, WindowDrawDetails}, settings::SETTINGS, window::keyboard_manager::KeyboardManager, - window::WindowSettings, + window::{UserEvent, WindowSettings}, }; fn clamp_position( @@ -422,7 +422,7 @@ impl MouseManager { pub fn handle_event( &mut self, - event: &Event<()>, + event: &Event, keyboard_manager: &KeyboardManager, renderer: &Renderer, window: &Window, diff --git a/src/window/update_loop.rs b/src/window/update_loop.rs index cff6337138..37f98966c7 100644 --- a/src/window/update_loop.rs +++ b/src/window/update_loop.rs @@ -8,11 +8,10 @@ use winit::{ #[cfg(target_os = "macos")] use super::draw_background; -use super::{WindowSettings, WinitWindowWrapper}; +use super::{UserEvent, WindowSettings, WinitWindowWrapper}; use crate::{ profiling::{tracy_create_gpu_context, tracy_zone}, - running_tracker::*, - settings::{save_window_size, SETTINGS}, + settings::SETTINGS, }; enum FocusedState { @@ -55,7 +54,7 @@ impl UpdateLoop { } } - fn get_event_deadline(&self) -> Instant { + pub fn get_event_wait_time(&self) -> (Duration, Instant) { let refresh_rate = match self.focused { FocusedState::Focused | FocusedState::UnfocusedNotDrawn => { SETTINGS.get::().refresh_rate as f32 @@ -65,29 +64,38 @@ impl UpdateLoop { .max(1.0); let expected_frame_duration = Duration::from_secs_f32(1.0 / refresh_rate); - self.previous_frame_start + expected_frame_duration + if self.num_consecutive_rendered > 0 { + (Duration::from_nanos(0), Instant::now()) + } else { + let deadline = self.previous_frame_start + expected_frame_duration; + (deadline.saturating_duration_since(Instant::now()), deadline) + } } pub fn step( &mut self, window_wrapper: &mut WinitWindowWrapper, - event: Event<()>, - ) -> ControlFlow { + event: Result, bool>, + ) -> Result { tracy_zone!("render loop", 0); match event { // Window focus changed - Event::WindowEvent { + Ok(Event::WindowEvent { event: WindowEvent::Focused(focused_event), .. - } => { + }) => { self.focused = if focused_event { FocusedState::Focused } else { FocusedState::UnfocusedNotDrawn }; } - Event::MainEventsCleared => { + Err(true) => { + // Disconnected + return Err(()); + } + Err(false) | Ok(Event::MainEventsCleared) => { let dt = if self.num_consecutive_rendered > 0 && self.frame_dt_avg.get_num_samples() > 0 { @@ -123,28 +131,21 @@ impl UpdateLoop { #[cfg(target_os = "macos")] draw_background(window_wrapper.windowed_context.window()); } - _ => (), + _ => {} } + window_wrapper.handle_window_commands(); + window_wrapper.synchronize_settings(); - if !RUNNING_TRACKER.is_running() { - let window = window_wrapper.windowed_context.window(); - save_window_size( - window.is_maximized(), - window.inner_size(), - window.outer_position().ok(), - ); - - std::process::exit(RUNNING_TRACKER.exit_code()); + if event.is_ok() { + self.should_render |= window_wrapper.handle_event(event.unwrap()); } - window_wrapper.handle_window_commands(); - window_wrapper.synchronize_settings(); - self.should_render |= window_wrapper.handle_event(event); + let (_, deadline) = self.get_event_wait_time(); if self.num_consecutive_rendered > 0 { - ControlFlow::Poll + Ok(ControlFlow::Poll) } else { - ControlFlow::WaitUntil(self.get_event_deadline()) + Ok(ControlFlow::WaitUntil(deadline)) } } } diff --git a/src/window/window_wrapper.rs b/src/window/window_wrapper.rs index 24ce90f3a4..b885913878 100644 --- a/src/window/window_wrapper.rs +++ b/src/window/window_wrapper.rs @@ -1,5 +1,6 @@ use super::{ - KeyboardManager, KeyboardSettings, MouseManager, SkiaRenderer, WindowCommand, WindowSettings, + KeyboardManager, KeyboardSettings, MouseManager, SkiaRenderer, UserEvent, WindowCommand, + WindowSettings, }; use crate::{ @@ -28,6 +29,12 @@ use winit::{ const MIN_WINDOW_WIDTH: u64 = 20; const MIN_WINDOW_HEIGHT: u64 = 6; +pub fn set_background(background: &str) { + EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::SetBackground( + background.to_string(), + ))); +} + pub struct WinitWindowWrapper { pub windowed_context: WindowedContext, skia_renderer: SkiaRenderer, @@ -45,12 +52,6 @@ pub struct WinitWindowWrapper { ime_enabled: bool, } -pub fn set_background(background: &str) { - EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::SetBackground( - background.to_string(), - ))); -} - impl WinitWindowWrapper { pub fn new( window: Window, @@ -121,7 +122,7 @@ impl WinitWindowWrapper { _ => {} } - let mut window_wrapper = WinitWindowWrapper { + let mut wrapper = WinitWindowWrapper { windowed_context, skia_renderer, renderer, @@ -138,8 +139,8 @@ impl WinitWindowWrapper { ime_enabled, }; - window_wrapper.set_ime(ime_enabled); - window_wrapper + wrapper.set_ime(ime_enabled); + wrapper } pub fn toggle_fullscreen(&mut self) { @@ -215,7 +216,7 @@ impl WinitWindowWrapper { EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::FocusGained)); } - pub fn handle_event(&mut self, event: Event<()>) -> bool { + pub fn handle_event(&mut self, event: Event) -> bool { tracy_zone!("handle_event", 0); let mut should_render = false; self.keyboard_manager.handle_event(&event); @@ -242,7 +243,8 @@ impl WinitWindowWrapper { Event::WindowEvent { event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, .. - } => { + } + | Event::UserEvent(UserEvent::ScaleFactorChanged(scale_factor)) => { self.handle_scale_factor_update(scale_factor); } Event::WindowEvent {