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()
|
.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
|
/// 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.
|
/// monogram fallback. `name`/`key` feed the monogram; `size` is the diameter.
|
||||||
fn avatar_view<'a>(
|
fn avatar_view<'a>(
|
||||||
@@ -2928,7 +2955,7 @@ fn avatar_view<'a>(
|
|||||||
.map(bytes::Bytes::from_static)
|
.map(bytes::Bytes::from_static)
|
||||||
.or_else(|| avatar.custom_png().map(bytes::Bytes::from));
|
.or_else(|| avatar.custom_png().map(bytes::Bytes::from));
|
||||||
match png_bytes {
|
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))
|
.width(iced::Length::Fixed(size))
|
||||||
.height(iced::Length::Fixed(size))
|
.height(iced::Length::Fixed(size))
|
||||||
.into(),
|
.into(),
|
||||||
|
|||||||
Reference in New Issue
Block a user