1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-02 06:40:06 -04:00

egui-wgpu: let paint callbacks read the backdrop

Adds `CallbackTrait::needs_backdrop`, so an effect can transform what egui has
already drawn rather than draw over it. Background blur behind a window or a
panel is the motivating case.

Nothing can sample the texture it is currently drawing into, so this needs the
render pass interrupted and the half-drawn frame copied aside. The renderer
does not own its pass, so the integration has to drive that: `render_from`
draws until it reaches a callback that wants a backdrop and stops, and
`capture_backdrop` copies the frame and lets the callback run its own passes
over it. `render` still works as before for integrations that do not care;
callbacks that asked for a backdrop just get a plain `paint`.

The surface texture cannot be copied from on every platform, which is why
`CaptureState` exists for screenshots. `Painter` now renders into that texture
whenever a backdrop is needed too, and blits it onto the surface afterwards, so
`copy_textures` is split into `copy_to_buffer` and `blit_to_surface`.

`egui_kittest`'s wgpu renderer drives the same loop, so backdrop effects can be
snapshot-tested.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucas Meurer
2026-07-30 21:58:31 +02:00
parent b941904d4d
commit 32a8d1c378
4 changed files with 573 additions and 95 deletions

View File

@@ -208,24 +208,64 @@ impl crate::TestRenderer for WgpuTestRenderer {
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
{
let mut pass = encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Egui Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
..Default::default()
})
.forget_lifetime();
// A paint callback may need to see what egui has already drawn, e.g. to blur the
// background behind a panel. Nothing can sample the texture it is drawing into, so
// that needs the render pass interrupted and the half-drawn frame copied aside.
let backdrop_texture = egui_wgpu::Renderer::needs_backdrop(&tessellated).then(|| {
egui_wgpu::BackdropTexture::new(
&self.render_state.device,
screen.size_in_pixels,
self.render_state.target_format,
)
});
let mut cursor = egui_wgpu::RenderCursor::default();
let mut backdrop = None;
let mut load = wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT);
renderer.render(&mut pass, &tessellated, &screen);
loop {
let progress = {
let mut pass = encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Egui Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &texture_view,
resolve_target: None,
ops: wgpu::Operations {
load,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
..Default::default()
})
.forget_lifetime();
renderer.render_from(
&mut pass,
&tessellated,
&screen,
&mut cursor,
backdrop.as_ref(),
)
};
if progress == egui_wgpu::RenderProgress::Done {
break;
}
let Some(backdrop_texture) = backdrop_texture.as_ref() else {
break;
};
backdrop = renderer.capture_backdrop(
&self.render_state.device,
&self.render_state.queue,
&mut encoder,
&tessellated,
cursor,
&texture,
backdrop_texture,
);
load = wgpu::LoadOp::Load;
}
self.render_state