Skip to content

Don't panic when wgpu swapchain frame is outdated #667

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

Merged
merged 8 commits into from
Aug 5, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
97 changes: 53 additions & 44 deletions examples/integration/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,55 +168,64 @@ pub fn main() {
resized = false;
}

let frame = swap_chain.get_current_frame().expect("Next frame");
match swap_chain.get_current_frame() {
Ok(frame) => {
let mut encoder = device.create_command_encoder(
&wgpu::CommandEncoderDescriptor { label: None },
);

let mut encoder = device.create_command_encoder(
&wgpu::CommandEncoderDescriptor { label: None },
);
let program = state.program();

{
// We clear the frame
let mut render_pass = scene.clear(
&frame.output.view,
&mut encoder,
program.background_color(),
);

// Draw the scene
scene.draw(&mut render_pass);
}

// And then iced on top
let mouse_interaction = renderer.backend_mut().draw(
&mut device,
&mut staging_belt,
&mut encoder,
&frame.output.view,
&viewport,
state.primitive(),
&debug.overlay(),
);

let program = state.program();
// Then we submit the work
staging_belt.finish();
queue.submit(Some(encoder.finish()));

{
// We clear the frame
let mut render_pass = scene.clear(
&frame.output.view,
&mut encoder,
program.background_color(),
);
// Update the mouse cursor
window.set_cursor_icon(
iced_winit::conversion::mouse_interaction(
mouse_interaction,
),
);

// Draw the scene
scene.draw(&mut render_pass);
}
// And recall staging buffers
local_pool
.spawner()
.spawn(staging_belt.recall())
.expect("Recall staging buffers");

// And then iced on top
let mouse_interaction = renderer.backend_mut().draw(
&mut device,
&mut staging_belt,
&mut encoder,
&frame.output.view,
&viewport,
state.primitive(),
&debug.overlay(),
);

// Then we submit the work
staging_belt.finish();
queue.submit(Some(encoder.finish()));

// Update the mouse cursor
window.set_cursor_icon(
iced_winit::conversion::mouse_interaction(
mouse_interaction,
),
);

// And recall staging buffers
local_pool
.spawner()
.spawn(staging_belt.recall())
.expect("Recall staging buffers");

local_pool.run_until_stalled();
local_pool.run_until_stalled();
}
Err(error) => match error {
wgpu::SwapChainError::Outdated => {
// Try rendering again next frame.
window.request_redraw();
}
_ => panic!("Swapchain error: {:?}", error),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we handle the other errors? I think the only error that makes sense to panic is OutOfMemory.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this makes sense.

},
}
}
_ => {}
}
Expand Down
5 changes: 4 additions & 1 deletion graphics/src/window/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ pub trait Compositor: Sized {

/// Draws the output primitives to the next frame of the given [`SwapChain`].
///
/// This will return an error if drawing could not be completed on this frame.
/// If an error occurs, try calling this again on the next frame.
///
/// [`SwapChain`]: Self::SwapChain
fn draw<T: AsRef<str>>(
&mut self,
Expand All @@ -49,5 +52,5 @@ pub trait Compositor: Sized {
background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction;
) -> Result<mouse::Interaction, ()>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a proper error type here would make sense, maybe just copy SwapChainError.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe? I think that the SwapChainError is only specific to wgpu though.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, yeah that could work. I've made those changes.

}
117 changes: 65 additions & 52 deletions wgpu/src/window/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,58 +120,71 @@ impl iced_graphics::window::Compositor for Compositor {
background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction {
let frame = swap_chain.get_current_frame().expect("Next frame");

let mut encoder = self.device.create_command_encoder(
&wgpu::CommandEncoderDescriptor {
label: Some("iced_wgpu encoder"),
) -> Result<mouse::Interaction, ()> {
match swap_chain.get_current_frame() {
Ok(frame) => {
let mut encoder = self.device.create_command_encoder(
&wgpu::CommandEncoderDescriptor {
label: Some("iced_wgpu encoder"),
},
);

let _ =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[
wgpu::RenderPassColorAttachmentDescriptor {
attachment: &frame.output.view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear({
let [r, g, b, a] =
background_color.into_linear();

wgpu::Color {
r: f64::from(r),
g: f64::from(g),
b: f64::from(b),
a: f64::from(a),
}
}),
store: true,
},
},
],
depth_stencil_attachment: None,
});

let mouse_interaction = renderer.backend_mut().draw(
&mut self.device,
&mut self.staging_belt,
&mut encoder,
&frame.output.view,
viewport,
output,
overlay,
);

// Submit work
self.staging_belt.finish();
self.queue.submit(Some(encoder.finish()));

// Recall staging buffers
self.local_pool
.spawner()
.spawn(self.staging_belt.recall())
.expect("Recall staging belt");

self.local_pool.run_until_stalled();

Ok(mouse_interaction)
}
Err(error) => match error {
wgpu::SwapChainError::Outdated => {
// Try again next frame.
Err(())
}
_ => panic!("Swapchain error: {:?}", error),
},
);

let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: &frame.output.view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear({
let [r, g, b, a] = background_color.into_linear();

wgpu::Color {
r: f64::from(r),
g: f64::from(g),
b: f64::from(b),
a: f64::from(a),
}
}),
store: true,
},
}],
depth_stencil_attachment: None,
});

let mouse_interaction = renderer.backend_mut().draw(
&mut self.device,
&mut self.staging_belt,
&mut encoder,
&frame.output.view,
viewport,
output,
overlay,
);

// Submit work
self.staging_belt.finish();
self.queue.submit(Some(encoder.finish()));

// Recall staging buffers
self.local_pool
.spawner()
.spawn(self.staging_belt.recall())
.expect("Recall staging belt");

self.local_pool.run_until_stalled();

mouse_interaction
}
}
}
25 changes: 15 additions & 10 deletions winit/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,27 +311,32 @@ async fn run_instance<A, E, C>(
viewport_version = current_viewport_version;
}

let new_mouse_interaction = compositor.draw(
if let Ok(new_mouse_interaction) = compositor.draw(
&mut renderer,
&mut swap_chain,
state.viewport(),
state.background_color(),
&primitive,
&debug.overlay(),
);
) {
debug.render_finished();

debug.render_finished();
if new_mouse_interaction != mouse_interaction {
window.set_cursor_icon(conversion::mouse_interaction(
new_mouse_interaction,
));

if new_mouse_interaction != mouse_interaction {
window.set_cursor_icon(conversion::mouse_interaction(
new_mouse_interaction,
));

mouse_interaction = new_mouse_interaction;
}
mouse_interaction = new_mouse_interaction;
}

// TODO: Handle animations!
// Maybe we can use `ControlFlow::WaitUntil` for this.
} else {
debug.render_finished();

// Rendering could not complete, try again next frame.
window.request_redraw();
}
}
event::Event::WindowEvent {
event: window_event,
Expand Down