Skip to content

Commit

Permalink
Add a render thread
Browse files Browse the repository at this point in the history
The default event loop on winit, at least on Windows is too slow. See
rust-windowing/winit#2782
  • Loading branch information
fredizzimo committed Jul 24, 2023
1 parent 8b53bdc commit 5cef507
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 58 deletions.
3 changes: 2 additions & 1 deletion src/renderer/cursor_renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{
renderer::animation_utils::*,
renderer::{GridRenderer, RenderedWindow},
settings::{ParseFromValue, SETTINGS},
window::UserEvent,
};

use blink::*;
Expand Down Expand Up @@ -198,7 +199,7 @@ impl CursorRenderer {
renderer
}

pub fn handle_event(&mut self, event: &Event<()>) {
pub fn handle_event(&mut self, event: &Event<UserEvent>) {
if let Event::WindowEvent {
event: WindowEvent::Focused(is_focused),
..
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::{
event_aggregator::EVENT_AGGREGATOR,
profiling::tracy_zone,
settings::*,
window::UserEvent,
WindowSettings,
};

Expand Down Expand Up @@ -130,7 +131,7 @@ impl Renderer {
}
}

pub fn handle_event(&mut self, event: &Event<()>) {
pub fn handle_event(&mut self, event: &Event<UserEvent>) {
self.cursor_renderer.handle_event(event);
}

Expand Down
4 changes: 3 additions & 1 deletion src/window/keyboard_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -24,7 +26,7 @@ impl KeyboardManager {
}
}

pub fn handle_event(&mut self, event: &Event<()>) {
pub fn handle_event(&mut self, event: &Event<UserEvent>) {
match event {
Event::WindowEvent {
event:
Expand Down
138 changes: 122 additions & 16 deletions src/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod draw_background;
use std::env;

use winit::{
event_loop::EventLoop,
event_loop::EventLoopBuilder,
window::{Icon, WindowBuilder},
};

Expand All @@ -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;
Expand All @@ -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};

Expand All @@ -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");
Expand All @@ -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::<UserEvent>::with_user_event().build();

let cmd_line_settings = SETTINGS.get::<CmdLineSettings>();

Expand Down Expand Up @@ -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::<Event<UserEvent>>();
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());
}

});
}
}
4 changes: 2 additions & 2 deletions src/window/mouse_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{
renderer::{Renderer, WindowDrawDetails},
settings::SETTINGS,
window::keyboard_manager::KeyboardManager,
window::WindowSettings,
window::{UserEvent, WindowSettings},
};

fn clamp_position(
Expand Down Expand Up @@ -422,7 +422,7 @@ impl MouseManager {

pub fn handle_event(
&mut self,
event: &Event<()>,
event: &Event<UserEvent>,
keyboard_manager: &KeyboardManager,
renderer: &Renderer,
window: &Window,
Expand Down
51 changes: 26 additions & 25 deletions src/window/update_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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::<WindowSettings>().refresh_rate as f32
Expand All @@ -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<Event<UserEvent>, bool>,
) -> Result<ControlFlow, ()> {
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
{
Expand Down Expand Up @@ -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 let Ok(event) = event {
self.should_render |= window_wrapper.handle_event(event);
}

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))
}
}
}
Loading

0 comments on commit 5cef507

Please sign in to comment.