Browse Source

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
pull/2143/head
Andreas Reich 2 years ago
committed by GitHub
parent
commit
c414af7aa2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      crates/eframe/CHANGELOG.md
  2. 46
      crates/eframe/src/web/web_painter_wgpu.rs
  3. 4
      crates/egui-wgpu/CHANGELOG.md
  4. 104
      crates/egui-wgpu/src/renderer.rs
  5. 86
      crates/egui-wgpu/src/winit.rs
  6. 2
      crates/egui_demo_app/src/apps/custom3d_wgpu.rs

1
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)):

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

4
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

104
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<PaintCallback>,
}
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<F>(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<SizedBuffer>,
vertex_buffers: Vec<SizedBuffer>,
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<wgpu::TextureFormat>,
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<wgpu::Color>,
) {
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);
}
}
}

86
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<wgpu::TextureFormat>,
depth_texture_view: Option<wgpu::TextureView>,
instance: Instance,
adapter: Option<Adapter>,
@ -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();

2
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);
})

Loading…
Cancel
Save