Merge pull request #3092 from iced-rs/async-image-rendering
Concurrent Image Decoding and Uploading (and more cool stuff)
This commit is contained in:
commit
bed9657ec2
46 changed files with 2262 additions and 712 deletions
|
|
@ -72,7 +72,7 @@ fn benchmark<'a>(
|
|||
view: impl Fn(usize) -> Element<'a, (), Theme, Renderer>,
|
||||
) {
|
||||
use iced_wgpu::graphics;
|
||||
use iced_wgpu::graphics::Antialiasing;
|
||||
use iced_wgpu::graphics::{Antialiasing, Shell};
|
||||
use iced_wgpu::wgpu;
|
||||
use iced_winit::core;
|
||||
use iced_winit::runtime;
|
||||
|
|
@ -85,6 +85,7 @@ fn benchmark<'a>(
|
|||
queue.clone(),
|
||||
format,
|
||||
Some(Antialiasing::MSAAx4),
|
||||
Shell::headless(),
|
||||
);
|
||||
|
||||
let mut renderer = Renderer::new(engine, Font::DEFAULT, Pixels::from(16));
|
||||
|
|
|
|||
|
|
@ -241,9 +241,9 @@ impl From<u8> for Radius {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<u16> for Radius {
|
||||
fn from(w: u16) -> Self {
|
||||
Self::from(f32::from(w))
|
||||
impl From<u32> for Radius {
|
||||
fn from(w: u32) -> Self {
|
||||
Self::from(w as f32)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
//! Load and draw raster graphics.
|
||||
pub use bytes::Bytes;
|
||||
|
||||
use crate::border;
|
||||
use crate::{Radians, Rectangle, Size};
|
||||
|
||||
use rustc_hash::FxHasher;
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
/// A raster image that can be drawn.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
|
@ -19,6 +23,11 @@ pub struct Image<H = Handle> {
|
|||
/// The rotation to be applied to the image; on its center.
|
||||
pub rotation: Radians,
|
||||
|
||||
/// The border radius of the [`Image`].
|
||||
///
|
||||
/// Currently, this will only be applied to the `clip_bounds`.
|
||||
pub border_radius: border::Radius,
|
||||
|
||||
/// The opacity of the image.
|
||||
///
|
||||
/// 0 means transparent. 1 means opaque.
|
||||
|
|
@ -38,6 +47,7 @@ impl Image<Handle> {
|
|||
handle: handle.into(),
|
||||
filter_method: FilterMethod::default(),
|
||||
rotation: Radians(0.0),
|
||||
border_radius: border::Radius::default(),
|
||||
opacity: 1.0,
|
||||
snap: false,
|
||||
}
|
||||
|
|
@ -177,10 +187,12 @@ impl From<&Handle> for Handle {
|
|||
impl std::fmt::Debug for Handle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Path(_, path) => write!(f, "Path({path:?})"),
|
||||
Self::Bytes(_, _) => write!(f, "Bytes(...)"),
|
||||
Self::Rgba { width, height, .. } => {
|
||||
write!(f, "Pixels({width} * {height})")
|
||||
Self::Path(id, path) => write!(f, "Path({id:?}, {path:?})"),
|
||||
Self::Bytes(id, _) => write!(f, "Bytes({id:?}, ...)"),
|
||||
Self::Rgba {
|
||||
id, width, height, ..
|
||||
} => {
|
||||
write!(f, "Pixels({id:?}, {width} * {height})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -227,6 +239,67 @@ 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: Handle,
|
||||
size: Size<u32>,
|
||||
}
|
||||
|
||||
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.handle
|
||||
}
|
||||
|
||||
/// Returns the [`Size`] of the image of this [`Allocation`].
|
||||
pub fn size(&self) -> Size<u32> {
|
||||
self.0.size
|
||||
}
|
||||
}
|
||||
|
||||
/// 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, size: Size<u32>) -> Allocation {
|
||||
Allocation(Arc::new(Memory {
|
||||
handle: handle.clone(),
|
||||
size,
|
||||
}))
|
||||
}
|
||||
|
||||
/// A [`Renderer`] that can render raster graphics.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
|
|
@ -236,9 +309,48 @@ pub trait Renderer: crate::Renderer {
|
|||
/// [`Handle`]: Self::Handle
|
||||
type Handle: Clone;
|
||||
|
||||
/// Loads an image and returns an explicit [`Allocation`] to it.
|
||||
///
|
||||
/// If the image is not already loaded, this method will block! You should
|
||||
/// generally not use it in drawing logic if you want to avoid frame drops.
|
||||
fn load_image(&self, handle: &Self::Handle) -> Result<Allocation, Error>;
|
||||
|
||||
/// Returns the dimensions of an image for the given [`Handle`].
|
||||
fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
|
||||
///
|
||||
/// If the image is not already loaded, the [`Renderer`] may choose to return
|
||||
/// `None`, load the image in the background, and then trigger a relayout.
|
||||
///
|
||||
/// If you need a measurement right away, consider using [`Renderer::load_image`].
|
||||
fn measure_image(&self, handle: &Self::Handle) -> Option<Size<u32>>;
|
||||
|
||||
/// Draws an [`Image`] inside the provided `bounds`.
|
||||
fn draw_image(&mut self, image: Image<Self::Handle>, bounds: Rectangle);
|
||||
///
|
||||
/// If the image is not already loaded, the [`Renderer`] may choose to render
|
||||
/// nothing, load the image in the background, and then trigger a redraw.
|
||||
///
|
||||
/// If you need to draw an image right away, consider using [`Renderer::load_image`]
|
||||
/// and hold on to an [`Allocation`] first.
|
||||
fn draw_image(
|
||||
&mut self,
|
||||
image: Image<Self::Handle>,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
);
|
||||
}
|
||||
|
||||
/// An image loading error.
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// The image data was invalid or could not be decoded.
|
||||
#[error("the image data was invalid or could not be decoded: {0}")]
|
||||
Invalid(Arc<dyn std::error::Error + Send + Sync>),
|
||||
/// The image file was not found.
|
||||
#[error("the image file could not be opened: {0}")]
|
||||
Inaccessible(Arc<io::Error>),
|
||||
/// Loading images is unsupported.
|
||||
#[error("loading images is unsupported")]
|
||||
Unsupported,
|
||||
/// Not enough memory to allocate the image.
|
||||
#[error("not enough memory to allocate the image")]
|
||||
OutOfMemory,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,15 @@ 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(Result<image::Allocation, image::Error>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
/// A polygon with four sides.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,17 @@ impl Renderer for () {
|
|||
_background: impl Into<Background>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn allocate_image(
|
||||
&mut self,
|
||||
handle: &image::Handle,
|
||||
callback: impl FnOnce(Result<image::Allocation, image::Error>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) {
|
||||
#[allow(unsafe_code)]
|
||||
callback(Ok(unsafe { image::allocate(handle, Size::new(100, 100)) }));
|
||||
}
|
||||
}
|
||||
|
||||
impl text::Renderer for () {
|
||||
|
|
@ -204,11 +215,25 @@ impl text::Editor for () {
|
|||
impl image::Renderer for () {
|
||||
type Handle = image::Handle;
|
||||
|
||||
fn measure_image(&self, _handle: &Self::Handle) -> Size<u32> {
|
||||
Size::default()
|
||||
fn load_image(
|
||||
&self,
|
||||
handle: &Self::Handle,
|
||||
) -> Result<image::Allocation, image::Error> {
|
||||
#[allow(unsafe_code)]
|
||||
Ok(unsafe { image::allocate(handle, Size::new(100, 100)) })
|
||||
}
|
||||
|
||||
fn draw_image(&mut self, _image: Image, _bounds: Rectangle) {}
|
||||
fn measure_image(&self, _handle: &Self::Handle) -> Option<Size<u32>> {
|
||||
Some(Size::new(100, 100))
|
||||
}
|
||||
|
||||
fn draw_image(
|
||||
&mut self,
|
||||
_image: Image,
|
||||
_bounds: Rectangle,
|
||||
_clip_bounds: Rectangle,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl svg::Renderer for () {
|
||||
|
|
@ -216,5 +241,11 @@ impl svg::Renderer for () {
|
|||
Size::default()
|
||||
}
|
||||
|
||||
fn draw_svg(&mut self, _svg: svg::Svg, _bounds: Rectangle) {}
|
||||
fn draw_svg(
|
||||
&mut self,
|
||||
_svg: svg::Svg,
|
||||
_bounds: Rectangle,
|
||||
_clip_bounds: Rectangle,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,5 +155,5 @@ pub trait Renderer: crate::Renderer {
|
|||
fn measure_svg(&self, handle: &Handle) -> Size<u32>;
|
||||
|
||||
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
|
||||
fn draw_svg(&mut self, svg: Svg, bounds: Rectangle);
|
||||
fn draw_svg(&mut self, svg: Svg, bounds: Rectangle, clip_bounds: Rectangle);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use bytes::Bytes;
|
||||
use serde::Deserialize;
|
||||
use sipper::{Straw, sipper};
|
||||
use tokio::task;
|
||||
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
static CLIENT: LazyLock<reqwest::Client> = LazyLock::new(reqwest::Client::new);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Image {
|
||||
|
|
@ -18,14 +19,12 @@ impl Image {
|
|||
pub const LIMIT: usize = 96;
|
||||
|
||||
pub async fn list() -> Result<Vec<Self>, Error> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
items: Vec<Image>,
|
||||
}
|
||||
|
||||
let response: Response = client
|
||||
let response: Response = CLIENT
|
||||
.get("https://civitai.com/api/v1/images")
|
||||
.query(&[
|
||||
("sort", "Most Reactions"),
|
||||
|
|
@ -39,7 +38,11 @@ impl Image {
|
|||
.json()
|
||||
.await?;
|
||||
|
||||
Ok(response.items)
|
||||
Ok(response
|
||||
.items
|
||||
.into_iter()
|
||||
.filter(|image| !image.url.ends_with(".mp4"))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn blurhash(
|
||||
|
|
@ -54,17 +57,15 @@ impl Image {
|
|||
rgba: Rgba {
|
||||
width,
|
||||
height,
|
||||
pixels: Bytes::from(pixels),
|
||||
pixels: Bytes(pixels.into()),
|
||||
},
|
||||
})
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
pub fn download(self, size: Size) -> impl Straw<Rgba, Blurhash, Error> {
|
||||
pub fn download(self, size: Size) -> impl Straw<Bytes, Blurhash, Error> {
|
||||
sipper(async move |mut sender| {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
if let Size::Thumbnail { width, height } = size {
|
||||
let image = self.clone();
|
||||
|
||||
|
|
@ -75,14 +76,16 @@ impl Image {
|
|||
}));
|
||||
}
|
||||
|
||||
let bytes = client
|
||||
let bytes = CLIENT
|
||||
.get(match size {
|
||||
Size::Original => self.url,
|
||||
Size::Thumbnail { width, .. } => self
|
||||
.url
|
||||
.split("/")
|
||||
.map(|part| {
|
||||
if part.starts_with("width=") {
|
||||
if part.starts_with("width=")
|
||||
|| part.starts_with("original=")
|
||||
{
|
||||
format!("width={}", width * 2) // High DPI
|
||||
} else {
|
||||
part.to_owned()
|
||||
|
|
@ -97,21 +100,7 @@ impl Image {
|
|||
.bytes()
|
||||
.await?;
|
||||
|
||||
let image = task::spawn_blocking(move || {
|
||||
Ok::<_, Error>(
|
||||
image::ImageReader::new(io::Cursor::new(bytes))
|
||||
.with_guessed_format()?
|
||||
.decode()?
|
||||
.to_rgba8(),
|
||||
)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Rgba {
|
||||
width: image.width(),
|
||||
height: image.height(),
|
||||
pixels: Bytes::from(image.into_raw()),
|
||||
})
|
||||
Ok(Bytes(bytes))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -142,6 +131,29 @@ impl fmt::Debug for Rgba {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bytes(bytes::Bytes);
|
||||
|
||||
impl Bytes {
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bytes> for bytes::Bytes {
|
||||
fn from(value: Bytes) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Bytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Compressed")
|
||||
.field("bytes", &self.0.len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Size {
|
||||
Original,
|
||||
|
|
@ -154,7 +166,7 @@ pub enum Error {
|
|||
RequestFailed(Arc<reqwest::Error>),
|
||||
IOFailed(Arc<io::Error>),
|
||||
JoinFailed(Arc<task::JoinError>),
|
||||
ImageDecodingFailed(Arc<image::ImageError>),
|
||||
ImageDecodingFailed,
|
||||
BlurhashDecodingFailed(Arc<blurhash::Error>),
|
||||
}
|
||||
|
||||
|
|
@ -176,12 +188,6 @@ impl From<task::JoinError> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<image::ImageError> for Error {
|
||||
fn from(error: image::ImageError) -> Self {
|
||||
Self::ImageDecodingFailed(Arc::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<blurhash::Error> for Error {
|
||||
fn from(error: blurhash::Error) -> Self {
|
||||
Self::BlurhashDecodingFailed(Arc::new(error))
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
//! some smooth animations.
|
||||
mod civitai;
|
||||
|
||||
use crate::civitai::{Error, Id, Image, Rgba, Size};
|
||||
use crate::civitai::{Bytes, Error, Id, Image, Rgba, Size};
|
||||
|
||||
use iced::animation;
|
||||
use iced::border;
|
||||
use iced::time::{Instant, milliseconds};
|
||||
use iced::widget::{
|
||||
button, container, float, grid, image, mouse_area, opaque, scrollable,
|
||||
|
|
@ -18,7 +19,7 @@ use iced::{
|
|||
Subscription, Task, Theme, color,
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
fn main() -> iced::Result {
|
||||
iced::application::timed(
|
||||
|
|
@ -35,6 +36,8 @@ fn main() -> iced::Result {
|
|||
struct Gallery {
|
||||
images: Vec<Image>,
|
||||
previews: HashMap<Id, Preview>,
|
||||
visible: HashSet<Id>,
|
||||
downloaded: HashSet<Id>,
|
||||
viewer: Viewer,
|
||||
now: Instant,
|
||||
}
|
||||
|
|
@ -43,8 +46,10 @@ struct Gallery {
|
|||
enum Message {
|
||||
ImagesListed(Result<Vec<Image>, Error>),
|
||||
ImagePoppedIn(Id),
|
||||
ImageDownloaded(Result<Rgba, Error>),
|
||||
ThumbnailDownloaded(Id, Result<Rgba, Error>),
|
||||
ImagePoppedOut(Id),
|
||||
ImageDownloaded(Result<image::Allocation, Error>),
|
||||
ThumbnailDownloaded(Id, Result<Bytes, Error>),
|
||||
ThumbnailAllocated(Id, Result<image::Allocation, image::Error>),
|
||||
ThumbnailHovered(Id, bool),
|
||||
BlurhashDecoded(Id, civitai::Blurhash),
|
||||
Open(Id),
|
||||
|
|
@ -58,6 +63,8 @@ impl Gallery {
|
|||
Self {
|
||||
images: Vec::new(),
|
||||
previews: HashMap::new(),
|
||||
visible: HashSet::new(),
|
||||
downloaded: HashSet::new(),
|
||||
viewer: Viewer::new(),
|
||||
now: Instant::now(),
|
||||
},
|
||||
|
|
@ -102,6 +109,28 @@ impl Gallery {
|
|||
return Task::none();
|
||||
};
|
||||
|
||||
let _ = self.visible.insert(id);
|
||||
|
||||
if self.downloaded.contains(&id) {
|
||||
let Some(Preview::Ready {
|
||||
thumbnail,
|
||||
blurhash,
|
||||
}) = self.previews.get_mut(&id)
|
||||
else {
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
if let Some(blurhash) = blurhash {
|
||||
blurhash.show(now);
|
||||
}
|
||||
|
||||
return to_rgba(thumbnail.bytes.clone())
|
||||
.then(image::allocate)
|
||||
.map(Message::ThumbnailAllocated.with(id));
|
||||
}
|
||||
|
||||
let _ = self.downloaded.insert(id);
|
||||
|
||||
Task::sip(
|
||||
image.download(Size::Thumbnail {
|
||||
width: Preview::WIDTH,
|
||||
|
|
@ -111,20 +140,53 @@ impl Gallery {
|
|||
Message::ThumbnailDownloaded.with(id),
|
||||
)
|
||||
}
|
||||
Message::ImageDownloaded(Ok(rgba)) => {
|
||||
self.viewer.show(rgba, self.now);
|
||||
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::ThumbnailDownloaded(id, Ok(rgba)) => {
|
||||
let thumbnail = if let Some(preview) = self.previews.remove(&id)
|
||||
{
|
||||
preview.load(rgba, self.now)
|
||||
Message::ImageDownloaded(Ok(allocation)) => {
|
||||
self.viewer.show(allocation, self.now);
|
||||
|
||||
Task::none()
|
||||
}
|
||||
Message::ThumbnailDownloaded(id, Ok(bytes)) => {
|
||||
let preview = if let Some(preview) = self.previews.remove(&id) {
|
||||
preview.load(bytes.clone())
|
||||
} else {
|
||||
Preview::ready(rgba, self.now)
|
||||
Preview::ready(bytes.clone())
|
||||
};
|
||||
|
||||
let _ = self.previews.insert(id, thumbnail);
|
||||
let _ = self.previews.insert(id, preview);
|
||||
|
||||
to_rgba(bytes)
|
||||
.then(image::allocate)
|
||||
.map(Message::ThumbnailAllocated.with(id))
|
||||
}
|
||||
Message::ThumbnailAllocated(id, Ok(allocation)) => {
|
||||
if !self.visible.contains(&id) {
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
let Some(Preview::Ready { thumbnail, .. }) =
|
||||
self.previews.get_mut(&id)
|
||||
else {
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
thumbnail.show(allocation, now);
|
||||
|
||||
Task::none()
|
||||
}
|
||||
|
|
@ -156,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_err(|_| Error::ImageDecodingFailed)
|
||||
})
|
||||
.map(Message::ImageDownloaded)
|
||||
}
|
||||
Message::Close => {
|
||||
self.viewer.close(self.now);
|
||||
|
|
@ -172,6 +236,11 @@ impl Gallery {
|
|||
| Message::ThumbnailDownloaded(_, Err(error)) => {
|
||||
dbg!(error);
|
||||
|
||||
Task::none()
|
||||
}
|
||||
Message::ThumbnailAllocated(_, Err(error)) => {
|
||||
dbg!(error);
|
||||
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
|
@ -181,8 +250,25 @@ impl Gallery {
|
|||
let images = self
|
||||
.images
|
||||
.iter()
|
||||
.map(|image| card(image, self.previews.get(&image.id), self.now))
|
||||
.chain((self.images.len()..=Image::LIMIT).map(|_| placeholder()));
|
||||
.map(|image| {
|
||||
card(
|
||||
image,
|
||||
if self.visible.contains(&image.id) {
|
||||
self.previews.get(&image.id)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
self.now,
|
||||
)
|
||||
})
|
||||
.chain(
|
||||
if self.images.is_empty() {
|
||||
0..Image::LIMIT
|
||||
} else {
|
||||
0..0
|
||||
}
|
||||
.map(|_| placeholder()),
|
||||
);
|
||||
|
||||
let gallery = grid(images)
|
||||
.fluid(Preview::WIDTH)
|
||||
|
|
@ -203,12 +289,15 @@ 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)),
|
||||
.opacity(thumbnail.fade_in.interpolate(0.0, 1.0, now))
|
||||
.border_radius(BORDER_RADIUS),
|
||||
)
|
||||
.scale(thumbnail.zoom.interpolate(1.0, 1.1, now))
|
||||
.translate(move |bounds, viewport| {
|
||||
|
|
@ -223,7 +312,7 @@ fn card<'a>(
|
|||
blur_radius: thumbnail.zoom.interpolate(0.0, 20.0, now),
|
||||
..Shadow::default()
|
||||
},
|
||||
..float::Style::default()
|
||||
shadow_border_radius: border::radius(BORDER_RADIUS),
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
|
|
@ -234,7 +323,8 @@ fn card<'a>(
|
|||
let blurhash = image(&blurhash.handle)
|
||||
.width(Fill)
|
||||
.content_fit(ContentFit::Cover)
|
||||
.opacity(blurhash.fade_in.interpolate(0.0, 1.0, now));
|
||||
.opacity(blurhash.fade_in.interpolate(0.0, 1.0, now))
|
||||
.border_radius(BORDER_RADIUS);
|
||||
|
||||
stack![blurhash, thumbnail].into()
|
||||
} else {
|
||||
|
|
@ -244,11 +334,11 @@ fn card<'a>(
|
|||
space::horizontal().into()
|
||||
};
|
||||
|
||||
let card = mouse_area(container(image).style(container::dark))
|
||||
let card = mouse_area(container(image).style(rounded))
|
||||
.on_enter(Message::ThumbnailHovered(metadata.id, true))
|
||||
.on_exit(Message::ThumbnailHovered(metadata.id, false));
|
||||
|
||||
if let Some(preview) = preview {
|
||||
let card: Element<'_, _> = if let Some(preview) = preview {
|
||||
let is_thumbnail = matches!(preview, Preview::Ready { .. });
|
||||
|
||||
button(card)
|
||||
|
|
@ -257,14 +347,17 @@ fn card<'a>(
|
|||
.style(button::text)
|
||||
.into()
|
||||
} else {
|
||||
sensor(card)
|
||||
.on_show(|_| Message::ImagePoppedIn(metadata.id))
|
||||
.into()
|
||||
}
|
||||
card.into()
|
||||
};
|
||||
|
||||
sensor(card)
|
||||
.on_show(|_| Message::ImagePoppedIn(metadata.id))
|
||||
.on_hide(Message::ImagePoppedOut(metadata.id))
|
||||
.into()
|
||||
}
|
||||
|
||||
fn placeholder<'a>() -> Element<'a, Message> {
|
||||
container(space()).style(container::dark).into()
|
||||
container(space()).style(rounded).into()
|
||||
}
|
||||
|
||||
enum Preview {
|
||||
|
|
@ -282,8 +375,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)
|
||||
.very_quick();
|
||||
}
|
||||
}
|
||||
|
||||
struct Thumbnail {
|
||||
handle: image::Handle,
|
||||
bytes: Bytes,
|
||||
allocation: Option<image::Allocation>,
|
||||
fade_in: Animation<bool>,
|
||||
zoom: Animation<bool>,
|
||||
}
|
||||
|
|
@ -308,21 +414,21 @@ impl Preview {
|
|||
}
|
||||
}
|
||||
|
||||
fn ready(rgba: Rgba, now: Instant) -> Self {
|
||||
fn ready(bytes: Bytes) -> Self {
|
||||
Self::Ready {
|
||||
blurhash: None,
|
||||
thumbnail: Thumbnail::new(rgba, now),
|
||||
thumbnail: Thumbnail::new(bytes),
|
||||
}
|
||||
}
|
||||
|
||||
fn load(self, rgba: Rgba, 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(rgba, now),
|
||||
thumbnail: Thumbnail::new(bytes),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,9 +441,15 @@ impl Preview {
|
|||
fn is_animating(&self, now: Instant) -> bool {
|
||||
match &self {
|
||||
Self::Loading { blurhash } => blurhash.fade_in.is_animating(now),
|
||||
Self::Ready { thumbnail, .. } => {
|
||||
Self::Ready {
|
||||
thumbnail,
|
||||
blurhash,
|
||||
} => {
|
||||
thumbnail.fade_in.is_animating(now)
|
||||
|| thumbnail.zoom.is_animating(now)
|
||||
|| blurhash.as_ref().is_some_and(|blurhash| {
|
||||
blurhash.fade_in.is_animating(now)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -349,30 +461,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(rgba: Rgba, now: Instant) -> Self {
|
||||
pub fn new(bytes: Bytes) -> Self {
|
||||
Self {
|
||||
handle: image::Handle::from_rgba(
|
||||
rgba.width,
|
||||
rgba.height,
|
||||
rgba.pixels,
|
||||
),
|
||||
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)
|
||||
.quick();
|
||||
}
|
||||
|
||||
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>,
|
||||
}
|
||||
|
|
@ -395,12 +522,8 @@ impl Viewer {
|
|||
self.background_fade_in.go_mut(true, now);
|
||||
}
|
||||
|
||||
fn show(&mut self, rgba: Rgba, now: Instant) {
|
||||
self.image = Some(image::Handle::from_rgba(
|
||||
rgba.width,
|
||||
rgba.height,
|
||||
rgba.pixels,
|
||||
));
|
||||
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);
|
||||
}
|
||||
|
|
@ -422,8 +545,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))
|
||||
|
|
@ -444,3 +567,30 @@ impl Viewer {
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn to_rgba(bytes: Bytes) -> Task<image::Handle> {
|
||||
Task::future(async move {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
match ::image::load_from_memory(bytes.as_slice()) {
|
||||
Ok(image) => {
|
||||
let rgba = image.to_rgba8();
|
||||
|
||||
image::Handle::from_rgba(
|
||||
rgba.width(),
|
||||
rgba.height(),
|
||||
rgba.into_raw(),
|
||||
)
|
||||
}
|
||||
_ => image::Handle::from_bytes(bytes),
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
fn rounded(theme: &Theme) -> container::Style {
|
||||
container::dark(theme).border(border::rounded(BORDER_RADIUS))
|
||||
}
|
||||
|
||||
const BORDER_RADIUS: u32 = 10;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ mod scene;
|
|||
use controls::Controls;
|
||||
use scene::Scene;
|
||||
|
||||
use iced_wgpu::graphics::Viewport;
|
||||
use iced_wgpu::graphics::{Shell, Viewport};
|
||||
use iced_wgpu::{Engine, Renderer, wgpu};
|
||||
use iced_winit::Clipboard;
|
||||
use iced_winit::conversion;
|
||||
|
|
@ -150,6 +150,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
|||
queue.clone(),
|
||||
format,
|
||||
None,
|
||||
Shell::headless(),
|
||||
);
|
||||
|
||||
Renderer::new(engine, Font::default(), Pixels::from(16))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//! surfaces.
|
||||
use crate::core::Color;
|
||||
use crate::futures::{MaybeSend, MaybeSync};
|
||||
use crate::{Error, Settings, Viewport};
|
||||
use crate::{Error, Settings, Shell, Viewport};
|
||||
|
||||
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||
use thiserror::Error;
|
||||
|
|
@ -21,8 +21,9 @@ pub trait Compositor: Sized {
|
|||
fn new<W: Window + Clone>(
|
||||
settings: Settings,
|
||||
compatible_window: W,
|
||||
shell: Shell,
|
||||
) -> impl Future<Output = Result<Self, Error>> {
|
||||
Self::with_backend(settings, compatible_window, None)
|
||||
Self::with_backend(settings, compatible_window, shell, None)
|
||||
}
|
||||
|
||||
/// Creates a new [`Compositor`] with a backend preference.
|
||||
|
|
@ -32,6 +33,7 @@ pub trait Compositor: Sized {
|
|||
fn with_backend<W: Window + Clone>(
|
||||
_settings: Settings,
|
||||
_compatible_window: W,
|
||||
_shell: Shell,
|
||||
_backend: Option<&str>,
|
||||
) -> impl Future<Output = Result<Self, Error>>;
|
||||
|
||||
|
|
@ -153,6 +155,7 @@ impl Compositor for () {
|
|||
async fn with_backend<W: Window + Clone>(
|
||||
_settings: Settings,
|
||||
_compatible_window: W,
|
||||
_shell: Shell,
|
||||
_preferred_backend: Option<&str>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -7,33 +7,45 @@ use crate::core::image;
|
|||
use crate::core::svg;
|
||||
|
||||
/// A raster or vector image.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Image {
|
||||
/// A raster image.
|
||||
Raster(image::Image, Rectangle),
|
||||
Raster {
|
||||
image: image::Image,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
},
|
||||
|
||||
/// A vector image.
|
||||
Vector(svg::Svg, Rectangle),
|
||||
Vector {
|
||||
svg: svg::Svg,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
},
|
||||
}
|
||||
|
||||
impl Image {
|
||||
/// Returns the bounds of the [`Image`].
|
||||
pub fn bounds(&self) -> Rectangle {
|
||||
match self {
|
||||
Image::Raster(image, bounds) => bounds.rotate(image.rotation),
|
||||
Image::Vector(svg, bounds) => bounds.rotate(svg.rotation),
|
||||
Image::Raster { image, bounds, .. } => {
|
||||
bounds.rotate(image.rotation)
|
||||
}
|
||||
Image::Vector { svg, bounds, .. } => bounds.rotate(svg.rotation),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An image buffer.
|
||||
#[cfg(feature = "image")]
|
||||
pub type Buffer = ::image::ImageBuffer<::image::Rgba<u8>, image::Bytes>;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
/// Tries to load an image by its [`Handle`].
|
||||
///
|
||||
/// [`Handle`]: image::Handle
|
||||
pub fn load(
|
||||
handle: &image::Handle,
|
||||
) -> ::image::ImageResult<::image::ImageBuffer<::image::Rgba<u8>, image::Bytes>>
|
||||
{
|
||||
pub fn load(handle: &image::Handle) -> Result<Buffer, image::Error> {
|
||||
use bitflags::bitflags;
|
||||
|
||||
bitflags! {
|
||||
|
|
@ -85,7 +97,7 @@ pub fn load(
|
|||
|
||||
let (width, height, pixels) = match handle {
|
||||
image::Handle::Path(_, path) => {
|
||||
let image = ::image::open(path)?;
|
||||
let image = ::image::open(path).map_err(to_error)?;
|
||||
|
||||
let operation = std::fs::File::open(path)
|
||||
.ok()
|
||||
|
|
@ -102,7 +114,8 @@ pub fn load(
|
|||
)
|
||||
}
|
||||
image::Handle::Bytes(_, bytes) => {
|
||||
let image = ::image::load_from_memory(bytes)?;
|
||||
let image = ::image::load_from_memory(bytes).map_err(to_error)?;
|
||||
|
||||
let operation =
|
||||
Operation::from_exif(&mut std::io::Cursor::new(bytes))
|
||||
.ok()
|
||||
|
|
@ -127,10 +140,22 @@ pub fn load(
|
|||
if let Some(image) = ::image::ImageBuffer::from_raw(width, height, pixels) {
|
||||
Ok(image)
|
||||
} else {
|
||||
Err(::image::error::ImageError::Limits(
|
||||
Err(to_error(::image::error::ImageError::Limits(
|
||||
::image::error::LimitError::from_kind(
|
||||
::image::error::LimitErrorKind::DimensionError,
|
||||
),
|
||||
))
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
fn to_error(error: ::image::ImageError) -> image::Error {
|
||||
use std::sync::Arc;
|
||||
|
||||
match error {
|
||||
::image::ImageError::IoError(error) => {
|
||||
image::Error::Inaccessible(Arc::new(error))
|
||||
}
|
||||
error => image::Error::Invalid(Arc::new(error)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ pub mod gradient;
|
|||
pub mod image;
|
||||
pub mod layer;
|
||||
pub mod mesh;
|
||||
pub mod shell;
|
||||
pub mod text;
|
||||
|
||||
#[cfg(feature = "geometry")]
|
||||
|
|
@ -35,6 +36,7 @@ pub use image::Image;
|
|||
pub use layer::Layer;
|
||||
pub use mesh::Mesh;
|
||||
pub use settings::Settings;
|
||||
pub use shell::Shell;
|
||||
pub use text::Text;
|
||||
pub use viewport::Viewport;
|
||||
|
||||
|
|
|
|||
45
graphics/src/shell.rs
Normal file
45
graphics/src/shell.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
//! Control the windowing runtime from a renderer.
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A windowing shell.
|
||||
#[derive(Clone)]
|
||||
pub struct Shell(Arc<dyn Notifier>);
|
||||
|
||||
impl Shell {
|
||||
/// Creates a new [`Shell`].
|
||||
pub fn new(notifier: impl Notifier) -> Self {
|
||||
Self(Arc::new(notifier))
|
||||
}
|
||||
|
||||
/// Creates a headless [`Shell`].
|
||||
pub fn headless() -> Self {
|
||||
struct Headless;
|
||||
|
||||
impl Notifier for Headless {
|
||||
fn request_redraw(&self) {}
|
||||
|
||||
fn invalidate_layout(&self) {}
|
||||
}
|
||||
|
||||
Self::new(Headless)
|
||||
}
|
||||
|
||||
/// Requests for all windows of the [`Shell`] to be redrawn.
|
||||
pub fn request_redraw(&self) {
|
||||
self.0.request_redraw();
|
||||
}
|
||||
|
||||
/// Requests for all layouts of the [`Shell`] to be recomputed.
|
||||
pub fn invalidate_layout(&self) {
|
||||
self.0.invalidate_layout();
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that can notify a shell of certain events.
|
||||
pub trait Notifier: Send + Sync + 'static {
|
||||
/// Requests for all windows of the [`Shell`] to be redrawn.
|
||||
fn request_redraw(&self);
|
||||
|
||||
/// Requests for all layouts of the [`Shell`] to be recomputed.
|
||||
fn invalidate_layout(&self);
|
||||
}
|
||||
|
|
@ -6,9 +6,9 @@ use crate::core::{
|
|||
self, Background, Color, Font, Image, Pixels, Point, Rectangle, Size, Svg,
|
||||
Transformation,
|
||||
};
|
||||
use crate::graphics;
|
||||
use crate::graphics::compositor;
|
||||
use crate::graphics::mesh;
|
||||
use crate::graphics::{self, Shell};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
|
@ -69,6 +69,16 @@ where
|
|||
fn end_transformation(&mut self) {
|
||||
delegate!(self, renderer, renderer.end_transformation());
|
||||
}
|
||||
|
||||
fn allocate_image(
|
||||
&mut self,
|
||||
handle: &image::Handle,
|
||||
callback: impl FnOnce(Result<image::Allocation, image::Error>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) {
|
||||
delegate!(self, renderer, renderer.allocate_image(handle, callback));
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> core::text::Renderer for Renderer<A, B>
|
||||
|
|
@ -146,12 +156,28 @@ where
|
|||
{
|
||||
type Handle = A::Handle;
|
||||
|
||||
fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
|
||||
fn load_image(
|
||||
&self,
|
||||
handle: &Self::Handle,
|
||||
) -> Result<image::Allocation, image::Error> {
|
||||
delegate!(self, renderer, renderer.load_image(handle))
|
||||
}
|
||||
|
||||
fn measure_image(&self, handle: &Self::Handle) -> Option<Size<u32>> {
|
||||
delegate!(self, renderer, renderer.measure_image(handle))
|
||||
}
|
||||
|
||||
fn draw_image(&mut self, image: Image<A::Handle>, bounds: Rectangle) {
|
||||
delegate!(self, renderer, renderer.draw_image(image, bounds));
|
||||
fn draw_image(
|
||||
&mut self,
|
||||
image: Image<A::Handle>,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
delegate!(
|
||||
self,
|
||||
renderer,
|
||||
renderer.draw_image(image, bounds, clip_bounds)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,8 +190,13 @@ where
|
|||
delegate!(self, renderer, renderer.measure_svg(handle))
|
||||
}
|
||||
|
||||
fn draw_svg(&mut self, svg: Svg, bounds: Rectangle) {
|
||||
delegate!(self, renderer, renderer.draw_svg(svg, bounds));
|
||||
fn draw_svg(
|
||||
&mut self,
|
||||
svg: Svg,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
delegate!(self, renderer, renderer.draw_svg(svg, bounds, clip_bounds));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -216,6 +247,7 @@ where
|
|||
async fn with_backend<W: compositor::Window + Clone>(
|
||||
settings: graphics::Settings,
|
||||
compatible_window: W,
|
||||
shell: Shell,
|
||||
backend: Option<&str>,
|
||||
) -> Result<Self, graphics::Error> {
|
||||
use std::env;
|
||||
|
|
@ -242,8 +274,13 @@ where
|
|||
let mut errors = vec![];
|
||||
|
||||
for backend in candidates.iter().map(Option::as_deref) {
|
||||
match A::with_backend(settings, compatible_window.clone(), backend)
|
||||
.await
|
||||
match A::with_backend(
|
||||
settings,
|
||||
compatible_window.clone(),
|
||||
shell.clone(),
|
||||
backend,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(compositor) => return Ok(Self::Primary(compositor)),
|
||||
Err(error) => {
|
||||
|
|
@ -251,8 +288,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
match B::with_backend(settings, compatible_window.clone(), backend)
|
||||
.await
|
||||
match B::with_backend(
|
||||
settings,
|
||||
compatible_window.clone(),
|
||||
shell.clone(),
|
||||
backend,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(compositor) => return Ok(Self::Secondary(compositor)),
|
||||
Err(error) => {
|
||||
|
|
|
|||
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, Error};
|
||||
|
||||
/// An image action.
|
||||
#[derive(Debug)]
|
||||
pub enum Action {
|
||||
/// Allocates the given [`Handle`].
|
||||
Allocate(Handle, oneshot::Sender<Result<Allocation, Error>>),
|
||||
}
|
||||
|
||||
/// 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: impl Into<Handle>) -> Task<Result<Allocation, Error>> {
|
||||
task::oneshot(|sender| {
|
||||
crate::Action::Image(Action::Allocate(handle.into(), 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,30 @@ 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)
|
||||
})
|
||||
}
|
||||
|
||||
/// Maps the error type of this [`Task`] to a different one using the given
|
||||
/// function.
|
||||
pub fn map_err<E2>(
|
||||
self,
|
||||
f: impl Fn(E) -> E2 + MaybeSend + 'static,
|
||||
) -> Task<Result<T, E2>>
|
||||
where
|
||||
T: MaybeSend + 'static,
|
||||
E: MaybeSend + 'static,
|
||||
E2: MaybeSend + 'static,
|
||||
{
|
||||
self.map(move |result| result.map_err(&f))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -174,6 +174,12 @@ pub enum Action {
|
|||
|
||||
/// Set the window size increment.
|
||||
SetResizeIncrements(Id, Option<Size>),
|
||||
|
||||
/// Redraws all the windows.
|
||||
RedrawAll,
|
||||
|
||||
/// Recomputes the layouts of all the windows.
|
||||
RelayoutAll,
|
||||
}
|
||||
|
||||
/// Subscribes to the frames of the window of the running application.
|
||||
|
|
|
|||
|
|
@ -198,10 +198,17 @@ impl<P: Program> Application<P> {
|
|||
#[cfg(feature = "tester")]
|
||||
let program = iced_tester::attach(self);
|
||||
|
||||
#[cfg(all(feature = "debug", not(feature = "tester")))]
|
||||
#[cfg(all(
|
||||
feature = "debug",
|
||||
not(feature = "tester"),
|
||||
not(target_arch = "wasm32")
|
||||
))]
|
||||
let program = iced_devtools::attach(self);
|
||||
|
||||
#[cfg(not(any(feature = "tester", feature = "debug")))]
|
||||
#[cfg(not(any(
|
||||
feature = "tester",
|
||||
all(feature = "debug", not(target_arch = "wasm32"))
|
||||
)))]
|
||||
let program = self;
|
||||
|
||||
Ok(shell::run(program)?)
|
||||
|
|
|
|||
|
|
@ -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, Error, 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -550,7 +550,7 @@ impl Engine {
|
|||
) {
|
||||
match image {
|
||||
#[cfg(feature = "image")]
|
||||
Image::Raster(raster, bounds) => {
|
||||
Image::Raster { image, bounds, .. } => {
|
||||
let physical_bounds = *bounds * _transformation;
|
||||
|
||||
if !_clip_bounds.intersects(&physical_bounds) {
|
||||
|
|
@ -561,7 +561,7 @@ impl Engine {
|
|||
.then_some(_clip_mask as &_);
|
||||
|
||||
let center = physical_bounds.center();
|
||||
let radians = f32::from(raster.rotation);
|
||||
let radians = f32::from(image.rotation);
|
||||
|
||||
let transform = into_transform(_transformation).post_rotate_at(
|
||||
radians.to_degrees(),
|
||||
|
|
@ -570,17 +570,17 @@ impl Engine {
|
|||
);
|
||||
|
||||
self.raster_pipeline.draw(
|
||||
&raster.handle,
|
||||
raster.filter_method,
|
||||
&image.handle,
|
||||
image.filter_method,
|
||||
*bounds,
|
||||
raster.opacity,
|
||||
image.opacity,
|
||||
_pixels,
|
||||
transform,
|
||||
clip_mask,
|
||||
);
|
||||
}
|
||||
#[cfg(feature = "svg")]
|
||||
Image::Vector(svg, bounds) => {
|
||||
Image::Vector { svg, bounds, .. } => {
|
||||
let physical_bounds = *bounds * _transformation;
|
||||
|
||||
if !_clip_bounds.intersects(&physical_bounds) {
|
||||
|
|
|
|||
|
|
@ -307,7 +307,11 @@ impl geometry::frame::Backend for Frame {
|
|||
|
||||
image.rotation += external_rotation;
|
||||
|
||||
self.images.push(graphics::Image::Raster(image, bounds));
|
||||
self.images.push(graphics::Image::Raster {
|
||||
image,
|
||||
bounds,
|
||||
clip_bounds: self.clip_bounds,
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
|
||||
|
|
@ -318,7 +322,11 @@ impl geometry::frame::Backend for Frame {
|
|||
|
||||
svg.rotation += external_rotation;
|
||||
|
||||
self.images.push(Image::Vector(svg, bounds));
|
||||
self.images.push(Image::Vector {
|
||||
svg,
|
||||
bounds,
|
||||
clip_bounds: self.clip_bounds,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -117,11 +117,19 @@ impl Layer {
|
|||
|
||||
pub fn draw_image(&mut self, image: Image, transformation: Transformation) {
|
||||
match image {
|
||||
Image::Raster(raster, bounds) => {
|
||||
self.draw_raster(raster, bounds, transformation);
|
||||
Image::Raster {
|
||||
image,
|
||||
bounds,
|
||||
clip_bounds,
|
||||
} => {
|
||||
self.draw_raster(image, bounds, clip_bounds, transformation);
|
||||
}
|
||||
Image::Vector(svg, bounds) => {
|
||||
self.draw_svg(svg, bounds, transformation);
|
||||
Image::Vector {
|
||||
svg,
|
||||
bounds,
|
||||
clip_bounds,
|
||||
} => {
|
||||
self.draw_svg(svg, bounds, clip_bounds, transformation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -130,9 +138,18 @@ impl Layer {
|
|||
&mut self,
|
||||
image: core::Image,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let image = Image::Raster(image, bounds * transformation);
|
||||
let image = Image::Raster {
|
||||
image: core::Image {
|
||||
border_radius: image.border_radius
|
||||
* transformation.scale_factor(),
|
||||
..image
|
||||
},
|
||||
bounds: bounds * transformation,
|
||||
clip_bounds: clip_bounds * transformation,
|
||||
};
|
||||
|
||||
self.images.push(image);
|
||||
}
|
||||
|
|
@ -141,9 +158,14 @@ impl Layer {
|
|||
&mut self,
|
||||
svg: Svg,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let svg = Image::Vector(svg, bounds * transformation);
|
||||
let svg = Image::Vector {
|
||||
svg,
|
||||
bounds: bounds * transformation,
|
||||
clip_bounds: clip_bounds * transformation,
|
||||
};
|
||||
|
||||
self.images.push(svg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,6 +228,22 @@ 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(Result<core::image::Allocation, core::image::Error>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) {
|
||||
#[cfg(feature = "image")]
|
||||
#[allow(unsafe_code)]
|
||||
// TODO: Concurrency
|
||||
callback(self.engine.raster_pipeline.load(handle));
|
||||
|
||||
#[cfg(not(feature = "image"))]
|
||||
callback(Err(core::image::Error::Unsupported))
|
||||
}
|
||||
}
|
||||
|
||||
impl core::text::Renderer for Renderer {
|
||||
|
|
@ -350,13 +366,28 @@ impl graphics::mesh::Renderer for Renderer {
|
|||
impl core::image::Renderer for Renderer {
|
||||
type Handle = core::image::Handle;
|
||||
|
||||
fn measure_image(&self, handle: &Self::Handle) -> crate::core::Size<u32> {
|
||||
fn load_image(
|
||||
&self,
|
||||
handle: &Self::Handle,
|
||||
) -> Result<core::image::Allocation, core::image::Error> {
|
||||
self.engine.raster_pipeline.load(handle)
|
||||
}
|
||||
|
||||
fn measure_image(
|
||||
&self,
|
||||
handle: &Self::Handle,
|
||||
) -> Option<crate::core::Size<u32>> {
|
||||
self.engine.raster_pipeline.dimensions(handle)
|
||||
}
|
||||
|
||||
fn draw_image(&mut self, image: core::Image, bounds: Rectangle) {
|
||||
fn draw_image(
|
||||
&mut self,
|
||||
image: core::Image,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_raster(image, bounds, transformation);
|
||||
layer.draw_raster(image, bounds, clip_bounds, transformation);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -369,9 +400,14 @@ impl core::svg::Renderer for Renderer {
|
|||
self.engine.vector_pipeline.viewport_dimensions(handle)
|
||||
}
|
||||
|
||||
fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) {
|
||||
fn draw_svg(
|
||||
&mut self,
|
||||
svg: core::Svg,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_svg(svg, bounds, transformation);
|
||||
layer.draw_svg(svg, bounds, clip_bounds, transformation);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,12 +18,24 @@ impl Pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn dimensions(&self, handle: &raster::Handle) -> Size<u32> {
|
||||
if let Some(image) = self.cache.borrow_mut().allocate(handle) {
|
||||
Size::new(image.width(), image.height())
|
||||
} else {
|
||||
Size::new(0, 0)
|
||||
}
|
||||
pub fn load(
|
||||
&self,
|
||||
handle: &raster::Handle,
|
||||
) -> Result<raster::Allocation, raster::Error> {
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
let image = cache.allocate(handle)?;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
Ok(unsafe {
|
||||
raster::allocate(handle, Size::new(image.width(), image.height()))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dimensions(&self, handle: &raster::Handle) -> Option<Size<u32>> {
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
let image = cache.allocate(handle).ok()?;
|
||||
|
||||
Some(Size::new(image.width(), image.height()))
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
|
|
@ -36,34 +48,34 @@ impl Pipeline {
|
|||
transform: tiny_skia::Transform,
|
||||
clip_mask: Option<&tiny_skia::Mask>,
|
||||
) {
|
||||
if let Some(image) = self.cache.borrow_mut().allocate(handle) {
|
||||
let width_scale = bounds.width / image.width() as f32;
|
||||
let height_scale = bounds.height / image.height() as f32;
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
|
||||
let transform = transform.pre_scale(width_scale, height_scale);
|
||||
let Ok(image) = cache.allocate(handle) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let quality = match filter_method {
|
||||
raster::FilterMethod::Linear => {
|
||||
tiny_skia::FilterQuality::Bilinear
|
||||
}
|
||||
raster::FilterMethod::Nearest => {
|
||||
tiny_skia::FilterQuality::Nearest
|
||||
}
|
||||
};
|
||||
let width_scale = bounds.width / image.width() as f32;
|
||||
let height_scale = bounds.height / image.height() as f32;
|
||||
|
||||
pixels.draw_pixmap(
|
||||
(bounds.x / width_scale) as i32,
|
||||
(bounds.y / height_scale) as i32,
|
||||
image,
|
||||
&tiny_skia::PixmapPaint {
|
||||
quality,
|
||||
opacity,
|
||||
..Default::default()
|
||||
},
|
||||
transform,
|
||||
clip_mask,
|
||||
);
|
||||
}
|
||||
let transform = transform.pre_scale(width_scale, height_scale);
|
||||
|
||||
let quality = match filter_method {
|
||||
raster::FilterMethod::Linear => tiny_skia::FilterQuality::Bilinear,
|
||||
raster::FilterMethod::Nearest => tiny_skia::FilterQuality::Nearest,
|
||||
};
|
||||
|
||||
pixels.draw_pixmap(
|
||||
(bounds.x / width_scale) as i32,
|
||||
(bounds.y / height_scale) as i32,
|
||||
image,
|
||||
&tiny_skia::PixmapPaint {
|
||||
quality,
|
||||
opacity,
|
||||
..Default::default()
|
||||
},
|
||||
transform,
|
||||
clip_mask,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn trim_cache(&mut self) {
|
||||
|
|
@ -81,11 +93,18 @@ impl Cache {
|
|||
pub fn allocate(
|
||||
&mut self,
|
||||
handle: &raster::Handle,
|
||||
) -> Option<tiny_skia::PixmapRef<'_>> {
|
||||
) -> Result<tiny_skia::PixmapRef<'_>, raster::Error> {
|
||||
let id = handle.id();
|
||||
|
||||
if let hash_map::Entry::Vacant(entry) = self.entries.entry(id) {
|
||||
let image = graphics::image::load(handle).ok()?;
|
||||
let image = match graphics::image::load(handle) {
|
||||
Ok(image) => image,
|
||||
Err(error) => {
|
||||
let _ = entry.insert(None);
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
let mut buffer =
|
||||
vec![0u32; image.width() as usize * image.height() as usize];
|
||||
|
|
@ -106,14 +125,21 @@ impl Cache {
|
|||
}
|
||||
|
||||
let _ = self.hits.insert(id);
|
||||
self.entries.get(&id).unwrap().as_ref().map(|entry| {
|
||||
tiny_skia::PixmapRef::from_bytes(
|
||||
bytemuck::cast_slice(&entry.pixels),
|
||||
entry.width,
|
||||
entry.height,
|
||||
)
|
||||
.expect("Build pixmap from image bytes")
|
||||
})
|
||||
|
||||
Ok(self
|
||||
.entries
|
||||
.get(&id)
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.map(|entry| {
|
||||
tiny_skia::PixmapRef::from_bytes(
|
||||
bytemuck::cast_slice(&entry.pixels),
|
||||
entry.width,
|
||||
entry.height,
|
||||
)
|
||||
.expect("Build pixmap from image bytes")
|
||||
})
|
||||
.expect("Image should be allocated"))
|
||||
}
|
||||
|
||||
fn trim(&mut self) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use crate::core::{Color, Rectangle, Size};
|
|||
use crate::graphics::compositor::{self, Information};
|
||||
use crate::graphics::damage;
|
||||
use crate::graphics::error::{self, Error};
|
||||
use crate::graphics::{self, Viewport};
|
||||
use crate::graphics::{self, Shell, Viewport};
|
||||
use crate::{Layer, Renderer, Settings};
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
|
@ -31,6 +31,7 @@ impl crate::graphics::Compositor for Compositor {
|
|||
async fn with_backend<W: compositor::Window>(
|
||||
settings: graphics::Settings,
|
||||
compatible_window: W,
|
||||
_shell: Shell,
|
||||
backend: Option<&str>,
|
||||
) -> Result<Self, Error> {
|
||||
match backend {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ impl<T: bytemuck::Pod> Buffer<T> {
|
|||
}
|
||||
|
||||
pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool {
|
||||
let new_size = (std::mem::size_of::<T>() * new_count) as u64;
|
||||
let new_size = next_copy_size::<T>(new_count);
|
||||
|
||||
if self.size < new_size {
|
||||
self.raw = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::graphics::Antialiasing;
|
||||
use crate::graphics::{Antialiasing, Shell};
|
||||
use crate::primitive;
|
||||
use crate::quad;
|
||||
use crate::text;
|
||||
|
|
@ -18,6 +18,7 @@ pub struct Engine {
|
|||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
pub(crate) image_pipeline: crate::image::Pipeline,
|
||||
pub(crate) primitive_storage: Arc<RwLock<primitive::Storage>>,
|
||||
_shell: Shell,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
|
|
@ -27,6 +28,7 @@ impl Engine {
|
|||
queue: wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
antialiasing: Option<Antialiasing>, // TODO: Initialize AA pipelines lazily
|
||||
shell: Shell,
|
||||
) -> Self {
|
||||
Self {
|
||||
format,
|
||||
|
|
@ -52,14 +54,16 @@ impl Engine {
|
|||
|
||||
device,
|
||||
queue,
|
||||
_shell: shell,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
pub fn create_image_cache(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
) -> crate::image::Cache {
|
||||
self.image_pipeline.create_cache(device)
|
||||
pub fn create_image_cache(&self) -> crate::image::Cache {
|
||||
self.image_pipeline.create_cache(
|
||||
&self.device,
|
||||
&self.queue,
|
||||
&self._shell,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -431,8 +431,14 @@ impl geometry::frame::Backend for Frame {
|
|||
self.transforms.current.transform_rectangle(bounds);
|
||||
|
||||
image.rotation += external_rotation;
|
||||
image.border_radius =
|
||||
image.border_radius * self.transforms.current.scale().0;
|
||||
|
||||
self.images.push(Image::Raster(image, bounds));
|
||||
self.images.push(Image::Raster {
|
||||
image,
|
||||
bounds,
|
||||
clip_bounds: self.clip_bounds,
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
|
||||
|
|
@ -443,7 +449,11 @@ impl geometry::frame::Backend for Frame {
|
|||
|
||||
svg.rotation += external_rotation;
|
||||
|
||||
self.images.push(Image::Vector(svg, bounds));
|
||||
self.images.push(Image::Vector {
|
||||
svg,
|
||||
bounds,
|
||||
clip_bounds: self.clip_bounds,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ pub use layer::Layer;
|
|||
|
||||
use allocator::Allocator;
|
||||
|
||||
pub const SIZE: u32 = 2048;
|
||||
pub const DEFAULT_SIZE: u32 = 2048;
|
||||
pub const MAX_SIZE: u32 = 2048;
|
||||
|
||||
use crate::core::Size;
|
||||
use crate::graphics::color;
|
||||
|
|
@ -19,11 +20,12 @@ use std::sync::Arc;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Atlas {
|
||||
size: u32,
|
||||
backend: wgpu::Backend,
|
||||
texture: wgpu::Texture,
|
||||
texture_view: wgpu::TextureView,
|
||||
texture_bind_group: wgpu::BindGroup,
|
||||
texture_layout: Arc<wgpu::BindGroupLayout>,
|
||||
texture_bind_group: Arc<wgpu::BindGroup>,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
layers: Vec<Layer>,
|
||||
}
|
||||
|
||||
|
|
@ -31,8 +33,19 @@ impl Atlas {
|
|||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
backend: wgpu::Backend,
|
||||
texture_layout: Arc<wgpu::BindGroupLayout>,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
) -> Self {
|
||||
Self::with_size(device, backend, texture_layout, DEFAULT_SIZE)
|
||||
}
|
||||
|
||||
pub fn with_size(
|
||||
device: &wgpu::Device,
|
||||
backend: wgpu::Backend,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
size: u32,
|
||||
) -> Self {
|
||||
let size = size.min(MAX_SIZE);
|
||||
|
||||
let layers = match backend {
|
||||
// On the GL backend we start with 2 layers, to help wgpu figure
|
||||
// out that this texture is `GL_TEXTURE_2D_ARRAY` rather than `GL_TEXTURE_2D`
|
||||
|
|
@ -42,8 +55,8 @@ impl Atlas {
|
|||
};
|
||||
|
||||
let extent = wgpu::Extent3d {
|
||||
width: SIZE,
|
||||
height: SIZE,
|
||||
width: size,
|
||||
height: size,
|
||||
depth_or_array_layers: layers.len() as u32,
|
||||
};
|
||||
|
||||
|
|
@ -80,30 +93,28 @@ impl Atlas {
|
|||
});
|
||||
|
||||
Atlas {
|
||||
size,
|
||||
backend,
|
||||
texture,
|
||||
texture_view,
|
||||
texture_bind_group,
|
||||
texture_bind_group: Arc::new(texture_bind_group),
|
||||
texture_layout,
|
||||
layers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_group(&self) -> &wgpu::BindGroup {
|
||||
pub fn bind_group(&self) -> &Arc<wgpu::BindGroup> {
|
||||
&self.texture_bind_group
|
||||
}
|
||||
|
||||
pub fn layer_count(&self) -> usize {
|
||||
self.layers.len()
|
||||
}
|
||||
|
||||
pub fn upload(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
width: u32,
|
||||
height: u32,
|
||||
data: &[u8],
|
||||
pixels: &[u8],
|
||||
) -> Option<Entry> {
|
||||
let entry = {
|
||||
let current_size = self.layers.len();
|
||||
|
|
@ -111,59 +122,32 @@ impl Atlas {
|
|||
|
||||
// We grow the internal texture after allocating if necessary
|
||||
let new_layers = self.layers.len() - current_size;
|
||||
self.grow(new_layers, device, encoder);
|
||||
self.grow(new_layers, device, encoder, self.backend);
|
||||
|
||||
entry
|
||||
};
|
||||
|
||||
log::debug!("Allocated atlas entry: {entry:?}");
|
||||
|
||||
// It is a webgpu requirement that:
|
||||
// BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
|
||||
// So we calculate padded_width by rounding width up to the next
|
||||
// multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT.
|
||||
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
|
||||
let padding = (align - (4 * width) % align) % align;
|
||||
let padded_width = (4 * width + padding) as usize;
|
||||
let padded_data_size = padded_width * height as usize;
|
||||
|
||||
let mut padded_data = vec![0; padded_data_size];
|
||||
|
||||
for row in 0..height as usize {
|
||||
let offset = row * padded_width;
|
||||
|
||||
padded_data[offset..offset + 4 * width as usize].copy_from_slice(
|
||||
&data[row * 4 * width as usize..(row + 1) * 4 * width as usize],
|
||||
);
|
||||
}
|
||||
|
||||
match &entry {
|
||||
Entry::Contiguous(allocation) => {
|
||||
self.upload_allocation(
|
||||
&padded_data,
|
||||
width,
|
||||
height,
|
||||
padding,
|
||||
0,
|
||||
allocation,
|
||||
device,
|
||||
encoder,
|
||||
pixels, width, 0, allocation, device, encoder, belt,
|
||||
);
|
||||
}
|
||||
Entry::Fragmented { fragments, .. } => {
|
||||
for fragment in fragments {
|
||||
let (x, y) = fragment.position;
|
||||
let offset = (y * padded_width as u32 + 4 * x) as usize;
|
||||
let offset = 4 * (y * width + x) as usize;
|
||||
|
||||
self.upload_allocation(
|
||||
&padded_data,
|
||||
pixels,
|
||||
width,
|
||||
height,
|
||||
padding,
|
||||
offset,
|
||||
&fragment.allocation,
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -172,7 +156,7 @@ impl Atlas {
|
|||
if log::log_enabled!(log::Level::Debug) {
|
||||
log::debug!(
|
||||
"Atlas layers: {} (busy: {}, allocations: {})",
|
||||
self.layer_count(),
|
||||
self.layers.len(),
|
||||
self.layers.iter().filter(|layer| !layer.is_empty()).count(),
|
||||
self.layers.iter().map(Layer::allocations).sum::<usize>(),
|
||||
);
|
||||
|
|
@ -198,7 +182,7 @@ impl Atlas {
|
|||
|
||||
fn allocate(&mut self, width: u32, height: u32) -> Option<Entry> {
|
||||
// Allocate one layer if texture fits perfectly
|
||||
if width == SIZE && height == SIZE {
|
||||
if width == self.size && height == self.size {
|
||||
let mut empty_layers = self
|
||||
.layers
|
||||
.iter_mut()
|
||||
|
|
@ -208,27 +192,31 @@ impl Atlas {
|
|||
if let Some((i, layer)) = empty_layers.next() {
|
||||
*layer = Layer::Full;
|
||||
|
||||
return Some(Entry::Contiguous(Allocation::Full { layer: i }));
|
||||
return Some(Entry::Contiguous(Allocation::Full {
|
||||
layer: i,
|
||||
size: self.size,
|
||||
}));
|
||||
}
|
||||
|
||||
self.layers.push(Layer::Full);
|
||||
|
||||
return Some(Entry::Contiguous(Allocation::Full {
|
||||
layer: self.layers.len() - 1,
|
||||
size: self.size,
|
||||
}));
|
||||
}
|
||||
|
||||
// Split big textures across multiple layers
|
||||
if width > SIZE || height > SIZE {
|
||||
if width > self.size || height > self.size {
|
||||
let mut fragments = Vec::new();
|
||||
let mut y = 0;
|
||||
|
||||
while y < height {
|
||||
let height = std::cmp::min(height - y, SIZE);
|
||||
let height = std::cmp::min(height - y, self.size);
|
||||
let mut x = 0;
|
||||
|
||||
while x < width {
|
||||
let width = std::cmp::min(width - x, SIZE);
|
||||
let width = std::cmp::min(width - x, self.size);
|
||||
|
||||
let allocation = self.allocate(width, height)?;
|
||||
|
||||
|
|
@ -255,7 +243,7 @@ impl Atlas {
|
|||
for (i, layer) in self.layers.iter_mut().enumerate() {
|
||||
match layer {
|
||||
Layer::Empty => {
|
||||
let mut allocator = Allocator::new(SIZE);
|
||||
let mut allocator = Allocator::new(self.size);
|
||||
|
||||
if let Some(region) = allocator.allocate(width, height) {
|
||||
*layer = Layer::Busy(allocator);
|
||||
|
|
@ -263,6 +251,7 @@ impl Atlas {
|
|||
return Some(Entry::Contiguous(Allocation::Partial {
|
||||
region,
|
||||
layer: i,
|
||||
atlas_size: self.size,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -271,6 +260,7 @@ impl Atlas {
|
|||
return Some(Entry::Contiguous(Allocation::Partial {
|
||||
region,
|
||||
layer: i,
|
||||
atlas_size: self.size,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -279,7 +269,7 @@ impl Atlas {
|
|||
}
|
||||
|
||||
// Create new layer with atlas allocator
|
||||
let mut allocator = Allocator::new(SIZE);
|
||||
let mut allocator = Allocator::new(self.size);
|
||||
|
||||
if let Some(region) = allocator.allocate(width, height) {
|
||||
self.layers.push(Layer::Busy(allocator));
|
||||
|
|
@ -287,6 +277,7 @@ impl Atlas {
|
|||
return Some(Entry::Contiguous(Allocation::Partial {
|
||||
region,
|
||||
layer: self.layers.len() - 1,
|
||||
atlas_size: self.size,
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -298,10 +289,10 @@ impl Atlas {
|
|||
log::debug!("Deallocating atlas: {allocation:?}");
|
||||
|
||||
match allocation {
|
||||
Allocation::Full { layer } => {
|
||||
Allocation::Full { layer, .. } => {
|
||||
self.layers[*layer] = Layer::Empty;
|
||||
}
|
||||
Allocation::Partial { layer, region } => {
|
||||
Allocation::Partial { layer, region, .. } => {
|
||||
let layer = &mut self.layers[*layer];
|
||||
|
||||
if let Layer::Busy(allocator) = layer {
|
||||
|
|
@ -316,55 +307,134 @@ impl Atlas {
|
|||
}
|
||||
|
||||
fn upload_allocation(
|
||||
&mut self,
|
||||
data: &[u8],
|
||||
&self,
|
||||
pixels: &[u8],
|
||||
image_width: u32,
|
||||
image_height: u32,
|
||||
padding: u32,
|
||||
offset: usize,
|
||||
allocation: &Allocation,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
) {
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
let (x, y) = allocation.position();
|
||||
let Size { width, height } = allocation.size();
|
||||
let layer = allocation.layer();
|
||||
let padding = allocation.padding();
|
||||
|
||||
let extent = wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
// It is a webgpu requirement that:
|
||||
// BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
|
||||
// So we calculate bytes_per_row by rounding width up to the next
|
||||
// multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT.
|
||||
let bytes_per_row = (4 * (width + padding.width * 2))
|
||||
.next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT)
|
||||
as usize;
|
||||
let total_bytes =
|
||||
bytes_per_row * (height + padding.height * 2) as usize;
|
||||
|
||||
let buffer =
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("image upload buffer"),
|
||||
contents: data,
|
||||
usage: wgpu::BufferUsages::COPY_SRC,
|
||||
});
|
||||
let buffer_slice = belt.allocate(
|
||||
wgpu::BufferSize::new(total_bytes as u64).unwrap(),
|
||||
wgpu::BufferSize::new(8 * 4).unwrap(),
|
||||
device,
|
||||
);
|
||||
|
||||
const PIXEL: usize = 4;
|
||||
|
||||
let mut fragment = buffer_slice.get_mapped_range_mut();
|
||||
let w = width as usize;
|
||||
let h = height as usize;
|
||||
let pad_w = padding.width as usize;
|
||||
let pad_h = padding.height as usize;
|
||||
let stride = PIXEL * w;
|
||||
|
||||
// Copy image rows
|
||||
for row in 0..h {
|
||||
let src = offset + row * PIXEL * image_width as usize;
|
||||
let dst = (row + pad_h) * bytes_per_row;
|
||||
|
||||
fragment[dst + PIXEL * pad_w..dst + PIXEL * pad_w + stride]
|
||||
.copy_from_slice(&pixels[src..src + stride]);
|
||||
|
||||
// Add padding to the sides, if needed
|
||||
for i in 0..pad_w {
|
||||
fragment[dst + PIXEL * i..dst + PIXEL * (i + 1)]
|
||||
.copy_from_slice(&pixels[src..src + PIXEL]);
|
||||
|
||||
fragment[dst + stride + PIXEL * (pad_w + i)
|
||||
..dst + stride + PIXEL * (pad_w + i + 1)]
|
||||
.copy_from_slice(
|
||||
&pixels[src + stride - PIXEL..src + stride],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add padding on top and bottom
|
||||
for row in 0..pad_h {
|
||||
let dst_top = row * bytes_per_row;
|
||||
let dst_bottom = (pad_h + h + row) * bytes_per_row;
|
||||
let src_top = offset;
|
||||
let src_bottom = offset + (h - 1) * PIXEL * image_width as usize;
|
||||
|
||||
// Top
|
||||
fragment[dst_top + PIXEL * pad_w..dst_top + PIXEL * (pad_w + w)]
|
||||
.copy_from_slice(&pixels[src_top..src_top + PIXEL * w]);
|
||||
|
||||
// Bottom
|
||||
fragment
|
||||
[dst_bottom + PIXEL * pad_w..dst_bottom + PIXEL * (pad_w + w)]
|
||||
.copy_from_slice(&pixels[src_bottom..src_bottom + PIXEL * w]);
|
||||
|
||||
// Corners
|
||||
for i in 0..pad_w {
|
||||
// Top left
|
||||
fragment[dst_top + PIXEL * i..dst_top + PIXEL * (i + 1)]
|
||||
.copy_from_slice(&pixels[offset..offset + PIXEL]);
|
||||
|
||||
// Top right
|
||||
fragment[dst_top + PIXEL * (w + pad_w + i)
|
||||
..dst_top + PIXEL * (w + pad_w + i + 1)]
|
||||
.copy_from_slice(
|
||||
&pixels[offset + PIXEL * (w - 1)..offset + PIXEL * w],
|
||||
);
|
||||
|
||||
// Bottom left
|
||||
fragment[dst_bottom + PIXEL * i..dst_bottom + PIXEL * (i + 1)]
|
||||
.copy_from_slice(&pixels[src_bottom..src_bottom + PIXEL]);
|
||||
|
||||
// Bottom right
|
||||
fragment[dst_bottom + PIXEL * (w + pad_w + i)
|
||||
..dst_bottom + PIXEL * (w + pad_w + i + 1)]
|
||||
.copy_from_slice(
|
||||
&pixels[src_bottom + PIXEL * (w - 1)
|
||||
..src_bottom + PIXEL * w],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy actual image
|
||||
encoder.copy_buffer_to_texture(
|
||||
wgpu::TexelCopyBufferInfo {
|
||||
buffer: &buffer,
|
||||
buffer: buffer_slice.buffer(),
|
||||
layout: wgpu::TexelCopyBufferLayout {
|
||||
offset: offset as u64,
|
||||
bytes_per_row: Some(4 * image_width + padding),
|
||||
rows_per_image: Some(image_height),
|
||||
offset: buffer_slice.offset(),
|
||||
bytes_per_row: Some(bytes_per_row as u32),
|
||||
rows_per_image: Some(height + padding.height * 2),
|
||||
},
|
||||
},
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &self.texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d {
|
||||
x,
|
||||
y,
|
||||
x: x - padding.width,
|
||||
y: y - padding.height,
|
||||
z: layer as u32,
|
||||
},
|
||||
aspect: wgpu::TextureAspect::default(),
|
||||
},
|
||||
extent,
|
||||
wgpu::Extent3d {
|
||||
width: width + padding.width * 2,
|
||||
height: height + padding.height * 2,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -373,6 +443,7 @@ impl Atlas {
|
|||
amount: usize,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
backend: wgpu::Backend,
|
||||
) {
|
||||
if amount == 0 {
|
||||
return;
|
||||
|
|
@ -383,7 +454,7 @@ impl Atlas {
|
|||
// some unused memory on GL, but it's better than not being able to grow the atlas past a depth
|
||||
// of 6!
|
||||
// https://github.com/gfx-rs/wgpu/blob/004e3efe84a320d9331371ed31fa50baa2414911/wgpu-hal/src/gles/mod.rs#L371
|
||||
let depth_or_array_layers = match self.backend {
|
||||
let depth_or_array_layers = match backend {
|
||||
wgpu::Backend::Gl if self.layers.len() == 6 => 7,
|
||||
_ => self.layers.len() as u32,
|
||||
};
|
||||
|
|
@ -391,8 +462,8 @@ impl Atlas {
|
|||
let new_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas"),
|
||||
size: wgpu::Extent3d {
|
||||
width: SIZE,
|
||||
height: SIZE,
|
||||
width: self.size,
|
||||
height: self.size,
|
||||
depth_or_array_layers,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
|
|
@ -440,8 +511,8 @@ impl Atlas {
|
|||
aspect: wgpu::TextureAspect::default(),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: SIZE,
|
||||
height: SIZE,
|
||||
width: self.size,
|
||||
height: self.size,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
|
|
@ -455,7 +526,7 @@ impl Atlas {
|
|||
});
|
||||
|
||||
self.texture_bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas bind group"),
|
||||
layout: &self.texture_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
|
|
@ -464,6 +535,6 @@ impl Atlas {
|
|||
&self.texture_view,
|
||||
),
|
||||
}],
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
use crate::core::Size;
|
||||
use crate::image::atlas::{self, allocator};
|
||||
use crate::image::atlas::allocator;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Allocation {
|
||||
Partial {
|
||||
layer: usize,
|
||||
region: allocator::Region,
|
||||
atlas_size: u32,
|
||||
},
|
||||
Full {
|
||||
layer: usize,
|
||||
size: u32,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -23,14 +25,28 @@ impl Allocation {
|
|||
pub fn size(&self) -> Size<u32> {
|
||||
match self {
|
||||
Allocation::Partial { region, .. } => region.size(),
|
||||
Allocation::Full { .. } => Size::new(atlas::SIZE, atlas::SIZE),
|
||||
Allocation::Full { size, .. } => Size::new(*size, *size),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn padding(&self) -> Size<u32> {
|
||||
match self {
|
||||
Allocation::Partial { region, .. } => region.padding(),
|
||||
Allocation::Full { .. } => Size::new(0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layer(&self) -> usize {
|
||||
match self {
|
||||
Allocation::Partial { layer, .. } => *layer,
|
||||
Allocation::Full { layer } => *layer,
|
||||
Allocation::Full { layer, .. } => *layer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn atlas_size(&self) -> u32 {
|
||||
match self {
|
||||
Allocation::Partial { atlas_size, .. } => *atlas_size,
|
||||
Allocation::Full { size, .. } => *size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use crate::core;
|
||||
|
||||
use guillotiere::{AtlasAllocator, Size};
|
||||
|
||||
pub struct Allocator {
|
||||
|
|
@ -6,6 +8,8 @@ pub struct Allocator {
|
|||
}
|
||||
|
||||
impl Allocator {
|
||||
const PADDING: u32 = 1;
|
||||
|
||||
pub fn new(size: u32) -> Allocator {
|
||||
let raw = AtlasAllocator::new(Size::new(size as i32, size as i32));
|
||||
|
||||
|
|
@ -16,12 +20,38 @@ impl Allocator {
|
|||
}
|
||||
|
||||
pub fn allocate(&mut self, width: u32, height: u32) -> Option<Region> {
|
||||
let allocation =
|
||||
self.raw.allocate(Size::new(width as i32, height as i32))?;
|
||||
let size = self.raw.size();
|
||||
|
||||
let padded_width = width + Self::PADDING * 2;
|
||||
let padded_height = height + Self::PADDING * 2;
|
||||
|
||||
let pad_width = padded_width as i32 <= size.width;
|
||||
let pad_height = padded_height as i32 <= size.height;
|
||||
|
||||
let mut allocation = self.raw.allocate(Size::new(
|
||||
if pad_width { padded_width } else { width } as i32,
|
||||
if pad_height { padded_height } else { height } as i32,
|
||||
))?;
|
||||
|
||||
if pad_width {
|
||||
allocation.rectangle.min.x += Self::PADDING as i32;
|
||||
allocation.rectangle.max.x -= Self::PADDING as i32;
|
||||
}
|
||||
|
||||
if pad_height {
|
||||
allocation.rectangle.min.y += Self::PADDING as i32;
|
||||
allocation.rectangle.max.y -= Self::PADDING as i32;
|
||||
}
|
||||
|
||||
self.allocations += 1;
|
||||
|
||||
Some(Region { allocation })
|
||||
Some(Region {
|
||||
allocation,
|
||||
padding: core::Size::new(
|
||||
if pad_width { Self::PADDING } else { 0 },
|
||||
if pad_height { Self::PADDING } else { 0 },
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deallocate(&mut self, region: &Region) {
|
||||
|
|
@ -41,6 +71,7 @@ impl Allocator {
|
|||
|
||||
pub struct Region {
|
||||
allocation: guillotiere::Allocation,
|
||||
padding: core::Size<u32>,
|
||||
}
|
||||
|
||||
impl Region {
|
||||
|
|
@ -50,10 +81,14 @@ impl Region {
|
|||
(rectangle.min.x as u32, rectangle.min.y as u32)
|
||||
}
|
||||
|
||||
pub fn size(&self) -> crate::core::Size<u32> {
|
||||
pub fn size(&self) -> core::Size<u32> {
|
||||
let size = self.allocation.rectangle.size();
|
||||
|
||||
crate::core::Size::new(size.width as u32, size.height as u32)
|
||||
core::Size::new(size.width as u32, size.height as u32)
|
||||
}
|
||||
|
||||
pub fn padding(&self) -> crate::core::Size<u32> {
|
||||
self.padding
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,47 +1,199 @@
|
|||
use crate::core::{self, Size};
|
||||
use crate::graphics::Shell;
|
||||
use crate::image::atlas::{self, Atlas};
|
||||
|
||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
use worker::Worker;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Cache {
|
||||
atlas: Atlas,
|
||||
#[cfg(feature = "image")]
|
||||
raster: crate::image::raster::Cache,
|
||||
raster: Raster,
|
||||
#[cfg(feature = "svg")]
|
||||
vector: crate::image::vector::Cache,
|
||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
worker: Worker,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
_queue: &wgpu::Queue,
|
||||
backend: wgpu::Backend,
|
||||
layout: Arc<wgpu::BindGroupLayout>,
|
||||
layout: wgpu::BindGroupLayout,
|
||||
_shell: &Shell,
|
||||
) -> Self {
|
||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
let worker =
|
||||
Worker::new(device, _queue, backend, layout.clone(), _shell);
|
||||
|
||||
Self {
|
||||
atlas: Atlas::new(device, backend, layout),
|
||||
#[cfg(feature = "image")]
|
||||
raster: crate::image::raster::Cache::default(),
|
||||
raster: Raster {
|
||||
cache: crate::image::raster::Cache::default(),
|
||||
pending: HashMap::new(),
|
||||
belt: wgpu::util::StagingBelt::new(2 * 1024 * 1024),
|
||||
},
|
||||
#[cfg(feature = "svg")]
|
||||
vector: crate::image::vector::Cache::default(),
|
||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
worker,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_group(&self) -> &wgpu::BindGroup {
|
||||
self.atlas.bind_group()
|
||||
}
|
||||
#[cfg(feature = "image")]
|
||||
pub fn allocate_image(
|
||||
&mut self,
|
||||
handle: &core::image::Handle,
|
||||
callback: impl FnOnce(Result<core::image::Allocation, core::image::Error>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) {
|
||||
use crate::image::raster::Memory;
|
||||
|
||||
pub fn layer_count(&self) -> usize {
|
||||
self.atlas.layer_count()
|
||||
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, entry, ..
|
||||
}) = self.raster.cache.get_mut(handle)
|
||||
{
|
||||
if let Some(allocation) = allocation
|
||||
.as_ref()
|
||||
.and_then(core::image::Allocation::upgrade)
|
||||
{
|
||||
callback(Ok(allocation));
|
||||
return;
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
let new = unsafe { core::image::allocate(handle, entry.size()) };
|
||||
*allocation = Some(new.downgrade());
|
||||
callback(Ok(new));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let _ = self.raster.pending.insert(handle.id(), vec![callback]);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.worker.load(handle);
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
|
||||
self.raster.load(handle).dimensions()
|
||||
pub fn load_image(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
handle: &core::image::Handle,
|
||||
) -> Result<core::image::Allocation, core::image::Error> {
|
||||
use crate::image::raster::Memory;
|
||||
|
||||
if !self.raster.cache.contains(handle) {
|
||||
self.raster.cache.insert(handle, Memory::load(handle));
|
||||
}
|
||||
|
||||
match self.raster.cache.get_mut(handle).unwrap() {
|
||||
Memory::Host(image) => {
|
||||
let mut encoder = device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("raster image upload"),
|
||||
},
|
||||
);
|
||||
|
||||
let entry = self.atlas.upload(
|
||||
device,
|
||||
&mut encoder,
|
||||
&mut self.raster.belt,
|
||||
image.width(),
|
||||
image.height(),
|
||||
image,
|
||||
);
|
||||
|
||||
self.raster.belt.finish();
|
||||
let submission = queue.submit([encoder.finish()]);
|
||||
self.raster.belt.recall();
|
||||
|
||||
let Some(entry) = entry else {
|
||||
return Err(core::image::Error::OutOfMemory);
|
||||
};
|
||||
|
||||
let _ = device
|
||||
.poll(wgpu::PollType::WaitForSubmissionIndex(submission));
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
let allocation = unsafe {
|
||||
core::image::allocate(
|
||||
handle,
|
||||
Size::new(image.width(), image.height()),
|
||||
)
|
||||
};
|
||||
|
||||
self.raster.cache.insert(
|
||||
handle,
|
||||
Memory::Device {
|
||||
entry,
|
||||
bind_group: None,
|
||||
allocation: Some(allocation.downgrade()),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(allocation)
|
||||
}
|
||||
Memory::Device {
|
||||
entry, allocation, ..
|
||||
} => {
|
||||
if let Some(allocation) = allocation
|
||||
.as_ref()
|
||||
.and_then(core::image::Allocation::upgrade)
|
||||
{
|
||||
return Ok(allocation);
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
let new =
|
||||
unsafe { core::image::allocate(handle, entry.size()) };
|
||||
|
||||
*allocation = Some(new.downgrade());
|
||||
|
||||
Ok(new)
|
||||
}
|
||||
Memory::Error(error) => Err(error.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
pub fn measure_image(
|
||||
&mut self,
|
||||
handle: &core::image::Handle,
|
||||
) -> Option<Size<u32>> {
|
||||
self.receive();
|
||||
|
||||
let image = load_image(
|
||||
&mut self.raster.cache,
|
||||
&mut self.raster.pending,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
&self.worker,
|
||||
handle,
|
||||
None,
|
||||
)?;
|
||||
|
||||
Some(image.dimensions())
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> {
|
||||
// TODO: Concurrency
|
||||
self.vector.load(handle).viewport_dimensions()
|
||||
}
|
||||
|
||||
|
|
@ -50,9 +202,66 @@ impl Cache {
|
|||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
handle: &core::image::Handle,
|
||||
) -> Option<&atlas::Entry> {
|
||||
self.raster.upload(device, encoder, handle, &mut self.atlas)
|
||||
) -> Option<(&atlas::Entry, &Arc<wgpu::BindGroup>)> {
|
||||
use crate::image::raster::Memory;
|
||||
|
||||
self.receive();
|
||||
|
||||
let memory = load_image(
|
||||
&mut self.raster.cache,
|
||||
&mut self.raster.pending,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
&self.worker,
|
||||
handle,
|
||||
None,
|
||||
)?;
|
||||
|
||||
if let Memory::Device {
|
||||
entry, bind_group, ..
|
||||
} = memory
|
||||
{
|
||||
return Some((
|
||||
entry,
|
||||
bind_group.as_ref().unwrap_or(self.atlas.bind_group()),
|
||||
));
|
||||
}
|
||||
|
||||
let image = memory.host()?;
|
||||
|
||||
const MAX_SYNC_SIZE: usize = 2 * 1024 * 1024;
|
||||
|
||||
// TODO: Concurrent Wasm support
|
||||
if image.len() < MAX_SYNC_SIZE || cfg!(target_arch = "wasm32") {
|
||||
let entry = self.atlas.upload(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
image.width(),
|
||||
image.height(),
|
||||
&image,
|
||||
)?;
|
||||
|
||||
*memory = Memory::Device {
|
||||
entry,
|
||||
bind_group: None,
|
||||
allocation: None,
|
||||
};
|
||||
|
||||
if let Memory::Device { entry, .. } = memory {
|
||||
return Some((entry, self.atlas.bind_group()));
|
||||
}
|
||||
}
|
||||
|
||||
if !self.raster.pending.contains_key(&handle.id()) {
|
||||
let _ = self.raster.pending.insert(handle.id(), Vec::new());
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.worker.upload(handle, image);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
|
|
@ -60,27 +269,361 @@ impl Cache {
|
|||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
handle: &core::svg::Handle,
|
||||
color: Option<core::Color>,
|
||||
size: [f32; 2],
|
||||
size: Size,
|
||||
scale: f32,
|
||||
) -> Option<&atlas::Entry> {
|
||||
self.vector.upload(
|
||||
device,
|
||||
encoder,
|
||||
handle,
|
||||
color,
|
||||
size,
|
||||
scale,
|
||||
&mut self.atlas,
|
||||
)
|
||||
) -> Option<(&atlas::Entry, &Arc<wgpu::BindGroup>)> {
|
||||
// TODO: Concurrency
|
||||
self.vector
|
||||
.upload(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
handle,
|
||||
color,
|
||||
size,
|
||||
scale,
|
||||
&mut self.atlas,
|
||||
)
|
||||
.map(|entry| (entry, self.atlas.bind_group()))
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
#[cfg(feature = "image")]
|
||||
self.raster.trim(&mut self.atlas);
|
||||
self.raster.cache.trim(&mut self.atlas, |_bind_group| {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.worker.drop(_bind_group);
|
||||
});
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
self.vector.trim(&mut self.atlas);
|
||||
self.vector.trim(&mut self.atlas); // TODO: Concurrency
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
fn receive(&mut self) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
while let Ok(work) = self.worker.try_recv() {
|
||||
use crate::image::raster::Memory;
|
||||
|
||||
match work {
|
||||
worker::Work::Upload {
|
||||
handle,
|
||||
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, entry.size())
|
||||
};
|
||||
|
||||
let reference = allocation.downgrade();
|
||||
|
||||
for callback in callbacks {
|
||||
callback(Ok(allocation.clone()));
|
||||
}
|
||||
|
||||
Some(reference)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.raster.cache.insert(
|
||||
&handle,
|
||||
Memory::Device {
|
||||
entry,
|
||||
bind_group: Some(bind_group),
|
||||
allocation,
|
||||
},
|
||||
);
|
||||
}
|
||||
worker::Work::Error { handle, error } => {
|
||||
let callbacks = self.raster.pending.remove(&handle.id());
|
||||
|
||||
if let Some(callbacks) = callbacks {
|
||||
for callback in callbacks {
|
||||
callback(Err(error.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
self.raster.cache.insert(&handle, Memory::Error(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
impl Drop for Cache {
|
||||
fn drop(&mut self) {
|
||||
self.worker.quit();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
struct Raster {
|
||||
cache: crate::image::raster::Cache,
|
||||
pending: HashMap<core::image::Id, Vec<Callback>>,
|
||||
belt: wgpu::util::StagingBelt,
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
type Callback =
|
||||
Box<dyn FnOnce(Result<core::image::Allocation, core::image::Error>) + Send>;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
fn load_image<'a>(
|
||||
cache: &'a mut crate::image::raster::Cache,
|
||||
pending: &mut HashMap<core::image::Id, Vec<Callback>>,
|
||||
#[cfg(not(target_arch = "wasm32"))] worker: &Worker,
|
||||
handle: &core::image::Handle,
|
||||
callback: Option<Callback>,
|
||||
) -> Option<&'a mut crate::image::raster::Memory> {
|
||||
use crate::image::raster::Memory;
|
||||
|
||||
if !cache.contains(handle) {
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
// TODO: Concurrent support for Wasm
|
||||
cache.insert(handle, Memory::load(handle));
|
||||
} 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_key(&handle.id()) {
|
||||
let _ = pending.insert(handle.id(), Vec::from_iter(callback));
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
worker.load(handle);
|
||||
}
|
||||
}
|
||||
|
||||
cache.get_mut(handle)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
mod worker {
|
||||
use crate::core::image;
|
||||
use crate::graphics::Shell;
|
||||
use crate::image::atlas::{self, Atlas};
|
||||
use crate::image::raster;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
|
||||
pub struct Worker {
|
||||
jobs: mpsc::SyncSender<Job>,
|
||||
quit: mpsc::SyncSender<()>,
|
||||
work: mpsc::Receiver<Work>,
|
||||
handle: Option<std::thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
backend: wgpu::Backend,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
shell: &Shell,
|
||||
) -> Self {
|
||||
let (jobs_sender, jobs_receiver) = mpsc::sync_channel(1_000);
|
||||
let (quit_sender, quit_receiver) = mpsc::sync_channel(1);
|
||||
let (work_sender, work_receiver) = mpsc::sync_channel(1_000);
|
||||
|
||||
let instance = Instance {
|
||||
device: device.clone(),
|
||||
queue: queue.clone(),
|
||||
backend,
|
||||
texture_layout,
|
||||
shell: shell.clone(),
|
||||
belt: wgpu::util::StagingBelt::new(4 * 1024 * 1024),
|
||||
jobs: jobs_receiver,
|
||||
output: work_sender,
|
||||
quit: quit_receiver,
|
||||
};
|
||||
|
||||
let handle = thread::spawn(move || instance.run());
|
||||
|
||||
Self {
|
||||
jobs: jobs_sender,
|
||||
quit: quit_sender,
|
||||
work: work_receiver,
|
||||
handle: Some(handle),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(&self, handle: &image::Handle) {
|
||||
let _ = self.jobs.send(Job::Load(handle.clone()));
|
||||
}
|
||||
|
||||
pub fn upload(&self, handle: &image::Handle, image: raster::Image) {
|
||||
let _ = self.jobs.send(Job::Upload {
|
||||
handle: handle.clone(),
|
||||
width: image.width(),
|
||||
height: image.height(),
|
||||
rgba: image.into_raw(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn drop(&self, bind_group: Arc<wgpu::BindGroup>) {
|
||||
let _ = self.jobs.send(Job::Drop(bind_group));
|
||||
}
|
||||
|
||||
pub fn try_recv(&self) -> Result<Work, mpsc::TryRecvError> {
|
||||
self.work.try_recv()
|
||||
}
|
||||
|
||||
pub fn quit(&mut self) {
|
||||
let _ = self.quit.try_send(());
|
||||
let _ = self.jobs.send(Job::Quit);
|
||||
let _ = self.handle.take().map(thread::JoinHandle::join);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Instance {
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
backend: wgpu::Backend,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
shell: Shell,
|
||||
belt: wgpu::util::StagingBelt,
|
||||
jobs: mpsc::Receiver<Job>,
|
||||
output: mpsc::SyncSender<Work>,
|
||||
quit: mpsc::Receiver<()>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Job {
|
||||
Load(image::Handle),
|
||||
Upload {
|
||||
handle: image::Handle,
|
||||
rgba: image::Bytes,
|
||||
width: u32,
|
||||
height: u32,
|
||||
},
|
||||
Drop(Arc<wgpu::BindGroup>),
|
||||
Quit,
|
||||
}
|
||||
|
||||
pub enum Work {
|
||||
Upload {
|
||||
handle: image::Handle,
|
||||
entry: atlas::Entry,
|
||||
bind_group: Arc<wgpu::BindGroup>,
|
||||
},
|
||||
Error {
|
||||
handle: image::Handle,
|
||||
error: image::Error,
|
||||
},
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
fn run(mut self) {
|
||||
loop {
|
||||
if self.quit.try_recv().is_ok() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(job) = self.jobs.recv() else {
|
||||
return;
|
||||
};
|
||||
|
||||
match job {
|
||||
Job::Load(handle) => {
|
||||
match crate::graphics::image::load(&handle) {
|
||||
Ok(image) => self.upload(
|
||||
handle,
|
||||
image.width(),
|
||||
image.height(),
|
||||
image.into_raw(),
|
||||
Shell::invalidate_layout,
|
||||
),
|
||||
Err(error) => {
|
||||
let _ = self
|
||||
.output
|
||||
.send(Work::Error { handle, error });
|
||||
}
|
||||
}
|
||||
}
|
||||
Job::Upload {
|
||||
handle,
|
||||
rgba,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
self.upload(
|
||||
handle,
|
||||
width,
|
||||
height,
|
||||
rgba,
|
||||
Shell::request_redraw,
|
||||
);
|
||||
}
|
||||
Job::Drop(bind_group) => {
|
||||
drop(bind_group);
|
||||
}
|
||||
Job::Quit => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn upload(
|
||||
&mut self,
|
||||
handle: image::Handle,
|
||||
width: u32,
|
||||
height: u32,
|
||||
rgba: image::Bytes,
|
||||
callback: fn(&Shell),
|
||||
) {
|
||||
let mut encoder = self.device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("raster image upload"),
|
||||
},
|
||||
);
|
||||
|
||||
let mut atlas = Atlas::with_size(
|
||||
&self.device,
|
||||
self.backend,
|
||||
self.texture_layout.clone(),
|
||||
width.max(height),
|
||||
);
|
||||
|
||||
let Some(entry) = atlas.upload(
|
||||
&self.device,
|
||||
&mut encoder,
|
||||
&mut self.belt,
|
||||
width,
|
||||
height,
|
||||
&rgba,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let output = self.output.clone();
|
||||
let shell = self.shell.clone();
|
||||
|
||||
self.belt.finish();
|
||||
let submission = self.queue.submit([encoder.finish()]);
|
||||
self.belt.recall();
|
||||
|
||||
let bind_group = atlas.bind_group().clone();
|
||||
|
||||
self.queue.on_submitted_work_done(move || {
|
||||
let _ = output.send(Work::Upload {
|
||||
handle,
|
||||
entry,
|
||||
bind_group,
|
||||
});
|
||||
|
||||
callback(&shell);
|
||||
});
|
||||
|
||||
let _ = self
|
||||
.device
|
||||
.poll(wgpu::PollType::WaitForSubmissionIndex(submission));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ mod raster;
|
|||
mod vector;
|
||||
|
||||
use crate::Buffer;
|
||||
use crate::core::border;
|
||||
use crate::core::{Rectangle, Size, Transformation};
|
||||
use crate::graphics::Shell;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
|
|
@ -27,7 +29,7 @@ pub struct Pipeline {
|
|||
backend: wgpu::Backend,
|
||||
nearest_sampler: wgpu::Sampler,
|
||||
linear_sampler: wgpu::Sampler,
|
||||
texture_layout: Arc<wgpu::BindGroupLayout>,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
constant_layout: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
|
|
@ -131,24 +133,26 @@ impl Pipeline {
|
|||
array_stride: mem::size_of::<Instance>() as u64,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &wgpu::vertex_attr_array!(
|
||||
// Position
|
||||
0 => Float32x2,
|
||||
// Center
|
||||
1 => Float32x2,
|
||||
// Scale
|
||||
2 => Float32x2,
|
||||
0 => Float32x2,
|
||||
// Clip bounds
|
||||
1 => Float32x4,
|
||||
// Border radius
|
||||
2 => Float32x4,
|
||||
// Tile
|
||||
3 => Float32x4,
|
||||
// Rotation
|
||||
3 => Float32,
|
||||
// Opacity
|
||||
4 => Float32,
|
||||
// Opacity
|
||||
5 => Float32,
|
||||
// Atlas position
|
||||
5 => Float32x2,
|
||||
// Atlas scale
|
||||
6 => Float32x2,
|
||||
// Atlas scale
|
||||
7 => Float32x2,
|
||||
// Layer
|
||||
7 => Sint32,
|
||||
8 => Sint32,
|
||||
// Snap
|
||||
8 => Uint32,
|
||||
9 => Uint32,
|
||||
),
|
||||
}],
|
||||
compilation_options:
|
||||
|
|
@ -196,13 +200,24 @@ impl Pipeline {
|
|||
backend,
|
||||
nearest_sampler,
|
||||
linear_sampler,
|
||||
texture_layout: Arc::new(texture_layout),
|
||||
texture_layout,
|
||||
constant_layout,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_cache(&self, device: &wgpu::Device) -> Cache {
|
||||
Cache::new(device, self.backend, self.texture_layout.clone())
|
||||
pub fn create_cache(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
shell: &Shell,
|
||||
) -> Cache {
|
||||
Cache::new(
|
||||
device,
|
||||
queue,
|
||||
self.backend,
|
||||
self.texture_layout.clone(),
|
||||
shell,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,6 +225,8 @@ impl Pipeline {
|
|||
pub struct State {
|
||||
layers: Vec<Layer>,
|
||||
prepare_layer: usize,
|
||||
nearest_instances: Vec<Instance>,
|
||||
linear_instances: Vec<Instance>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
|
@ -228,69 +245,6 @@ impl State {
|
|||
transformation: Transformation,
|
||||
scale: f32,
|
||||
) {
|
||||
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
|
||||
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
|
||||
|
||||
for image in images {
|
||||
match &image {
|
||||
#[cfg(feature = "image")]
|
||||
Image::Raster(image, bounds) => {
|
||||
if let Some(atlas_entry) =
|
||||
cache.upload_raster(device, encoder, &image.handle)
|
||||
{
|
||||
add_instances(
|
||||
[bounds.x, bounds.y],
|
||||
[bounds.width, bounds.height],
|
||||
f32::from(image.rotation),
|
||||
image.opacity,
|
||||
image.snap,
|
||||
atlas_entry,
|
||||
match image.filter_method {
|
||||
crate::core::image::FilterMethod::Nearest => {
|
||||
nearest_instances
|
||||
}
|
||||
crate::core::image::FilterMethod::Linear => {
|
||||
linear_instances
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "image"))]
|
||||
Image::Raster { .. } => {}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
Image::Vector(svg, bounds) => {
|
||||
let size = [bounds.width, bounds.height];
|
||||
|
||||
if let Some(atlas_entry) = cache.upload_vector(
|
||||
device,
|
||||
encoder,
|
||||
&svg.handle,
|
||||
svg.color,
|
||||
size,
|
||||
scale,
|
||||
) {
|
||||
add_instances(
|
||||
[bounds.x, bounds.y],
|
||||
size,
|
||||
f32::from(svg.rotation),
|
||||
svg.opacity,
|
||||
true,
|
||||
atlas_entry,
|
||||
nearest_instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "svg"))]
|
||||
Image::Vector { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
if nearest_instances.is_empty() && linear_instances.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.layers.len() <= self.prepare_layer {
|
||||
self.layers.push(Layer::new(
|
||||
device,
|
||||
|
|
@ -302,23 +256,129 @@ impl State {
|
|||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
|
||||
let mut atlas: Option<Arc<wgpu::BindGroup>> = None;
|
||||
|
||||
for image in images {
|
||||
match &image {
|
||||
#[cfg(feature = "image")]
|
||||
Image::Raster {
|
||||
image,
|
||||
bounds,
|
||||
clip_bounds,
|
||||
} => {
|
||||
if let Some((atlas_entry, bind_group)) = cache
|
||||
.upload_raster(device, encoder, belt, &image.handle)
|
||||
{
|
||||
match atlas.as_mut() {
|
||||
None => {
|
||||
atlas = Some(bind_group.clone());
|
||||
}
|
||||
Some(atlas) if atlas != bind_group => {
|
||||
layer.push(
|
||||
atlas,
|
||||
&self.nearest_instances,
|
||||
&self.linear_instances,
|
||||
);
|
||||
|
||||
*atlas = Arc::clone(bind_group);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
add_instances(
|
||||
*bounds,
|
||||
*clip_bounds,
|
||||
image.border_radius,
|
||||
f32::from(image.rotation),
|
||||
image.opacity,
|
||||
image.snap,
|
||||
atlas_entry,
|
||||
match image.filter_method {
|
||||
crate::core::image::FilterMethod::Nearest => {
|
||||
&mut self.nearest_instances
|
||||
}
|
||||
crate::core::image::FilterMethod::Linear => {
|
||||
&mut self.linear_instances
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "image"))]
|
||||
Image::Raster { .. } => continue,
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
Image::Vector {
|
||||
svg,
|
||||
bounds,
|
||||
clip_bounds,
|
||||
} => {
|
||||
if let Some((atlas_entry, bind_group)) = cache
|
||||
.upload_vector(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&svg.handle,
|
||||
svg.color,
|
||||
bounds.size(),
|
||||
scale,
|
||||
)
|
||||
{
|
||||
match atlas.as_mut() {
|
||||
None => {
|
||||
atlas = Some(bind_group.clone());
|
||||
}
|
||||
Some(atlas) if atlas != bind_group => {
|
||||
layer.push(
|
||||
atlas,
|
||||
&self.nearest_instances,
|
||||
&self.linear_instances,
|
||||
);
|
||||
|
||||
*atlas = bind_group.clone();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
add_instances(
|
||||
*bounds,
|
||||
*clip_bounds,
|
||||
border::radius(0),
|
||||
f32::from(svg.rotation),
|
||||
svg.opacity,
|
||||
true,
|
||||
atlas_entry,
|
||||
&mut self.nearest_instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "svg"))]
|
||||
Image::Vector { .. } => continue,
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(atlas) = &atlas {
|
||||
layer.push(atlas, &self.nearest_instances, &self.linear_instances);
|
||||
}
|
||||
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
nearest_instances,
|
||||
linear_instances,
|
||||
transformation,
|
||||
scale,
|
||||
&self.nearest_instances,
|
||||
&self.linear_instances,
|
||||
);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
self.nearest_instances.clear();
|
||||
self.linear_instances.clear();
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
pipeline: &'a Pipeline,
|
||||
cache: &'a Cache,
|
||||
layer: usize,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
|
|
@ -333,13 +393,15 @@ impl State {
|
|||
bounds.height,
|
||||
);
|
||||
|
||||
render_pass.set_bind_group(1, cache.bind_group(), &[]);
|
||||
|
||||
layer.render(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
for layer in &mut self.layers[..self.prepare_layer] {
|
||||
layer.clear();
|
||||
}
|
||||
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -347,8 +409,19 @@ impl State {
|
|||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
uniforms: wgpu::Buffer,
|
||||
nearest: Data,
|
||||
linear: Data,
|
||||
instances: Buffer<Instance>,
|
||||
nearest: Vec<Group>,
|
||||
nearest_layout: wgpu::BindGroup,
|
||||
nearest_total: usize,
|
||||
linear: Vec<Group>,
|
||||
linear_layout: wgpu::BindGroup,
|
||||
linear_total: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Group {
|
||||
atlas: Arc<wgpu::BindGroup>,
|
||||
instance_count: usize,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
|
|
@ -365,16 +438,70 @@ impl Layer {
|
|||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let nearest =
|
||||
Data::new(device, constant_layout, nearest_sampler, &uniforms);
|
||||
let instances = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu::image instance buffer",
|
||||
Instance::INITIAL,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
let linear =
|
||||
Data::new(device, constant_layout, linear_sampler, &uniforms);
|
||||
let nearest_layout =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image constants bind group"),
|
||||
layout: constant_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: &uniforms,
|
||||
offset: 0,
|
||||
size: None,
|
||||
},
|
||||
),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(
|
||||
nearest_sampler,
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let linear_layout =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image constants bind group"),
|
||||
layout: constant_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: &uniforms,
|
||||
offset: 0,
|
||||
size: None,
|
||||
},
|
||||
),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(
|
||||
linear_sampler,
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
Self {
|
||||
uniforms,
|
||||
nearest,
|
||||
linear,
|
||||
instances,
|
||||
nearest: Vec::new(),
|
||||
nearest_layout,
|
||||
nearest_total: 0,
|
||||
linear: Vec::new(),
|
||||
linear_layout,
|
||||
linear_total: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -383,10 +510,10 @@ impl Layer {
|
|||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
nearest_instances: &[Instance],
|
||||
linear_instances: &[Instance],
|
||||
transformation: Transformation,
|
||||
scale_factor: f32,
|
||||
nearest: &[Instance],
|
||||
linear: &[Instance],
|
||||
) {
|
||||
let uniforms = Uniforms {
|
||||
transform: transformation.into(),
|
||||
|
|
@ -405,102 +532,96 @@ impl Layer {
|
|||
)
|
||||
.copy_from_slice(bytes);
|
||||
|
||||
self.nearest
|
||||
.upload(device, encoder, belt, nearest_instances);
|
||||
let _ = self
|
||||
.instances
|
||||
.resize(device, self.nearest_total + self.linear_total);
|
||||
|
||||
self.linear.upload(device, encoder, belt, linear_instances);
|
||||
}
|
||||
let mut offset = 0;
|
||||
|
||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
self.nearest.render(render_pass);
|
||||
self.linear.render(render_pass);
|
||||
}
|
||||
}
|
||||
if !nearest.is_empty() {
|
||||
offset += self.instances.write(device, encoder, belt, 0, nearest);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Data {
|
||||
constants: wgpu::BindGroup,
|
||||
instances: Buffer<Instance>,
|
||||
instance_count: usize,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
constant_layout: &wgpu::BindGroupLayout,
|
||||
sampler: &wgpu::Sampler,
|
||||
uniforms: &wgpu::Buffer,
|
||||
) -> Self {
|
||||
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image constants bind group"),
|
||||
layout: constant_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: uniforms,
|
||||
offset: 0,
|
||||
size: None,
|
||||
},
|
||||
),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let instances = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu::image instance buffer",
|
||||
Instance::INITIAL,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
Self {
|
||||
constants,
|
||||
instances,
|
||||
instance_count: 0,
|
||||
if !linear.is_empty() {
|
||||
let _ = self.instances.write(device, encoder, belt, offset, linear);
|
||||
}
|
||||
}
|
||||
|
||||
fn upload(
|
||||
fn push(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
instances: &[Instance],
|
||||
atlas: &Arc<wgpu::BindGroup>,
|
||||
nearest: &[Instance],
|
||||
linear: &[Instance],
|
||||
) {
|
||||
self.instance_count = instances.len();
|
||||
let new_nearest = nearest.len() - self.nearest_total;
|
||||
|
||||
if self.instance_count == 0 {
|
||||
return;
|
||||
if new_nearest > 0 {
|
||||
self.nearest.push(Group {
|
||||
atlas: atlas.clone(),
|
||||
instance_count: new_nearest,
|
||||
});
|
||||
|
||||
self.nearest_total = nearest.len();
|
||||
}
|
||||
|
||||
let _ = self.instances.resize(device, instances.len());
|
||||
let _ = self.instances.write(device, encoder, belt, 0, instances);
|
||||
let new_linear = linear.len() - self.linear_total;
|
||||
|
||||
if new_linear > 0 {
|
||||
self.linear.push(Group {
|
||||
atlas: atlas.clone(),
|
||||
instance_count: new_linear,
|
||||
});
|
||||
|
||||
self.linear_total = linear.len();
|
||||
}
|
||||
}
|
||||
|
||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
if self.instance_count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
render_pass.set_bind_group(0, &self.constants, &[]);
|
||||
render_pass.set_vertex_buffer(0, self.instances.slice(..));
|
||||
|
||||
render_pass.draw(0..6, 0..self.instance_count as u32);
|
||||
let mut offset = 0;
|
||||
|
||||
if !self.nearest.is_empty() {
|
||||
render_pass.set_bind_group(0, &self.nearest_layout, &[]);
|
||||
|
||||
for group in &self.nearest {
|
||||
render_pass.set_bind_group(1, group.atlas.as_ref(), &[]);
|
||||
render_pass
|
||||
.draw(0..6, offset..offset + group.instance_count as u32);
|
||||
|
||||
offset += group.instance_count as u32;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.linear.is_empty() {
|
||||
render_pass.set_bind_group(0, &self.linear_layout, &[]);
|
||||
|
||||
for group in &self.linear {
|
||||
render_pass.set_bind_group(1, group.atlas.as_ref(), &[]);
|
||||
render_pass
|
||||
.draw(0..6, offset..offset + group.instance_count as u32);
|
||||
|
||||
offset += group.instance_count as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.nearest.clear();
|
||||
self.nearest_total = 0;
|
||||
|
||||
self.linear.clear();
|
||||
self.linear_total = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
struct Instance {
|
||||
_position: [f32; 2],
|
||||
_center: [f32; 2],
|
||||
_size: [f32; 2],
|
||||
_clip_bounds: [f32; 4],
|
||||
_border_radius: [f32; 4],
|
||||
_tile: [f32; 4],
|
||||
_rotation: f32,
|
||||
_opacity: f32,
|
||||
_position_in_atlas: [f32; 2],
|
||||
|
|
@ -524,8 +645,9 @@ struct Uniforms {
|
|||
}
|
||||
|
||||
fn add_instances(
|
||||
image_position: [f32; 2],
|
||||
image_size: [f32; 2],
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
border_radius: border::Radius,
|
||||
rotation: f32,
|
||||
opacity: f32,
|
||||
snap: bool,
|
||||
|
|
@ -533,16 +655,26 @@ fn add_instances(
|
|||
instances: &mut Vec<Instance>,
|
||||
) {
|
||||
let center = [
|
||||
image_position[0] + image_size[0] / 2.0,
|
||||
image_position[1] + image_size[1] / 2.0,
|
||||
bounds.x + bounds.width / 2.0,
|
||||
bounds.y + bounds.height / 2.0,
|
||||
];
|
||||
|
||||
let clip_bounds = [
|
||||
clip_bounds.x,
|
||||
clip_bounds.y,
|
||||
clip_bounds.width,
|
||||
clip_bounds.height,
|
||||
];
|
||||
|
||||
let border_radius = border_radius.into();
|
||||
|
||||
match entry {
|
||||
atlas::Entry::Contiguous(allocation) => {
|
||||
add_instance(
|
||||
image_position,
|
||||
center,
|
||||
image_size,
|
||||
clip_bounds,
|
||||
border_radius,
|
||||
[bounds.x, bounds.y, bounds.width, bounds.height],
|
||||
rotation,
|
||||
opacity,
|
||||
snap,
|
||||
|
|
@ -551,32 +683,35 @@ fn add_instances(
|
|||
);
|
||||
}
|
||||
atlas::Entry::Fragmented { fragments, size } => {
|
||||
let scaling_x = image_size[0] / size.width as f32;
|
||||
let scaling_y = image_size[1] / size.height as f32;
|
||||
let scaling_x = bounds.width / size.width as f32;
|
||||
let scaling_y = bounds.height / size.height as f32;
|
||||
|
||||
for fragment in fragments {
|
||||
let allocation = &fragment.allocation;
|
||||
|
||||
let [x, y] = image_position;
|
||||
let (fragment_x, fragment_y) = fragment.position;
|
||||
|
||||
let Size {
|
||||
width: fragment_width,
|
||||
height: fragment_height,
|
||||
} = allocation.size();
|
||||
|
||||
let position = [
|
||||
x + fragment_x as f32 * scaling_x,
|
||||
y + fragment_y as f32 * scaling_y,
|
||||
];
|
||||
|
||||
let size = [
|
||||
let tile = [
|
||||
bounds.x + fragment_x as f32 * scaling_x,
|
||||
bounds.y + fragment_y as f32 * scaling_y,
|
||||
fragment_width as f32 * scaling_x,
|
||||
fragment_height as f32 * scaling_y,
|
||||
];
|
||||
|
||||
add_instance(
|
||||
position, center, size, rotation, opacity, snap,
|
||||
allocation, instances,
|
||||
center,
|
||||
clip_bounds,
|
||||
border_radius,
|
||||
tile,
|
||||
rotation,
|
||||
opacity,
|
||||
snap,
|
||||
allocation,
|
||||
instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -585,9 +720,10 @@ fn add_instances(
|
|||
|
||||
#[inline]
|
||||
fn add_instance(
|
||||
position: [f32; 2],
|
||||
center: [f32; 2],
|
||||
size: [f32; 2],
|
||||
clip_bounds: [f32; 4],
|
||||
border_radius: [f32; 4],
|
||||
tile: [f32; 4],
|
||||
rotation: f32,
|
||||
opacity: f32,
|
||||
snap: bool,
|
||||
|
|
@ -597,20 +733,22 @@ fn add_instance(
|
|||
let (x, y) = allocation.position();
|
||||
let Size { width, height } = allocation.size();
|
||||
let layer = allocation.layer();
|
||||
let atlas_size = allocation.atlas_size();
|
||||
|
||||
let instance = Instance {
|
||||
_position: position,
|
||||
_center: center,
|
||||
_size: size,
|
||||
_clip_bounds: clip_bounds,
|
||||
_border_radius: border_radius,
|
||||
_tile: tile,
|
||||
_rotation: rotation,
|
||||
_opacity: opacity,
|
||||
_position_in_atlas: [
|
||||
(x as f32 + 0.5) / atlas::SIZE as f32,
|
||||
(y as f32 + 0.5) / atlas::SIZE as f32,
|
||||
x as f32 / atlas_size as f32,
|
||||
y as f32 / atlas_size as f32,
|
||||
],
|
||||
_size_in_atlas: [
|
||||
(width as f32 - 1.0) / atlas::SIZE as f32,
|
||||
(height as f32 - 1.0) / atlas::SIZE as f32,
|
||||
width as f32 / atlas_size as f32,
|
||||
height as f32 / atlas_size as f32,
|
||||
],
|
||||
_layer: layer as u32,
|
||||
_snap: snap as u32,
|
||||
|
|
|
|||
|
|
@ -1,26 +1,35 @@
|
|||
use crate::core::Size;
|
||||
use crate::core::image;
|
||||
use crate::graphics;
|
||||
use crate::graphics::image::image_rs;
|
||||
use crate::image::atlas::{self, Atlas};
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
pub type Image = graphics::image::Buffer;
|
||||
|
||||
/// Entry in cache corresponding to an image handle
|
||||
#[derive(Debug)]
|
||||
pub enum Memory {
|
||||
/// Image data on host
|
||||
Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>),
|
||||
Host(Image),
|
||||
/// Storage entry
|
||||
Device(atlas::Entry),
|
||||
/// Image not found
|
||||
NotFound,
|
||||
/// Invalid image data
|
||||
Invalid,
|
||||
Device {
|
||||
entry: atlas::Entry,
|
||||
bind_group: Option<Arc<wgpu::BindGroup>>,
|
||||
allocation: Option<Weak<image::Memory>>,
|
||||
},
|
||||
Error(image::Error),
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
/// Width and height of image
|
||||
pub fn load(handle: &image::Handle) -> Self {
|
||||
match graphics::image::load(handle) {
|
||||
Ok(image) => Self::Host(image),
|
||||
Err(error) => Self::Error(error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dimensions(&self) -> Size<u32> {
|
||||
match self {
|
||||
Memory::Host(image) => {
|
||||
|
|
@ -28,14 +37,19 @@ impl Memory {
|
|||
|
||||
Size::new(width, height)
|
||||
}
|
||||
Memory::Device(entry) => entry.size(),
|
||||
Memory::NotFound => Size::new(1, 1),
|
||||
Memory::Invalid => Size::new(1, 1),
|
||||
Memory::Device { entry, .. } => entry.size(),
|
||||
Memory::Error(_) => Size::new(1, 1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn host(&self) -> Option<Image> {
|
||||
match self {
|
||||
Memory::Host(image) => Some(image.clone()),
|
||||
Memory::Device { .. } | Memory::Error(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Caches image raster data
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Cache {
|
||||
map: FxHashMap<image::Id, Memory>,
|
||||
|
|
@ -44,51 +58,28 @@ pub struct Cache {
|
|||
}
|
||||
|
||||
impl Cache {
|
||||
/// Load image
|
||||
pub fn load(&mut self, handle: &image::Handle) -> &mut Memory {
|
||||
if self.contains(handle) {
|
||||
return self.get(handle).unwrap();
|
||||
}
|
||||
pub fn get_mut(&mut self, handle: &image::Handle) -> Option<&mut Memory> {
|
||||
let _ = self.hits.insert(handle.id());
|
||||
|
||||
let memory = match graphics::image::load(handle) {
|
||||
Ok(image) => Memory::Host(image),
|
||||
Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound,
|
||||
Err(_) => Memory::Invalid,
|
||||
};
|
||||
self.map.get_mut(&handle.id())
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, handle: &image::Handle, memory: Memory) {
|
||||
let _ = self.map.insert(handle.id(), memory);
|
||||
let _ = self.hits.insert(handle.id());
|
||||
|
||||
self.should_trim = true;
|
||||
|
||||
self.insert(handle, memory);
|
||||
self.get(handle).unwrap()
|
||||
}
|
||||
|
||||
/// Load image and upload raster data
|
||||
pub fn upload(
|
||||
pub fn contains(&self, handle: &image::Handle) -> bool {
|
||||
self.map.contains_key(&handle.id())
|
||||
}
|
||||
|
||||
pub fn trim(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
handle: &image::Handle,
|
||||
atlas: &mut Atlas,
|
||||
) -> Option<&atlas::Entry> {
|
||||
let memory = self.load(handle);
|
||||
|
||||
if let Memory::Host(image) = memory {
|
||||
let (width, height) = image.dimensions();
|
||||
|
||||
let entry = atlas.upload(device, encoder, width, height, image)?;
|
||||
|
||||
*memory = Memory::Device(entry);
|
||||
}
|
||||
|
||||
if let Memory::Device(allocation) = memory {
|
||||
Some(allocation)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Trim cache misses from cache
|
||||
pub fn trim(&mut self, atlas: &mut Atlas) {
|
||||
on_drop: impl Fn(Arc<wgpu::BindGroup>),
|
||||
) {
|
||||
// Only trim if new entries have landed in the `Cache`
|
||||
if !self.should_trim {
|
||||
return;
|
||||
|
|
@ -96,11 +87,31 @@ impl Cache {
|
|||
|
||||
let hits = &self.hits;
|
||||
|
||||
self.map.retain(|k, memory| {
|
||||
let retain = hits.contains(k);
|
||||
self.map.retain(|id, memory| {
|
||||
// Retain active allocations
|
||||
if let Memory::Device { allocation, .. } = memory
|
||||
&& allocation
|
||||
.as_ref()
|
||||
.is_some_and(|allocation| allocation.strong_count() > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if !retain && let Memory::Device(entry) = memory {
|
||||
atlas.remove(entry);
|
||||
let retain = hits.contains(id);
|
||||
|
||||
if !retain {
|
||||
log::debug!("Dropping image allocation: {id:?}");
|
||||
|
||||
if let Memory::Device {
|
||||
entry, bind_group, ..
|
||||
} = memory
|
||||
{
|
||||
if let Some(bind_group) = bind_group.take() {
|
||||
on_drop(bind_group);
|
||||
} else {
|
||||
atlas.remove(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retain
|
||||
|
|
@ -109,18 +120,4 @@ impl Cache {
|
|||
self.hits.clear();
|
||||
self.should_trim = false;
|
||||
}
|
||||
|
||||
fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> {
|
||||
let _ = self.hits.insert(handle.id());
|
||||
|
||||
self.map.get_mut(&handle.id())
|
||||
}
|
||||
|
||||
fn insert(&mut self, handle: &image::Handle, memory: Memory) {
|
||||
let _ = self.map.insert(handle.id(), memory);
|
||||
}
|
||||
|
||||
fn contains(&self, handle: &image::Handle) -> bool {
|
||||
self.map.contains_key(&handle.id())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,17 +94,18 @@ impl Cache {
|
|||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
handle: &svg::Handle,
|
||||
color: Option<Color>,
|
||||
[width, height]: [f32; 2],
|
||||
size: Size,
|
||||
scale: f32,
|
||||
atlas: &mut Atlas,
|
||||
) -> Option<&atlas::Entry> {
|
||||
let id = handle.id();
|
||||
|
||||
let (width, height) = (
|
||||
(scale * width).ceil() as u32,
|
||||
(scale * height).ceil() as u32,
|
||||
(scale * size.width).ceil() as u32,
|
||||
(scale * size.height).ceil() as u32,
|
||||
);
|
||||
|
||||
let color = color.map(Color::into_rgba8);
|
||||
|
|
@ -167,14 +168,15 @@ impl Cache {
|
|||
});
|
||||
}
|
||||
|
||||
let allocation =
|
||||
atlas.upload(device, encoder, width, height, &rgba)?;
|
||||
let allocation = atlas
|
||||
.upload(device, encoder, belt, width, height, &rgba)?;
|
||||
|
||||
log::debug!("allocating {id} {width}x{height}");
|
||||
|
||||
let _ = self.svg_hits.insert(id);
|
||||
let _ = self.rasterized_hits.insert(key);
|
||||
let _ = self.rasterized.insert(key, allocation);
|
||||
self.should_trim = true;
|
||||
|
||||
self.rasterized.get(&key)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,11 +128,19 @@ impl Layer {
|
|||
|
||||
pub fn draw_image(&mut self, image: Image, transformation: Transformation) {
|
||||
match image {
|
||||
Image::Raster(image, bounds) => {
|
||||
self.draw_raster(image, bounds, transformation);
|
||||
Image::Raster {
|
||||
image,
|
||||
bounds,
|
||||
clip_bounds,
|
||||
} => {
|
||||
self.draw_raster(image, bounds, clip_bounds, transformation);
|
||||
}
|
||||
Image::Vector(svg, bounds) => {
|
||||
self.draw_svg(svg, bounds, transformation);
|
||||
Image::Vector {
|
||||
svg,
|
||||
bounds,
|
||||
clip_bounds,
|
||||
} => {
|
||||
self.draw_svg(svg, bounds, clip_bounds, transformation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -141,9 +149,18 @@ impl Layer {
|
|||
&mut self,
|
||||
image: core::Image,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let image = Image::Raster(image, bounds * transformation);
|
||||
let image = Image::Raster {
|
||||
image: core::Image {
|
||||
border_radius: image.border_radius
|
||||
* transformation.scale_factor(),
|
||||
..image
|
||||
},
|
||||
bounds: bounds * transformation,
|
||||
clip_bounds: clip_bounds * transformation,
|
||||
};
|
||||
|
||||
self.images.push(image);
|
||||
}
|
||||
|
|
@ -152,9 +169,14 @@ impl Layer {
|
|||
&mut self,
|
||||
svg: Svg,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let svg = Image::Vector(svg, bounds * transformation);
|
||||
let svg = Image::Vector {
|
||||
svg,
|
||||
bounds: bounds * transformation,
|
||||
clip_bounds: clip_bounds * transformation,
|
||||
};
|
||||
|
||||
self.images.push(svg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ use crate::core::renderer;
|
|||
use crate::core::{
|
||||
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
||||
};
|
||||
use crate::graphics::Viewport;
|
||||
use crate::graphics::text::{Editor, Paragraph};
|
||||
use crate::graphics::{Shell, Viewport};
|
||||
|
||||
/// A [`wgpu`] graphics renderer for [`iced`].
|
||||
///
|
||||
|
|
@ -117,9 +117,7 @@ impl Renderer {
|
|||
image: image::State::new(),
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
image_cache: std::cell::RefCell::new(
|
||||
engine.create_image_cache(&engine.device),
|
||||
),
|
||||
image_cache: std::cell::RefCell::new(engine.create_image_cache()),
|
||||
|
||||
// TODO: Resize belt smartly (?)
|
||||
// It would be great if the `StagingBelt` API exposed methods
|
||||
|
|
@ -313,8 +311,10 @@ impl Renderer {
|
|||
self.layers.merge();
|
||||
|
||||
for layer in self.layers.iter() {
|
||||
let clip_bounds = layer.bounds * scale_factor;
|
||||
|
||||
if physical_bounds
|
||||
.intersection(&(layer.bounds * scale_factor))
|
||||
.intersection(&clip_bounds)
|
||||
.and_then(Rectangle::snap)
|
||||
.is_none()
|
||||
{
|
||||
|
|
@ -460,8 +460,6 @@ impl Renderer {
|
|||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
let mut image_layer = 0;
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
let image_cache = self.image_cache.borrow();
|
||||
|
||||
let scale_factor = viewport.scale_factor();
|
||||
let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
|
||||
|
|
@ -632,7 +630,6 @@ impl Renderer {
|
|||
let render_span = debug::render(debug::Primitive::Image);
|
||||
self.image.render(
|
||||
&self.engine.image_pipeline,
|
||||
&image_cache,
|
||||
image_layer,
|
||||
scissor_rect,
|
||||
&mut render_pass,
|
||||
|
|
@ -701,6 +698,19 @@ 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(Result<core::image::Allocation, core::image::Error>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) {
|
||||
#[cfg(feature = "image")]
|
||||
self.image_cache
|
||||
.get_mut()
|
||||
.allocate_image(_handle, _callback);
|
||||
}
|
||||
}
|
||||
|
||||
impl core::text::Renderer for Renderer {
|
||||
|
|
@ -765,13 +775,29 @@ impl core::text::Renderer for Renderer {
|
|||
impl core::image::Renderer for Renderer {
|
||||
type Handle = core::image::Handle;
|
||||
|
||||
fn measure_image(&self, handle: &Self::Handle) -> core::Size<u32> {
|
||||
fn load_image(
|
||||
&self,
|
||||
handle: &Self::Handle,
|
||||
) -> Result<core::image::Allocation, core::image::Error> {
|
||||
self.image_cache.borrow_mut().load_image(
|
||||
&self.engine.device,
|
||||
&self.engine.queue,
|
||||
handle,
|
||||
)
|
||||
}
|
||||
|
||||
fn measure_image(&self, handle: &Self::Handle) -> Option<core::Size<u32>> {
|
||||
self.image_cache.borrow_mut().measure_image(handle)
|
||||
}
|
||||
|
||||
fn draw_image(&mut self, image: core::Image, bounds: Rectangle) {
|
||||
fn draw_image(
|
||||
&mut self,
|
||||
image: core::Image,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_raster(image, bounds, transformation);
|
||||
layer.draw_raster(image, bounds, clip_bounds, transformation);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -781,9 +807,14 @@ impl core::svg::Renderer for Renderer {
|
|||
self.image_cache.borrow_mut().measure_svg(handle)
|
||||
}
|
||||
|
||||
fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) {
|
||||
fn draw_svg(
|
||||
&mut self,
|
||||
svg: core::Svg,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_svg(svg, bounds, transformation);
|
||||
layer.draw_svg(svg, bounds, clip_bounds, transformation);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -910,6 +941,7 @@ impl renderer::Headless for Renderer {
|
|||
wgpu::TextureFormat::Rgba8Unorm
|
||||
},
|
||||
Some(graphics::Antialiasing::MSAAx4),
|
||||
Shell::headless(),
|
||||
);
|
||||
|
||||
Some(Self::new(engine, default_font, default_text_size))
|
||||
|
|
|
|||
|
|
@ -9,63 +9,117 @@ struct Globals {
|
|||
|
||||
struct VertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) pos: vec2<f32>,
|
||||
@location(1) center: vec2<f32>,
|
||||
@location(2) scale: vec2<f32>,
|
||||
@location(3) rotation: f32,
|
||||
@location(4) opacity: f32,
|
||||
@location(5) atlas_pos: vec2<f32>,
|
||||
@location(6) atlas_scale: vec2<f32>,
|
||||
@location(7) layer: i32,
|
||||
@location(8) snap: u32,
|
||||
@location(0) center: vec2<f32>,
|
||||
@location(1) clip_bounds: vec4<f32>,
|
||||
@location(2) border_radius: vec4<f32>,
|
||||
@location(3) tile: vec4<f32>,
|
||||
@location(4) rotation: f32,
|
||||
@location(5) opacity: f32,
|
||||
@location(6) atlas_pos: vec2<f32>,
|
||||
@location(7) atlas_scale: vec2<f32>,
|
||||
@location(8) layer: i32,
|
||||
@location(9) snap: u32,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
@location(1) layer: f32, // this should be an i32, but naga currently reads that as requiring interpolation.
|
||||
@location(2) opacity: f32,
|
||||
@location(0) @interpolate(flat) clip_bounds: vec4<f32>,
|
||||
@location(1) @interpolate(flat) border_radius: vec4<f32>,
|
||||
@location(2) @interpolate(flat) atlas: vec4<f32>,
|
||||
@location(3) @interpolate(flat) layer: i32,
|
||||
@location(4) @interpolate(flat) opacity: f32,
|
||||
@location(5) uv: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
// Generate a vertex position in the range [0, 1] from the vertex index.
|
||||
var v_pos = vertex_position(input.vertex_index);
|
||||
// Generate a vertex position in the range [0, 1] from the vertex index
|
||||
let corner = vertex_position(input.vertex_index);
|
||||
|
||||
// Map the vertex position to the atlas texture.
|
||||
out.uv = vec2<f32>(v_pos * input.atlas_scale + input.atlas_pos);
|
||||
out.layer = f32(input.layer);
|
||||
out.opacity = input.opacity;
|
||||
let tile = input.tile;
|
||||
let center = input.center;
|
||||
|
||||
// Calculate the vertex position and move the center to the origin
|
||||
v_pos = input.pos + v_pos * input.scale - input.center;
|
||||
|
||||
// Apply the rotation around the center of the image
|
||||
let cos_rot = cos(input.rotation);
|
||||
let sin_rot = sin(input.rotation);
|
||||
let rotate = mat4x4<f32>(
|
||||
vec4<f32>(cos_rot, sin_rot, 0.0, 0.0),
|
||||
vec4<f32>(-sin_rot, cos_rot, 0.0, 0.0),
|
||||
vec4<f32>(0.0, 0.0, 1.0, 0.0),
|
||||
vec4<f32>(0.0, 0.0, 0.0, 1.0)
|
||||
// List the unrotated tile corners
|
||||
let corners = array<vec2<f32>, 4>(
|
||||
tile.xy, // Top left
|
||||
tile.xy + vec2<f32>(tile.z, 0.0), // Top right
|
||||
tile.xy + vec2<f32>(0.0, tile.w), // Bottom left
|
||||
tile.xy + tile.zw // Bottom right
|
||||
);
|
||||
|
||||
// Calculate the final position of the vertex
|
||||
out.position = vec4(vec2(globals.scale_factor), 1.0, 1.0) * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0));
|
||||
// Rotate tile corners around center
|
||||
let cos_r = cos(-input.rotation); // Clockwise
|
||||
let sin_r = sin(-input.rotation);
|
||||
var rotated = array<vec2<f32>, 4>();
|
||||
|
||||
for (var i = 0u; i < 4u; i++) {
|
||||
let c = corners[i] - input.center;
|
||||
rotated[i] = vec2<f32>(c.x * cos_r - c.y * sin_r, c.x * sin_r + c.y * cos_r) + input.center;
|
||||
}
|
||||
|
||||
// Find bounding box of rotated tile
|
||||
var min_xy = rotated[0];
|
||||
var max_xy = rotated[0];
|
||||
for (var i = 1u; i < 4u; i++) {
|
||||
min_xy = min(min_xy, rotated[i]);
|
||||
max_xy = max(max_xy, rotated[i]);
|
||||
}
|
||||
let rotated_bounds = vec4<f32>(min_xy, max_xy - min_xy);
|
||||
|
||||
// Intersect with clip bounds
|
||||
let clip_min = max(rotated_bounds.xy, input.clip_bounds.xy);
|
||||
let clip_max = min(rotated_bounds.xy + rotated_bounds.zw, input.clip_bounds.xy + input.clip_bounds.zw);
|
||||
let clipped_tile = vec4<f32>(clip_min, max(vec2<f32>(0.0), clip_max - clip_min));
|
||||
|
||||
// Calculate the vertex position
|
||||
let v_pos = clipped_tile.xy + corner * clipped_tile.zw;
|
||||
out.position = vec4(vec2(globals.scale_factor), 1.0, 1.0) * vec4<f32>(v_pos, 0.0, 1.0);
|
||||
|
||||
// Calculate rotated UV
|
||||
let uv = input.atlas_pos + (v_pos - tile.xy) / tile.zw * input.atlas_scale;
|
||||
let uv_center = input.atlas_pos + input.atlas_scale / 2.0;
|
||||
|
||||
let d = uv - uv_center;
|
||||
out.uv = vec2<f32>(d.x * cos_r - d.y * sin_r, d.x * sin_r + d.y * cos_r) + uv_center;
|
||||
|
||||
// Snap position to the pixel grid
|
||||
if bool(input.snap) {
|
||||
out.position = round(out.position);
|
||||
}
|
||||
|
||||
out.position = globals.transform * out.position;
|
||||
out.clip_bounds = globals.scale_factor * input.clip_bounds;
|
||||
out.border_radius = globals.scale_factor * input.border_radius;
|
||||
out.atlas = vec4(input.atlas_pos, input.atlas_pos + input.atlas_scale);
|
||||
out.layer = input.layer;
|
||||
out.opacity = input.opacity;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Sample the texture at the given UV coordinate and layer.
|
||||
return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)) * vec4<f32>(1.0, 1.0, 1.0, input.opacity);
|
||||
let fragment = input.position.xy;
|
||||
let position = input.clip_bounds.xy;
|
||||
let scale = input.clip_bounds.zw;
|
||||
|
||||
let d = rounded_box_sdf(
|
||||
2.0 * (fragment - position - scale / 2.0),
|
||||
scale,
|
||||
input.border_radius * 2.0,
|
||||
) / 2.0;
|
||||
|
||||
let antialias: f32 = clamp(1.0 - d, 0.0, 1.0);
|
||||
let inside = all(input.uv >= input.atlas.xy) && all(input.uv <= input.atlas.zw);
|
||||
|
||||
return textureSample(u_texture, u_sampler, input.uv, input.layer) * vec4<f32>(1.0, 1.0, 1.0, antialias * input.opacity * f32(inside));
|
||||
}
|
||||
|
||||
fn rounded_box_sdf(p: vec2<f32>, size: vec2<f32>, corners: vec4<f32>) -> f32 {
|
||||
var box_half = select(corners.yz, corners.xw, p.x > 0.0);
|
||||
var corner = select(box_half.y, box_half.x, p.y > 0.0);
|
||||
var q = abs(p) - size + corner;
|
||||
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - corner;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::core::Color;
|
|||
use crate::graphics::color;
|
||||
use crate::graphics::compositor;
|
||||
use crate::graphics::error;
|
||||
use crate::graphics::{self, Viewport};
|
||||
use crate::graphics::{self, Shell, Viewport};
|
||||
use crate::settings::{self, Settings};
|
||||
use crate::{Engine, Renderer};
|
||||
|
||||
|
|
@ -50,6 +50,7 @@ impl Compositor {
|
|||
pub async fn request<W: compositor::Window>(
|
||||
settings: Settings,
|
||||
compatible_window: Option<W>,
|
||||
shell: Shell,
|
||||
) -> Result<Self, Error> {
|
||||
let instance = wgpu::util::new_instance_with_webgpu_detection(
|
||||
&wgpu::InstanceDescriptor {
|
||||
|
|
@ -181,6 +182,7 @@ impl Compositor {
|
|||
queue,
|
||||
format,
|
||||
settings.antialiasing,
|
||||
shell,
|
||||
);
|
||||
|
||||
return Ok(Compositor {
|
||||
|
|
@ -206,8 +208,9 @@ impl Compositor {
|
|||
pub async fn new<W: compositor::Window>(
|
||||
settings: Settings,
|
||||
compatible_window: W,
|
||||
shell: Shell,
|
||||
) -> Result<Compositor, Error> {
|
||||
Compositor::request(settings, Some(compatible_window)).await
|
||||
Compositor::request(settings, Some(compatible_window), shell).await
|
||||
}
|
||||
|
||||
/// Presents the given primitives with the given [`Compositor`].
|
||||
|
|
@ -260,6 +263,7 @@ impl graphics::Compositor for Compositor {
|
|||
async fn with_backend<W: compositor::Window>(
|
||||
settings: graphics::Settings,
|
||||
compatible_window: W,
|
||||
shell: Shell,
|
||||
backend: Option<&str>,
|
||||
) -> Result<Self, graphics::Error> {
|
||||
match backend {
|
||||
|
|
@ -274,7 +278,7 @@ impl graphics::Compositor for Compositor {
|
|||
settings.present_mode = present_mode;
|
||||
}
|
||||
|
||||
Ok(new(settings, compatible_window).await?)
|
||||
Ok(new(settings, compatible_window, shell).await?)
|
||||
}
|
||||
Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
|
||||
backend: "wgpu",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
pub mod viewer;
|
||||
pub use viewer::Viewer;
|
||||
|
||||
use crate::core::border;
|
||||
use crate::core::image;
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
|
|
@ -59,6 +60,7 @@ pub struct Image<Handle = image::Handle> {
|
|||
width: Length,
|
||||
height: Length,
|
||||
crop: Option<Rectangle<u32>>,
|
||||
border_radius: border::Radius,
|
||||
content_fit: ContentFit,
|
||||
filter_method: FilterMethod,
|
||||
rotation: Rotation,
|
||||
|
|
@ -75,6 +77,7 @@ impl<Handle> Image<Handle> {
|
|||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
crop: None,
|
||||
border_radius: border::Radius::default(),
|
||||
content_fit: ContentFit::default(),
|
||||
filter_method: FilterMethod::default(),
|
||||
rotation: Rotation::default(),
|
||||
|
|
@ -164,6 +167,18 @@ impl<Handle> Image<Handle> {
|
|||
self.crop = Some(region);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`border::Radius`] of the [`Image`].
|
||||
///
|
||||
/// Currently, it will only be applied around the rectangular bounding box
|
||||
/// of the [`Image`].
|
||||
pub fn border_radius(
|
||||
mut self,
|
||||
border_radius: impl Into<border::Radius>,
|
||||
) -> Self {
|
||||
self.border_radius = border_radius.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the layout of an [`Image`].
|
||||
|
|
@ -182,7 +197,8 @@ where
|
|||
Renderer: image::Renderer<Handle = Handle>,
|
||||
{
|
||||
// The raw w/h of the underlying image
|
||||
let image_size = crop(renderer.measure_image(handle), region);
|
||||
let image_size =
|
||||
crop(renderer.measure_image(handle).unwrap_or_default(), region);
|
||||
|
||||
// The rotated size of the image
|
||||
let rotated_size = rotation.apply(image_size);
|
||||
|
|
@ -224,7 +240,7 @@ fn drawing_bounds<Renderer, Handle>(
|
|||
where
|
||||
Renderer: image::Renderer<Handle = Handle>,
|
||||
{
|
||||
let original_size = renderer.measure_image(handle);
|
||||
let original_size = renderer.measure_image(handle).unwrap_or_default();
|
||||
let image_size = crop(original_size, region);
|
||||
let rotated_size = rotation.apply(image_size);
|
||||
let adjusted_fit = content_fit.fit(rotated_size, bounds.size());
|
||||
|
|
@ -281,10 +297,6 @@ where
|
|||
Rectangle::new(position + crop_offset, final_size)
|
||||
}
|
||||
|
||||
fn must_clip(bounds: Rectangle, drawing_bounds: Rectangle) -> bool {
|
||||
drawing_bounds.width > bounds.width || drawing_bounds.height > bounds.height
|
||||
}
|
||||
|
||||
fn crop(size: Size<u32>, region: Option<Rectangle<u32>>) -> Size<f32> {
|
||||
if let Some(region) = region {
|
||||
Size::new(
|
||||
|
|
@ -300,9 +312,9 @@ fn crop(size: Size<u32>, region: Option<Rectangle<u32>>) -> Size<f32> {
|
|||
pub fn draw<Renderer, Handle>(
|
||||
renderer: &mut Renderer,
|
||||
layout: Layout<'_>,
|
||||
viewport: &Rectangle,
|
||||
handle: &Handle,
|
||||
crop: Option<Rectangle<u32>>,
|
||||
border_radius: border::Radius,
|
||||
content_fit: ContentFit,
|
||||
filter_method: FilterMethod,
|
||||
rotation: Rotation,
|
||||
|
|
@ -323,51 +335,17 @@ pub fn draw<Renderer, Handle>(
|
|||
scale,
|
||||
);
|
||||
|
||||
if must_clip(bounds, drawing_bounds) {
|
||||
if let Some(bounds) = bounds.intersection(viewport) {
|
||||
renderer.with_layer(bounds, |renderer| {
|
||||
render(
|
||||
renderer,
|
||||
handle,
|
||||
filter_method,
|
||||
rotation,
|
||||
opacity,
|
||||
drawing_bounds,
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
render(
|
||||
renderer,
|
||||
handle,
|
||||
filter_method,
|
||||
rotation,
|
||||
opacity,
|
||||
drawing_bounds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn render<Renderer, Handle>(
|
||||
renderer: &mut Renderer,
|
||||
handle: &Handle,
|
||||
filter_method: FilterMethod,
|
||||
rotation: Rotation,
|
||||
opacity: f32,
|
||||
drawing_bounds: Rectangle,
|
||||
) where
|
||||
Renderer: image::Renderer<Handle = Handle>,
|
||||
Handle: Clone,
|
||||
{
|
||||
renderer.draw_image(
|
||||
image::Image {
|
||||
handle: handle.clone(),
|
||||
border_radius,
|
||||
filter_method,
|
||||
rotation: rotation.radians(),
|
||||
opacity,
|
||||
snap: true,
|
||||
},
|
||||
drawing_bounds,
|
||||
bounds,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -411,14 +389,14 @@ where
|
|||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
draw(
|
||||
renderer,
|
||||
layout,
|
||||
viewport,
|
||||
&self.handle,
|
||||
self.crop,
|
||||
self.border_radius,
|
||||
self.content_fit,
|
||||
self.filter_method,
|
||||
self.rotation,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//! Zoom and pan on an image.
|
||||
use crate::core::border;
|
||||
use crate::core::image::{self, FilterMethod};
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
|
|
@ -122,7 +123,9 @@ where
|
|||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
// The raw w/h of the underlying image
|
||||
let image_size = renderer.measure_image(&self.handle);
|
||||
let image_size =
|
||||
renderer.measure_image(&self.handle).unwrap_or_default();
|
||||
|
||||
let image_size =
|
||||
Size::new(image_size.width as f32, image_size.height as f32);
|
||||
|
||||
|
|
@ -313,7 +316,7 @@ where
|
|||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
let bounds = layout.bounds();
|
||||
|
|
@ -347,12 +350,14 @@ where
|
|||
renderer.draw_image(
|
||||
Image {
|
||||
handle: self.handle.clone(),
|
||||
border_radius: border::Radius::default(),
|
||||
filter_method: self.filter_method,
|
||||
rotation: Radians(0.0),
|
||||
opacity: 1.0,
|
||||
snap: true,
|
||||
},
|
||||
drawing_bounds,
|
||||
*viewport,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
@ -433,7 +438,9 @@ pub fn scaled_image_size<Renderer>(
|
|||
where
|
||||
Renderer: image::Renderer,
|
||||
{
|
||||
let Size { width, height } = renderer.measure_image(handle);
|
||||
let Size { width, height } =
|
||||
renderer.measure_image(handle).unwrap_or_default();
|
||||
|
||||
let image_size = Size::new(width as f32, height as f32);
|
||||
|
||||
let adjusted_fit = content_fit.fit(image_size, bounds);
|
||||
|
|
|
|||
|
|
@ -240,25 +240,16 @@ where
|
|||
|
||||
let style = theme.style(&self.class, status);
|
||||
|
||||
let render = |renderer: &mut Renderer| {
|
||||
renderer.draw_svg(
|
||||
svg::Svg {
|
||||
handle: self.handle.clone(),
|
||||
color: style.color,
|
||||
rotation: self.rotation.radians(),
|
||||
opacity: self.opacity,
|
||||
},
|
||||
drawing_bounds,
|
||||
);
|
||||
};
|
||||
|
||||
if adjusted_fit.width > bounds.width
|
||||
|| adjusted_fit.height > bounds.height
|
||||
{
|
||||
renderer.with_layer(bounds, render);
|
||||
} else {
|
||||
render(renderer);
|
||||
}
|
||||
renderer.draw_svg(
|
||||
svg::Svg {
|
||||
handle: self.handle.clone(),
|
||||
color: style.color,
|
||||
rotation: self.rotation.radians(),
|
||||
opacity: self.opacity,
|
||||
},
|
||||
drawing_bounds,
|
||||
bounds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,8 @@ use crate::futures::futures::task;
|
|||
use crate::futures::futures::{Future, StreamExt};
|
||||
use crate::futures::subscription;
|
||||
use crate::futures::{Executor, Runtime};
|
||||
use crate::graphics::{Compositor, compositor};
|
||||
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};
|
||||
|
|
@ -587,8 +588,10 @@ async fn run_instance<P>(
|
|||
let default_fonts = default_fonts.clone();
|
||||
|
||||
async move {
|
||||
let shell = Shell::new(proxy.clone());
|
||||
|
||||
let mut compositor =
|
||||
<P::Renderer as compositor::Default>::Compositor::new(graphics_settings, window).await;
|
||||
<P::Renderer as compositor::Default>::Compositor::new(graphics_settings, window, shell).await;
|
||||
|
||||
if let Ok(compositor) = &mut compositor {
|
||||
for font in default_fonts {
|
||||
|
|
@ -824,7 +827,7 @@ async fn run_instance<P>(
|
|||
.get_mut(&id)
|
||||
.expect("Get user interface");
|
||||
|
||||
let draw_span = debug::draw(id);
|
||||
let interact_span = debug::interact(id);
|
||||
let mut change_count = 0;
|
||||
|
||||
let state = loop {
|
||||
|
|
@ -947,7 +950,9 @@ async fn run_instance<P>(
|
|||
user_interfaces.get_mut(&id).unwrap();
|
||||
}
|
||||
};
|
||||
interact_span.finish();
|
||||
|
||||
let draw_span = debug::draw(id);
|
||||
interface.draw(
|
||||
&mut window.renderer,
|
||||
window.state.theme(),
|
||||
|
|
@ -1646,6 +1651,26 @@ fn run_action<'a, P, C>(
|
|||
let _ = window.raw.set_cursor_hittest(true);
|
||||
}
|
||||
}
|
||||
window::Action::RedrawAll => {
|
||||
for (_id, window) in window_manager.iter_mut() {
|
||||
window.raw.request_redraw();
|
||||
}
|
||||
}
|
||||
window::Action::RelayoutAll => {
|
||||
for (id, window) in window_manager.iter_mut() {
|
||||
if let Some(ui) = interfaces.remove(&id) {
|
||||
let _ = interfaces.insert(
|
||||
id,
|
||||
ui.relayout(
|
||||
window.state.logical_size(),
|
||||
&mut window.renderer,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
window.raw.request_redraw();
|
||||
}
|
||||
}
|
||||
},
|
||||
Action::System(action) => match action {
|
||||
system::Action::GetInformation(_channel) => {
|
||||
|
|
@ -1706,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 (?)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ use crate::futures::futures::{
|
|||
select,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use crate::graphics::shell;
|
||||
use crate::runtime::Action;
|
||||
use crate::runtime::window;
|
||||
use std::pin::Pin;
|
||||
|
||||
/// An event loop proxy with backpressure that implements `Sink`.
|
||||
|
|
@ -134,3 +136,16 @@ impl<T: 'static> Sink<Action<T>> for Proxy<T> {
|
|||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> shell::Notifier for Proxy<T>
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
fn request_redraw(&self) {
|
||||
self.send_action(Action::Window(window::Action::RedrawAll));
|
||||
}
|
||||
|
||||
fn invalidate_layout(&self) {
|
||||
self.send_action(Action::Window(window::Action::RelayoutAll));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue