Skip to content

Commit

Permalink
Wgpu render pass on paint callback has now static lifetime (#5149)
Browse files Browse the repository at this point in the history
A very common usability issue on egui-wgpu callbacks is that `paint`
can't access any data that doesn't strictly outlive the callback
resources' data. E.g. if the callback resources have an `Arc` to some
resource manager, you can't easily pull out resources since you
statically needed to ensure that those resource references outlived the
renderpass, whose lifetime was only constrained to the callback
resources themselves.

Wgpu 22 no longer has this restriction! Its (render/compute-)passes take
care of the lifetime of any passed resource internally. The lifetime
constraint is _still_ opt-out since it protects from a common runtime
error of adding commands/passes on the parent encoder while a previously
created pass wasn't closed yet.
This is not a concern in egui-wgpu since the paint method where we have
to access the render pass doesn't even have access to the encoder!
  • Loading branch information
Wumpf authored Sep 23, 2024
1 parent 6f7b9b9 commit 1603f05
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 16 deletions.
11 changes: 9 additions & 2 deletions crates/eframe/src/web/web_painter_wgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ impl WebPainter for WebPainterWgpu {
let frame_view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &frame_view,
resolve_target: None,
Expand Down Expand Up @@ -333,7 +333,14 @@ impl WebPainter for WebPainterWgpu {
timestamp_writes: None,
});

renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor);
// Forgetting the pass' lifetime means that we are no longer compile-time protected from
// runtime errors caused by accessing the parent encoder before the render pass is dropped.
// Since we don't pass it on to the renderer, we should be perfectly safe against this mistake here!
renderer.render(
&mut render_pass.forget_lifetime(),
clipped_primitives,
&screen_descriptor,
);
}

Some(frame)
Expand Down
22 changes: 14 additions & 8 deletions crates/egui-wgpu/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ pub trait CallbackTrait: Send + Sync {
///
/// It is given access to the [`wgpu::RenderPass`] so that it can issue draw commands
/// into the same [`wgpu::RenderPass`] that is used for all other egui elements.
fn paint<'a>(
&'a self,
fn paint(
&self,
info: PaintCallbackInfo,
render_pass: &mut wgpu::RenderPass<'a>,
callback_resources: &'a CallbackResources,
render_pass: &mut wgpu::RenderPass<'static>,
callback_resources: &CallbackResources,
);
}

Expand Down Expand Up @@ -408,10 +408,16 @@ impl Renderer {
}

/// Executes the egui renderer onto an existing wgpu renderpass.
pub fn render<'rp>(
&'rp self,
render_pass: &mut wgpu::RenderPass<'rp>,
paint_jobs: &'rp [epaint::ClippedPrimitive],
///
/// Note that the lifetime of `render_pass` is `'static` which requires a call to [`wgpu::RenderPass::forget_lifetime`].
/// This allows users to pass resources that live outside of the callback resources to the render pass.
/// The render pass internally keeps all referenced resources alive as long as necessary.
/// The only consequence of `forget_lifetime` is that any operation on the parent encoder will cause a runtime error
/// instead of a compile time error.
pub fn render(
&self,
render_pass: &mut wgpu::RenderPass<'static>,
paint_jobs: &[epaint::ClippedPrimitive],
screen_descriptor: &ScreenDescriptor,
) {
crate::profile_function!();
Expand Down
11 changes: 9 additions & 2 deletions crates/egui-wgpu/src/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ impl Painter {
(texture_view, Some(&frame_view))
});

let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("egui_render"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view,
Expand Down Expand Up @@ -658,7 +658,14 @@ impl Painter {
occlusion_query_set: None,
});

renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor);
// Forgetting the pass' lifetime means that we are no longer compile-time protected from
// runtime errors caused by accessing the parent encoder before the render pass is dropped.
// Since we don't pass it on to the renderer, we should be perfectly safe against this mistake here!
renderer.render(
&mut render_pass.forget_lifetime(),
clipped_primitives,
&screen_descriptor,
);
}

{
Expand Down
8 changes: 4 additions & 4 deletions crates/egui_demo_app/src/apps/custom3d_wgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,11 @@ impl egui_wgpu::CallbackTrait for CustomTriangleCallback {
Vec::new()
}

fn paint<'a>(
fn paint(
&self,
_info: egui::PaintCallbackInfo,
render_pass: &mut wgpu::RenderPass<'a>,
resources: &'a egui_wgpu::CallbackResources,
render_pass: &mut wgpu::RenderPass<'static>,
resources: &egui_wgpu::CallbackResources,
) {
let resources: &TriangleRenderResources = resources.get().unwrap();
resources.paint(render_pass);
Expand Down Expand Up @@ -200,7 +200,7 @@ impl TriangleRenderResources {
);
}

fn paint<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>) {
fn paint(&self, render_pass: &mut wgpu::RenderPass<'_>) {
// Draw our triangle!
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.bind_group, &[]);
Expand Down

0 comments on commit 1603f05

Please sign in to comment.