From fcb00723bc7e57ca7571d408b0f9019db8478592 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Fri, 4 Nov 2022 09:54:29 +0100 Subject: [PATCH] wgpu: add support for user-level command buffers + viewport clarification (#2230) * wgpu: add support for user-level command buffers * updated wgpu demo app --- crates/eframe/src/web/web_painter_wgpu.rs | 15 ++- crates/egui-wgpu/CHANGELOG.md | 1 + crates/egui-wgpu/src/renderer.rs | 94 ++++++++++++------- crates/egui-wgpu/src/winit.rs | 14 ++- .../egui_demo_app/src/apps/custom3d_wgpu.rs | 6 ++ 5 files changed, 88 insertions(+), 42 deletions(-) diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 0cf111ea..b1d835e4 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -148,7 +148,8 @@ impl WebPainter for WebPainterWgpu { size_in_pixels, pixels_per_point, }; - { + + let user_cmd_bufs = { let mut renderer = render_state.renderer.write(); for (id, image_delta) in &textures_delta.set { renderer.update_texture( @@ -165,8 +166,8 @@ impl WebPainter for WebPainterWgpu { &mut encoder, clipped_primitives, &screen_descriptor, - ); - } + ) + }; { let renderer = render_state.renderer.read(); @@ -201,8 +202,12 @@ impl WebPainter for WebPainterWgpu { } } - // Submit the commands. - render_state.queue.submit(std::iter::once(encoder.finish())); + // Submit the commands: both the main buffer and user-defined ones. + render_state.queue.submit( + user_cmd_bufs + .into_iter() + .chain(std::iter::once(encoder.finish())), + ); frame.present(); Ok(()) diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index 6a591f58..c5499396 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to the `egui-wgpu` integration will be noted in this file. * 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)) +* `PrepareCallback` can now returns `wgpu::CommandBuffer` that are bundled into a single `wgpu::Queue::submit` call ([#2230](https://github.com/emilk/egui/pull/2230)) * Only a single vertex & index buffer is now created and resized when necessary (previously, vertex/index buffers were allocated for every mesh) ([#2148](https://github.com/emilk/egui/pull/2148)). * `Renderer::update_texture` no longer creates a new `wgpu::Sampler` with every new texture ([#2198](https://github.com/emilk/egui/pull/2198)) * `Painter`'s instance/device/adapter/surface creation is now configurable via `WgpuConfiguration` ([#2207](https://github.com/emilk/egui/pull/2207)) diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index e8cf87b9..d56ff483 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -14,17 +14,12 @@ use wgpu::util::DeviceExt as _; /// A callback function that can be used to compose an [`egui::PaintCallback`] for custom WGPU /// rendering. /// -/// The callback is composed of two functions: `prepare` and `paint`. -/// -/// `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 [`wgpu::RenderPass`] so that it -/// can issue draw commands into the same [`wgpu::RenderPass`] that is used for all other egui elements. +/// The callback is composed of two functions: `prepare` and `paint`: +/// - `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. +/// - `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]. @@ -40,8 +35,14 @@ pub struct CallbackFn { paint: Box, } -type PrepareCallback = - dyn Fn(&wgpu::Device, &wgpu::Queue, &mut wgpu::CommandEncoder, &mut TypeMap) + Sync + Send; +type PrepareCallback = dyn Fn( + &wgpu::Device, + &wgpu::Queue, + &mut wgpu::CommandEncoder, + &mut TypeMap, + ) -> Vec + + Sync + + Send; type PaintCallback = dyn for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap) + Sync + Send; @@ -49,7 +50,7 @@ type PaintCallback = impl Default for CallbackFn { fn default() -> Self { CallbackFn { - prepare: Box::new(|_, _, _, _| ()), + prepare: Box::new(|_, _, _, _| Vec::new()), paint: Box::new(|_, _, _| ()), } } @@ -60,10 +61,29 @@ impl CallbackFn { Self::default() } - /// Set the prepare callback + /// Set the prepare callback. + /// + /// The passed-in `CommandEncoder` is egui's and can be used directly to register + /// wgpu commands for simple use cases. + /// This allows reusing the same [`wgpu::CommandEncoder`] for all callbacks and egui + /// rendering itself. + /// + /// For more complicated use cases, one can also return a list of arbitrary + /// `CommandBuffer`s and have complete control over how they get created and fed. + /// In particular, this gives an opportunity to parallelize command registration and + /// prevents a faulty callback from poisoning the main wgpu pipeline. + /// + /// When using eframe, the main egui command buffer, as well as all user-defined + /// command buffers returned by this function, are guaranteed to all be submitted + /// at once in a single call. pub fn prepare(mut self, prepare: F) -> Self where - F: Fn(&wgpu::Device, &wgpu::Queue, &mut wgpu::CommandEncoder, &mut TypeMap) + F: Fn( + &wgpu::Device, + &wgpu::Queue, + &mut wgpu::CommandEncoder, + &mut TypeMap, + ) -> Vec + Sync + Send + 'static, @@ -404,23 +424,23 @@ impl Renderer { needs_reset = true; { - // Set the viewport rect - // Transform callback rect to physical pixels: - let rect_min_x = pixels_per_point * callback.rect.min.x; - let rect_min_y = pixels_per_point * callback.rect.min.y; - let rect_max_x = pixels_per_point * callback.rect.max.x; - let rect_max_y = pixels_per_point * callback.rect.max.y; + // We're setting a default viewport for the render pass as a + // courtesy for the user, so that they don't have to think about + // it in the simple case where they just want to fill the whole + // paint area. + // + // The user still has the possibility of setting their own custom + // viewport during the paint callback, effectively overriding this + // one. - let rect_min_x = rect_min_x.round(); - let rect_min_y = rect_min_y.round(); - let rect_max_x = rect_max_x.round(); - let rect_max_y = rect_max_y.round(); + let min = (callback.rect.min.to_vec2() * pixels_per_point).round(); + let max = (callback.rect.max.to_vec2() * pixels_per_point).round(); render_pass.set_viewport( - rect_min_x, - rect_min_y, - rect_max_x - rect_min_x, - rect_max_y - rect_min_y, + min.x, + min.y, + max.x - min.x, + max.y - min.y, 0.0, 1.0, ); @@ -702,6 +722,8 @@ impl Renderer { /// Uploads the uniform, vertex and index data used by the renderer. /// Should be called before `render()`. + /// + /// Returns all user-defined command buffers gathered from prepare callbacks. pub fn update_buffers( &mut self, device: &wgpu::Device, @@ -709,7 +731,7 @@ impl Renderer { encoder: &mut wgpu::CommandEncoder, paint_jobs: &[egui::epaint::ClippedPrimitive], screen_descriptor: &ScreenDescriptor, - ) { + ) -> Vec { let screen_size_in_points = screen_descriptor.screen_size_in_points(); // Update uniform buffer @@ -755,6 +777,7 @@ impl Renderer { } // Upload index & vertex data and call user callbacks + let mut user_cmd_bufs = Vec::new(); // collect user command buffers for egui::ClippedPrimitive { primitive, .. } in paint_jobs.iter() { match primitive { Primitive::Mesh(mesh) => { @@ -783,10 +806,17 @@ impl Renderer { continue; }; - (cbfn.prepare)(device, queue, encoder, &mut self.paint_callback_resources); + user_cmd_bufs.extend((cbfn.prepare)( + device, + queue, + encoder, + &mut self.paint_callback_resources, + )); } } } + + user_cmd_bufs } } diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 0919c9e0..d37c7c3b 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -264,7 +264,7 @@ impl Painter { pixels_per_point, }; - { + let user_cmd_bufs = { let mut renderer = render_state.renderer.write(); for (id, image_delta) in &textures_delta.set { renderer.update_texture( @@ -281,8 +281,8 @@ impl Painter { &mut encoder, clipped_primitives, &screen_descriptor, - ); - } + ) + }; { let renderer = render_state.renderer.read(); @@ -326,8 +326,12 @@ impl Painter { } } - // Submit the commands. - render_state.queue.submit(std::iter::once(encoder.finish())); + // Submit the commands: both the main buffer and user-defined ones. + render_state.queue.submit( + user_cmd_bufs + .into_iter() + .chain(std::iter::once(encoder.finish())), + ); // Redraw egui output_frame.present(); diff --git a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs index f678ab05..5a2b30c3 100644 --- a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -135,12 +135,18 @@ impl Custom3d { // Device and Queue, which can be used, for instance, to update buffers and uniforms before // rendering. // + // You can use the main `CommandEncoder` that is passed-in, return an arbitrary number + // of user-defined `CommandBuffer`s, or both. + // The main command buffer, as well as all user-defined ones, will be submitted together + // to the GPU in a single call. + // // 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, _encoder, paint_callback_resources| { let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap(); resources.prepare(device, queue, angle); + Vec::new() }) .paint(move |_info, render_pass, paint_callback_resources| { let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();