From 1603f0581882c4ca299db1d6efdba9148ebcddb6 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Mon, 23 Sep 2024 11:48:09 +0200 Subject: [PATCH] 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! --- crates/eframe/src/web/web_painter_wgpu.rs | 11 ++++++++-- crates/egui-wgpu/src/renderer.rs | 22 ++++++++++++------- crates/egui-wgpu/src/winit.rs | 11 ++++++++-- .../egui_demo_app/src/apps/custom3d_wgpu.rs | 8 +++---- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 7d845a969e5..bbf38e06a15 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -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, @@ -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) diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index eda934944ea..a3fcd667f5f 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/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 /// 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, ); } @@ -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!(); diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 46db8821e02..26b3c86ae2c 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -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, @@ -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, + ); } { diff --git a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs index 27d5c3cac8d..11030a6727c 100644 --- a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -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); @@ -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, &[]);