Introduce explicit image::allocate API
This commit is contained in:
parent
6fa54f7f6b
commit
23039e758e
15 changed files with 355 additions and 51 deletions
|
|
@ -4,8 +4,10 @@ pub use bytes::Bytes;
|
|||
use crate::{Radians, Rectangle, Size};
|
||||
|
||||
use rustc_hash::FxHasher;
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
/// A raster image that can be drawn.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
|
@ -227,6 +229,56 @@ pub enum FilterMethod {
|
|||
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.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#[cfg(debug_assertions)]
|
||||
mod null;
|
||||
|
||||
use crate::image;
|
||||
use crate::{
|
||||
Background, Border, Color, Font, Pixels, Rectangle, Shadow, Size,
|
||||
Transformation, Vector,
|
||||
|
|
@ -62,6 +63,13 @@ pub trait Renderer {
|
|||
|
||||
/// Resets the [`Renderer`] to start drawing in the `new_bounds` from scratch.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,15 @@ impl Renderer for () {
|
|||
_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 () {
|
||||
|
|
|
|||
|
|
@ -46,8 +46,9 @@ enum Message {
|
|||
ImagesListed(Result<Vec<Image>, Error>),
|
||||
ImagePoppedIn(Id),
|
||||
ImagePoppedOut(Id),
|
||||
ImageDownloaded(Result<Bytes, Error>),
|
||||
ImageDownloaded(Result<image::Allocation, Error>),
|
||||
ThumbnailDownloaded(Id, Result<Bytes, Error>),
|
||||
ThumbnailAllocated(Id, image::Allocation),
|
||||
ThumbnailHovered(Id, bool),
|
||||
BlurhashDecoded(Id, civitai::Blurhash),
|
||||
Open(Id),
|
||||
|
|
@ -110,14 +111,16 @@ impl Gallery {
|
|||
let _ = self.visible.insert(id);
|
||||
|
||||
if self.downloaded.contains(&id) {
|
||||
if let Some(Preview::Ready { thumbnail, .. }) =
|
||||
let Some(Preview::Ready { thumbnail, .. }) =
|
||||
self.previews.get_mut(&id)
|
||||
{
|
||||
thumbnail.fade_in =
|
||||
Animation::new(false).slow().go(true, now);
|
||||
}
|
||||
else {
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
return Task::none();
|
||||
return image::allocate(image::Handle::from_bytes(
|
||||
thumbnail.bytes.clone(),
|
||||
))
|
||||
.map(Message::ThumbnailAllocated.with(id));
|
||||
}
|
||||
|
||||
let _ = self.downloaded.insert(id);
|
||||
|
|
@ -134,22 +137,56 @@ impl Gallery {
|
|||
Message::ImagePoppedOut(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()
|
||||
}
|
||||
Message::ImageDownloaded(Ok(bytes)) => {
|
||||
self.viewer.show(bytes, self.now);
|
||||
Message::ImageDownloaded(Ok(allocation)) => {
|
||||
self.viewer.show(allocation, self.now);
|
||||
|
||||
Task::none()
|
||||
}
|
||||
Message::ThumbnailDownloaded(id, Ok(bytes)) => {
|
||||
let thumbnail = if let Some(preview) = self.previews.remove(&id)
|
||||
{
|
||||
preview.load(bytes, self.now)
|
||||
let preview = if let Some(preview) = self.previews.remove(&id) {
|
||||
preview.load(bytes.clone())
|
||||
} 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()
|
||||
}
|
||||
|
|
@ -181,10 +218,12 @@ impl Gallery {
|
|||
|
||||
self.viewer.open(self.now);
|
||||
|
||||
Task::perform(
|
||||
image.download(Size::Original),
|
||||
Message::ImageDownloaded,
|
||||
)
|
||||
Task::future(image.download(Size::Original))
|
||||
.and_then(|bytes| {
|
||||
image::allocate(image::Handle::from_bytes(bytes))
|
||||
.map(Ok)
|
||||
})
|
||||
.map(Message::ImageDownloaded)
|
||||
}
|
||||
Message::Close => {
|
||||
self.viewer.close(self.now);
|
||||
|
|
@ -238,9 +277,11 @@ fn card<'a>(
|
|||
) -> Element<'a, Message> {
|
||||
let image = if let Some(preview) = preview {
|
||||
let thumbnail: Element<'_, _> =
|
||||
if let Preview::Ready { thumbnail, .. } = &preview {
|
||||
if let Preview::Ready { thumbnail, .. } = &preview
|
||||
&& let Some(allocation) = &thumbnail.allocation
|
||||
{
|
||||
float(
|
||||
image(&thumbnail.handle)
|
||||
image(allocation.handle())
|
||||
.width(Fill)
|
||||
.content_fit(ContentFit::Cover)
|
||||
.opacity(thumbnail.fade_in.interpolate(0.0, 1.0, now)),
|
||||
|
|
@ -320,8 +361,21 @@ struct Blurhash {
|
|||
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 {
|
||||
handle: image::Handle,
|
||||
bytes: Bytes,
|
||||
allocation: Option<image::Allocation>,
|
||||
fade_in: 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 {
|
||||
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 {
|
||||
return self;
|
||||
};
|
||||
|
||||
Self::Ready {
|
||||
blurhash: Some(blurhash),
|
||||
thumbnail: Thumbnail::new(bytes, now),
|
||||
thumbnail: Thumbnail::new(bytes),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -387,26 +441,45 @@ impl Preview {
|
|||
blurhash: Some(blurhash),
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Thumbnail {
|
||||
pub fn new(bytes: Bytes, now: Instant) -> Self {
|
||||
pub fn new(bytes: Bytes) -> Self {
|
||||
Self {
|
||||
handle: image::Handle::from_bytes(bytes),
|
||||
fade_in: Animation::new(false).slow().go(true, now),
|
||||
bytes,
|
||||
allocation: None,
|
||||
fade_in: Animation::new(false)
|
||||
.easing(animation::Easing::EaseIn)
|
||||
.slow(),
|
||||
zoom: Animation::new(false)
|
||||
.quick()
|
||||
.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 {
|
||||
image: Option<image::Handle>,
|
||||
image: Option<image::Allocation>,
|
||||
background_fade_in: Animation<bool>,
|
||||
image_fade_in: Animation<bool>,
|
||||
}
|
||||
|
|
@ -429,8 +502,8 @@ impl Viewer {
|
|||
self.background_fade_in.go_mut(true, now);
|
||||
}
|
||||
|
||||
fn show(&mut self, bytes: Bytes, now: Instant) {
|
||||
self.image = Some(image::Handle::from_bytes(bytes));
|
||||
fn show(&mut self, allocation: image::Allocation, now: Instant) {
|
||||
self.image = Some(allocation);
|
||||
self.background_fade_in.go_mut(true, now);
|
||||
self.image_fade_in.go_mut(true, now);
|
||||
}
|
||||
|
|
@ -452,8 +525,8 @@ impl Viewer {
|
|||
return None;
|
||||
}
|
||||
|
||||
let image = self.image.as_ref().map(|handle| {
|
||||
image(handle)
|
||||
let image = self.image.as_ref().map(|allocation| {
|
||||
image(allocation.handle())
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.opacity(self.image_fade_in.interpolate(0.0, 1.0, now))
|
||||
|
|
|
|||
|
|
@ -69,6 +69,14 @@ where
|
|||
fn end_transformation(&mut self) {
|
||||
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>
|
||||
|
|
|
|||
24
runtime/src/image.rs
Normal file
24
runtime/src/image.rs
Normal 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))
|
||||
})
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
pub mod clipboard;
|
||||
pub mod font;
|
||||
pub mod image;
|
||||
pub mod keyboard;
|
||||
pub mod system;
|
||||
pub mod task;
|
||||
|
|
@ -55,6 +56,9 @@ pub enum Action<T> {
|
|||
/// Run a system action.
|
||||
System(system::Action),
|
||||
|
||||
/// An image action.
|
||||
Image(image::Action),
|
||||
|
||||
/// Recreate all user interfaces and redraw all windows.
|
||||
Reload,
|
||||
|
||||
|
|
@ -81,6 +85,7 @@ impl<T> Action<T> {
|
|||
Action::Clipboard(action) => Err(Action::Clipboard(action)),
|
||||
Action::Window(action) => Err(Action::Window(action)),
|
||||
Action::System(action) => Err(Action::System(action)),
|
||||
Action::Image(action) => Err(Action::Image(action)),
|
||||
Action::Reload => Err(Action::Reload),
|
||||
Action::Exit => Err(Action::Exit),
|
||||
}
|
||||
|
|
@ -105,6 +110,7 @@ where
|
|||
}
|
||||
Action::Window(_) => write!(f, "Action::Window"),
|
||||
Action::System(action) => write!(f, "Action::System({action:?})"),
|
||||
Action::Image(_) => write!(f, "Action::Image"),
|
||||
Action::Reload => write!(f, "Action::Reload"),
|
||||
Action::Exit => write!(f, "Action::Exit"),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -379,14 +379,16 @@ impl<T, E> Task<Result<T, E>> {
|
|||
/// The success value is provided to the closure to create the subsequent [`Task`].
|
||||
pub fn and_then<A>(
|
||||
self,
|
||||
f: impl Fn(T) -> Task<A> + MaybeSend + 'static,
|
||||
) -> Task<A>
|
||||
f: impl Fn(T) -> Task<Result<A, E>> + MaybeSend + 'static,
|
||||
) -> Task<Result<A, E>>
|
||||
where
|
||||
T: MaybeSend + 'static,
|
||||
E: 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -625,6 +625,13 @@ pub mod widget {
|
|||
pub use iced_runtime::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`
|
||||
mod core {}
|
||||
mod graphics {}
|
||||
|
|
|
|||
|
|
@ -257,6 +257,10 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
// TODO
|
||||
dbg!(action);
|
||||
}
|
||||
iced_runtime::Action::Image(action) => {
|
||||
// TODO
|
||||
dbg!(action);
|
||||
}
|
||||
runtime::Action::Exit => {
|
||||
// TODO
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,6 +228,16 @@ impl core::Renderer for Renderer {
|
|||
fn reset(&mut self, new_bounds: Rectangle) {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ use crate::graphics::Shell;
|
|||
use crate::image::atlas::{self, Atlas};
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
use std::sync::mpsc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Cache {
|
||||
atlas: Atlas,
|
||||
#[cfg(feature = "image")]
|
||||
|
|
@ -46,7 +45,7 @@ impl Cache {
|
|||
#[cfg(feature = "image")]
|
||||
raster: Raster {
|
||||
cache: crate::image::raster::Cache::default(),
|
||||
pending: BTreeSet::new(),
|
||||
pending: HashMap::new(),
|
||||
jobs: jobs.clone(),
|
||||
},
|
||||
#[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")]
|
||||
pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
|
||||
self.receive();
|
||||
|
|
@ -69,6 +106,7 @@ impl Cache {
|
|||
&mut self.raster.pending,
|
||||
&mut self.raster.jobs,
|
||||
handle,
|
||||
None,
|
||||
) {
|
||||
return memory.dimensions();
|
||||
}
|
||||
|
|
@ -99,9 +137,13 @@ impl Cache {
|
|||
&mut self.raster.pending,
|
||||
&mut self.raster.jobs,
|
||||
handle,
|
||||
None,
|
||||
)?;
|
||||
|
||||
if let Memory::Device { entry, bind_group } = memory {
|
||||
if let Memory::Device {
|
||||
entry, bind_group, ..
|
||||
} = memory
|
||||
{
|
||||
return Some((
|
||||
entry,
|
||||
bind_group.as_ref().unwrap_or(self.atlas.bind_group()),
|
||||
|
|
@ -126,6 +168,7 @@ impl Cache {
|
|||
*memory = Memory::Device {
|
||||
entry,
|
||||
bind_group: None,
|
||||
allocation: None,
|
||||
};
|
||||
|
||||
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 {
|
||||
handle: handle.clone(),
|
||||
rgba: image.clone().into_raw(),
|
||||
|
|
@ -141,7 +184,7 @@ impl Cache {
|
|||
height: image.height(),
|
||||
});
|
||||
|
||||
let _ = self.raster.pending.insert(handle.id());
|
||||
let _ = self.raster.pending.insert(handle.id(), Vec::new());
|
||||
}
|
||||
|
||||
None
|
||||
|
|
@ -194,15 +237,32 @@ impl Cache {
|
|||
entry,
|
||||
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(
|
||||
&handle,
|
||||
Memory::Device {
|
||||
entry,
|
||||
bind_group: Some(bind_group),
|
||||
allocation,
|
||||
},
|
||||
);
|
||||
|
||||
let _ = self.raster.pending.remove(&handle.id());
|
||||
}
|
||||
Work::Error { handle, error } => {
|
||||
self.raster.cache.insert(&handle, Memory::error(error));
|
||||
|
|
@ -225,19 +285,22 @@ impl Drop for Cache {
|
|||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
#[derive(Debug)]
|
||||
struct Raster {
|
||||
cache: crate::image::raster::Cache,
|
||||
pending: BTreeSet<core::image::Id>,
|
||||
pending: HashMap<core::image::Id, Vec<Callback>>,
|
||||
jobs: mpsc::SyncSender<Job>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
type Callback = Box<dyn FnOnce(core::image::Allocation) + Send>;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
fn load_image<'a>(
|
||||
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>,
|
||||
handle: &core::image::Handle,
|
||||
callback: Option<Callback>,
|
||||
) -> Option<&'a mut 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 {
|
||||
// Load RGBA handles synchronously, since it's very cheap
|
||||
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 _ = pending.insert(handle.id());
|
||||
let _ = pending.insert(handle.id(), Vec::from_iter(callback));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::graphics::image::image_rs;
|
|||
use crate::image::atlas::{self, Atlas};
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::sync::Weak;
|
||||
|
||||
type Image = image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>;
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ pub enum Memory {
|
|||
Device {
|
||||
entry: atlas::Entry,
|
||||
bind_group: Option<wgpu::BindGroup>,
|
||||
allocation: Option<Weak<image::Memory>>,
|
||||
},
|
||||
/// Image not found
|
||||
NotFound,
|
||||
|
|
@ -100,7 +102,16 @@ impl Cache {
|
|||
self.map.retain(|k, memory| {
|
||||
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() {
|
||||
on_drop(bind_group);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -696,6 +696,17 @@ impl core::Renderer for Renderer {
|
|||
fn reset(&mut self, new_bounds: Rectangle) {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ use crate::futures::futures::{Future, StreamExt};
|
|||
use crate::futures::subscription;
|
||||
use crate::futures::{Executor, Runtime};
|
||||
use crate::graphics::{Compositor, Shell, compositor};
|
||||
use crate::runtime::image;
|
||||
use crate::runtime::system;
|
||||
use crate::runtime::user_interface::{self, UserInterface};
|
||||
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 } => {
|
||||
if let Some(compositor) = compositor {
|
||||
// TODO: Error handling (?)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue