mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 14:50:03 -04:00
Prevent accidentally dropping TexturesDelta (#8356)
We had a ton of issues around `TexturesDelta` that weren't properly applied because we early-out of some function: * https://github.com/emilk/egui/pull/8313 * https://github.com/emilk/egui/pull/8250 * https://github.com/emilk/egui/pull/8279 This PR changes texture updates, so that we always store them after taking them out of `FullOutput` and keep the delta around until it's actually applied (by passing &mut refs and draining instead of iterating). So even if we add a new early return somewhere, that can't break texture updates. It also optimizes `TexturesDelta::append` by dropping any previous deltas if there's a new `whole` delta or a `free`. It also adds a debug assert that any `TexturesDelta` is empty when dropped, as an additional safeguard in case the bug sneaks back in. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -147,7 +147,7 @@ impl<'a, State> Harness<'a, State> {
|
||||
response = app.run(ui, &mut state, false);
|
||||
});
|
||||
|
||||
renderer.handle_delta(&output.textures_delta);
|
||||
renderer.handle_delta(&mut output.textures_delta);
|
||||
|
||||
let mut harness = Self {
|
||||
app,
|
||||
@@ -269,7 +269,7 @@ impl<'a, State> Harness<'a, State> {
|
||||
.take()
|
||||
.expect("AccessKit was disabled"),
|
||||
);
|
||||
self.renderer.handle_delta(&output.textures_delta);
|
||||
self.renderer.handle_delta(&mut output.textures_delta);
|
||||
self.output = output;
|
||||
|
||||
self.handle_viewport_commands();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use egui::TexturesDelta;
|
||||
use std::mem;
|
||||
|
||||
pub trait TestRenderer {
|
||||
/// We use this to pass the glow / wgpu render state to [`eframe::Frame`].
|
||||
@@ -6,7 +7,7 @@ pub trait TestRenderer {
|
||||
fn setup_eframe(&self, _cc: &mut eframe::CreationContext<'_>, _frame: &mut eframe::Frame) {}
|
||||
|
||||
/// Handle a [`TexturesDelta`] by updating the renderer's textures.
|
||||
fn handle_delta(&mut self, delta: &TexturesDelta);
|
||||
fn handle_delta(&mut self, delta: &mut TexturesDelta);
|
||||
|
||||
/// Render the [`crate::Harness`] and return the resulting image.
|
||||
///
|
||||
@@ -25,7 +26,7 @@ pub trait TestRenderer {
|
||||
/// By default, this will create a wgpu renderer if the wgpu feature is enabled.
|
||||
pub enum LazyRenderer {
|
||||
Uninitialized {
|
||||
texture_ops: Vec<egui::TexturesDelta>,
|
||||
textures_delta: TexturesDelta,
|
||||
builder: Option<Box<dyn FnOnce() -> Box<dyn TestRenderer>>>,
|
||||
},
|
||||
Initialized {
|
||||
@@ -39,7 +40,7 @@ impl Default for LazyRenderer {
|
||||
return Self::new(crate::wgpu::WgpuTestRenderer::new);
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
return Self::Uninitialized {
|
||||
texture_ops: Vec::new(),
|
||||
textures_delta: Vec::new(),
|
||||
builder: None,
|
||||
};
|
||||
}
|
||||
@@ -48,16 +49,19 @@ impl Default for LazyRenderer {
|
||||
impl LazyRenderer {
|
||||
pub fn new<T: TestRenderer + 'static>(create_renderer: impl FnOnce() -> T + 'static) -> Self {
|
||||
Self::Uninitialized {
|
||||
texture_ops: Vec::new(),
|
||||
textures_delta: Default::default(),
|
||||
builder: Some(Box::new(move || Box::new(create_renderer()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRenderer for LazyRenderer {
|
||||
fn handle_delta(&mut self, delta: &TexturesDelta) {
|
||||
fn handle_delta(&mut self, delta: &mut TexturesDelta) {
|
||||
match self {
|
||||
Self::Uninitialized { texture_ops, .. } => texture_ops.push(delta.clone()),
|
||||
Self::Uninitialized {
|
||||
textures_delta: texture_ops,
|
||||
..
|
||||
} => texture_ops.append(mem::take(delta)),
|
||||
Self::Initialized { renderer } => renderer.handle_delta(delta),
|
||||
}
|
||||
}
|
||||
@@ -70,15 +74,15 @@ impl TestRenderer for LazyRenderer {
|
||||
) -> Result<image::RgbaImage, String> {
|
||||
match self {
|
||||
Self::Uninitialized {
|
||||
texture_ops,
|
||||
textures_delta,
|
||||
builder: build,
|
||||
} => {
|
||||
let mut renderer = build.take().ok_or({
|
||||
"No default renderer available. \
|
||||
Enable the wgpu feature or set one via HarnessBuilder::renderer"
|
||||
})?();
|
||||
for delta in texture_ops.drain(..) {
|
||||
renderer.handle_delta(&delta);
|
||||
if !textures_delta.is_empty() {
|
||||
renderer.handle_delta(textures_delta);
|
||||
}
|
||||
let image = renderer.render(ctx, output)?;
|
||||
*self = Self::Initialized { renderer };
|
||||
@@ -88,3 +92,14 @@ impl TestRenderer for LazyRenderer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LazyRenderer {
|
||||
fn drop(&mut self) {
|
||||
match self {
|
||||
Self::Uninitialized { textures_delta, .. } => {
|
||||
textures_delta.clear(); // Don't panic when dropping unapplied deltas
|
||||
}
|
||||
Self::Initialized { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,15 +137,23 @@ impl crate::TestRenderer for WgpuTestRenderer {
|
||||
frame.wgpu_render_state = Some(self.render_state.clone());
|
||||
}
|
||||
|
||||
fn handle_delta(&mut self, delta: &TexturesDelta) {
|
||||
fn handle_delta(&mut self, delta: &mut TexturesDelta) {
|
||||
let mut renderer = self.render_state.renderer.write();
|
||||
for (id, image) in &delta.set {
|
||||
renderer.update_texture(
|
||||
&self.render_state.device,
|
||||
&self.render_state.queue,
|
||||
*id,
|
||||
image,
|
||||
);
|
||||
#[expect(clippy::iter_over_hash_type)] // Order doesn't matter here
|
||||
for (id, images) in delta.set.drain() {
|
||||
for image in images {
|
||||
renderer.update_texture(
|
||||
&self.render_state.device,
|
||||
&self.render_state.queue,
|
||||
id,
|
||||
&image,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::iter_over_hash_type)] // Order doesn't matter here
|
||||
for id in delta.free.drain() {
|
||||
renderer.free_texture(&id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user