Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

examples/winit: Implement proper resumed() semantics #127

Merged
merged 2 commits into from
Dec 16, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -79,21 +79,27 @@ mod winit_app;
fn main() {
let event_loop = EventLoop::new().unwrap();

let mut app = winit_app::WinitAppBuilder::with_init(|elwt| {
let window = {
let window = elwt.create_window(Window::default_attributes());
Rc::new(window.unwrap())
};
let context = softbuffer::Context::new(window.clone()).unwrap();
let surface = softbuffer::Surface::new(&context, window.clone()).unwrap();

(window, surface)
}).with_event_handler(|state, event, elwt| {
let (window, surface) = state;
let mut app = winit_app::WinitAppBuilder::with_init(
|elwt| {
let window = {
let window = elwt.create_window(Window::default_attributes());
Rc::new(window.unwrap())
};
let context = softbuffer::Context::new(window.clone()).unwrap();

(window, context)
},
|_elwt, (window, context)| softbuffer::Surface::new(context, window.clone()).unwrap(),
)
.with_event_handler(|(window, _context), surface, event, elwt| {
elwt.set_control_flow(ControlFlow::Wait);

match event {
Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested } if window_id == window.id() => {
let Some(surface) = surface else {
eprintln!("RedrawRequested fired before Resumed or after Suspended");
return;
};
let (width, height) = {
let size = window.inner_size();
(size.width, size.height)
34 changes: 24 additions & 10 deletions examples/animation.rs
Original file line number Diff line number Diff line change
@@ -14,19 +14,23 @@ fn main() {
let event_loop = EventLoop::new().unwrap();
let start = Instant::now();

let app = winit_app::WinitAppBuilder::with_init(|event_loop| {
let window = winit_app::make_window(event_loop, |w| w);
let app = winit_app::WinitAppBuilder::with_init(
|event_loop| {
let window = winit_app::make_window(event_loop, |w| w);

let context = softbuffer::Context::new(window.clone()).unwrap();
let surface = softbuffer::Surface::new(&context, window.clone()).unwrap();
let context = softbuffer::Context::new(window.clone()).unwrap();

let old_size = (0, 0);
let frames = pre_render_frames(0, 0);
let old_size = (0, 0);
let frames = pre_render_frames(0, 0);

(window, surface, old_size, frames)
})
.with_event_handler(move |state, event, elwt| {
let (window, surface, old_size, frames) = state;
(window, context, old_size, frames)
},
|_elwft, (window, context, _old_size, _frames)| {
softbuffer::Surface::new(context, window.clone()).unwrap()
},
)
.with_event_handler(move |state, surface, event, elwt| {
let (window, _context, old_size, frames) = state;

elwt.set_control_flow(ControlFlow::Poll);

@@ -35,6 +39,11 @@ fn main() {
window_id,
event: WindowEvent::Resized(size),
} if window_id == window.id() => {
let Some(surface) = surface else {
eprintln!("Resized fired before Resumed or after Suspended");
return;
};

if let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
{
@@ -45,6 +54,11 @@ fn main() {
window_id,
event: WindowEvent::RedrawRequested,
} if window_id == window.id() => {
let Some(surface) = surface else {
eprintln!("RedrawRequested fired before Resumed or after Suspended");
return;
};

let size = window.inner_size();
if let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
49 changes: 29 additions & 20 deletions examples/fruit.rs
Original file line number Diff line number Diff line change
@@ -14,35 +14,44 @@ fn main() {

let event_loop = EventLoop::new().unwrap();

let app = winit_app::WinitAppBuilder::with_init(move |elwt| {
let window = winit_app::make_window(elwt, |w| {
w.with_inner_size(winit::dpi::PhysicalSize::new(width, height))
});
let app = winit_app::WinitAppBuilder::with_init(
move |elwt| {
let window = winit_app::make_window(elwt, |w| {
w.with_inner_size(winit::dpi::PhysicalSize::new(width, height))
});

let context = softbuffer::Context::new(window.clone()).unwrap();
let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap();
let context = softbuffer::Context::new(window.clone()).unwrap();

// Intentionally only set the size of the surface once, at creation.
// This is needed if the window chooses to ignore the size we passed in above, and for the
// platforms softbuffer supports that don't yet extract the size from the window.
surface
.resize(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
)
.unwrap();

(window, surface)
})
.with_event_handler(move |state, event, elwt| {
let (window, surface) = state;
(window, context)
},
move |_elwt, (window, context)| {
let mut surface = softbuffer::Surface::new(context, window.clone()).unwrap();
// Intentionally only set the size of the surface once, at creation.
// This is needed if the window chooses to ignore the size we passed in above, and for the
// platforms softbuffer supports that don't yet extract the size from the window.
surface
.resize(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
)
.unwrap();
surface
},
)
.with_event_handler(move |state, surface, event, elwt| {
let (window, _context) = state;
elwt.set_control_flow(ControlFlow::Wait);

match event {
Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
} if window_id == window.id() => {
let Some(surface) = surface else {
eprintln!("RedrawRequested fired before Resumed or after Suspended");
return;
};

let mut buffer = surface.buffer_mut().unwrap();
let width = fruit.width() as usize;
for (x, y, pixel) in fruit.pixels() {
35 changes: 24 additions & 11 deletions examples/rectangle.rs
Original file line number Diff line number Diff line change
@@ -25,20 +25,24 @@ fn redraw(buffer: &mut [u32], width: usize, height: usize, flag: bool) {
fn main() {
let event_loop = EventLoop::new().unwrap();

let app = winit_app::WinitAppBuilder::with_init(|elwt| {
let window = winit_app::make_window(elwt, |w| {
w.with_title("Press space to show/hide a rectangle")
});
let app = winit_app::WinitAppBuilder::with_init(
|elwt| {
let window = winit_app::make_window(elwt, |w| {
w.with_title("Press space to show/hide a rectangle")
});

let context = softbuffer::Context::new(window.clone()).unwrap();
let surface = softbuffer::Surface::new(&context, window.clone()).unwrap();
let context = softbuffer::Context::new(window.clone()).unwrap();

let flag = false;
let flag = false;

(window, surface, flag)
})
.with_event_handler(|state, event, elwt| {
let (window, surface, flag) = state;
(window, context, flag)
},
|_elwt, (window, context, _flag)| {
softbuffer::Surface::new(context, window.clone()).unwrap()
},
)
.with_event_handler(|state, surface, event, elwt| {
let (window, _context, flag) = state;

elwt.set_control_flow(ControlFlow::Wait);

@@ -47,6 +51,11 @@ fn main() {
window_id,
event: WindowEvent::Resized(size),
} if window_id == window.id() => {
let Some(surface) = surface else {
eprintln!("Resized fired before Resumed or after Suspended");
return;
};

if let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
{
@@ -58,6 +67,10 @@ fn main() {
window_id,
event: WindowEvent::RedrawRequested,
} if window_id == window.id() => {
let Some(surface) = surface else {
eprintln!("RedrawRequested fired before Resumed or after Suspended");
return;
};
// Grab the window's client area dimensions, and ensure they're valid
let size = window.inner_size();
if let (Some(width), Some(height)) =
71 changes: 50 additions & 21 deletions examples/utils/winit_app.rs
Original file line number Diff line number Diff line change
@@ -31,76 +31,94 @@ pub(crate) fn make_window(
}

/// Easily constructable winit application.
pub(crate) struct WinitApp<T, Init, Handler> {
/// Closure to initialize state.
pub(crate) struct WinitApp<T, S, Init, InitSurface, Handler> {
/// Closure to initialize `state`.
init: Init,

/// Closure to initialize `surface_state`.
init_surface: InitSurface,

/// Closure to run on window events.
event: Handler,

/// Contained state.
state: Option<T>,

/// Contained surface state.
surface_state: Option<S>,
}

/// Builder that makes it so we don't have to name `T`.
pub(crate) struct WinitAppBuilder<T, Init> {
/// Closure to initialize state.
pub(crate) struct WinitAppBuilder<T, S, Init, InitSurface> {
/// Closure to initialize `state`.
init: Init,

/// Closure to initialize `surface_state`.
init_surface: InitSurface,

/// Eat the type parameter.
_marker: PhantomData<Option<T>>,
_marker: PhantomData<(Option<T>, Option<S>)>,
}

impl<T, Init> WinitAppBuilder<T, Init>
impl<T, S, Init, InitSurface> WinitAppBuilder<T, S, Init, InitSurface>
where
Init: FnMut(&ActiveEventLoop) -> T,
InitSurface: FnMut(&ActiveEventLoop, &mut T) -> S,
{
/// Create with an "init" closure.
pub(crate) fn with_init(init: Init) -> Self {
pub(crate) fn with_init(init: Init, init_surface: InitSurface) -> Self {
Self {
init,
init_surface,
_marker: PhantomData,
}
}

/// Build a new application.
pub(crate) fn with_event_handler<F>(self, handler: F) -> WinitApp<T, Init, F>
pub(crate) fn with_event_handler<F>(self, handler: F) -> WinitApp<T, S, Init, InitSurface, F>
where
F: FnMut(&mut T, Event<()>, &ActiveEventLoop),
F: FnMut(&mut T, Option<&mut S>, Event<()>, &ActiveEventLoop),
{
WinitApp::new(self.init, handler)
WinitApp::new(self.init, self.init_surface, handler)
}
}

impl<T, Init, Handler> WinitApp<T, Init, Handler>
impl<T, S, Init, InitSurface, Handler> WinitApp<T, S, Init, InitSurface, Handler>
where
Init: FnMut(&ActiveEventLoop) -> T,
Handler: FnMut(&mut T, Event<()>, &ActiveEventLoop),
InitSurface: FnMut(&ActiveEventLoop, &mut T) -> S,
Handler: FnMut(&mut T, Option<&mut S>, Event<()>, &ActiveEventLoop),
{
/// Create a new application.
pub(crate) fn new(init: Init, event: Handler) -> Self {
pub(crate) fn new(init: Init, init_surface: InitSurface, event: Handler) -> Self {
Self {
init,
init_surface,
event,
state: None,
surface_state: None,
}
}
}

impl<T, Init, Handler> ApplicationHandler for WinitApp<T, Init, Handler>
impl<T, S, Init, InitSurface, Handler> ApplicationHandler
for WinitApp<T, S, Init, InitSurface, Handler>
where
Init: FnMut(&ActiveEventLoop) -> T,
Handler: FnMut(&mut T, Event<()>, &ActiveEventLoop),
InitSurface: FnMut(&ActiveEventLoop, &mut T) -> S,
Handler: FnMut(&mut T, Option<&mut S>, Event<()>, &ActiveEventLoop),
{
fn resumed(&mut self, el: &ActiveEventLoop) {
debug_assert!(self.state.is_none());
self.state = Some((self.init)(el));
let mut state = (self.init)(el);
self.surface_state = Some((self.init_surface)(el, &mut state));
self.state = Some(state);
}

fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
let state = self.state.take();
debug_assert!(state.is_some());
drop(state);
let surface_state = self.surface_state.take();
debug_assert!(surface_state.is_some());
drop(surface_state);
}

fn window_event(
@@ -110,12 +128,23 @@ where
event: WindowEvent,
) {
let state = self.state.as_mut().unwrap();
(self.event)(state, Event::WindowEvent { window_id, event }, event_loop);
let surface_state = self.surface_state.as_mut();
(self.event)(
state,
surface_state,
Event::WindowEvent { window_id, event },
event_loop,
);
}

fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
if let Some(state) = self.state.as_mut() {
(self.event)(state, Event::AboutToWait, event_loop);
(self.event)(
state,
self.surface_state.as_mut(),
Event::AboutToWait,
event_loop,
);
}
}
}
26 changes: 18 additions & 8 deletions examples/winit.rs
Original file line number Diff line number Diff line change
@@ -9,23 +9,29 @@ mod winit_app;
fn main() {
let event_loop = EventLoop::new().unwrap();

let app = winit_app::WinitAppBuilder::with_init(|elwt| {
let window = winit_app::make_window(elwt, |w| w);
let app = winit_app::WinitAppBuilder::with_init(
|elwt| {
let window = winit_app::make_window(elwt, |w| w);

let context = softbuffer::Context::new(window.clone()).unwrap();
let surface = softbuffer::Surface::new(&context, window.clone()).unwrap();
let context = softbuffer::Context::new(window.clone()).unwrap();

(window, surface)
})
.with_event_handler(|state, event, elwt| {
let (window, surface) = state;
(window, context)
},
|_elwt, (window, context)| softbuffer::Surface::new(context, window.clone()).unwrap(),
)
.with_event_handler(|(window, _context), surface, event, elwt| {
elwt.set_control_flow(ControlFlow::Wait);

match event {
Event::WindowEvent {
window_id,
event: WindowEvent::Resized(size),
} if window_id == window.id() => {
let Some(surface) = surface else {
eprintln!("Resized fired before Resumed or after Suspended");
return;
};

if let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
{
@@ -36,6 +42,10 @@ fn main() {
window_id,
event: WindowEvent::RedrawRequested,
} if window_id == window.id() => {
let Some(surface) = surface else {
eprintln!("RedrawRequested fired before Resumed or after Suspended");
return;
};
let size = window.inner_size();
if let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
74 changes: 41 additions & 33 deletions examples/winit_multithread.rs
Original file line number Diff line number Diff line change
@@ -19,16 +19,15 @@ mod ex {

fn render_thread(
window: Arc<Window>,
surface: Arc<Mutex<Surface>>,
do_render: mpsc::Receiver<()>,
do_render: mpsc::Receiver<Arc<Mutex<Surface>>>,
done: mpsc::Sender<()>,
) {
loop {
println!("waiting for render...");
if do_render.recv().is_err() {
// Main thread is dead.
let Ok(surface) = do_render.recv() else {
println!("main thread destroyed");
break;
}
};

// Perform the rendering.
let mut surface = surface.lock().unwrap();
@@ -63,43 +62,52 @@ mod ex {
pub(super) fn entry() {
let event_loop = EventLoop::new().unwrap();

let app = winit_app::WinitAppBuilder::with_init(|elwt| {
let attributes = Window::default_attributes();
#[cfg(target_arch = "wasm32")]
let attributes =
winit::platform::web::WindowAttributesExtWebSys::with_append(attributes, true);
let window = Arc::new(elwt.create_window(attributes).unwrap());

let context = softbuffer::Context::new(window.clone()).unwrap();
let surface = {
let app = winit_app::WinitAppBuilder::with_init(
|elwt| {
let attributes = Window::default_attributes();
#[cfg(target_arch = "wasm32")]
let attributes =
winit::platform::web::WindowAttributesExtWebSys::with_append(attributes, true);
let window = Arc::new(elwt.create_window(attributes).unwrap());

let context = softbuffer::Context::new(window.clone()).unwrap();

// Spawn a thread to handle rendering for this specific surface. The channels will
// be closed and the thread will be stopped whenever this surface (the returned
// context below) is dropped, so that it can all be recreated again (on Android)
// when a new surface is created.
let (start_render, do_render) = mpsc::channel();
let (render_done, finish_render) = mpsc::channel();
println!("starting thread...");
std::thread::spawn({
let window = window.clone();
move || render_thread(window, do_render, render_done)
});

(window, context, start_render, finish_render)
},
|_elwt, (window, context, _start_render, _finish_render)| {
println!("making surface...");
let surface = softbuffer::Surface::new(&context, window.clone()).unwrap();
Arc::new(Mutex::new(surface))
};

// Spawn a thread to handle rendering.
let (start_render, do_render) = mpsc::channel();
let (render_done, finish_render) = mpsc::channel();
println!("starting thread...");
std::thread::spawn({
let window = window.clone();
let surface = surface.clone();
move || render_thread(window, surface, do_render, render_done)
});

(window, surface, start_render, finish_render)
})
.with_event_handler(|state, event, elwt| {
let (window, _surface, start_render, finish_render) = state;
Arc::new(Mutex::new(
softbuffer::Surface::new(context, window.clone()).unwrap(),
))
},
)
.with_event_handler(|state, surface, event, elwt| {
let (window, _context, start_render, finish_render) = state;
elwt.set_control_flow(ControlFlow::Wait);

match event {
Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
} if window_id == window.id() => {
let Some(surface) = surface else {
eprintln!("RedrawRequested fired before Resumed or after Suspended");
return;
};
// Start the render and then finish it.
start_render.send(()).unwrap();
start_render.send(surface.clone()).unwrap();
finish_render.recv().unwrap();
}
Event::WindowEvent {
41 changes: 25 additions & 16 deletions examples/winit_wrong_sized_buffer.rs
Original file line number Diff line number Diff line change
@@ -12,31 +12,40 @@ const BUFFER_HEIGHT: usize = 128;
fn main() {
let event_loop = EventLoop::new().unwrap();

let app = winit_app::WinitAppBuilder::with_init(|elwt| {
let window = winit_app::make_window(elwt, |w| w);
let app = winit_app::WinitAppBuilder::with_init(
|elwt| {
let window = winit_app::make_window(elwt, |w| w);

let context = softbuffer::Context::new(window.clone()).unwrap();
let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap();
let context = softbuffer::Context::new(window.clone()).unwrap();

// Intentionally set the size of the surface to something different than the size of the window.
surface
.resize(
NonZeroU32::new(BUFFER_WIDTH as u32).unwrap(),
NonZeroU32::new(BUFFER_HEIGHT as u32).unwrap(),
)
.unwrap();

(window, surface)
})
.with_event_handler(|state, event, elwt| {
let (window, surface) = state;
(window, context)
},
|_elwt, (window, context)| {
let mut surface = softbuffer::Surface::new(context, window.clone()).unwrap();
// Intentionally set the size of the surface to something different than the size of the window.
surface
.resize(
NonZeroU32::new(BUFFER_WIDTH as u32).unwrap(),
NonZeroU32::new(BUFFER_HEIGHT as u32).unwrap(),
)
.unwrap();
surface
},
)
.with_event_handler(|state, surface, event, elwt| {
let (window, _context) = state;
elwt.set_control_flow(ControlFlow::Wait);

match event {
Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
} if window_id == window.id() => {
let Some(surface) = surface else {
eprintln!("RedrawRequested fired before Resumed or after Suspended");
return;
};

let mut buffer = surface.buffer_mut().unwrap();
for y in 0..BUFFER_HEIGHT {
for x in 0..BUFFER_WIDTH {