Introduce explicit image::allocate API

This commit is contained in:
Héctor Ramón Jiménez 2025-10-25 00:07:13 +02:00
parent 6fa54f7f6b
commit 23039e758e
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
15 changed files with 355 additions and 51 deletions

View file

@ -4,8 +4,10 @@ pub use bytes::Bytes;
use crate::{Radians, Rectangle, Size}; use crate::{Radians, Rectangle, Size};
use rustc_hash::FxHasher; use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, Weak};
/// A raster image that can be drawn. /// A raster image that can be drawn.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -227,6 +229,56 @@ pub enum FilterMethod {
Nearest, Nearest,
} }
/// A memory allocation of a [`Handle`], often in GPU memory.
///
/// Renderers tend to decode and upload image data concurrently to
/// avoid blocking the user interface. This means that when you use a
/// [`Handle`] in a widget, there may be a slight frame delay until it
/// is finally visible. If you are animating images, this can cause
/// undesirable flicker.
///
/// When you obtain an [`Allocation`] explicitly, you get the guarantee
/// that using a [`Handle`] will draw the corresponding [`Image`]
/// immediately in the next frame.
///
/// This guarantee is valid as long as you hold an [`Allocation`].
/// Only when you drop all its clones, the renderer may choose to free
/// the memory of the [`Handle`]. Be careful!
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Allocation(Arc<Memory>);
/// Some memory taken by an [`Allocation`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Memory(Handle);
impl Allocation {
/// Returns a weak reference to the [`Memory`] of the [`Allocation`].
pub fn downgrade(&self) -> Weak<Memory> {
Arc::downgrade(&self.0)
}
/// Upgrades a [`Weak`] memory reference to an [`Allocation`].
pub fn upgrade(weak: &Weak<Memory>) -> Option<Allocation> {
Weak::upgrade(weak).map(Allocation)
}
/// Returns the [`Handle`] of this [`Allocation`].
pub fn handle(&self) -> &Handle {
&self.0.0
}
}
/// Creates a new [`Allocation`] for the given handle.
///
/// This should only be used internally by renderer implementations.
///
/// # Safety
/// Must only be created once the [`Handle`] is allocated in memory.
#[allow(unsafe_code)]
pub unsafe fn allocate(handle: &Handle) -> Allocation {
Allocation(Arc::new(Memory(handle.clone())))
}
/// A [`Renderer`] that can render raster graphics. /// A [`Renderer`] that can render raster graphics.
/// ///
/// [renderer]: crate::renderer /// [renderer]: crate::renderer

View file

@ -2,6 +2,7 @@
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
mod null; mod null;
use crate::image;
use crate::{ use crate::{
Background, Border, Color, Font, Pixels, Rectangle, Shadow, Size, Background, Border, Color, Font, Pixels, Rectangle, Shadow, Size,
Transformation, Vector, Transformation, Vector,
@ -62,6 +63,13 @@ pub trait Renderer {
/// Resets the [`Renderer`] to start drawing in the `new_bounds` from scratch. /// Resets the [`Renderer`] to start drawing in the `new_bounds` from scratch.
fn reset(&mut self, new_bounds: Rectangle); fn reset(&mut self, new_bounds: Rectangle);
/// Creates an [`image::Allocation`] for the given [`image::Handle`] and calls the given callback with it.
fn allocate_image(
&mut self,
handle: &image::Handle,
callback: impl FnOnce(image::Allocation) + Send + 'static,
);
} }
/// A polygon with four sides. /// A polygon with four sides.

View file

@ -24,6 +24,15 @@ impl Renderer for () {
_background: impl Into<Background>, _background: impl Into<Background>,
) { ) {
} }
fn allocate_image(
&mut self,
handle: &image::Handle,
callback: impl FnOnce(image::Allocation) + Send + 'static,
) {
#[allow(unsafe_code)]
callback(unsafe { image::allocate(handle) });
}
} }
impl text::Renderer for () { impl text::Renderer for () {

View file

@ -46,8 +46,9 @@ enum Message {
ImagesListed(Result<Vec<Image>, Error>), ImagesListed(Result<Vec<Image>, Error>),
ImagePoppedIn(Id), ImagePoppedIn(Id),
ImagePoppedOut(Id), ImagePoppedOut(Id),
ImageDownloaded(Result<Bytes, Error>), ImageDownloaded(Result<image::Allocation, Error>),
ThumbnailDownloaded(Id, Result<Bytes, Error>), ThumbnailDownloaded(Id, Result<Bytes, Error>),
ThumbnailAllocated(Id, image::Allocation),
ThumbnailHovered(Id, bool), ThumbnailHovered(Id, bool),
BlurhashDecoded(Id, civitai::Blurhash), BlurhashDecoded(Id, civitai::Blurhash),
Open(Id), Open(Id),
@ -110,14 +111,16 @@ impl Gallery {
let _ = self.visible.insert(id); let _ = self.visible.insert(id);
if self.downloaded.contains(&id) { if self.downloaded.contains(&id) {
if let Some(Preview::Ready { thumbnail, .. }) = let Some(Preview::Ready { thumbnail, .. }) =
self.previews.get_mut(&id) self.previews.get_mut(&id)
{ else {
thumbnail.fade_in = return Task::none();
Animation::new(false).slow().go(true, now); };
}
return Task::none(); return image::allocate(image::Handle::from_bytes(
thumbnail.bytes.clone(),
))
.map(Message::ThumbnailAllocated.with(id));
} }
let _ = self.downloaded.insert(id); let _ = self.downloaded.insert(id);
@ -134,22 +137,56 @@ impl Gallery {
Message::ImagePoppedOut(id) => { Message::ImagePoppedOut(id) => {
let _ = self.visible.remove(&id); let _ = self.visible.remove(&id);
if let Some(Preview::Ready {
thumbnail,
blurhash,
}) = self.previews.get_mut(&id)
{
thumbnail.reset();
if let Some(blurhash) = blurhash {
blurhash.reset();
}
}
Task::none() Task::none()
} }
Message::ImageDownloaded(Ok(bytes)) => { Message::ImageDownloaded(Ok(allocation)) => {
self.viewer.show(bytes, self.now); self.viewer.show(allocation, self.now);
Task::none() Task::none()
} }
Message::ThumbnailDownloaded(id, Ok(bytes)) => { Message::ThumbnailDownloaded(id, Ok(bytes)) => {
let thumbnail = if let Some(preview) = self.previews.remove(&id) let preview = if let Some(preview) = self.previews.remove(&id) {
{ preview.load(bytes.clone())
preview.load(bytes, self.now)
} else { } else {
Preview::ready(bytes, self.now) Preview::ready(bytes.clone())
}; };
let _ = self.previews.insert(id, thumbnail); let _ = self.previews.insert(id, preview);
image::allocate(image::Handle::from_bytes(bytes))
.map(Message::ThumbnailAllocated.with(id))
}
Message::ThumbnailAllocated(id, allocation) => {
if !self.visible.contains(&id) {
return Task::none();
}
let Some(Preview::Ready {
thumbnail,
blurhash,
..
}) = self.previews.get_mut(&id)
else {
return Task::none();
};
if let Some(blurhash) = blurhash {
blurhash.show(now);
}
thumbnail.show(allocation, now);
Task::none() Task::none()
} }
@ -181,10 +218,12 @@ impl Gallery {
self.viewer.open(self.now); self.viewer.open(self.now);
Task::perform( Task::future(image.download(Size::Original))
image.download(Size::Original), .and_then(|bytes| {
Message::ImageDownloaded, image::allocate(image::Handle::from_bytes(bytes))
) .map(Ok)
})
.map(Message::ImageDownloaded)
} }
Message::Close => { Message::Close => {
self.viewer.close(self.now); self.viewer.close(self.now);
@ -238,9 +277,11 @@ fn card<'a>(
) -> Element<'a, Message> { ) -> Element<'a, Message> {
let image = if let Some(preview) = preview { let image = if let Some(preview) = preview {
let thumbnail: Element<'_, _> = let thumbnail: Element<'_, _> =
if let Preview::Ready { thumbnail, .. } = &preview { if let Preview::Ready { thumbnail, .. } = &preview
&& let Some(allocation) = &thumbnail.allocation
{
float( float(
image(&thumbnail.handle) image(allocation.handle())
.width(Fill) .width(Fill)
.content_fit(ContentFit::Cover) .content_fit(ContentFit::Cover)
.opacity(thumbnail.fade_in.interpolate(0.0, 1.0, now)), .opacity(thumbnail.fade_in.interpolate(0.0, 1.0, now)),
@ -320,8 +361,21 @@ struct Blurhash {
fade_in: Animation<bool>, fade_in: Animation<bool>,
} }
impl Blurhash {
pub fn show(&mut self, now: Instant) {
self.fade_in.go_mut(true, now);
}
pub fn reset(&mut self) {
self.fade_in = Animation::new(false)
.easing(animation::Easing::EaseIn)
.slow();
}
}
struct Thumbnail { struct Thumbnail {
handle: image::Handle, bytes: Bytes,
allocation: Option<image::Allocation>,
fade_in: Animation<bool>, fade_in: Animation<bool>,
zoom: Animation<bool>, zoom: Animation<bool>,
} }
@ -346,21 +400,21 @@ impl Preview {
} }
} }
fn ready(bytes: Bytes, now: Instant) -> Self { fn ready(bytes: Bytes) -> Self {
Self::Ready { Self::Ready {
blurhash: None, blurhash: None,
thumbnail: Thumbnail::new(bytes, now), thumbnail: Thumbnail::new(bytes),
} }
} }
fn load(self, bytes: Bytes, now: Instant) -> Self { fn load(self, bytes: Bytes) -> Self {
let Self::Loading { blurhash } = self else { let Self::Loading { blurhash } = self else {
return self; return self;
}; };
Self::Ready { Self::Ready {
blurhash: Some(blurhash), blurhash: Some(blurhash),
thumbnail: Thumbnail::new(bytes, now), thumbnail: Thumbnail::new(bytes),
} }
} }
@ -387,26 +441,45 @@ impl Preview {
blurhash: Some(blurhash), blurhash: Some(blurhash),
thumbnail, thumbnail,
.. ..
} if thumbnail.fade_in.is_animating(now) => Some(blurhash), } if !thumbnail.fade_in.value()
|| thumbnail.fade_in.is_animating(now) =>
{
Some(blurhash)
}
Self::Ready { .. } => None, Self::Ready { .. } => None,
} }
} }
} }
impl Thumbnail { impl Thumbnail {
pub fn new(bytes: Bytes, now: Instant) -> Self { pub fn new(bytes: Bytes) -> Self {
Self { Self {
handle: image::Handle::from_bytes(bytes), bytes,
fade_in: Animation::new(false).slow().go(true, now), allocation: None,
fade_in: Animation::new(false)
.easing(animation::Easing::EaseIn)
.slow(),
zoom: Animation::new(false) zoom: Animation::new(false)
.quick() .quick()
.easing(animation::Easing::EaseInOut), .easing(animation::Easing::EaseInOut),
} }
} }
pub fn reset(&mut self) {
self.allocation = None;
self.fade_in = Animation::new(false)
.easing(animation::Easing::EaseIn)
.slow();
}
pub fn show(&mut self, allocation: image::Allocation, now: Instant) {
self.allocation = Some(allocation);
self.fade_in.go_mut(true, now);
}
} }
struct Viewer { struct Viewer {
image: Option<image::Handle>, image: Option<image::Allocation>,
background_fade_in: Animation<bool>, background_fade_in: Animation<bool>,
image_fade_in: Animation<bool>, image_fade_in: Animation<bool>,
} }
@ -429,8 +502,8 @@ impl Viewer {
self.background_fade_in.go_mut(true, now); self.background_fade_in.go_mut(true, now);
} }
fn show(&mut self, bytes: Bytes, now: Instant) { fn show(&mut self, allocation: image::Allocation, now: Instant) {
self.image = Some(image::Handle::from_bytes(bytes)); self.image = Some(allocation);
self.background_fade_in.go_mut(true, now); self.background_fade_in.go_mut(true, now);
self.image_fade_in.go_mut(true, now); self.image_fade_in.go_mut(true, now);
} }
@ -452,8 +525,8 @@ impl Viewer {
return None; return None;
} }
let image = self.image.as_ref().map(|handle| { let image = self.image.as_ref().map(|allocation| {
image(handle) image(allocation.handle())
.width(Fill) .width(Fill)
.height(Fill) .height(Fill)
.opacity(self.image_fade_in.interpolate(0.0, 1.0, now)) .opacity(self.image_fade_in.interpolate(0.0, 1.0, now))

View file

@ -69,6 +69,14 @@ where
fn end_transformation(&mut self) { fn end_transformation(&mut self) {
delegate!(self, renderer, renderer.end_transformation()); delegate!(self, renderer, renderer.end_transformation());
} }
fn allocate_image(
&mut self,
handle: &image::Handle,
callback: impl FnOnce(image::Allocation) + Send + 'static,
) {
delegate!(self, renderer, renderer.allocate_image(handle, callback));
}
} }
impl<A, B> core::text::Renderer for Renderer<A, B> impl<A, B> core::text::Renderer for Renderer<A, B>

24
runtime/src/image.rs Normal file
View file

@ -0,0 +1,24 @@
//! Allocate images explicitly to control presentation.
use crate::core::image::Handle;
use crate::futures::futures::channel::oneshot;
use crate::task::{self, Task};
pub use crate::core::image::Allocation;
/// An image action.
#[derive(Debug)]
pub enum Action {
/// Allocates the given [`Handle`].
Allocate(Handle, oneshot::Sender<Allocation>),
}
/// Allocates an image [`Handle`].
///
/// When you obtain an [`Allocation`] explicitly, you get the guarantee
/// that using a [`Handle`] will draw the corresponding image immediately
/// in the next frame.
pub fn allocate(handle: Handle) -> Task<Allocation> {
task::oneshot(|sender| {
crate::Action::Image(Action::Allocate(handle, sender))
})
}

View file

@ -11,6 +11,7 @@
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
pub mod clipboard; pub mod clipboard;
pub mod font; pub mod font;
pub mod image;
pub mod keyboard; pub mod keyboard;
pub mod system; pub mod system;
pub mod task; pub mod task;
@ -55,6 +56,9 @@ pub enum Action<T> {
/// Run a system action. /// Run a system action.
System(system::Action), System(system::Action),
/// An image action.
Image(image::Action),
/// Recreate all user interfaces and redraw all windows. /// Recreate all user interfaces and redraw all windows.
Reload, Reload,
@ -81,6 +85,7 @@ impl<T> Action<T> {
Action::Clipboard(action) => Err(Action::Clipboard(action)), Action::Clipboard(action) => Err(Action::Clipboard(action)),
Action::Window(action) => Err(Action::Window(action)), Action::Window(action) => Err(Action::Window(action)),
Action::System(action) => Err(Action::System(action)), Action::System(action) => Err(Action::System(action)),
Action::Image(action) => Err(Action::Image(action)),
Action::Reload => Err(Action::Reload), Action::Reload => Err(Action::Reload),
Action::Exit => Err(Action::Exit), Action::Exit => Err(Action::Exit),
} }
@ -105,6 +110,7 @@ where
} }
Action::Window(_) => write!(f, "Action::Window"), Action::Window(_) => write!(f, "Action::Window"),
Action::System(action) => write!(f, "Action::System({action:?})"), Action::System(action) => write!(f, "Action::System({action:?})"),
Action::Image(_) => write!(f, "Action::Image"),
Action::Reload => write!(f, "Action::Reload"), Action::Reload => write!(f, "Action::Reload"),
Action::Exit => write!(f, "Action::Exit"), Action::Exit => write!(f, "Action::Exit"),
} }

View file

@ -379,14 +379,16 @@ impl<T, E> Task<Result<T, E>> {
/// The success value is provided to the closure to create the subsequent [`Task`]. /// The success value is provided to the closure to create the subsequent [`Task`].
pub fn and_then<A>( pub fn and_then<A>(
self, self,
f: impl Fn(T) -> Task<A> + MaybeSend + 'static, f: impl Fn(T) -> Task<Result<A, E>> + MaybeSend + 'static,
) -> Task<A> ) -> Task<Result<A, E>>
where where
T: MaybeSend + 'static, T: MaybeSend + 'static,
E: MaybeSend + 'static, E: MaybeSend + 'static,
A: MaybeSend + 'static, A: MaybeSend + 'static,
{ {
self.then(move |option| option.map_or_else(|_| Task::none(), &f)) self.then(move |result| {
result.map_or_else(|error| Task::done(Err(error)), &f)
})
} }
} }

View file

@ -625,6 +625,13 @@ pub mod widget {
pub use iced_runtime::widget::*; pub use iced_runtime::widget::*;
pub use iced_widget::*; pub use iced_widget::*;
#[cfg(feature = "image")]
pub mod image {
//! Images display raster graphics in different formats (PNG, JPG, etc.).
pub use iced_runtime::image::{Allocation, allocate};
pub use iced_widget::image::*;
}
// We hide the re-exported modules by `iced_widget` // We hide the re-exported modules by `iced_widget`
mod core {} mod core {}
mod graphics {} mod graphics {}

View file

@ -257,6 +257,10 @@ impl<P: Program + 'static> Emulator<P> {
// TODO // TODO
dbg!(action); dbg!(action);
} }
iced_runtime::Action::Image(action) => {
// TODO
dbg!(action);
}
runtime::Action::Exit => { runtime::Action::Exit => {
// TODO // TODO
} }

View file

@ -228,6 +228,16 @@ impl core::Renderer for Renderer {
fn reset(&mut self, new_bounds: Rectangle) { fn reset(&mut self, new_bounds: Rectangle) {
self.layers.reset(new_bounds); self.layers.reset(new_bounds);
} }
fn allocate_image(
&mut self,
handle: &core::image::Handle,
callback: impl FnOnce(core::image::Allocation) + Send + 'static,
) {
// TODO: Concurrency
#[allow(unsafe_code)]
callback(unsafe { core::image::allocate(handle) });
}
} }
impl core::text::Renderer for Renderer { impl core::text::Renderer for Renderer {

View file

@ -3,12 +3,11 @@ use crate::graphics::Shell;
use crate::image::atlas::{self, Atlas}; use crate::image::atlas::{self, Atlas};
#[cfg(feature = "image")] #[cfg(feature = "image")]
use std::collections::BTreeSet; use std::collections::HashMap;
#[cfg(feature = "image")] #[cfg(feature = "image")]
use std::sync::mpsc; use std::sync::mpsc;
#[derive(Debug)]
pub struct Cache { pub struct Cache {
atlas: Atlas, atlas: Atlas,
#[cfg(feature = "image")] #[cfg(feature = "image")]
@ -46,7 +45,7 @@ impl Cache {
#[cfg(feature = "image")] #[cfg(feature = "image")]
raster: Raster { raster: Raster {
cache: crate::image::raster::Cache::default(), cache: crate::image::raster::Cache::default(),
pending: BTreeSet::new(), pending: HashMap::new(),
jobs: jobs.clone(), jobs: jobs.clone(),
}, },
#[cfg(feature = "svg")] #[cfg(feature = "svg")]
@ -60,6 +59,44 @@ impl Cache {
} }
} }
#[cfg(feature = "image")]
pub fn allocate_image(
&mut self,
handle: &core::image::Handle,
callback: impl FnOnce(core::image::Allocation) + Send + 'static,
) {
use crate::image::raster::Memory;
let callback = Box::new(callback);
if let Some(callbacks) = self.raster.pending.get_mut(&handle.id()) {
callbacks.push(callback);
return;
}
if let Some(Memory::Device { allocation, .. }) =
self.raster.cache.get_mut(handle)
{
if let Some(allocation) = allocation
.as_ref()
.and_then(core::image::Allocation::upgrade)
{
callback(allocation);
return;
}
#[allow(unsafe_code)]
let new = unsafe { core::image::allocate(handle) };
*allocation = Some(new.downgrade());
callback(new);
return;
}
let _ = self.raster.pending.insert(handle.id(), vec![callback]);
let _ = self.raster.jobs.send(Job::Load(handle.clone()));
}
#[cfg(feature = "image")] #[cfg(feature = "image")]
pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> { pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
self.receive(); self.receive();
@ -69,6 +106,7 @@ impl Cache {
&mut self.raster.pending, &mut self.raster.pending,
&mut self.raster.jobs, &mut self.raster.jobs,
handle, handle,
None,
) { ) {
return memory.dimensions(); return memory.dimensions();
} }
@ -99,9 +137,13 @@ impl Cache {
&mut self.raster.pending, &mut self.raster.pending,
&mut self.raster.jobs, &mut self.raster.jobs,
handle, handle,
None,
)?; )?;
if let Memory::Device { entry, bind_group } = memory { if let Memory::Device {
entry, bind_group, ..
} = memory
{
return Some(( return Some((
entry, entry,
bind_group.as_ref().unwrap_or(self.atlas.bind_group()), bind_group.as_ref().unwrap_or(self.atlas.bind_group()),
@ -126,6 +168,7 @@ impl Cache {
*memory = Memory::Device { *memory = Memory::Device {
entry, entry,
bind_group: None, bind_group: None,
allocation: None,
}; };
if let Memory::Device { entry, .. } = memory { if let Memory::Device { entry, .. } = memory {
@ -133,7 +176,7 @@ impl Cache {
} }
} }
if !self.raster.pending.contains(&handle.id()) { if !self.raster.pending.contains_key(&handle.id()) {
let _ = self.jobs.send(Job::Upload { let _ = self.jobs.send(Job::Upload {
handle: handle.clone(), handle: handle.clone(),
rgba: image.clone().into_raw(), rgba: image.clone().into_raw(),
@ -141,7 +184,7 @@ impl Cache {
height: image.height(), height: image.height(),
}); });
let _ = self.raster.pending.insert(handle.id()); let _ = self.raster.pending.insert(handle.id(), Vec::new());
} }
None None
@ -194,15 +237,32 @@ impl Cache {
entry, entry,
bind_group, bind_group,
} => { } => {
let callbacks = self.raster.pending.remove(&handle.id());
let allocation = if let Some(callbacks) = callbacks {
#[allow(unsafe_code)]
let allocation =
unsafe { core::image::allocate(&handle) };
let reference = allocation.downgrade();
for callback in callbacks {
callback(allocation.clone());
}
Some(reference)
} else {
None
};
self.raster.cache.insert( self.raster.cache.insert(
&handle, &handle,
Memory::Device { Memory::Device {
entry, entry,
bind_group: Some(bind_group), bind_group: Some(bind_group),
allocation,
}, },
); );
let _ = self.raster.pending.remove(&handle.id());
} }
Work::Error { handle, error } => { Work::Error { handle, error } => {
self.raster.cache.insert(&handle, Memory::error(error)); self.raster.cache.insert(&handle, Memory::error(error));
@ -225,19 +285,22 @@ impl Drop for Cache {
} }
#[cfg(feature = "image")] #[cfg(feature = "image")]
#[derive(Debug)]
struct Raster { struct Raster {
cache: crate::image::raster::Cache, cache: crate::image::raster::Cache,
pending: BTreeSet<core::image::Id>, pending: HashMap<core::image::Id, Vec<Callback>>,
jobs: mpsc::SyncSender<Job>, jobs: mpsc::SyncSender<Job>,
} }
#[cfg(feature = "image")]
type Callback = Box<dyn FnOnce(core::image::Allocation) + Send>;
#[cfg(feature = "image")] #[cfg(feature = "image")]
fn load_image<'a>( fn load_image<'a>(
cache: &'a mut crate::image::raster::Cache, cache: &'a mut crate::image::raster::Cache,
pending: &mut BTreeSet<core::image::Id>, pending: &mut HashMap<core::image::Id, Vec<Callback>>,
jobs: &mut mpsc::SyncSender<Job>, jobs: &mut mpsc::SyncSender<Job>,
handle: &core::image::Handle, handle: &core::image::Handle,
callback: Option<Callback>,
) -> Option<&'a mut crate::image::raster::Memory> { ) -> Option<&'a mut crate::image::raster::Memory> {
use crate::image::raster::Memory; use crate::image::raster::Memory;
@ -248,9 +311,9 @@ fn load_image<'a>(
} else if let core::image::Handle::Rgba { .. } = handle { } else if let core::image::Handle::Rgba { .. } = handle {
// Load RGBA handles synchronously, since it's very cheap // Load RGBA handles synchronously, since it's very cheap
cache.insert(handle, Memory::load(handle)); cache.insert(handle, Memory::load(handle));
} else if !pending.contains(&handle.id()) { } else if !pending.contains_key(&handle.id()) {
let _ = jobs.send(Job::Load(handle.clone())); let _ = jobs.send(Job::Load(handle.clone()));
let _ = pending.insert(handle.id()); let _ = pending.insert(handle.id(), Vec::from_iter(callback));
} }
} }

View file

@ -5,6 +5,7 @@ use crate::graphics::image::image_rs;
use crate::image::atlas::{self, Atlas}; use crate::image::atlas::{self, Atlas};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use std::sync::Weak;
type Image = image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>; type Image = image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>;
@ -17,6 +18,7 @@ pub enum Memory {
Device { Device {
entry: atlas::Entry, entry: atlas::Entry,
bind_group: Option<wgpu::BindGroup>, bind_group: Option<wgpu::BindGroup>,
allocation: Option<Weak<image::Memory>>,
}, },
/// Image not found /// Image not found
NotFound, NotFound,
@ -100,7 +102,16 @@ impl Cache {
self.map.retain(|k, memory| { self.map.retain(|k, memory| {
let retain = hits.contains(k); let retain = hits.contains(k);
if !retain && let Memory::Device { entry, bind_group } = memory { if !retain
&& let Memory::Device {
entry,
bind_group,
allocation: memory,
} = memory
&& memory
.as_ref()
.is_none_or(|memory| memory.strong_count() == 0)
{
if let Some(bind_group) = bind_group.take() { if let Some(bind_group) = bind_group.take() {
on_drop(bind_group); on_drop(bind_group);
} else { } else {

View file

@ -696,6 +696,17 @@ impl core::Renderer for Renderer {
fn reset(&mut self, new_bounds: Rectangle) { fn reset(&mut self, new_bounds: Rectangle) {
self.layers.reset(new_bounds); self.layers.reset(new_bounds);
} }
fn allocate_image(
&mut self,
_handle: &core::image::Handle,
_callback: impl FnOnce(core::image::Allocation) + Send + 'static,
) {
#[cfg(feature = "image")]
self.image_cache
.get_mut()
.allocate_image(_handle, _callback);
}
} }
impl core::text::Renderer for Renderer { impl core::text::Renderer for Renderer {

View file

@ -50,6 +50,7 @@ use crate::futures::futures::{Future, StreamExt};
use crate::futures::subscription; use crate::futures::subscription;
use crate::futures::{Executor, Runtime}; use crate::futures::{Executor, Runtime};
use crate::graphics::{Compositor, Shell, compositor}; use crate::graphics::{Compositor, Shell, compositor};
use crate::runtime::image;
use crate::runtime::system; use crate::runtime::system;
use crate::runtime::user_interface::{self, UserInterface}; use crate::runtime::user_interface::{self, UserInterface};
use crate::runtime::{Action, Task}; use crate::runtime::{Action, Task};
@ -1730,6 +1731,21 @@ fn run_action<'a, P, C>(
} }
} }
} }
Action::Image(action) => match action {
image::Action::Allocate(handle, sender) => {
use core::Renderer as _;
// TODO: Shared image cache in compositor
if let Some((_id, window)) = window_manager.iter_mut().next() {
window.renderer.allocate_image(
&handle,
move |allocation| {
let _ = sender.send(allocation);
},
);
}
}
},
Action::LoadFont { bytes, channel } => { Action::LoadFont { bytes, channel } => {
if let Some(compositor) = compositor { if let Some(compositor) = compositor {
// TODO: Error handling (?) // TODO: Error handling (?)