Last active
September 2, 2022 11:45
-
-
Save Nnubes256/19b382ec19abf41b8d29749d5910251e to your computer and use it in GitHub Desktop.
learn-wgpu macOS M1 memory leak fix, CVDisplayLink variant
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Add this to Cargo.toml | |
[target.'cfg(target_os = "macos")'.dependencies] | |
display-link = "0.2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ... | |
#[cfg_attr(target_arch="wasm32", wasm_bindgen(start))] | |
pub async fn run() { | |
cfg_if::cfg_if! { | |
if #[cfg(target_arch = "wasm32")] { | |
std::panic::set_hook(Box::new(console_error_panic_hook::hook)); | |
console_log::init_with_level(log::Level::Warn).expect("Could't initialize logger"); | |
} else { | |
env_logger::init(); | |
} | |
} | |
let event_loop = EventLoop::new(); | |
let window = WindowBuilder::new().build(&event_loop).unwrap(); | |
#[cfg(target_arch = "wasm32")] | |
{ | |
// Winit prevents sizing with CSS, so we have to set | |
// the size manually when on web. | |
use winit::dpi::PhysicalSize; | |
window.set_inner_size(PhysicalSize::new(450, 400)); | |
use winit::platform::web::WindowExtWebSys; | |
web_sys::window() | |
.and_then(|win| win.document()) | |
.and_then(|doc| { | |
let dst = doc.get_element_by_id("wasm-example")?; | |
let canvas = web_sys::Element::from(window.canvas()); | |
dst.append_child(&canvas).ok()?; | |
Some(()) | |
}) | |
.expect("Couldn't append canvas to document body."); | |
} | |
// State::new uses async code, so we're going to wait for it to finish | |
let mut state = State::new(&window).await; | |
// !!! NEW !!! | |
#[cfg(target_os = "macos")] | |
let (_dl, semaphore) = { | |
use std::sync::{Arc, Mutex, Condvar}; | |
// Create a makeshift semaphore from a Mutex and a Condvar. | |
// This technique is discussed in the Rust standard library docs here: | |
// https://doc.rust-lang.org/std/sync/struct.Condvar.html | |
let pair = Arc::new((Mutex::new(false), Condvar::new())); | |
// Setup CVDisplayLink | |
let pair2 = Arc::clone(&pair); | |
let mut dl = display_link::DisplayLink::new(move |_ts| { | |
// This will be called on every vsync. | |
// TODO: _ts contains an absolute timestamp which can be used | |
// to calculate a delta time for updates, could be interesting | |
// to figure out how to pass it over? | |
let (lock, cvar) = &*pair2; | |
let mut do_redraw = lock.lock().unwrap(); | |
*do_redraw = true; | |
// Notify the Winit runloop if it's sleeping. | |
cvar.notify_one(); | |
}).unwrap(); | |
// Start the CVDisplayLink | |
dl.resume().unwrap(); | |
// CVDisplayLink needs to be hoisted out of here, otherwise it | |
// doesn't seem to work. | |
(_dl, pair) | |
}; | |
event_loop.run(move |event, _, control_flow| { | |
*control_flow = ControlFlow::Poll; | |
match event { | |
Event::WindowEvent { | |
ref event, | |
window_id, | |
} if window_id == window.id() => { | |
if !state.input(event) { | |
match event { | |
WindowEvent::CloseRequested | |
| WindowEvent::KeyboardInput { | |
input: | |
KeyboardInput { | |
state: ElementState::Pressed, | |
virtual_keycode: Some(VirtualKeyCode::Escape), | |
.. | |
}, | |
.. | |
} => *control_flow = ControlFlow::Exit, | |
WindowEvent::Resized(physical_size) => { | |
state.resize(*physical_size); | |
} | |
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { | |
// new_inner_size is &mut so w have to dereference it twice | |
state.resize(**new_inner_size); | |
} | |
_ => {} | |
} | |
} | |
} | |
Event::RedrawRequested(window_id) if window_id == window.id() => { | |
state.update(); | |
match state.render() { | |
Ok(_) => {} | |
// Reconfigure the surface if it's lost or outdated | |
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size), | |
// The system is out of memory, we should probably quit | |
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, | |
// We're ignoring timeouts | |
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"), | |
} | |
} | |
Event::MainEventsCleared => { | |
// RedrawRequested will only trigger once, unless we manually | |
// request it. | |
// !!! NEW !!! | |
#[cfg(target_os = "macos")] | |
{ | |
// Wait until CVDisplayLink tells us we can request a redraw. | |
let (lock, cvar) = &*semaphore; | |
let mut do_redraw = lock.lock().unwrap(); | |
// As long as the value inside the `Mutex<bool>` is `false`, | |
// we wait. | |
while !*do_redraw { | |
do_redraw = cvar.wait(do_redraw).unwrap(); | |
} | |
// Reset the value | |
*do_redraw = false; | |
} | |
window.request_redraw(); | |
} | |
_ => {} | |
} | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment