Fix laggy rendering when resizing win32 window

This commit is contained in:
Osspial 2017-08-02 20:49:50 -04:00
parent a582df443b
commit 13bd116891

View file

@ -22,6 +22,7 @@ use std::ptr;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use std::sync::Barrier;
use std::thread; use std::thread;
use kernel32; use kernel32;
@ -78,12 +79,16 @@ pub struct EventsLoop {
thread_id: winapi::DWORD, thread_id: winapi::DWORD,
// Receiver for the events. The sender is in the background thread. // Receiver for the events. The sender is in the background thread.
receiver: mpsc::Receiver<Event>, receiver: mpsc::Receiver<Event>,
// Barrier used to unblock the event loop thread after a resize event is processed.
resize_barrier: Arc<Barrier>
} }
impl EventsLoop { impl EventsLoop {
pub fn new() -> EventsLoop { pub fn new() -> EventsLoop {
// The main events transfer channel. // The main events transfer channel.
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let resize_barrier = Arc::new(Barrier::new(2));
let resize_barrier_child = resize_barrier.clone();
// Local channel in order to block the `new()` function until the background thread has // Local channel in order to block the `new()` function until the background thread has
// an events queue. // an events queue.
@ -94,6 +99,8 @@ impl EventsLoop {
*context_stash.borrow_mut() = Some(ThreadLocalData { *context_stash.borrow_mut() = Some(ThreadLocalData {
sender: tx, sender: tx,
windows: HashMap::with_capacity(4), windows: HashMap::with_capacity(4),
resize_barrier: resize_barrier_child,
deferred_waits: 0
}); });
}); });
@ -123,6 +130,15 @@ impl EventsLoop {
x if x == *WAKEUP_MSG_ID => { x if x == *WAKEUP_MSG_ID => {
send_event(Event::Awakened); send_event(Event::Awakened);
}, },
x if x == *USE_WAIT_ID => CONTEXT_STASH.with(|context_stash| {
let mut context_stash = context_stash.borrow_mut();
let cstash = context_stash.as_mut().unwrap();
// Run a deferred wait
if cstash.deferred_waits > 0 {
cstash.deferred_waits -= 1;
cstash.resize_barrier.wait();
}
}),
_ => { _ => {
// Calls `callback` below. // Calls `callback` below.
user32::TranslateMessage(&msg); user32::TranslateMessage(&msg);
@ -139,6 +155,7 @@ impl EventsLoop {
EventsLoop { EventsLoop {
thread_id: unsafe { kernel32::GetThreadId(thread.as_raw_handle()) }, thread_id: unsafe { kernel32::GetThreadId(thread.as_raw_handle()) },
receiver: rx, receiver: rx,
resize_barrier: resize_barrier
} }
} }
@ -150,8 +167,12 @@ impl EventsLoop {
Ok(e) => e, Ok(e) => e,
Err(_) => return Err(_) => return
}; };
let is_resize = event_is_resize(&event);
callback(event); callback(event);
if is_resize {
self.sync_with_thread();
}
} }
} }
@ -163,8 +184,12 @@ impl EventsLoop {
Ok(e) => e, Ok(e) => e,
Err(_) => return Err(_) => return
}; };
let is_resize = event_is_resize(&event);
let flow = callback(event); let flow = callback(event);
if is_resize {
self.sync_with_thread();
}
match flow { match flow {
ControlFlow::Continue => continue, ControlFlow::Continue => continue,
ControlFlow::Break => break, ControlFlow::Break => break,
@ -178,6 +203,12 @@ impl EventsLoop {
} }
} }
fn sync_with_thread(&self) {
let res = unsafe{ user32::PostThreadMessageA(self.thread_id, *USE_WAIT_ID, 0, 0) };
self.resize_barrier.wait();
assert!(res != 0, "PostThreadMessage failed ; is the messages queue full?");
}
/// Executes a function in the background thread. /// Executes a function in the background thread.
/// ///
/// Note that we use a FnMut instead of a FnOnce because we're too lazy to create an equivalent /// Note that we use a FnMut instead of a FnOnce because we're too lazy to create an equivalent
@ -251,6 +282,13 @@ lazy_static! {
user32::RegisterWindowMessageA("Winit::ExecMsg".as_ptr() as *const i8) user32::RegisterWindowMessageA("Winit::ExecMsg".as_ptr() as *const i8)
} }
}; };
// Message sent when the parent thread receives a resize event and wants this thread to use any
// deferred waits it may have.
static ref USE_WAIT_ID: u32 = {
unsafe {
user32::RegisterWindowMessageA("Winit::UseWait\0".as_ptr() as *const i8)
}
};
} }
// There's no parameters passed to the callback function, so it needs to get its context stashed // There's no parameters passed to the callback function, so it needs to get its context stashed
@ -259,12 +297,22 @@ thread_local!(static CONTEXT_STASH: RefCell<Option<ThreadLocalData>> = RefCell::
struct ThreadLocalData { struct ThreadLocalData {
sender: mpsc::Sender<Event>, sender: mpsc::Sender<Event>,
windows: HashMap<winapi::HWND, Arc<Mutex<WindowState>>>, windows: HashMap<winapi::HWND, Arc<Mutex<WindowState>>>,
resize_barrier: Arc<Barrier>,
deferred_waits: u32
}
fn event_is_resize(event: &Event) -> bool {
match *event {
Event::WindowEvent{ event: WindowEvent::Resized(..), .. } => true,
_ => false
}
} }
// Utility function that dispatches an event on the current thread. // Utility function that dispatches an event on the current thread.
fn send_event(event: Event) { fn send_event(event: Event) {
CONTEXT_STASH.with(|context_stash| { CONTEXT_STASH.with(|context_stash| {
let context_stash = context_stash.borrow(); let context_stash = context_stash.borrow();
let _ = context_stash.as_ref().unwrap().sender.send(event); // Ignoring if closed let _ = context_stash.as_ref().unwrap().sender.send(event); // Ignoring if closed
}); });
} }
@ -306,6 +354,22 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
window_id: SuperWindowId(WindowId(window)), window_id: SuperWindowId(WindowId(window)),
event: Resized(w, h), event: Resized(w, h),
}); });
// Wait for the parent thread to process the resize event before returning from the
// callback.
CONTEXT_STASH.with(|context_stash| {
let mut context_stash = context_stash.borrow_mut();
let cstash = context_stash.as_mut().unwrap();
if cstash.windows.get(&window).is_some() {
cstash.resize_barrier.wait();
} else {
// If the window isn't in the hashmap, this is the resize event that was sent
// upon window creation. The parent thread isn't going to wait until the event
// loop, so record that a wait must happen when the event loop is reached.
cstash.deferred_waits += 1;
}
});
0 0
}, },