wgpu: add support for user-level command buffers + viewport clarification (#2230)

* wgpu: add support for user-level command buffers
* updated wgpu demo app
This commit is contained in:
Clement Rey 2022-11-04 09:54:29 +01:00 committed by GitHub
parent 4d1e858a52
commit fcb00723bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 88 additions and 42 deletions

View file

@ -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(())

View file

@ -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))

View file

@ -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<PaintCallback>,
}
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<wgpu::CommandBuffer>
+ 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<F>(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<wgpu::CommandBuffer>
+ 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<wgpu::CommandBuffer> {
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
}
}

View file

@ -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();

View file

@ -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();