Browse Source

Wgpu render pass on paint callback has now static lifetime (#5149)

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!
pull/4955/merge
Andreas Reich 1 month ago
committed by GitHub
parent
commit
1603f05818
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 11
      crates/eframe/src/web/web_painter_wgpu.rs
  2. 22
      crates/egui-wgpu/src/renderer.rs
  3. 11
      crates/egui-wgpu/src/winit.rs
  4. 8
      crates/egui_demo_app/src/apps/custom3d_wgpu.rs

11
crates/eframe/src/web/web_painter_wgpu.rs

@ -302,7 +302,7 @@ impl WebPainter for WebPainterWgpu {
let frame_view = frame let frame_view = frame
.texture .texture
.create_view(&wgpu::TextureViewDescriptor::default()); .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 { color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &frame_view, view: &frame_view,
resolve_target: None, resolve_target: None,
@ -333,7 +333,14 @@ impl WebPainter for WebPainterWgpu {
timestamp_writes: None, 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) Some(frame)

22
crates/egui-wgpu/src/renderer.rs

@ -112,11 +112,11 @@ pub trait CallbackTrait: Send + Sync {
/// ///
/// It is given access to the [`wgpu::RenderPass`] so that it can issue draw commands /// 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. /// into the same [`wgpu::RenderPass`] that is used for all other egui elements.
fn paint<'a>( fn paint(
&'a self, &self,
info: PaintCallbackInfo, info: PaintCallbackInfo,
render_pass: &mut wgpu::RenderPass<'a>, render_pass: &mut wgpu::RenderPass<'static>,
callback_resources: &'a CallbackResources, callback_resources: &CallbackResources,
); );
} }
@ -408,10 +408,16 @@ impl Renderer {
} }
/// Executes the egui renderer onto an existing wgpu renderpass. /// Executes the egui renderer onto an existing wgpu renderpass.
pub fn render<'rp>( ///
&'rp self, /// Note that the lifetime of `render_pass` is `'static` which requires a call to [`wgpu::RenderPass::forget_lifetime`].
render_pass: &mut wgpu::RenderPass<'rp>, /// This allows users to pass resources that live outside of the callback resources to the render pass.
paint_jobs: &'rp [epaint::ClippedPrimitive], /// 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, screen_descriptor: &ScreenDescriptor,
) { ) {
crate::profile_function!(); crate::profile_function!();

11
crates/egui-wgpu/src/winit.rs

@ -627,7 +627,7 @@ impl Painter {
(texture_view, Some(&frame_view)) (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"), label: Some("egui_render"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment { color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view, view,
@ -658,7 +658,14 @@ impl Painter {
occlusion_query_set: None, 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,
);
} }
{ {

8
crates/egui_demo_app/src/apps/custom3d_wgpu.rs

@ -160,11 +160,11 @@ impl egui_wgpu::CallbackTrait for CustomTriangleCallback {
Vec::new() Vec::new()
} }
fn paint<'a>( fn paint(
&self, &self,
_info: egui::PaintCallbackInfo, _info: egui::PaintCallbackInfo,
render_pass: &mut wgpu::RenderPass<'a>, render_pass: &mut wgpu::RenderPass<'static>,
resources: &'a egui_wgpu::CallbackResources, resources: &egui_wgpu::CallbackResources,
) { ) {
let resources: &TriangleRenderResources = resources.get().unwrap(); let resources: &TriangleRenderResources = resources.get().unwrap();
resources.paint(render_pass); resources.paint(render_pass);
@ -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! // Draw our triangle!
render_pass.set_pipeline(&self.pipeline); render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.bind_group, &[]); render_pass.set_bind_group(0, &self.bind_group, &[]);

Loading…
Cancel
Save