Draft multi-threaded image rendering in iced_wgpu
This commit is contained in:
parent
92888a3639
commit
cb8d2710da
22 changed files with 886 additions and 305 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2620,6 +2620,7 @@ version = "0.14.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"bytes",
|
||||||
"cryoglyph",
|
"cryoglyph",
|
||||||
"futures",
|
"futures",
|
||||||
"glam",
|
"glam",
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ fn benchmark<'a>(
|
||||||
view: impl Fn(usize) -> Element<'a, (), Theme, Renderer>,
|
view: impl Fn(usize) -> Element<'a, (), Theme, Renderer>,
|
||||||
) {
|
) {
|
||||||
use iced_wgpu::graphics;
|
use iced_wgpu::graphics;
|
||||||
use iced_wgpu::graphics::Antialiasing;
|
use iced_wgpu::graphics::{Antialiasing, Shell};
|
||||||
use iced_wgpu::wgpu;
|
use iced_wgpu::wgpu;
|
||||||
use iced_winit::core;
|
use iced_winit::core;
|
||||||
use iced_winit::runtime;
|
use iced_winit::runtime;
|
||||||
|
|
@ -85,6 +85,7 @@ fn benchmark<'a>(
|
||||||
queue.clone(),
|
queue.clone(),
|
||||||
format,
|
format,
|
||||||
Some(Antialiasing::MSAAx4),
|
Some(Antialiasing::MSAAx4),
|
||||||
|
Shell::headless(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut renderer = Renderer::new(engine, Font::DEFAULT, Pixels::from(16));
|
let mut renderer = Renderer::new(engine, Font::DEFAULT, Pixels::from(16));
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ use iced::{
|
||||||
Subscription, Task, Theme, color,
|
Subscription, Task, Theme, color,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
fn main() -> iced::Result {
|
fn main() -> iced::Result {
|
||||||
iced::application::timed(
|
iced::application::timed(
|
||||||
|
|
@ -35,6 +35,8 @@ fn main() -> iced::Result {
|
||||||
struct Gallery {
|
struct Gallery {
|
||||||
images: Vec<Image>,
|
images: Vec<Image>,
|
||||||
previews: HashMap<Id, Preview>,
|
previews: HashMap<Id, Preview>,
|
||||||
|
visible: HashSet<Id>,
|
||||||
|
downloaded: HashSet<Id>,
|
||||||
viewer: Viewer,
|
viewer: Viewer,
|
||||||
now: Instant,
|
now: Instant,
|
||||||
}
|
}
|
||||||
|
|
@ -43,6 +45,7 @@ struct Gallery {
|
||||||
enum Message {
|
enum Message {
|
||||||
ImagesListed(Result<Vec<Image>, Error>),
|
ImagesListed(Result<Vec<Image>, Error>),
|
||||||
ImagePoppedIn(Id),
|
ImagePoppedIn(Id),
|
||||||
|
ImagePoppedOut(Id),
|
||||||
ImageDownloaded(Result<Rgba, Error>),
|
ImageDownloaded(Result<Rgba, Error>),
|
||||||
ThumbnailDownloaded(Id, Result<Rgba, Error>),
|
ThumbnailDownloaded(Id, Result<Rgba, Error>),
|
||||||
ThumbnailHovered(Id, bool),
|
ThumbnailHovered(Id, bool),
|
||||||
|
|
@ -58,6 +61,8 @@ impl Gallery {
|
||||||
Self {
|
Self {
|
||||||
images: Vec::new(),
|
images: Vec::new(),
|
||||||
previews: HashMap::new(),
|
previews: HashMap::new(),
|
||||||
|
visible: HashSet::new(),
|
||||||
|
downloaded: HashSet::new(),
|
||||||
viewer: Viewer::new(),
|
viewer: Viewer::new(),
|
||||||
now: Instant::now(),
|
now: Instant::now(),
|
||||||
},
|
},
|
||||||
|
|
@ -102,6 +107,14 @@ impl Gallery {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let _ = self.visible.insert(id);
|
||||||
|
|
||||||
|
if self.downloaded.contains(&id) {
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = self.downloaded.insert(id);
|
||||||
|
|
||||||
Task::sip(
|
Task::sip(
|
||||||
image.download(Size::Thumbnail {
|
image.download(Size::Thumbnail {
|
||||||
width: Preview::WIDTH,
|
width: Preview::WIDTH,
|
||||||
|
|
@ -111,6 +124,11 @@ impl Gallery {
|
||||||
Message::ThumbnailDownloaded.with(id),
|
Message::ThumbnailDownloaded.with(id),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Message::ImagePoppedOut(id) => {
|
||||||
|
let _ = self.visible.remove(&id);
|
||||||
|
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
Message::ImageDownloaded(Ok(rgba)) => {
|
Message::ImageDownloaded(Ok(rgba)) => {
|
||||||
self.viewer.show(rgba, self.now);
|
self.viewer.show(rgba, self.now);
|
||||||
|
|
||||||
|
|
@ -181,7 +199,17 @@ impl Gallery {
|
||||||
let images = self
|
let images = self
|
||||||
.images
|
.images
|
||||||
.iter()
|
.iter()
|
||||||
.map(|image| card(image, self.previews.get(&image.id), self.now))
|
.map(|image| {
|
||||||
|
card(
|
||||||
|
image,
|
||||||
|
if self.visible.contains(&image.id) {
|
||||||
|
self.previews.get(&image.id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
self.now,
|
||||||
|
)
|
||||||
|
})
|
||||||
.chain((self.images.len()..=Image::LIMIT).map(|_| placeholder()));
|
.chain((self.images.len()..=Image::LIMIT).map(|_| placeholder()));
|
||||||
|
|
||||||
let gallery = grid(images)
|
let gallery = grid(images)
|
||||||
|
|
@ -248,7 +276,7 @@ fn card<'a>(
|
||||||
.on_enter(Message::ThumbnailHovered(metadata.id, true))
|
.on_enter(Message::ThumbnailHovered(metadata.id, true))
|
||||||
.on_exit(Message::ThumbnailHovered(metadata.id, false));
|
.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 { .. });
|
let is_thumbnail = matches!(preview, Preview::Ready { .. });
|
||||||
|
|
||||||
button(card)
|
button(card)
|
||||||
|
|
@ -257,10 +285,13 @@ fn card<'a>(
|
||||||
.style(button::text)
|
.style(button::text)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
sensor(card)
|
card.into()
|
||||||
.on_show(|_| Message::ImagePoppedIn(metadata.id))
|
};
|
||||||
.into()
|
|
||||||
}
|
sensor(card)
|
||||||
|
.on_show(|_| Message::ImagePoppedIn(metadata.id))
|
||||||
|
.on_hide(Message::ImagePoppedOut(metadata.id))
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn placeholder<'a>() -> Element<'a, Message> {
|
fn placeholder<'a>() -> Element<'a, Message> {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ mod scene;
|
||||||
use controls::Controls;
|
use controls::Controls;
|
||||||
use scene::Scene;
|
use scene::Scene;
|
||||||
|
|
||||||
use iced_wgpu::graphics::Viewport;
|
use iced_wgpu::graphics::{Shell, Viewport};
|
||||||
use iced_wgpu::{Engine, Renderer, wgpu};
|
use iced_wgpu::{Engine, Renderer, wgpu};
|
||||||
use iced_winit::Clipboard;
|
use iced_winit::Clipboard;
|
||||||
use iced_winit::conversion;
|
use iced_winit::conversion;
|
||||||
|
|
@ -150,6 +150,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||||
queue.clone(),
|
queue.clone(),
|
||||||
format,
|
format,
|
||||||
None,
|
None,
|
||||||
|
Shell::headless(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Renderer::new(engine, Font::default(), Pixels::from(16))
|
Renderer::new(engine, Font::default(), Pixels::from(16))
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
//! surfaces.
|
//! surfaces.
|
||||||
use crate::core::Color;
|
use crate::core::Color;
|
||||||
use crate::futures::{MaybeSend, MaybeSync};
|
use crate::futures::{MaybeSend, MaybeSync};
|
||||||
use crate::{Error, Settings, Viewport};
|
use crate::{Error, Settings, Shell, Viewport};
|
||||||
|
|
||||||
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -21,8 +21,9 @@ pub trait Compositor: Sized {
|
||||||
fn new<W: Window + Clone>(
|
fn new<W: Window + Clone>(
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
compatible_window: W,
|
compatible_window: W,
|
||||||
|
shell: Shell,
|
||||||
) -> impl Future<Output = Result<Self, Error>> {
|
) -> 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.
|
/// Creates a new [`Compositor`] with a backend preference.
|
||||||
|
|
@ -32,6 +33,7 @@ pub trait Compositor: Sized {
|
||||||
fn with_backend<W: Window + Clone>(
|
fn with_backend<W: Window + Clone>(
|
||||||
_settings: Settings,
|
_settings: Settings,
|
||||||
_compatible_window: W,
|
_compatible_window: W,
|
||||||
|
_shell: Shell,
|
||||||
_backend: Option<&str>,
|
_backend: Option<&str>,
|
||||||
) -> impl Future<Output = Result<Self, Error>>;
|
) -> impl Future<Output = Result<Self, Error>>;
|
||||||
|
|
||||||
|
|
@ -153,6 +155,7 @@ impl Compositor for () {
|
||||||
async fn with_backend<W: Window + Clone>(
|
async fn with_backend<W: Window + Clone>(
|
||||||
_settings: Settings,
|
_settings: Settings,
|
||||||
_compatible_window: W,
|
_compatible_window: W,
|
||||||
|
_shell: Shell,
|
||||||
_preferred_backend: Option<&str>,
|
_preferred_backend: Option<&str>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ pub mod gradient;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod layer;
|
pub mod layer;
|
||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
|
pub mod shell;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
#[cfg(feature = "geometry")]
|
#[cfg(feature = "geometry")]
|
||||||
|
|
@ -35,6 +36,7 @@ pub use image::Image;
|
||||||
pub use layer::Layer;
|
pub use layer::Layer;
|
||||||
pub use mesh::Mesh;
|
pub use mesh::Mesh;
|
||||||
pub use settings::Settings;
|
pub use settings::Settings;
|
||||||
|
pub use shell::Shell;
|
||||||
pub use text::Text;
|
pub use text::Text;
|
||||||
pub use viewport::Viewport;
|
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,
|
self, Background, Color, Font, Image, Pixels, Point, Rectangle, Size, Svg,
|
||||||
Transformation,
|
Transformation,
|
||||||
};
|
};
|
||||||
use crate::graphics;
|
|
||||||
use crate::graphics::compositor;
|
use crate::graphics::compositor;
|
||||||
use crate::graphics::mesh;
|
use crate::graphics::mesh;
|
||||||
|
use crate::graphics::{self, Shell};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
@ -216,6 +216,7 @@ where
|
||||||
async fn with_backend<W: compositor::Window + Clone>(
|
async fn with_backend<W: compositor::Window + Clone>(
|
||||||
settings: graphics::Settings,
|
settings: graphics::Settings,
|
||||||
compatible_window: W,
|
compatible_window: W,
|
||||||
|
shell: Shell,
|
||||||
backend: Option<&str>,
|
backend: Option<&str>,
|
||||||
) -> Result<Self, graphics::Error> {
|
) -> Result<Self, graphics::Error> {
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
@ -242,8 +243,13 @@ where
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
|
|
||||||
for backend in candidates.iter().map(Option::as_deref) {
|
for backend in candidates.iter().map(Option::as_deref) {
|
||||||
match A::with_backend(settings, compatible_window.clone(), backend)
|
match A::with_backend(
|
||||||
.await
|
settings,
|
||||||
|
compatible_window.clone(),
|
||||||
|
shell.clone(),
|
||||||
|
backend,
|
||||||
|
)
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
Ok(compositor) => return Ok(Self::Primary(compositor)),
|
Ok(compositor) => return Ok(Self::Primary(compositor)),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
|
@ -251,8 +257,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match B::with_backend(settings, compatible_window.clone(), backend)
|
match B::with_backend(
|
||||||
.await
|
settings,
|
||||||
|
compatible_window.clone(),
|
||||||
|
shell.clone(),
|
||||||
|
backend,
|
||||||
|
)
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
Ok(compositor) => return Ok(Self::Secondary(compositor)),
|
Ok(compositor) => return Ok(Self::Secondary(compositor)),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,12 @@ pub enum Action {
|
||||||
|
|
||||||
/// Set the window size increment.
|
/// Set the window size increment.
|
||||||
SetResizeIncrements(Id, Option<Size>),
|
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.
|
/// Subscribes to the frames of the window of the running application.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use crate::core::{Color, Rectangle, Size};
|
||||||
use crate::graphics::compositor::{self, Information};
|
use crate::graphics::compositor::{self, Information};
|
||||||
use crate::graphics::damage;
|
use crate::graphics::damage;
|
||||||
use crate::graphics::error::{self, Error};
|
use crate::graphics::error::{self, Error};
|
||||||
use crate::graphics::{self, Viewport};
|
use crate::graphics::{self, Shell, Viewport};
|
||||||
use crate::{Layer, Renderer, Settings};
|
use crate::{Layer, Renderer, Settings};
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
@ -31,6 +31,7 @@ impl crate::graphics::Compositor for Compositor {
|
||||||
async fn with_backend<W: compositor::Window>(
|
async fn with_backend<W: compositor::Window>(
|
||||||
settings: graphics::Settings,
|
settings: graphics::Settings,
|
||||||
compatible_window: W,
|
compatible_window: W,
|
||||||
|
_shell: Shell,
|
||||||
backend: Option<&str>,
|
backend: Option<&str>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
match backend {
|
match backend {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ iced_graphics.workspace = true
|
||||||
|
|
||||||
bitflags.workspace = true
|
bitflags.workspace = true
|
||||||
bytemuck.workspace = true
|
bytemuck.workspace = true
|
||||||
|
bytes.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
glam.workspace = true
|
glam.workspace = true
|
||||||
cryoglyph.workspace = true
|
cryoglyph.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::graphics::Antialiasing;
|
use crate::graphics::{Antialiasing, Shell};
|
||||||
use crate::primitive;
|
use crate::primitive;
|
||||||
use crate::quad;
|
use crate::quad;
|
||||||
use crate::text;
|
use crate::text;
|
||||||
|
|
@ -18,6 +18,7 @@ pub struct Engine {
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
pub(crate) image_pipeline: crate::image::Pipeline,
|
pub(crate) image_pipeline: crate::image::Pipeline,
|
||||||
pub(crate) primitive_storage: Arc<RwLock<primitive::Storage>>,
|
pub(crate) primitive_storage: Arc<RwLock<primitive::Storage>>,
|
||||||
|
_shell: Shell,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
|
|
@ -27,6 +28,7 @@ impl Engine {
|
||||||
queue: wgpu::Queue,
|
queue: wgpu::Queue,
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
antialiasing: Option<Antialiasing>, // TODO: Initialize AA pipelines lazily
|
antialiasing: Option<Antialiasing>, // TODO: Initialize AA pipelines lazily
|
||||||
|
shell: Shell,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
format,
|
format,
|
||||||
|
|
@ -52,14 +54,16 @@ impl Engine {
|
||||||
|
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
|
_shell: shell,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
pub fn create_image_cache(
|
pub fn create_image_cache(&self) -> crate::image::Cache {
|
||||||
&self,
|
self.image_pipeline.create_cache(
|
||||||
device: &wgpu::Device,
|
&self.device,
|
||||||
) -> crate::image::Cache {
|
&self.queue,
|
||||||
self.image_pipeline.create_cache(device)
|
&self._shell,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,20 +10,20 @@ pub use layer::Layer;
|
||||||
|
|
||||||
use allocator::Allocator;
|
use allocator::Allocator;
|
||||||
|
|
||||||
pub const SIZE: u32 = 2048;
|
pub const DEFAULT_SIZE: u32 = 512;
|
||||||
|
pub const MAX_SIZE: u32 = 2048;
|
||||||
|
|
||||||
use crate::core::Size;
|
use crate::core::Size;
|
||||||
use crate::graphics::color;
|
use crate::graphics::color;
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Atlas {
|
pub struct Atlas {
|
||||||
|
size: u32,
|
||||||
backend: wgpu::Backend,
|
backend: wgpu::Backend,
|
||||||
texture: wgpu::Texture,
|
texture: wgpu::Texture,
|
||||||
texture_view: wgpu::TextureView,
|
texture_view: wgpu::TextureView,
|
||||||
texture_bind_group: wgpu::BindGroup,
|
texture_bind_group: wgpu::BindGroup,
|
||||||
texture_layout: Arc<wgpu::BindGroupLayout>,
|
texture_layout: wgpu::BindGroupLayout,
|
||||||
layers: Vec<Layer>,
|
layers: Vec<Layer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,8 +31,19 @@ impl Atlas {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
backend: wgpu::Backend,
|
backend: wgpu::Backend,
|
||||||
texture_layout: Arc<wgpu::BindGroupLayout>,
|
texture_layout: wgpu::BindGroupLayout,
|
||||||
) -> Self {
|
) -> 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 {
|
let layers = match backend {
|
||||||
// On the GL backend we start with 2 layers, to help wgpu figure
|
// 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`
|
// out that this texture is `GL_TEXTURE_2D_ARRAY` rather than `GL_TEXTURE_2D`
|
||||||
|
|
@ -42,8 +53,8 @@ impl Atlas {
|
||||||
};
|
};
|
||||||
|
|
||||||
let extent = wgpu::Extent3d {
|
let extent = wgpu::Extent3d {
|
||||||
width: SIZE,
|
width: size,
|
||||||
height: SIZE,
|
height: size,
|
||||||
depth_or_array_layers: layers.len() as u32,
|
depth_or_array_layers: layers.len() as u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -80,6 +91,7 @@ impl Atlas {
|
||||||
});
|
});
|
||||||
|
|
||||||
Atlas {
|
Atlas {
|
||||||
|
size,
|
||||||
backend,
|
backend,
|
||||||
texture,
|
texture,
|
||||||
texture_view,
|
texture_view,
|
||||||
|
|
@ -93,14 +105,11 @@ impl Atlas {
|
||||||
&self.texture_bind_group
|
&self.texture_bind_group
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layer_count(&self) -> usize {
|
|
||||||
self.layers.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn upload(
|
pub fn upload(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
|
|
@ -111,7 +120,7 @@ impl Atlas {
|
||||||
|
|
||||||
// We grow the internal texture after allocating if necessary
|
// We grow the internal texture after allocating if necessary
|
||||||
let new_layers = self.layers.len() - current_size;
|
let new_layers = self.layers.len() - current_size;
|
||||||
self.grow(new_layers, device, encoder);
|
self.grow(new_layers, device, encoder, self.backend);
|
||||||
|
|
||||||
entry
|
entry
|
||||||
};
|
};
|
||||||
|
|
@ -127,7 +136,13 @@ impl Atlas {
|
||||||
let padded_width = (4 * width + padding) as usize;
|
let padded_width = (4 * width + padding) as usize;
|
||||||
let padded_data_size = padded_width * height as usize;
|
let padded_data_size = padded_width * height as usize;
|
||||||
|
|
||||||
let mut padded_data = vec![0; padded_data_size];
|
let buffer_slice = belt.allocate(
|
||||||
|
wgpu::BufferSize::new(padded_data_size as u64).unwrap(),
|
||||||
|
wgpu::BufferSize::new(8 * 4).unwrap(),
|
||||||
|
device,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut padded_data = buffer_slice.get_mapped_range_mut();
|
||||||
|
|
||||||
for row in 0..height as usize {
|
for row in 0..height as usize {
|
||||||
let offset = row * padded_width;
|
let offset = row * padded_width;
|
||||||
|
|
@ -140,13 +155,12 @@ impl Atlas {
|
||||||
match &entry {
|
match &entry {
|
||||||
Entry::Contiguous(allocation) => {
|
Entry::Contiguous(allocation) => {
|
||||||
self.upload_allocation(
|
self.upload_allocation(
|
||||||
&padded_data,
|
buffer_slice.buffer(),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
padding,
|
padding,
|
||||||
0,
|
buffer_slice.offset() as usize,
|
||||||
allocation,
|
allocation,
|
||||||
device,
|
|
||||||
encoder,
|
encoder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -156,13 +170,12 @@ impl Atlas {
|
||||||
let offset = (y * padded_width as u32 + 4 * x) as usize;
|
let offset = (y * padded_width as u32 + 4 * x) as usize;
|
||||||
|
|
||||||
self.upload_allocation(
|
self.upload_allocation(
|
||||||
&padded_data,
|
buffer_slice.buffer(),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
padding,
|
padding,
|
||||||
offset,
|
offset + buffer_slice.offset() as usize,
|
||||||
&fragment.allocation,
|
&fragment.allocation,
|
||||||
device,
|
|
||||||
encoder,
|
encoder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +185,7 @@ impl Atlas {
|
||||||
if log::log_enabled!(log::Level::Debug) {
|
if log::log_enabled!(log::Level::Debug) {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Atlas layers: {} (busy: {}, allocations: {})",
|
"Atlas layers: {} (busy: {}, allocations: {})",
|
||||||
self.layer_count(),
|
self.layers.len(),
|
||||||
self.layers.iter().filter(|layer| !layer.is_empty()).count(),
|
self.layers.iter().filter(|layer| !layer.is_empty()).count(),
|
||||||
self.layers.iter().map(Layer::allocations).sum::<usize>(),
|
self.layers.iter().map(Layer::allocations).sum::<usize>(),
|
||||||
);
|
);
|
||||||
|
|
@ -198,7 +211,7 @@ impl Atlas {
|
||||||
|
|
||||||
fn allocate(&mut self, width: u32, height: u32) -> Option<Entry> {
|
fn allocate(&mut self, width: u32, height: u32) -> Option<Entry> {
|
||||||
// Allocate one layer if texture fits perfectly
|
// Allocate one layer if texture fits perfectly
|
||||||
if width == SIZE && height == SIZE {
|
if width == self.size && height == self.size {
|
||||||
let mut empty_layers = self
|
let mut empty_layers = self
|
||||||
.layers
|
.layers
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
|
@ -208,27 +221,31 @@ impl Atlas {
|
||||||
if let Some((i, layer)) = empty_layers.next() {
|
if let Some((i, layer)) = empty_layers.next() {
|
||||||
*layer = Layer::Full;
|
*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);
|
self.layers.push(Layer::Full);
|
||||||
|
|
||||||
return Some(Entry::Contiguous(Allocation::Full {
|
return Some(Entry::Contiguous(Allocation::Full {
|
||||||
layer: self.layers.len() - 1,
|
layer: self.layers.len() - 1,
|
||||||
|
size: self.size,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split big textures across multiple layers
|
// 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 fragments = Vec::new();
|
||||||
let mut y = 0;
|
let mut y = 0;
|
||||||
|
|
||||||
while y < height {
|
while y < height {
|
||||||
let height = std::cmp::min(height - y, SIZE);
|
let height = std::cmp::min(height - y, self.size);
|
||||||
let mut x = 0;
|
let mut x = 0;
|
||||||
|
|
||||||
while x < width {
|
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)?;
|
let allocation = self.allocate(width, height)?;
|
||||||
|
|
||||||
|
|
@ -255,7 +272,7 @@ impl Atlas {
|
||||||
for (i, layer) in self.layers.iter_mut().enumerate() {
|
for (i, layer) in self.layers.iter_mut().enumerate() {
|
||||||
match layer {
|
match layer {
|
||||||
Layer::Empty => {
|
Layer::Empty => {
|
||||||
let mut allocator = Allocator::new(SIZE);
|
let mut allocator = Allocator::new(self.size);
|
||||||
|
|
||||||
if let Some(region) = allocator.allocate(width, height) {
|
if let Some(region) = allocator.allocate(width, height) {
|
||||||
*layer = Layer::Busy(allocator);
|
*layer = Layer::Busy(allocator);
|
||||||
|
|
@ -263,6 +280,7 @@ impl Atlas {
|
||||||
return Some(Entry::Contiguous(Allocation::Partial {
|
return Some(Entry::Contiguous(Allocation::Partial {
|
||||||
region,
|
region,
|
||||||
layer: i,
|
layer: i,
|
||||||
|
atlas_size: self.size,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -271,6 +289,7 @@ impl Atlas {
|
||||||
return Some(Entry::Contiguous(Allocation::Partial {
|
return Some(Entry::Contiguous(Allocation::Partial {
|
||||||
region,
|
region,
|
||||||
layer: i,
|
layer: i,
|
||||||
|
atlas_size: self.size,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -279,7 +298,7 @@ impl Atlas {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new layer with atlas allocator
|
// 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) {
|
if let Some(region) = allocator.allocate(width, height) {
|
||||||
self.layers.push(Layer::Busy(allocator));
|
self.layers.push(Layer::Busy(allocator));
|
||||||
|
|
@ -287,6 +306,7 @@ impl Atlas {
|
||||||
return Some(Entry::Contiguous(Allocation::Partial {
|
return Some(Entry::Contiguous(Allocation::Partial {
|
||||||
region,
|
region,
|
||||||
layer: self.layers.len() - 1,
|
layer: self.layers.len() - 1,
|
||||||
|
atlas_size: self.size,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,10 +318,10 @@ impl Atlas {
|
||||||
log::debug!("Deallocating atlas: {allocation:?}");
|
log::debug!("Deallocating atlas: {allocation:?}");
|
||||||
|
|
||||||
match allocation {
|
match allocation {
|
||||||
Allocation::Full { layer } => {
|
Allocation::Full { layer, .. } => {
|
||||||
self.layers[*layer] = Layer::Empty;
|
self.layers[*layer] = Layer::Empty;
|
||||||
}
|
}
|
||||||
Allocation::Partial { layer, region } => {
|
Allocation::Partial { layer, region, .. } => {
|
||||||
let layer = &mut self.layers[*layer];
|
let layer = &mut self.layers[*layer];
|
||||||
|
|
||||||
if let Layer::Busy(allocator) = layer {
|
if let Layer::Busy(allocator) = layer {
|
||||||
|
|
@ -316,18 +336,15 @@ impl Atlas {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upload_allocation(
|
fn upload_allocation(
|
||||||
&mut self,
|
&self,
|
||||||
data: &[u8],
|
buffer: &wgpu::Buffer,
|
||||||
image_width: u32,
|
image_width: u32,
|
||||||
image_height: u32,
|
image_height: u32,
|
||||||
padding: u32,
|
padding: u32,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
allocation: &Allocation,
|
allocation: &Allocation,
|
||||||
device: &wgpu::Device,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
) {
|
) {
|
||||||
use wgpu::util::DeviceExt;
|
|
||||||
|
|
||||||
let (x, y) = allocation.position();
|
let (x, y) = allocation.position();
|
||||||
let Size { width, height } = allocation.size();
|
let Size { width, height } = allocation.size();
|
||||||
let layer = allocation.layer();
|
let layer = allocation.layer();
|
||||||
|
|
@ -338,16 +355,9 @@ impl Atlas {
|
||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let buffer =
|
|
||||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
|
||||||
label: Some("image upload buffer"),
|
|
||||||
contents: data,
|
|
||||||
usage: wgpu::BufferUsages::COPY_SRC,
|
|
||||||
});
|
|
||||||
|
|
||||||
encoder.copy_buffer_to_texture(
|
encoder.copy_buffer_to_texture(
|
||||||
wgpu::TexelCopyBufferInfo {
|
wgpu::TexelCopyBufferInfo {
|
||||||
buffer: &buffer,
|
buffer,
|
||||||
layout: wgpu::TexelCopyBufferLayout {
|
layout: wgpu::TexelCopyBufferLayout {
|
||||||
offset: offset as u64,
|
offset: offset as u64,
|
||||||
bytes_per_row: Some(4 * image_width + padding),
|
bytes_per_row: Some(4 * image_width + padding),
|
||||||
|
|
@ -373,6 +383,7 @@ impl Atlas {
|
||||||
amount: usize,
|
amount: usize,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
backend: wgpu::Backend,
|
||||||
) {
|
) {
|
||||||
if amount == 0 {
|
if amount == 0 {
|
||||||
return;
|
return;
|
||||||
|
|
@ -383,7 +394,7 @@ impl Atlas {
|
||||||
// some unused memory on GL, but it's better than not being able to grow the atlas past a depth
|
// some unused memory on GL, but it's better than not being able to grow the atlas past a depth
|
||||||
// of 6!
|
// of 6!
|
||||||
// https://github.com/gfx-rs/wgpu/blob/004e3efe84a320d9331371ed31fa50baa2414911/wgpu-hal/src/gles/mod.rs#L371
|
// 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,
|
wgpu::Backend::Gl if self.layers.len() == 6 => 7,
|
||||||
_ => self.layers.len() as u32,
|
_ => self.layers.len() as u32,
|
||||||
};
|
};
|
||||||
|
|
@ -391,8 +402,8 @@ impl Atlas {
|
||||||
let new_texture = device.create_texture(&wgpu::TextureDescriptor {
|
let new_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
label: Some("iced_wgpu::image texture atlas"),
|
label: Some("iced_wgpu::image texture atlas"),
|
||||||
size: wgpu::Extent3d {
|
size: wgpu::Extent3d {
|
||||||
width: SIZE,
|
width: self.size,
|
||||||
height: SIZE,
|
height: self.size,
|
||||||
depth_or_array_layers,
|
depth_or_array_layers,
|
||||||
},
|
},
|
||||||
mip_level_count: 1,
|
mip_level_count: 1,
|
||||||
|
|
@ -440,8 +451,8 @@ impl Atlas {
|
||||||
aspect: wgpu::TextureAspect::default(),
|
aspect: wgpu::TextureAspect::default(),
|
||||||
},
|
},
|
||||||
wgpu::Extent3d {
|
wgpu::Extent3d {
|
||||||
width: SIZE,
|
width: self.size,
|
||||||
height: SIZE,
|
height: self.size,
|
||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
use crate::core::Size;
|
use crate::core::Size;
|
||||||
use crate::image::atlas::{self, allocator};
|
use crate::image::atlas::allocator;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Allocation {
|
pub enum Allocation {
|
||||||
Partial {
|
Partial {
|
||||||
layer: usize,
|
layer: usize,
|
||||||
region: allocator::Region,
|
region: allocator::Region,
|
||||||
|
atlas_size: u32,
|
||||||
},
|
},
|
||||||
Full {
|
Full {
|
||||||
layer: usize,
|
layer: usize,
|
||||||
|
size: u32,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,14 +25,21 @@ impl Allocation {
|
||||||
pub fn size(&self) -> Size<u32> {
|
pub fn size(&self) -> Size<u32> {
|
||||||
match self {
|
match self {
|
||||||
Allocation::Partial { region, .. } => region.size(),
|
Allocation::Partial { region, .. } => region.size(),
|
||||||
Allocation::Full { .. } => Size::new(atlas::SIZE, atlas::SIZE),
|
Allocation::Full { size, .. } => Size::new(*size, *size),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layer(&self) -> usize {
|
pub fn layer(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Allocation::Partial { layer, .. } => *layer,
|
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,47 +1,79 @@
|
||||||
use crate::core::{self, Size};
|
use crate::core::{self, Size};
|
||||||
|
use crate::graphics::Shell;
|
||||||
use crate::image::atlas::{self, Atlas};
|
use crate::image::atlas::{self, Atlas};
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::collections::BTreeSet;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
atlas: Atlas,
|
atlas: Atlas,
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
raster: crate::image::raster::Cache,
|
raster: Raster,
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
vector: crate::image::vector::Cache,
|
vector: crate::image::vector::Cache,
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
jobs: mpsc::SyncSender<Job>,
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
work: mpsc::Receiver<Work>,
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
worker_: Option<thread::JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cache {
|
impl Cache {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
backend: wgpu::Backend,
|
backend: wgpu::Backend,
|
||||||
layout: Arc<wgpu::BindGroupLayout>,
|
layout: wgpu::BindGroupLayout,
|
||||||
|
shell: &Shell,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
let (worker, jobs, work) =
|
||||||
|
Worker::new(device, queue, backend, layout.clone(), shell);
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
let handle = thread::spawn(move || worker.run());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
atlas: Atlas::new(device, backend, layout),
|
atlas: Atlas::new(device, backend, layout),
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
raster: crate::image::raster::Cache::default(),
|
raster: Raster {
|
||||||
|
cache: crate::image::raster::Cache::default(),
|
||||||
|
pending: BTreeSet::new(),
|
||||||
|
jobs: jobs.clone(),
|
||||||
|
},
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
vector: crate::image::vector::Cache::default(),
|
vector: crate::image::vector::Cache::default(),
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
jobs,
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
work,
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
worker_: Some(handle),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bind_group(&self) -> &wgpu::BindGroup {
|
|
||||||
self.atlas.bind_group()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn layer_count(&self) -> usize {
|
|
||||||
self.atlas.layer_count()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
|
pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
|
||||||
self.raster.load(handle).dimensions()
|
self.receive();
|
||||||
|
|
||||||
|
if let Some(memory) = load_image(
|
||||||
|
&mut self.raster.cache,
|
||||||
|
&mut self.raster.pending,
|
||||||
|
&mut self.raster.jobs,
|
||||||
|
handle,
|
||||||
|
) {
|
||||||
|
return memory.dimensions();
|
||||||
|
}
|
||||||
|
|
||||||
|
Size::new(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> {
|
pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> {
|
||||||
|
// TODO: Concurrency
|
||||||
self.vector.load(handle).viewport_dimensions()
|
self.vector.load(handle).viewport_dimensions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,9 +82,63 @@ impl Cache {
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
handle: &core::image::Handle,
|
handle: &core::image::Handle,
|
||||||
) -> Option<&atlas::Entry> {
|
) -> Option<(&atlas::Entry, &wgpu::BindGroup)> {
|
||||||
self.raster.upload(device, encoder, handle, &mut self.atlas)
|
use crate::image::raster::Memory;
|
||||||
|
|
||||||
|
self.receive();
|
||||||
|
|
||||||
|
let memory = load_image(
|
||||||
|
&mut self.raster.cache,
|
||||||
|
&mut self.raster.pending,
|
||||||
|
&mut self.raster.jobs,
|
||||||
|
handle,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if image.len() < MAX_SYNC_SIZE {
|
||||||
|
let entry = self.atlas.upload(
|
||||||
|
device,
|
||||||
|
encoder,
|
||||||
|
belt,
|
||||||
|
image.width(),
|
||||||
|
image.height(),
|
||||||
|
&image,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
*memory = Memory::Device {
|
||||||
|
entry,
|
||||||
|
bind_group: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Memory::Device { entry, .. } = memory {
|
||||||
|
return Some((entry, self.atlas.bind_group()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.raster.pending.contains(&handle.id()) {
|
||||||
|
let _ = self.jobs.send(Job::Upload {
|
||||||
|
handle: handle.clone(),
|
||||||
|
rgba: image.clone().into_raw(),
|
||||||
|
width: image.width(),
|
||||||
|
height: image.height(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let _ = self.raster.pending.insert(handle.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
|
|
@ -60,27 +146,261 @@ impl Cache {
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
handle: &core::svg::Handle,
|
handle: &core::svg::Handle,
|
||||||
color: Option<core::Color>,
|
color: Option<core::Color>,
|
||||||
size: [f32; 2],
|
size: [f32; 2],
|
||||||
scale: f32,
|
scale: f32,
|
||||||
) -> Option<&atlas::Entry> {
|
) -> Option<(&atlas::Entry, &wgpu::BindGroup)> {
|
||||||
self.vector.upload(
|
// TODO: Concurrency
|
||||||
device,
|
self.vector
|
||||||
encoder,
|
.upload(
|
||||||
handle,
|
device,
|
||||||
color,
|
encoder,
|
||||||
size,
|
belt,
|
||||||
scale,
|
handle,
|
||||||
&mut self.atlas,
|
color,
|
||||||
)
|
size,
|
||||||
|
scale,
|
||||||
|
&mut self.atlas,
|
||||||
|
)
|
||||||
|
.map(|entry| (entry, self.atlas.bind_group()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trim(&mut self) {
|
pub fn trim(&mut self) {
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
self.raster.trim(&mut self.atlas);
|
self.raster.cache.trim(&mut self.atlas, |bind_group| {
|
||||||
|
let _ = self.jobs.send(Job::Drop(bind_group));
|
||||||
|
});
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
self.vector.trim(&mut self.atlas);
|
self.vector.trim(&mut self.atlas); // TODO: Concurrency
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive(&mut self) {
|
||||||
|
use crate::image::raster::Memory;
|
||||||
|
|
||||||
|
while let Ok(work) = self.work.try_recv() {
|
||||||
|
match work {
|
||||||
|
Work::Upload {
|
||||||
|
handle,
|
||||||
|
entry,
|
||||||
|
bind_group,
|
||||||
|
} => {
|
||||||
|
self.raster.cache.insert(
|
||||||
|
&handle,
|
||||||
|
Memory::Device {
|
||||||
|
entry,
|
||||||
|
bind_group: Some(bind_group),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = self.raster.pending.remove(&handle.id());
|
||||||
|
}
|
||||||
|
Work::Error { handle, error } => {
|
||||||
|
self.raster.cache.insert(&handle, Memory::error(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Cache {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Stop worker gracefully
|
||||||
|
let (sender, _) = mpsc::sync_channel(1);
|
||||||
|
self.jobs = sender.clone();
|
||||||
|
self.raster.jobs = sender;
|
||||||
|
|
||||||
|
let _ = self.worker_.take().unwrap().join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Raster {
|
||||||
|
cache: crate::image::raster::Cache,
|
||||||
|
pending: BTreeSet<core::image::Id>,
|
||||||
|
jobs: mpsc::SyncSender<Job>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
fn load_image<'a>(
|
||||||
|
cache: &'a mut crate::image::raster::Cache,
|
||||||
|
pending: &mut BTreeSet<core::image::Id>,
|
||||||
|
jobs: &mut mpsc::SyncSender<Job>,
|
||||||
|
handle: &core::image::Handle,
|
||||||
|
) -> Option<&'a mut crate::image::raster::Memory> {
|
||||||
|
use crate::image::raster::Memory;
|
||||||
|
|
||||||
|
if !cache.contains(handle) {
|
||||||
|
// Load RGBA handles synchronously, since it's very cheap
|
||||||
|
if let core::image::Handle::Rgba { .. } = handle {
|
||||||
|
cache.insert(handle, Memory::load(handle));
|
||||||
|
} else {
|
||||||
|
let _ = jobs.send(Job::Load(handle.clone()));
|
||||||
|
let _ = pending.insert(handle.id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.get_mut(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
enum Job {
|
||||||
|
Load(core::image::Handle),
|
||||||
|
Upload {
|
||||||
|
handle: core::image::Handle,
|
||||||
|
rgba: core::image::Bytes,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
},
|
||||||
|
Drop(wgpu::BindGroup),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
enum Work {
|
||||||
|
Upload {
|
||||||
|
handle: core::image::Handle,
|
||||||
|
entry: atlas::Entry,
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
},
|
||||||
|
Error {
|
||||||
|
handle: core::image::Handle,
|
||||||
|
error: crate::graphics::image::image_rs::error::ImageError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
struct Worker {
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
impl Worker {
|
||||||
|
fn new(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
backend: wgpu::Backend,
|
||||||
|
texture_layout: wgpu::BindGroupLayout,
|
||||||
|
shell: &Shell,
|
||||||
|
) -> (Self, mpsc::SyncSender<Job>, mpsc::Receiver<Work>) {
|
||||||
|
let (jobs_sender, jobs_receiver) = mpsc::sync_channel(1_000);
|
||||||
|
let (work_sender, work_receiver) = mpsc::sync_channel(1_000);
|
||||||
|
|
||||||
|
(
|
||||||
|
Self {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
jobs_sender,
|
||||||
|
work_receiver,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(mut self) {
|
||||||
|
while let Ok(job) = self.jobs.recv() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload(
|
||||||
|
&mut self,
|
||||||
|
handle: core::image::Handle,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
rgba: core::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();
|
||||||
|
|
||||||
|
self.queue.on_submitted_work_done(move || {
|
||||||
|
let _ = output.send(Work::Upload {
|
||||||
|
handle,
|
||||||
|
entry,
|
||||||
|
bind_group: atlas.bind_group().clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
callback(&shell);
|
||||||
|
});
|
||||||
|
|
||||||
|
let _ = self
|
||||||
|
.device
|
||||||
|
.poll(wgpu::PollType::WaitForSubmissionIndex(submission));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,11 @@ mod vector;
|
||||||
|
|
||||||
use crate::Buffer;
|
use crate::Buffer;
|
||||||
use crate::core::{Rectangle, Size, Transformation};
|
use crate::core::{Rectangle, Size, Transformation};
|
||||||
|
use crate::graphics::Shell;
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub use crate::graphics::Image;
|
pub use crate::graphics::Image;
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ pub struct Pipeline {
|
||||||
backend: wgpu::Backend,
|
backend: wgpu::Backend,
|
||||||
nearest_sampler: wgpu::Sampler,
|
nearest_sampler: wgpu::Sampler,
|
||||||
linear_sampler: wgpu::Sampler,
|
linear_sampler: wgpu::Sampler,
|
||||||
texture_layout: Arc<wgpu::BindGroupLayout>,
|
texture_layout: wgpu::BindGroupLayout,
|
||||||
constant_layout: wgpu::BindGroupLayout,
|
constant_layout: wgpu::BindGroupLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,13 +196,24 @@ impl Pipeline {
|
||||||
backend,
|
backend,
|
||||||
nearest_sampler,
|
nearest_sampler,
|
||||||
linear_sampler,
|
linear_sampler,
|
||||||
texture_layout: Arc::new(texture_layout),
|
texture_layout,
|
||||||
constant_layout,
|
constant_layout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_cache(&self, device: &wgpu::Device) -> Cache {
|
pub fn create_cache(
|
||||||
Cache::new(device, self.backend, self.texture_layout.clone())
|
&self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
shell: &Shell,
|
||||||
|
) -> Cache {
|
||||||
|
Cache::new(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
self.backend,
|
||||||
|
self.texture_layout.clone(),
|
||||||
|
shell,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,16 +239,50 @@ impl State {
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
) {
|
) {
|
||||||
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
|
if self.layers.len() <= self.prepare_layer {
|
||||||
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
|
self.layers.push(Layer::new(
|
||||||
|
device,
|
||||||
|
&pipeline.constant_layout,
|
||||||
|
&pipeline.nearest_sampler,
|
||||||
|
&pipeline.linear_sampler,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer = &mut self.layers[self.prepare_layer];
|
||||||
|
layer.prepare(device, encoder, belt, transformation, scale);
|
||||||
|
|
||||||
|
let mut atlas = None;
|
||||||
|
let nearest_instances = &mut Vec::new();
|
||||||
|
let linear_instances = &mut Vec::new();
|
||||||
|
|
||||||
for image in images {
|
for image in images {
|
||||||
match &image {
|
match &image {
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
Image::Raster(image, bounds) => {
|
Image::Raster(image, bounds) => {
|
||||||
if let Some(atlas_entry) =
|
if let Some((atlas_entry, bind_group)) = cache
|
||||||
cache.upload_raster(device, encoder, &image.handle)
|
.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(
|
||||||
|
device,
|
||||||
|
encoder,
|
||||||
|
belt,
|
||||||
|
atlas,
|
||||||
|
nearest_instances,
|
||||||
|
linear_instances,
|
||||||
|
);
|
||||||
|
|
||||||
|
*atlas = bind_group.clone();
|
||||||
|
nearest_instances.clear();
|
||||||
|
linear_instances.clear();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
add_instances(
|
add_instances(
|
||||||
[bounds.x, bounds.y],
|
[bounds.x, bounds.y],
|
||||||
[bounds.width, bounds.height],
|
[bounds.width, bounds.height],
|
||||||
|
|
@ -257,20 +302,44 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "image"))]
|
#[cfg(not(feature = "image"))]
|
||||||
Image::Raster { .. } => {}
|
Image::Raster { .. } => continue,
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
Image::Vector(svg, bounds) => {
|
Image::Vector(svg, bounds) => {
|
||||||
let size = [bounds.width, bounds.height];
|
let size = [bounds.width, bounds.height];
|
||||||
|
|
||||||
if let Some(atlas_entry) = cache.upload_vector(
|
if let Some((atlas_entry, bind_group)) = cache
|
||||||
device,
|
.upload_vector(
|
||||||
encoder,
|
device,
|
||||||
&svg.handle,
|
encoder,
|
||||||
svg.color,
|
belt,
|
||||||
size,
|
&svg.handle,
|
||||||
scale,
|
svg.color,
|
||||||
) {
|
size,
|
||||||
|
scale,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
match atlas.as_mut() {
|
||||||
|
None => {
|
||||||
|
atlas = Some(bind_group.clone());
|
||||||
|
}
|
||||||
|
Some(atlas) if atlas != bind_group => {
|
||||||
|
layer.push(
|
||||||
|
device,
|
||||||
|
encoder,
|
||||||
|
belt,
|
||||||
|
atlas,
|
||||||
|
nearest_instances,
|
||||||
|
linear_instances,
|
||||||
|
);
|
||||||
|
|
||||||
|
*atlas = bind_group.clone();
|
||||||
|
nearest_instances.clear();
|
||||||
|
linear_instances.clear();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
add_instances(
|
add_instances(
|
||||||
[bounds.x, bounds.y],
|
[bounds.x, bounds.y],
|
||||||
size,
|
size,
|
||||||
|
|
@ -283,42 +352,27 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "svg"))]
|
#[cfg(not(feature = "svg"))]
|
||||||
Image::Vector { .. } => {}
|
Image::Vector { .. } => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if nearest_instances.is_empty() && linear_instances.is_empty() {
|
if !nearest_instances.is_empty() || !linear_instances.is_empty() {
|
||||||
return;
|
layer.push(
|
||||||
}
|
|
||||||
|
|
||||||
if self.layers.len() <= self.prepare_layer {
|
|
||||||
self.layers.push(Layer::new(
|
|
||||||
device,
|
device,
|
||||||
&pipeline.constant_layout,
|
encoder,
|
||||||
&pipeline.nearest_sampler,
|
belt,
|
||||||
&pipeline.linear_sampler,
|
&atlas.expect("atlas should be defined"),
|
||||||
));
|
nearest_instances,
|
||||||
|
linear_instances,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let layer = &mut self.layers[self.prepare_layer];
|
|
||||||
|
|
||||||
layer.prepare(
|
|
||||||
device,
|
|
||||||
encoder,
|
|
||||||
belt,
|
|
||||||
nearest_instances,
|
|
||||||
linear_instances,
|
|
||||||
transformation,
|
|
||||||
scale,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.prepare_layer += 1;
|
self.prepare_layer += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<'a>(
|
pub fn render<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
pipeline: &'a Pipeline,
|
pipeline: &'a Pipeline,
|
||||||
cache: &'a Cache,
|
|
||||||
layer: usize,
|
layer: usize,
|
||||||
bounds: Rectangle<u32>,
|
bounds: Rectangle<u32>,
|
||||||
render_pass: &mut wgpu::RenderPass<'a>,
|
render_pass: &mut wgpu::RenderPass<'a>,
|
||||||
|
|
@ -333,13 +387,15 @@ impl State {
|
||||||
bounds.height,
|
bounds.height,
|
||||||
);
|
);
|
||||||
|
|
||||||
render_pass.set_bind_group(1, cache.bind_group(), &[]);
|
|
||||||
|
|
||||||
layer.render(render_pass);
|
layer.render(render_pass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trim(&mut self) {
|
pub fn trim(&mut self) {
|
||||||
|
for layer in &mut self.layers[..self.prepare_layer] {
|
||||||
|
layer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
self.prepare_layer = 0;
|
self.prepare_layer = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -347,8 +403,18 @@ impl State {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Layer {
|
struct Layer {
|
||||||
uniforms: wgpu::Buffer,
|
uniforms: wgpu::Buffer,
|
||||||
nearest: Data,
|
instances: Buffer<Instance>,
|
||||||
linear: Data,
|
total: usize,
|
||||||
|
nearest: Vec<Group>,
|
||||||
|
nearest_layout: wgpu::BindGroup,
|
||||||
|
linear: Vec<Group>,
|
||||||
|
linear_layout: wgpu::BindGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Group {
|
||||||
|
atlas: wgpu::BindGroup,
|
||||||
|
instance_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layer {
|
impl Layer {
|
||||||
|
|
@ -365,16 +431,69 @@ impl Layer {
|
||||||
mapped_at_creation: false,
|
mapped_at_creation: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let nearest =
|
let instances = Buffer::new(
|
||||||
Data::new(device, constant_layout, nearest_sampler, &uniforms);
|
device,
|
||||||
|
"iced_wgpu::image instance buffer",
|
||||||
|
Instance::INITIAL,
|
||||||
|
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||||
|
);
|
||||||
|
|
||||||
let linear =
|
let nearest_layout =
|
||||||
Data::new(device, constant_layout, linear_sampler, &uniforms);
|
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 {
|
Self {
|
||||||
uniforms,
|
uniforms,
|
||||||
nearest,
|
instances,
|
||||||
linear,
|
total: 0,
|
||||||
|
nearest: Vec::new(),
|
||||||
|
nearest_layout,
|
||||||
|
linear: Vec::new(),
|
||||||
|
linear_layout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -383,8 +502,6 @@ impl Layer {
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
belt: &mut wgpu::util::StagingBelt,
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
nearest_instances: &[Instance],
|
|
||||||
linear_instances: &[Instance],
|
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
scale_factor: f32,
|
scale_factor: f32,
|
||||||
) {
|
) {
|
||||||
|
|
@ -404,94 +521,79 @@ impl Layer {
|
||||||
device,
|
device,
|
||||||
)
|
)
|
||||||
.copy_from_slice(bytes);
|
.copy_from_slice(bytes);
|
||||||
|
|
||||||
self.nearest
|
|
||||||
.upload(device, encoder, belt, nearest_instances);
|
|
||||||
|
|
||||||
self.linear.upload(device, encoder, belt, linear_instances);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
fn push(
|
||||||
self.nearest.render(render_pass);
|
|
||||||
self.linear.render(render_pass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn upload(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
belt: &mut wgpu::util::StagingBelt,
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
instances: &[Instance],
|
atlas: &wgpu::BindGroup,
|
||||||
|
nearest: &[Instance],
|
||||||
|
linear: &[Instance],
|
||||||
) {
|
) {
|
||||||
self.instance_count = instances.len();
|
let new = nearest.len() + linear.len();
|
||||||
|
|
||||||
if self.instance_count == 0 {
|
let _ = self.instances.resize(device, self.total + new);
|
||||||
return;
|
|
||||||
|
if !nearest.is_empty() {
|
||||||
|
self.total += self
|
||||||
|
.instances
|
||||||
|
.write(device, encoder, belt, self.total, nearest);
|
||||||
|
|
||||||
|
self.nearest.push(Group {
|
||||||
|
atlas: atlas.clone(),
|
||||||
|
instance_count: nearest.len(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.instances.resize(device, instances.len());
|
if !linear.is_empty() {
|
||||||
let _ = self.instances.write(device, encoder, belt, 0, instances);
|
self.total += self
|
||||||
|
.instances
|
||||||
|
.write(device, encoder, belt, self.total, linear);
|
||||||
|
|
||||||
|
self.linear.push(Group {
|
||||||
|
atlas: atlas.clone(),
|
||||||
|
instance_count: linear.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
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.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, &[]);
|
||||||
|
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, &[]);
|
||||||
|
render_pass
|
||||||
|
.draw(0..6, offset..offset + group.instance_count as u32);
|
||||||
|
|
||||||
|
offset += group.instance_count as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.instances.clear();
|
||||||
|
self.nearest.clear();
|
||||||
|
self.linear.clear();
|
||||||
|
self.total = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -597,6 +699,7 @@ fn add_instance(
|
||||||
let (x, y) = allocation.position();
|
let (x, y) = allocation.position();
|
||||||
let Size { width, height } = allocation.size();
|
let Size { width, height } = allocation.size();
|
||||||
let layer = allocation.layer();
|
let layer = allocation.layer();
|
||||||
|
let atlas_size = allocation.atlas_size();
|
||||||
|
|
||||||
let instance = Instance {
|
let instance = Instance {
|
||||||
_position: position,
|
_position: position,
|
||||||
|
|
@ -605,12 +708,12 @@ fn add_instance(
|
||||||
_rotation: rotation,
|
_rotation: rotation,
|
||||||
_opacity: opacity,
|
_opacity: opacity,
|
||||||
_position_in_atlas: [
|
_position_in_atlas: [
|
||||||
(x as f32 + 0.5) / atlas::SIZE as f32,
|
(x as f32 + 0.5) / atlas_size as f32,
|
||||||
(y as f32 + 0.5) / atlas::SIZE as f32,
|
(y as f32 + 0.5) / atlas_size as f32,
|
||||||
],
|
],
|
||||||
_size_in_atlas: [
|
_size_in_atlas: [
|
||||||
(width as f32 - 1.0) / atlas::SIZE as f32,
|
(width as f32 - 1.0) / atlas_size as f32,
|
||||||
(height as f32 - 1.0) / atlas::SIZE as f32,
|
(height as f32 - 1.0) / atlas_size as f32,
|
||||||
],
|
],
|
||||||
_layer: layer as u32,
|
_layer: layer as u32,
|
||||||
_snap: snap as u32,
|
_snap: snap as u32,
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,18 @@ use crate::image::atlas::{self, Atlas};
|
||||||
|
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
|
type Image = image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>;
|
||||||
|
|
||||||
/// Entry in cache corresponding to an image handle
|
/// Entry in cache corresponding to an image handle
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Memory {
|
pub enum Memory {
|
||||||
/// Image data on host
|
/// Image data on host
|
||||||
Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>),
|
Host(Image),
|
||||||
/// Storage entry
|
/// Storage entry
|
||||||
Device(atlas::Entry),
|
Device {
|
||||||
|
entry: atlas::Entry,
|
||||||
|
bind_group: Option<wgpu::BindGroup>,
|
||||||
|
},
|
||||||
/// Image not found
|
/// Image not found
|
||||||
NotFound,
|
NotFound,
|
||||||
/// Invalid image data
|
/// Invalid image data
|
||||||
|
|
@ -20,7 +25,20 @@ pub enum Memory {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Memory {
|
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 error(error: image_rs::error::ImageError) -> Self {
|
||||||
|
match error {
|
||||||
|
image_rs::error::ImageError::IoError(_) => Self::NotFound,
|
||||||
|
_ => Self::Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dimensions(&self) -> Size<u32> {
|
pub fn dimensions(&self) -> Size<u32> {
|
||||||
match self {
|
match self {
|
||||||
Memory::Host(image) => {
|
Memory::Host(image) => {
|
||||||
|
|
@ -28,14 +46,20 @@ impl Memory {
|
||||||
|
|
||||||
Size::new(width, height)
|
Size::new(width, height)
|
||||||
}
|
}
|
||||||
Memory::Device(entry) => entry.size(),
|
Memory::Device { entry, .. } => entry.size(),
|
||||||
Memory::NotFound => Size::new(1, 1),
|
Memory::NotFound => Size::new(1, 1),
|
||||||
Memory::Invalid => Size::new(1, 1),
|
Memory::Invalid => Size::new(1, 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn host(&self) -> Option<Image> {
|
||||||
|
match self {
|
||||||
|
Memory::Host(image) => Some(image.clone()),
|
||||||
|
Memory::Device { .. } | Memory::NotFound | Memory::Invalid => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Caches image raster data
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
map: FxHashMap<image::Id, Memory>,
|
map: FxHashMap<image::Id, Memory>,
|
||||||
|
|
@ -44,51 +68,28 @@ pub struct Cache {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cache {
|
impl Cache {
|
||||||
/// Load image
|
pub fn get_mut(&mut self, handle: &image::Handle) -> Option<&mut Memory> {
|
||||||
pub fn load(&mut self, handle: &image::Handle) -> &mut Memory {
|
let _ = self.hits.insert(handle.id());
|
||||||
if self.contains(handle) {
|
|
||||||
return self.get(handle).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let memory = match graphics::image::load(handle) {
|
self.map.get_mut(&handle.id())
|
||||||
Ok(image) => Memory::Host(image),
|
}
|
||||||
Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound,
|
|
||||||
Err(_) => Memory::Invalid,
|
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.should_trim = true;
|
||||||
|
|
||||||
self.insert(handle, memory);
|
|
||||||
self.get(handle).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load image and upload raster data
|
pub fn contains(&self, handle: &image::Handle) -> bool {
|
||||||
pub fn upload(
|
self.map.contains_key(&handle.id())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trim(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
handle: &image::Handle,
|
|
||||||
atlas: &mut Atlas,
|
atlas: &mut Atlas,
|
||||||
) -> Option<&atlas::Entry> {
|
on_drop: impl Fn(wgpu::BindGroup),
|
||||||
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) {
|
|
||||||
// Only trim if new entries have landed in the `Cache`
|
// Only trim if new entries have landed in the `Cache`
|
||||||
if !self.should_trim {
|
if !self.should_trim {
|
||||||
return;
|
return;
|
||||||
|
|
@ -99,8 +100,12 @@ impl Cache {
|
||||||
self.map.retain(|k, memory| {
|
self.map.retain(|k, memory| {
|
||||||
let retain = hits.contains(k);
|
let retain = hits.contains(k);
|
||||||
|
|
||||||
if !retain && let Memory::Device(entry) = memory {
|
if !retain && let Memory::Device { entry, bind_group } = memory {
|
||||||
atlas.remove(entry);
|
if let Some(bind_group) = bind_group.take() {
|
||||||
|
on_drop(bind_group);
|
||||||
|
} else {
|
||||||
|
atlas.remove(entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
retain
|
retain
|
||||||
|
|
@ -109,18 +114,4 @@ impl Cache {
|
||||||
self.hits.clear();
|
self.hits.clear();
|
||||||
self.should_trim = false;
|
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,6 +94,7 @@ impl Cache {
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
handle: &svg::Handle,
|
handle: &svg::Handle,
|
||||||
color: Option<Color>,
|
color: Option<Color>,
|
||||||
[width, height]: [f32; 2],
|
[width, height]: [f32; 2],
|
||||||
|
|
@ -167,8 +168,8 @@ impl Cache {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let allocation =
|
let allocation = atlas
|
||||||
atlas.upload(device, encoder, width, height, &rgba)?;
|
.upload(device, encoder, belt, width, height, &rgba)?;
|
||||||
|
|
||||||
log::debug!("allocating {id} {width}x{height}");
|
log::debug!("allocating {id} {width}x{height}");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,8 @@ use crate::core::renderer;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
||||||
};
|
};
|
||||||
use crate::graphics::Viewport;
|
|
||||||
use crate::graphics::text::{Editor, Paragraph};
|
use crate::graphics::text::{Editor, Paragraph};
|
||||||
|
use crate::graphics::{Shell, Viewport};
|
||||||
|
|
||||||
/// A [`wgpu`] graphics renderer for [`iced`].
|
/// A [`wgpu`] graphics renderer for [`iced`].
|
||||||
///
|
///
|
||||||
|
|
@ -117,9 +117,7 @@ impl Renderer {
|
||||||
image: image::State::new(),
|
image: image::State::new(),
|
||||||
|
|
||||||
#[cfg(any(feature = "svg", feature = "image"))]
|
#[cfg(any(feature = "svg", feature = "image"))]
|
||||||
image_cache: std::cell::RefCell::new(
|
image_cache: std::cell::RefCell::new(engine.create_image_cache()),
|
||||||
engine.create_image_cache(&engine.device),
|
|
||||||
),
|
|
||||||
|
|
||||||
// TODO: Resize belt smartly (?)
|
// TODO: Resize belt smartly (?)
|
||||||
// It would be great if the `StagingBelt` API exposed methods
|
// It would be great if the `StagingBelt` API exposed methods
|
||||||
|
|
@ -460,8 +458,6 @@ impl Renderer {
|
||||||
|
|
||||||
#[cfg(any(feature = "svg", feature = "image"))]
|
#[cfg(any(feature = "svg", feature = "image"))]
|
||||||
let mut image_layer = 0;
|
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 scale_factor = viewport.scale_factor();
|
||||||
let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
|
let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
|
||||||
|
|
@ -632,7 +628,6 @@ impl Renderer {
|
||||||
let render_span = debug::render(debug::Primitive::Image);
|
let render_span = debug::render(debug::Primitive::Image);
|
||||||
self.image.render(
|
self.image.render(
|
||||||
&self.engine.image_pipeline,
|
&self.engine.image_pipeline,
|
||||||
&image_cache,
|
|
||||||
image_layer,
|
image_layer,
|
||||||
scissor_rect,
|
scissor_rect,
|
||||||
&mut render_pass,
|
&mut render_pass,
|
||||||
|
|
@ -910,6 +905,7 @@ impl renderer::Headless for Renderer {
|
||||||
wgpu::TextureFormat::Rgba8Unorm
|
wgpu::TextureFormat::Rgba8Unorm
|
||||||
},
|
},
|
||||||
Some(graphics::Antialiasing::MSAAx4),
|
Some(graphics::Antialiasing::MSAAx4),
|
||||||
|
Shell::headless(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(Self::new(engine, default_font, default_text_size))
|
Some(Self::new(engine, default_font, default_text_size))
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::core::Color;
|
||||||
use crate::graphics::color;
|
use crate::graphics::color;
|
||||||
use crate::graphics::compositor;
|
use crate::graphics::compositor;
|
||||||
use crate::graphics::error;
|
use crate::graphics::error;
|
||||||
use crate::graphics::{self, Viewport};
|
use crate::graphics::{self, Shell, Viewport};
|
||||||
use crate::settings::{self, Settings};
|
use crate::settings::{self, Settings};
|
||||||
use crate::{Engine, Renderer};
|
use crate::{Engine, Renderer};
|
||||||
|
|
||||||
|
|
@ -50,6 +50,7 @@ impl Compositor {
|
||||||
pub async fn request<W: compositor::Window>(
|
pub async fn request<W: compositor::Window>(
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
compatible_window: Option<W>,
|
compatible_window: Option<W>,
|
||||||
|
shell: Shell,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let instance = wgpu::util::new_instance_with_webgpu_detection(
|
let instance = wgpu::util::new_instance_with_webgpu_detection(
|
||||||
&wgpu::InstanceDescriptor {
|
&wgpu::InstanceDescriptor {
|
||||||
|
|
@ -181,6 +182,7 @@ impl Compositor {
|
||||||
queue,
|
queue,
|
||||||
format,
|
format,
|
||||||
settings.antialiasing,
|
settings.antialiasing,
|
||||||
|
shell,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Ok(Compositor {
|
return Ok(Compositor {
|
||||||
|
|
@ -206,8 +208,9 @@ impl Compositor {
|
||||||
pub async fn new<W: compositor::Window>(
|
pub async fn new<W: compositor::Window>(
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
compatible_window: W,
|
compatible_window: W,
|
||||||
|
shell: Shell,
|
||||||
) -> Result<Compositor, Error> {
|
) -> 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`].
|
/// Presents the given primitives with the given [`Compositor`].
|
||||||
|
|
@ -260,6 +263,7 @@ impl graphics::Compositor for Compositor {
|
||||||
async fn with_backend<W: compositor::Window>(
|
async fn with_backend<W: compositor::Window>(
|
||||||
settings: graphics::Settings,
|
settings: graphics::Settings,
|
||||||
compatible_window: W,
|
compatible_window: W,
|
||||||
|
shell: Shell,
|
||||||
backend: Option<&str>,
|
backend: Option<&str>,
|
||||||
) -> Result<Self, graphics::Error> {
|
) -> Result<Self, graphics::Error> {
|
||||||
match backend {
|
match backend {
|
||||||
|
|
@ -274,7 +278,7 @@ impl graphics::Compositor for Compositor {
|
||||||
settings.present_mode = present_mode;
|
settings.present_mode = present_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(new(settings, compatible_window).await?)
|
Ok(new(settings, compatible_window, shell).await?)
|
||||||
}
|
}
|
||||||
Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
|
Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
|
||||||
backend: "wgpu",
|
backend: "wgpu",
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ use crate::futures::futures::task;
|
||||||
use crate::futures::futures::{Future, StreamExt};
|
use crate::futures::futures::{Future, StreamExt};
|
||||||
use crate::futures::subscription;
|
use crate::futures::subscription;
|
||||||
use crate::futures::{Executor, Runtime};
|
use crate::futures::{Executor, Runtime};
|
||||||
use crate::graphics::{Compositor, compositor};
|
use crate::graphics::{Compositor, Shell, compositor};
|
||||||
use crate::runtime::system;
|
use crate::runtime::system;
|
||||||
use crate::runtime::user_interface::{self, UserInterface};
|
use crate::runtime::user_interface::{self, UserInterface};
|
||||||
use crate::runtime::{Action, Task};
|
use crate::runtime::{Action, Task};
|
||||||
|
|
@ -587,8 +587,10 @@ async fn run_instance<P>(
|
||||||
let default_fonts = default_fonts.clone();
|
let default_fonts = default_fonts.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
|
let shell = Shell::new(proxy.clone());
|
||||||
|
|
||||||
let mut compositor =
|
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 {
|
if let Ok(compositor) = &mut compositor {
|
||||||
for font in default_fonts {
|
for font in default_fonts {
|
||||||
|
|
@ -824,7 +826,7 @@ async fn run_instance<P>(
|
||||||
.get_mut(&id)
|
.get_mut(&id)
|
||||||
.expect("Get user interface");
|
.expect("Get user interface");
|
||||||
|
|
||||||
let draw_span = debug::draw(id);
|
let interact_span = debug::interact(id);
|
||||||
let mut change_count = 0;
|
let mut change_count = 0;
|
||||||
|
|
||||||
let state = loop {
|
let state = loop {
|
||||||
|
|
@ -947,7 +949,9 @@ async fn run_instance<P>(
|
||||||
user_interfaces.get_mut(&id).unwrap();
|
user_interfaces.get_mut(&id).unwrap();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
interact_span.finish();
|
||||||
|
|
||||||
|
let draw_span = debug::draw(id);
|
||||||
interface.draw(
|
interface.draw(
|
||||||
&mut window.renderer,
|
&mut window.renderer,
|
||||||
window.state.theme(),
|
window.state.theme(),
|
||||||
|
|
@ -1646,6 +1650,26 @@ fn run_action<'a, P, C>(
|
||||||
let _ = window.raw.set_cursor_hittest(true);
|
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 {
|
Action::System(action) => match action {
|
||||||
system::Action::GetInformation(_channel) => {
|
system::Action::GetInformation(_channel) => {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ use crate::futures::futures::{
|
||||||
select,
|
select,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
use crate::graphics::shell;
|
||||||
use crate::runtime::Action;
|
use crate::runtime::Action;
|
||||||
|
use crate::runtime::window;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
/// An event loop proxy with backpressure that implements `Sink`.
|
/// 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(()))
|
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