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,
|
size_in_pixels,
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
};
|
};
|
||||||
{
|
|
||||||
|
let user_cmd_bufs = {
|
||||||
let mut renderer = render_state.renderer.write();
|
let mut renderer = render_state.renderer.write();
|
||||||
for (id, image_delta) in &textures_delta.set {
|
for (id, image_delta) in &textures_delta.set {
|
||||||
renderer.update_texture(
|
renderer.update_texture(
|
||||||
|
@ -165,8 +166,8 @@ impl WebPainter for WebPainterWgpu {
|
||||||
&mut encoder,
|
&mut encoder,
|
||||||
clipped_primitives,
|
clipped_primitives,
|
||||||
&screen_descriptor,
|
&screen_descriptor,
|
||||||
);
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
let renderer = render_state.renderer.read();
|
let renderer = render_state.renderer.read();
|
||||||
|
@ -201,8 +202,12 @@ impl WebPainter for WebPainterWgpu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit the commands.
|
// Submit the commands: both the main buffer and user-defined ones.
|
||||||
render_state.queue.submit(std::iter::once(encoder.finish()));
|
render_state.queue.submit(
|
||||||
|
user_cmd_bufs
|
||||||
|
.into_iter()
|
||||||
|
.chain(std::iter::once(encoder.finish())),
|
||||||
|
);
|
||||||
frame.present();
|
frame.present();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -9,6 +9,7 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.
|
||||||
* Reexported `Renderer`.
|
* Reexported `Renderer`.
|
||||||
* `Renderer` no longer handles pass creation and depth buffer creation ([#2136](https://github.com/emilk/egui/pull/2136))
|
* `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` 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)).
|
* 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))
|
* `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))
|
* `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
|
/// A callback function that can be used to compose an [`egui::PaintCallback`] for custom WGPU
|
||||||
/// rendering.
|
/// rendering.
|
||||||
///
|
///
|
||||||
/// The callback is composed of two functions: `prepare` and `paint`.
|
/// The callback is composed of two functions: `prepare` and `paint`:
|
||||||
///
|
/// - `prepare` is called every frame before `paint`, and can use the passed-in
|
||||||
/// `prepare` is called every frame before `paint`, and can use the passed-in [`wgpu::Device`] and
|
/// [`wgpu::Device`] and [`wgpu::Buffer`] to allocate or modify GPU resources such as buffers.
|
||||||
/// [`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
|
||||||
/// Additionally, a [`wgpu::CommandEncoder`] is provided in order to allow creation of
|
/// that it can issue draw commands into the same [`wgpu::RenderPass`] that is used for
|
||||||
/// custom [`wgpu::RenderPass`]/[`wgpu::ComputePass`] or perform buffer/texture copies
|
/// all other egui elements.
|
||||||
/// 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 final argument of both the `prepare` and `paint` callbacks is a the
|
/// The final argument of both the `prepare` and `paint` callbacks is a the
|
||||||
/// [`paint_callback_resources`][crate::renderer::Renderer::paint_callback_resources].
|
/// [`paint_callback_resources`][crate::renderer::Renderer::paint_callback_resources].
|
||||||
|
@ -40,8 +35,14 @@ pub struct CallbackFn {
|
||||||
paint: Box<PaintCallback>,
|
paint: Box<PaintCallback>,
|
||||||
}
|
}
|
||||||
|
|
||||||
type PrepareCallback =
|
type PrepareCallback = dyn Fn(
|
||||||
dyn Fn(&wgpu::Device, &wgpu::Queue, &mut wgpu::CommandEncoder, &mut TypeMap) + Sync + Send;
|
&wgpu::Device,
|
||||||
|
&wgpu::Queue,
|
||||||
|
&mut wgpu::CommandEncoder,
|
||||||
|
&mut TypeMap,
|
||||||
|
) -> Vec<wgpu::CommandBuffer>
|
||||||
|
+ Sync
|
||||||
|
+ Send;
|
||||||
|
|
||||||
type PaintCallback =
|
type PaintCallback =
|
||||||
dyn for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap) + Sync + Send;
|
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 {
|
impl Default for CallbackFn {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
CallbackFn {
|
CallbackFn {
|
||||||
prepare: Box::new(|_, _, _, _| ()),
|
prepare: Box::new(|_, _, _, _| Vec::new()),
|
||||||
paint: Box::new(|_, _, _| ()),
|
paint: Box::new(|_, _, _| ()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,10 +61,29 @@ impl CallbackFn {
|
||||||
Self::default()
|
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
|
pub fn prepare<F>(mut self, prepare: F) -> Self
|
||||||
where
|
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
|
+ Sync
|
||||||
+ Send
|
+ Send
|
||||||
+ 'static,
|
+ 'static,
|
||||||
|
@ -404,23 +424,23 @@ impl Renderer {
|
||||||
needs_reset = true;
|
needs_reset = true;
|
||||||
|
|
||||||
{
|
{
|
||||||
// Set the viewport rect
|
// We're setting a default viewport for the render pass as a
|
||||||
// Transform callback rect to physical pixels:
|
// courtesy for the user, so that they don't have to think about
|
||||||
let rect_min_x = pixels_per_point * callback.rect.min.x;
|
// it in the simple case where they just want to fill the whole
|
||||||
let rect_min_y = pixels_per_point * callback.rect.min.y;
|
// paint area.
|
||||||
let rect_max_x = pixels_per_point * callback.rect.max.x;
|
//
|
||||||
let rect_max_y = pixels_per_point * callback.rect.max.y;
|
// 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 min = (callback.rect.min.to_vec2() * pixels_per_point).round();
|
||||||
let rect_min_y = rect_min_y.round();
|
let max = (callback.rect.max.to_vec2() * pixels_per_point).round();
|
||||||
let rect_max_x = rect_max_x.round();
|
|
||||||
let rect_max_y = rect_max_y.round();
|
|
||||||
|
|
||||||
render_pass.set_viewport(
|
render_pass.set_viewport(
|
||||||
rect_min_x,
|
min.x,
|
||||||
rect_min_y,
|
min.y,
|
||||||
rect_max_x - rect_min_x,
|
max.x - min.x,
|
||||||
rect_max_y - rect_min_y,
|
max.y - min.y,
|
||||||
0.0,
|
0.0,
|
||||||
1.0,
|
1.0,
|
||||||
);
|
);
|
||||||
|
@ -702,6 +722,8 @@ impl Renderer {
|
||||||
|
|
||||||
/// Uploads the uniform, vertex and index data used by the renderer.
|
/// Uploads the uniform, vertex and index data used by the renderer.
|
||||||
/// Should be called before `render()`.
|
/// Should be called before `render()`.
|
||||||
|
///
|
||||||
|
/// Returns all user-defined command buffers gathered from prepare callbacks.
|
||||||
pub fn update_buffers(
|
pub fn update_buffers(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
@ -709,7 +731,7 @@ impl Renderer {
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
paint_jobs: &[egui::epaint::ClippedPrimitive],
|
paint_jobs: &[egui::epaint::ClippedPrimitive],
|
||||||
screen_descriptor: &ScreenDescriptor,
|
screen_descriptor: &ScreenDescriptor,
|
||||||
) {
|
) -> Vec<wgpu::CommandBuffer> {
|
||||||
let screen_size_in_points = screen_descriptor.screen_size_in_points();
|
let screen_size_in_points = screen_descriptor.screen_size_in_points();
|
||||||
|
|
||||||
// Update uniform buffer
|
// Update uniform buffer
|
||||||
|
@ -755,6 +777,7 @@ impl Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload index & vertex data and call user callbacks
|
// 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() {
|
for egui::ClippedPrimitive { primitive, .. } in paint_jobs.iter() {
|
||||||
match primitive {
|
match primitive {
|
||||||
Primitive::Mesh(mesh) => {
|
Primitive::Mesh(mesh) => {
|
||||||
|
@ -783,10 +806,17 @@ impl Renderer {
|
||||||
continue;
|
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,
|
pixels_per_point,
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
let user_cmd_bufs = {
|
||||||
let mut renderer = render_state.renderer.write();
|
let mut renderer = render_state.renderer.write();
|
||||||
for (id, image_delta) in &textures_delta.set {
|
for (id, image_delta) in &textures_delta.set {
|
||||||
renderer.update_texture(
|
renderer.update_texture(
|
||||||
|
@ -281,8 +281,8 @@ impl Painter {
|
||||||
&mut encoder,
|
&mut encoder,
|
||||||
clipped_primitives,
|
clipped_primitives,
|
||||||
&screen_descriptor,
|
&screen_descriptor,
|
||||||
);
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
let renderer = render_state.renderer.read();
|
let renderer = render_state.renderer.read();
|
||||||
|
@ -326,8 +326,12 @@ impl Painter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit the commands.
|
// Submit the commands: both the main buffer and user-defined ones.
|
||||||
render_state.queue.submit(std::iter::once(encoder.finish()));
|
render_state.queue.submit(
|
||||||
|
user_cmd_bufs
|
||||||
|
.into_iter()
|
||||||
|
.chain(std::iter::once(encoder.finish())),
|
||||||
|
);
|
||||||
|
|
||||||
// Redraw egui
|
// Redraw egui
|
||||||
output_frame.present();
|
output_frame.present();
|
||||||
|
|
|
@ -135,12 +135,18 @@ impl Custom3d {
|
||||||
// Device and Queue, which can be used, for instance, to update buffers and uniforms before
|
// Device and Queue, which can be used, for instance, to update buffers and uniforms before
|
||||||
// rendering.
|
// 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
|
// The paint callback is called after prepare and is given access to the render pass, which
|
||||||
// can be used to issue draw commands.
|
// can be used to issue draw commands.
|
||||||
let cb = egui_wgpu::CallbackFn::new()
|
let cb = egui_wgpu::CallbackFn::new()
|
||||||
.prepare(move |device, queue, _encoder, paint_callback_resources| {
|
.prepare(move |device, queue, _encoder, paint_callback_resources| {
|
||||||
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
||||||
resources.prepare(device, queue, angle);
|
resources.prepare(device, queue, angle);
|
||||||
|
Vec::new()
|
||||||
})
|
})
|
||||||
.paint(move |_info, render_pass, paint_callback_resources| {
|
.paint(move |_info, render_pass, paint_callback_resources| {
|
||||||
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
||||||
|
|
Loading…
Reference in a new issue