From c414af7aa2858703d2ff5d72b8dbf1a657da5991 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Wed, 12 Oct 2022 14:27:24 +0200 Subject: [PATCH] wgpu renderer now always requires a RenderPass being passed in, pass command encoder to prepare callback (#2136) * wgpu renderer now always requires a RenderPass being passed in This also implies that it no longer owns the depth buffer! (why would it anyways!) * wgpu-renderer now passes a command encoder to prepare * add changelog entries * fixup changelogs, fix variable name --- crates/eframe/CHANGELOG.md | 1 + crates/eframe/src/web/web_painter_wgpu.rs | 46 ++++---- crates/egui-wgpu/CHANGELOG.md | 4 +- crates/egui-wgpu/src/renderer.rs | 104 ++++-------------- crates/egui-wgpu/src/winit.rs | 86 ++++++++++----- .../egui_demo_app/src/apps/custom3d_wgpu.rs | 2 +- 6 files changed, 115 insertions(+), 128 deletions(-) diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 4b519e0e..1934e3d9 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -16,6 +16,7 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C * Web: You can now use WebGL on top of `wgpu` by enabling the `wgpu` feature (and disabling `glow` via disabling default features) ([#2107](https://github.com/emilk/egui/pull/2107)). + ## 0.19.0 - 2022-08-20 * MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)). * Added `wgpu` rendering backed ([#1564](https://github.com/emilk/egui/pull/1564)): diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 1566d2ee..9f33f82e 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -54,12 +54,10 @@ impl WebPainterWgpu { .await .map_err(|err| format!("Failed to find wgpu device: {}", err))?; - // TODO(Wumpf): MSAA & depth - let target_format = egui_wgpu::preferred_framebuffer_format(&surface.get_supported_formats(&adapter)); - let renderer = egui_wgpu::Renderer::new(&device, target_format, 1, 0); + let renderer = egui_wgpu::Renderer::new(&device, target_format, None, 1); let render_state = RenderState { device: Arc::new(device), queue: Arc::new(queue), @@ -127,9 +125,6 @@ impl WebPainter for WebPainterWgpu { err )) })?; - let view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); let mut encoder = render_state @@ -158,24 +153,37 @@ impl WebPainter for WebPainterWgpu { renderer.update_buffers( &render_state.device, &render_state.queue, + &mut encoder, clipped_primitives, &screen_descriptor, ); } - // Record all render passes. - render_state.renderer.read().render( - &mut encoder, - &view, - clipped_primitives, - &screen_descriptor, - Some(wgpu::Color { - r: clear_color.r() as f64, - g: clear_color.g() as f64, - b: clear_color.b() as f64, - a: clear_color.a() as f64, - }), - ); + { + let renderer = render_state.renderer.read(); + let frame_view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &frame_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: clear_color.r() as f64, + g: clear_color.g() as f64, + b: clear_color.b() as f64, + a: clear_color.a() as f64, + }), + store: true, + }, + })], + depth_stencil_attachment: None, + label: Some("egui_render"), + }); + + renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor); + } { let mut renderer = render_state.renderer.write(); diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index c652281d..b6835c66 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -5,8 +5,10 @@ All notable changes to the `egui-wgpu` integration will be noted in this file. ## Unreleased * Renamed `RenderPass` to `Renderer`. * Renamed `RenderPass::execute` to `RenderPass::render`. -* Renamed `RenderPass::execute_with_renderpass` to `Renderer::render_onto_renderpass`. +* Renamed `RenderPass::execute_with_renderpass` to `Renderer::render` (replacing existing `Renderer::render`) * Reexported `Renderer`. +* `Renderer` no longer handles pass creation and depth buffer creation ([#2136](https://github.com/emilk/egui/pull/2136)) +* `PrepareCallback` now passes `wgpu::CommandEncoder` ([#2136](https://github.com/emilk/egui/pull/2136)) ## 0.19.0 - 2022-08-20 diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index e81b4b0a..fb6b1130 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -15,9 +15,13 @@ use wgpu::util::DeviceExt as _; /// /// `prepare` is called every frame before `paint`, and can use the passed-in [`wgpu::Device`] and /// [`wgpu::Buffer`] to allocate or modify GPU resources such as buffers. +/// Additionally, a [`wgpu::CommandEncoder`] is provided in order to allow creation of +/// custom [`wgpu::RenderPass`]/[`wgpu::ComputePass`] or perform buffer/texture copies +/// which may serve as preparation to the final `paint`. +/// (This allows reusing the same [`wgpu::CommandEncoder`] for all callbacks and egui rendering itself) /// -/// `paint` is called after `prepare` and is given access to the the [`wgpu::RenderPass`] so that it -/// can issue draw commands. +/// `paint` is called after `prepare` and 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. /// /// The final argument of both the `prepare` and `paint` callbacks is a the /// [`paint_callback_resources`][crate::renderer::Renderer::paint_callback_resources]. @@ -33,7 +37,8 @@ pub struct CallbackFn { paint: Box, } -type PrepareCallback = dyn Fn(&wgpu::Device, &wgpu::Queue, &mut TypeMap) + Sync + Send; +type PrepareCallback = + dyn Fn(&wgpu::Device, &wgpu::Queue, &mut wgpu::CommandEncoder, &mut TypeMap) + Sync + Send; type PaintCallback = dyn for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap) + Sync + Send; @@ -41,7 +46,7 @@ type PaintCallback = impl Default for CallbackFn { fn default() -> Self { CallbackFn { - prepare: Box::new(|_, _, _| ()), + prepare: Box::new(|_, _, _, _| ()), paint: Box::new(|_, _, _| ()), } } @@ -55,7 +60,10 @@ impl CallbackFn { /// Set the prepare callback pub fn prepare(mut self, prepare: F) -> Self where - F: Fn(&wgpu::Device, &wgpu::Queue, &mut TypeMap) + Sync + Send + 'static, + F: Fn(&wgpu::Device, &wgpu::Queue, &mut wgpu::CommandEncoder, &mut TypeMap) + + Sync + + Send + + 'static, { self.prepare = Box::new(prepare) as _; self @@ -122,7 +130,6 @@ struct SizedBuffer { /// Renderer for a egui based GUI. pub struct Renderer { pipeline: wgpu::RenderPipeline, - depth_texture: Option<(wgpu::Texture, wgpu::TextureView)>, index_buffers: Vec, vertex_buffers: Vec, uniform_buffer: SizedBuffer, @@ -141,13 +148,13 @@ pub struct Renderer { impl Renderer { /// Creates a renderer for a egui UI. /// - /// `output_format` should preferably be [`wgpu::TextureFormat::Rgba8Unorm`] or + /// `output_color_format` should preferably be [`wgpu::TextureFormat::Rgba8Unorm`] or /// [`wgpu::TextureFormat::Bgra8Unorm`], i.e. in gamma-space. pub fn new( device: &wgpu::Device, - output_format: wgpu::TextureFormat, + output_color_format: wgpu::TextureFormat, + output_depth_format: Option, msaa_samples: u32, - depth_bits: u8, ) -> Self { let shader = wgpu::ShaderModuleDescriptor { label: Some("egui"), @@ -225,8 +232,8 @@ impl Renderer { push_constant_ranges: &[], }); - let depth_stencil = (depth_bits > 0).then(|| wgpu::DepthStencilState { - format: wgpu::TextureFormat::Depth32Float, + let depth_stencil = output_depth_format.map(|format| wgpu::DepthStencilState { + format, depth_write_enabled: false, depth_compare: wgpu::CompareFunction::Always, stencil: wgpu::StencilState::default(), @@ -266,14 +273,14 @@ impl Renderer { fragment: Some(wgpu::FragmentState { module: &module, - entry_point: if output_format.describe().srgb { - tracing::warn!("Detected a linear (sRGBA aware) framebuffer {:?}. egui prefers Rgba8Unorm or Bgra8Unorm", output_format); + entry_point: if output_color_format.describe().srgb { + tracing::warn!("Detected a linear (sRGBA aware) framebuffer {:?}. egui prefers Rgba8Unorm or Bgra8Unorm", output_color_format); "fs_main_linear_framebuffer" } else { "fs_main_gamma_framebuffer" // this is what we prefer }, targets: &[Some(wgpu::ColorTargetState { - format: output_format, + format: output_color_format, blend: Some(wgpu::BlendState { color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, @@ -302,75 +309,11 @@ impl Renderer { textures: HashMap::new(), next_user_texture_id: 0, paint_callback_resources: TypeMap::default(), - depth_texture: None, } } - pub fn update_depth_texture(&mut self, device: &wgpu::Device, width: u32, height: u32) { - // TODO(wumpf) don't recreate texture if size hasn't changed - let texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("egui_depth_texture"), - size: wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth32Float, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, - }); - - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - - self.depth_texture = Some((texture, view)); - } - - /// Executes the renderer on its own render pass. - pub fn render( - &self, - encoder: &mut wgpu::CommandEncoder, - color_attachment: &wgpu::TextureView, - paint_jobs: &[egui::epaint::ClippedPrimitive], - screen_descriptor: &ScreenDescriptor, - clear_color: Option, - ) { - let load_operation = if let Some(color) = clear_color { - wgpu::LoadOp::Clear(color) - } else { - wgpu::LoadOp::Load - }; - - let depth_stencil_attachment = self.depth_texture.as_ref().map(|(_texture, view)| { - wgpu::RenderPassDepthStencilAttachment { - view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: true, - }), - stencil_ops: None, - } - }); - - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: color_attachment, - resolve_target: None, - ops: wgpu::Operations { - load: load_operation, - store: true, - }, - })], - depth_stencil_attachment, - label: Some("egui_render"), - }); - - self.render_onto_renderpass(&mut render_pass, paint_jobs, screen_descriptor); - } - /// Executes the egui renderer onto an existing wgpu renderpass. - pub fn render_onto_renderpass<'rp>( + pub fn render<'rp>( &'rp self, render_pass: &mut wgpu::RenderPass<'rp>, paint_jobs: &[egui::epaint::ClippedPrimitive], @@ -760,6 +703,7 @@ impl Renderer { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, paint_jobs: &[egui::epaint::ClippedPrimitive], screen_descriptor: &ScreenDescriptor, ) { @@ -821,7 +765,7 @@ impl Renderer { continue; }; - (cbfn.prepare)(device, queue, &mut self.paint_callback_resources); + (cbfn.prepare)(device, queue, encoder, &mut self.paint_callback_resources); } } } diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index f3170866..3c9bd748 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -20,7 +20,8 @@ pub struct Painter<'a> { device_descriptor: wgpu::DeviceDescriptor<'a>, present_mode: wgpu::PresentMode, msaa_samples: u32, - depth_bits: u8, + depth_format: Option, + depth_texture_view: Option, instance: Instance, adapter: Option, @@ -56,7 +57,8 @@ impl<'a> Painter<'a> { device_descriptor, present_mode, msaa_samples, - depth_bits, + depth_format: (depth_bits > 0).then(|| wgpu::TextureFormat::Depth32Float), + depth_texture_view: None, instance, adapter: None, @@ -80,7 +82,7 @@ impl<'a> Painter<'a> { let (device, queue) = pollster::block_on(adapter.request_device(&self.device_descriptor, None)).unwrap(); - let renderer = Renderer::new(&device, target_format, self.msaa_samples, self.depth_bits); + let renderer = Renderer::new(&device, target_format, self.depth_format, self.msaa_samples); RenderState { device: Arc::new(device), @@ -141,14 +143,6 @@ impl<'a> Painter<'a> { .configure(&render_state.device, &config); surface_state.width = width_in_pixels; surface_state.height = height_in_pixels; - - if self.depth_bits > 0 { - render_state.renderer.write().update_depth_texture( - &render_state.device, - width_in_pixels, - height_in_pixels, - ); - } } /// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`] @@ -212,6 +206,25 @@ impl<'a> Painter<'a> { pub fn on_window_resized(&mut self, width_in_pixels: u32, height_in_pixels: u32) { if self.surface_state.is_some() { self.configure_surface(width_in_pixels, height_in_pixels); + let device = &self.render_state.as_ref().unwrap().device; + self.depth_texture_view = self.depth_format.map(|depth_format| { + device + .create_texture(&wgpu::TextureDescriptor { + label: Some("egui_depth_texture"), + size: wgpu::Extent3d { + width: width_in_pixels, + height: height_in_pixels, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: depth_format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING, + }) + .create_view(&wgpu::TextureViewDescriptor::default()) + }); } else { error!("Ignoring window resize notification with no surface created via Painter::set_window()"); } @@ -246,9 +259,6 @@ impl<'a> Painter<'a> { return; } }; - let output_view = output_frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); let mut encoder = render_state @@ -277,24 +287,46 @@ impl<'a> Painter<'a> { renderer.update_buffers( &render_state.device, &render_state.queue, + &mut encoder, clipped_primitives, &screen_descriptor, ); } - // Record all render passes. - render_state.renderer.read().render( - &mut encoder, - &output_view, - clipped_primitives, - &screen_descriptor, - Some(wgpu::Color { - r: clear_color.r() as f64, - g: clear_color.g() as f64, - b: clear_color.b() as f64, - a: clear_color.a() as f64, - }), - ); + { + let renderer = render_state.renderer.read(); + let frame_view = output_frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &frame_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: clear_color.r() as f64, + g: clear_color.g() as f64, + b: clear_color.b() as f64, + a: clear_color.a() as f64, + }), + store: true, + }, + })], + depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| { + wgpu::RenderPassDepthStencilAttachment { + view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + } + }), + label: Some("egui_render"), + }); + + renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor); + } { let mut renderer = render_state.renderer.write(); diff --git a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs index f3957e22..f678ab05 100644 --- a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -138,7 +138,7 @@ impl Custom3d { // The paint callback is called after prepare and is given access to the render pass, which // can be used to issue draw commands. let cb = egui_wgpu::CallbackFn::new() - .prepare(move |device, queue, paint_callback_resources| { + .prepare(move |device, queue, _encoder, paint_callback_resources| { let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap(); resources.prepare(device, queue, angle); })