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 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
99 changes: 55 additions & 44 deletions examples/integration/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,55 +169,66 @@ 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::OutOfMemory => {
panic!("Swapchain error: {}. Rendering cannot continue.", error)
}
_ => {
// Try rendering again next frame.
window.request_redraw();
}
},
}
}
_ => {}
}
Expand Down
2 changes: 1 addition & 1 deletion graphics/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ mod compositor;
#[cfg(feature = "opengl")]
mod gl_compositor;

pub use compositor::Compositor;
pub use compositor::{Compositor, SwapChainError};

#[cfg(feature = "opengl")]
pub use gl_compositor::GLCompositor;
26 changes: 25 additions & 1 deletion graphics/src/window/compositor.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{Color, Error, Viewport};

use iced_native::mouse;

use raw_window_handle::HasRawWindowHandle;
use thiserror::Error;

/// A graphics compositor that can draw to windows.
pub trait Compositor: Sized {
Expand Down Expand Up @@ -52,5 +55,26 @@ pub trait Compositor: Sized {
background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction;
) -> Result<mouse::Interaction, SwapChainError>;
}

/// Result of an unsuccessful call to [`Compositor::draw`].
#[derive(Clone, PartialEq, Eq, Debug, Error)]
pub enum SwapChainError {
/// A timeout was encountered while trying to acquire the next frame.
#[error(
"A timeout was encountered while trying to acquire the next frame"
)]
Timeout,
/// The underlying surface has changed, and therefore the swap chain must be updated.
#[error(
"The underlying surface has changed, and therefore the swap chain must be updated."
)]
Outdated,
/// The swap chain has been lost and needs to be recreated.
#[error("The swap chain has been lost and needs to be recreated")]
Lost,
/// There is no more memory left to allocate a new frame.
#[error("There is no more memory left to allocate a new frame")]
OutOfMemory,
}
126 changes: 73 additions & 53 deletions wgpu/src/window/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,59 +135,79 @@ 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, iced_graphics::window::SwapChainError> {
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 {
label: Some(
"iced_wgpu::window::Compositor render pass",
),
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &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::Timeout => {
Err(iced_graphics::window::SwapChainError::Timeout)
}
wgpu::SwapChainError::Outdated => {
Err(iced_graphics::window::SwapChainError::Outdated)
}
wgpu::SwapChainError::Lost => {
Err(iced_graphics::window::SwapChainError::Lost)
}
wgpu::SwapChainError::OutOfMemory => {
Err(iced_graphics::window::SwapChainError::OutOfMemory)
}
},
);

let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::window::Compositor render pass"),
color_attachments: &[wgpu::RenderPassColorAttachment {
view: &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
}
}
}
38 changes: 27 additions & 11 deletions winit/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,27 +366,43 @@ async fn run_instance<A, E, C>(
viewport_version = current_viewport_version;
}

let new_mouse_interaction = compositor.draw(
match compositor.draw(
&mut renderer,
&mut swap_chain,
state.viewport(),
state.background_color(),
&primitive,
&debug.overlay(),
);
) {
Ok(new_mouse_interaction) => {
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.
}
Err(error) => match error {
// This is an unrecoverable error.
window::SwapChainError::OutOfMemory => {
panic!("{}", error);
}
_ => {
debug.render_finished();

// Try rendering again next frame.
window.request_redraw();
}
},
}

// TODO: Handle animations!
// Maybe we can use `ControlFlow::WaitUntil` for this.
}
event::Event::WindowEvent {
event: event::WindowEvent::MenuEntryActivated(entry_id),
Expand Down