fix(avatars): cache image handles so avatars don't flicker on redraw
iced's `image::Handle::from_bytes` assigns a fresh *unique* id on every call (unlike `from_path`, which hashes). `avatar_view` built the handle inline in `view()`, so every repaint produced a "new" image and iced re-uploaded the texture each frame. Any redraw triggered it — notably the redraws fired on mouse movement — so visible avatars flickered constantly while the mouse moved. Fix: cache handles by a content hash of the PNG bytes (thread-local, UI thread) and reuse the same `Handle` across redraws, giving a stable texture id. Covers both presets and custom uploads. Build + clippy clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+28
-1
@@ -2915,6 +2915,33 @@ fn avatar_badge<'a>(name: &str, key: &str, size: f32) -> Element<'a, AppMessage>
|
||||
.into()
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// Cache of avatar image handles, keyed by a hash of the PNG bytes, so the
|
||||
/// SAME `image::Handle` (and thus the same GPU texture id) is reused across
|
||||
/// redraws. `image::Handle::from_bytes` mints a fresh *unique* id on every
|
||||
/// call, so building handles inline in `view()` made iced re-upload the
|
||||
/// texture on every repaint — including the redraws fired on each mouse move —
|
||||
/// which showed up as constant flicker. Lives on the (single) UI thread.
|
||||
static AVATAR_HANDLE_CACHE: std::cell::RefCell<HashMap<u64, iced::widget::image::Handle>> =
|
||||
std::cell::RefCell::new(HashMap::new());
|
||||
}
|
||||
|
||||
/// A stable image handle for these exact PNG bytes (cached by content hash), so
|
||||
/// it keeps the same id across redraws — see [`AVATAR_HANDLE_CACHE`].
|
||||
fn cached_image_handle(bytes: bytes::Bytes) -> iced::widget::image::Handle {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
bytes.as_ref().hash(&mut hasher);
|
||||
let key = hasher.finish();
|
||||
AVATAR_HANDLE_CACHE.with(|cache| {
|
||||
cache
|
||||
.borrow_mut()
|
||||
.entry(key)
|
||||
.or_insert_with(|| iced::widget::image::Handle::from_bytes(bytes))
|
||||
.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Render a participant's avatar (W4): the chosen preset image if any, else the
|
||||
/// monogram fallback. `name`/`key` feed the monogram; `size` is the diameter.
|
||||
fn avatar_view<'a>(
|
||||
@@ -2928,7 +2955,7 @@ fn avatar_view<'a>(
|
||||
.map(bytes::Bytes::from_static)
|
||||
.or_else(|| avatar.custom_png().map(bytes::Bytes::from));
|
||||
match png_bytes {
|
||||
Some(b) => iced::widget::image(iced::widget::image::Handle::from_bytes(b))
|
||||
Some(b) => iced::widget::image(cached_image_handle(b))
|
||||
.width(iced::Length::Fixed(size))
|
||||
.height(iced::Length::Fixed(size))
|
||||
.into(),
|
||||
|
||||
Reference in New Issue
Block a user