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:
parent
4d1e858a52
commit
fcb00723bc
5 changed files with 88 additions and 42 deletions
|
@ -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(())
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue