diff --git a/Cargo.lock b/Cargo.lock index c43fa494..f1bea206 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2620,6 +2620,7 @@ version = "0.14.0-dev" dependencies = [ "bitflags 2.9.4", "bytemuck", + "bytes", "cryoglyph", "futures", "glam", diff --git a/benches/wgpu.rs b/benches/wgpu.rs index 35696abc..d09277ed 100644 --- a/benches/wgpu.rs +++ b/benches/wgpu.rs @@ -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)); diff --git a/examples/gallery/src/main.rs b/examples/gallery/src/main.rs index 182fd606..42e916db 100644 --- a/examples/gallery/src/main.rs +++ b/examples/gallery/src/main.rs @@ -18,7 +18,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 +35,8 @@ fn main() -> iced::Result { struct Gallery { images: Vec, previews: HashMap, + visible: HashSet, + downloaded: HashSet, viewer: Viewer, now: Instant, } @@ -43,6 +45,7 @@ struct Gallery { enum Message { ImagesListed(Result, Error>), ImagePoppedIn(Id), + ImagePoppedOut(Id), ImageDownloaded(Result), ThumbnailDownloaded(Id, Result), ThumbnailHovered(Id, bool), @@ -58,6 +61,8 @@ impl Gallery { Self { images: Vec::new(), previews: HashMap::new(), + visible: HashSet::new(), + downloaded: HashSet::new(), viewer: Viewer::new(), now: Instant::now(), }, @@ -102,6 +107,14 @@ impl Gallery { return Task::none(); }; + let _ = self.visible.insert(id); + + if self.downloaded.contains(&id) { + return Task::none(); + } + + let _ = self.downloaded.insert(id); + Task::sip( image.download(Size::Thumbnail { width: Preview::WIDTH, @@ -111,6 +124,11 @@ impl Gallery { Message::ThumbnailDownloaded.with(id), ) } + Message::ImagePoppedOut(id) => { + let _ = self.visible.remove(&id); + + Task::none() + } Message::ImageDownloaded(Ok(rgba)) => { self.viewer.show(rgba, self.now); @@ -181,7 +199,17 @@ impl Gallery { let images = self .images .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())); let gallery = grid(images) @@ -248,7 +276,7 @@ fn card<'a>( .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,10 +285,13 @@ 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> { diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index b98b8273..5084742b 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -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)) diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index e0c8eb46..3ec99542 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -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( settings: Settings, compatible_window: W, + shell: Shell, ) -> impl Future> { - 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( _settings: Settings, _compatible_window: W, + _shell: Shell, _backend: Option<&str>, ) -> impl Future>; @@ -153,6 +155,7 @@ impl Compositor for () { async fn with_backend( _settings: Settings, _compatible_window: W, + _shell: Shell, _preferred_backend: Option<&str>, ) -> Result { Ok(()) diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index ed6ec87f..ff5f39c4 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -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; diff --git a/graphics/src/shell.rs b/graphics/src/shell.rs new file mode 100644 index 00000000..55004d7d --- /dev/null +++ b/graphics/src/shell.rs @@ -0,0 +1,45 @@ +//! Control the windowing runtime from a renderer. +use std::sync::Arc; + +/// A windowing shell. +#[derive(Clone)] +pub struct Shell(Arc); + +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); +} diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 5e775e38..d488e6ea 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -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; @@ -216,6 +216,7 @@ where async fn with_backend( settings: graphics::Settings, compatible_window: W, + shell: Shell, backend: Option<&str>, ) -> Result { use std::env; @@ -242,8 +243,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 +257,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) => { diff --git a/runtime/src/window.rs b/runtime/src/window.rs index f2ed4e5e..057acb8f 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -174,6 +174,12 @@ pub enum Action { /// Set the window size increment. SetResizeIncrements(Id, Option), + + /// Redraws all the windows. + RedrawAll, + + /// Recomputes the layouts of all the windows. + RelayoutAll, } /// Subscribes to the frames of the window of the running application. diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 9ede982d..b7511d71 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -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( settings: graphics::Settings, compatible_window: W, + _shell: Shell, backend: Option<&str>, ) -> Result { match backend { diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 7160421b..d4f36964 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -31,6 +31,7 @@ iced_graphics.workspace = true bitflags.workspace = true bytemuck.workspace = true +bytes.workspace = true futures.workspace = true glam.workspace = true cryoglyph.workspace = true diff --git a/wgpu/src/engine.rs b/wgpu/src/engine.rs index 574223a4..4bc30b7f 100644 --- a/wgpu/src/engine.rs +++ b/wgpu/src/engine.rs @@ -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>, + _shell: Shell, } impl Engine { @@ -27,6 +28,7 @@ impl Engine { queue: wgpu::Queue, format: wgpu::TextureFormat, antialiasing: Option, // 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, + ) } } diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 4e01fbe5..6cc28ce0 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -10,20 +10,20 @@ pub use layer::Layer; 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::graphics::color; -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, + texture_layout: wgpu::BindGroupLayout, layers: Vec, } @@ -31,8 +31,19 @@ impl Atlas { pub fn new( device: &wgpu::Device, backend: wgpu::Backend, - texture_layout: Arc, + 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 +53,8 @@ impl Atlas { }; let extent = wgpu::Extent3d { - width: SIZE, - height: SIZE, + width: size, + height: size, depth_or_array_layers: layers.len() as u32, }; @@ -80,6 +91,7 @@ impl Atlas { }); Atlas { + size, backend, texture, texture_view, @@ -93,14 +105,11 @@ impl Atlas { &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], @@ -111,7 +120,7 @@ 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 }; @@ -127,7 +136,13 @@ impl Atlas { 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]; + 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 { let offset = row * padded_width; @@ -140,13 +155,12 @@ impl Atlas { match &entry { Entry::Contiguous(allocation) => { self.upload_allocation( - &padded_data, + buffer_slice.buffer(), width, height, padding, - 0, + buffer_slice.offset() as usize, allocation, - device, encoder, ); } @@ -156,13 +170,12 @@ impl Atlas { let offset = (y * padded_width as u32 + 4 * x) as usize; self.upload_allocation( - &padded_data, + buffer_slice.buffer(), width, height, padding, - offset, + offset + buffer_slice.offset() as usize, &fragment.allocation, - device, encoder, ); } @@ -172,7 +185,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::(), ); @@ -198,7 +211,7 @@ impl Atlas { fn allocate(&mut self, width: u32, height: u32) -> Option { // 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 +221,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 +272,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 +280,7 @@ impl Atlas { return Some(Entry::Contiguous(Allocation::Partial { region, layer: i, + atlas_size: self.size, })); } } @@ -271,6 +289,7 @@ impl Atlas { return Some(Entry::Contiguous(Allocation::Partial { region, layer: i, + atlas_size: self.size, })); } } @@ -279,7 +298,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 +306,7 @@ impl Atlas { return Some(Entry::Contiguous(Allocation::Partial { region, layer: self.layers.len() - 1, + atlas_size: self.size, })); } @@ -298,10 +318,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,18 +336,15 @@ impl Atlas { } fn upload_allocation( - &mut self, - data: &[u8], + &self, + buffer: &wgpu::Buffer, image_width: u32, image_height: u32, padding: u32, offset: usize, allocation: &Allocation, - device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, ) { - use wgpu::util::DeviceExt; - let (x, y) = allocation.position(); let Size { width, height } = allocation.size(); let layer = allocation.layer(); @@ -338,16 +355,9 @@ impl Atlas { 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( wgpu::TexelCopyBufferInfo { - buffer: &buffer, + buffer, layout: wgpu::TexelCopyBufferLayout { offset: offset as u64, bytes_per_row: Some(4 * image_width + padding), @@ -373,6 +383,7 @@ impl Atlas { amount: usize, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, + backend: wgpu::Backend, ) { if amount == 0 { 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 // 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 +402,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 +451,8 @@ impl Atlas { aspect: wgpu::TextureAspect::default(), }, wgpu::Extent3d { - width: SIZE, - height: SIZE, + width: self.size, + height: self.size, depth_or_array_layers: 1, }, ); diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs index 11289771..2e8a7b14 100644 --- a/wgpu/src/image/atlas/allocation.rs +++ b/wgpu/src/image/atlas/allocation.rs @@ -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,21 @@ impl Allocation { pub fn size(&self) -> Size { match self { 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 { 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, } } } diff --git a/wgpu/src/image/cache.rs b/wgpu/src/image/cache.rs index 94f7071d..32617f01 100644 --- a/wgpu/src/image/cache.rs +++ b/wgpu/src/image/cache.rs @@ -1,47 +1,79 @@ use crate::core::{self, Size}; +use crate::graphics::Shell; use crate::image::atlas::{self, Atlas}; -use std::sync::Arc; +use std::collections::BTreeSet; +use std::sync::mpsc; +use std::thread; #[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(feature = "image")] + jobs: mpsc::SyncSender, + #[cfg(feature = "image")] + work: mpsc::Receiver, + #[cfg(feature = "image")] + worker_: Option>, } impl Cache { pub fn new( device: &wgpu::Device, + queue: &wgpu::Queue, backend: wgpu::Backend, - layout: Arc, + layout: wgpu::BindGroupLayout, + shell: &Shell, ) -> 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 { atlas: Atlas::new(device, backend, layout), #[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")] 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")] pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size { - 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")] pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size { + // TODO: Concurrency self.vector.load(handle).viewport_dimensions() } @@ -50,9 +82,63 @@ 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, &wgpu::BindGroup)> { + 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")] @@ -60,27 +146,261 @@ impl Cache { &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, handle: &core::svg::Handle, color: Option, size: [f32; 2], scale: f32, - ) -> Option<&atlas::Entry> { - self.vector.upload( - device, - encoder, - handle, - color, - size, - scale, - &mut self.atlas, - ) + ) -> Option<(&atlas::Entry, &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| { + let _ = self.jobs.send(Job::Drop(bind_group)); + }); #[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, + jobs: mpsc::SyncSender, +} + +#[cfg(feature = "image")] +fn load_image<'a>( + cache: &'a mut crate::image::raster::Cache, + pending: &mut BTreeSet, + jobs: &mut mpsc::SyncSender, + 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, + output: mpsc::SyncSender, +} + +#[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, mpsc::Receiver) { + 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)); } } diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index 51d2acef..d530d9b4 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -11,11 +11,11 @@ mod vector; use crate::Buffer; use crate::core::{Rectangle, Size, Transformation}; +use crate::graphics::Shell; use bytemuck::{Pod, Zeroable}; use std::mem; -use std::sync::Arc; pub use crate::graphics::Image; @@ -27,7 +27,7 @@ pub struct Pipeline { backend: wgpu::Backend, nearest_sampler: wgpu::Sampler, linear_sampler: wgpu::Sampler, - texture_layout: Arc, + texture_layout: wgpu::BindGroupLayout, constant_layout: wgpu::BindGroupLayout, } @@ -196,13 +196,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, + ) } } @@ -228,16 +239,50 @@ impl State { transformation: Transformation, scale: f32, ) { - let nearest_instances: &mut Vec = &mut Vec::new(); - let linear_instances: &mut Vec = &mut Vec::new(); + if self.layers.len() <= self.prepare_layer { + 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 { match &image { #[cfg(feature = "image")] Image::Raster(image, bounds) => { - if let Some(atlas_entry) = - cache.upload_raster(device, encoder, &image.handle) + 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( + device, + encoder, + belt, + atlas, + nearest_instances, + linear_instances, + ); + + *atlas = bind_group.clone(); + nearest_instances.clear(); + linear_instances.clear(); + } + _ => {} + } + add_instances( [bounds.x, bounds.y], [bounds.width, bounds.height], @@ -257,20 +302,44 @@ impl State { } } #[cfg(not(feature = "image"))] - Image::Raster { .. } => {} + Image::Raster { .. } => continue, #[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, - ) { + if let Some((atlas_entry, bind_group)) = cache + .upload_vector( + device, + encoder, + belt, + &svg.handle, + 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( [bounds.x, bounds.y], size, @@ -283,42 +352,27 @@ impl State { } } #[cfg(not(feature = "svg"))] - Image::Vector { .. } => {} + Image::Vector { .. } => continue, } } - if nearest_instances.is_empty() && linear_instances.is_empty() { - return; - } - - if self.layers.len() <= self.prepare_layer { - self.layers.push(Layer::new( + if !nearest_instances.is_empty() || !linear_instances.is_empty() { + layer.push( device, - &pipeline.constant_layout, - &pipeline.nearest_sampler, - &pipeline.linear_sampler, - )); + encoder, + belt, + &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; } pub fn render<'a>( &'a self, pipeline: &'a Pipeline, - cache: &'a Cache, layer: usize, bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, @@ -333,13 +387,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 +403,18 @@ impl State { #[derive(Debug)] struct Layer { uniforms: wgpu::Buffer, - nearest: Data, - linear: Data, + instances: Buffer, + total: usize, + nearest: Vec, + nearest_layout: wgpu::BindGroup, + linear: Vec, + linear_layout: wgpu::BindGroup, +} + +#[derive(Debug)] +struct Group { + atlas: wgpu::BindGroup, + instance_count: usize, } impl Layer { @@ -365,16 +431,69 @@ 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, + total: 0, + nearest: Vec::new(), + nearest_layout, + linear: Vec::new(), + linear_layout, } } @@ -383,8 +502,6 @@ 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, ) { @@ -404,94 +521,79 @@ impl Layer { device, ) .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>) { - self.nearest.render(render_pass); - self.linear.render(render_pass); - } -} - -#[derive(Debug)] -struct Data { - constants: wgpu::BindGroup, - instances: Buffer, - 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( + fn push( &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, 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 { - return; + let _ = self.instances.resize(device, self.total + new); + + 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()); - let _ = self.instances.write(device, encoder, belt, 0, instances); + if !linear.is_empty() { + 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>) { - 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, &[]); + 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 Size { width, height } = allocation.size(); let layer = allocation.layer(); + let atlas_size = allocation.atlas_size(); let instance = Instance { _position: position, @@ -605,12 +708,12 @@ fn add_instance( _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 + 0.5) / atlas_size as f32, + (y as f32 + 0.5) / 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 - 1.0) / atlas_size as f32, + (height as f32 - 1.0) / atlas_size as f32, ], _layer: layer as u32, _snap: snap as u32, diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 470c39e2..57662433 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -6,13 +6,18 @@ use crate::image::atlas::{self, Atlas}; use rustc_hash::{FxHashMap, FxHashSet}; +type Image = image_rs::ImageBuffer, image::Bytes>; + /// Entry in cache corresponding to an image handle #[derive(Debug)] pub enum Memory { /// Image data on host - Host(image_rs::ImageBuffer, image::Bytes>), + Host(Image), /// Storage entry - Device(atlas::Entry), + Device { + entry: atlas::Entry, + bind_group: Option, + }, /// Image not found NotFound, /// Invalid image data @@ -20,7 +25,20 @@ pub enum 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 { match self { Memory::Host(image) => { @@ -28,14 +46,20 @@ impl Memory { Size::new(width, height) } - Memory::Device(entry) => entry.size(), + Memory::Device { entry, .. } => entry.size(), Memory::NotFound => Size::new(1, 1), Memory::Invalid => Size::new(1, 1), } } + + pub fn host(&self) -> Option { + match self { + Memory::Host(image) => Some(image.clone()), + Memory::Device { .. } | Memory::NotFound | Memory::Invalid => None, + } + } } -/// Caches image raster data #[derive(Debug, Default)] pub struct Cache { map: FxHashMap, @@ -44,51 +68,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(wgpu::BindGroup), + ) { // Only trim if new entries have landed in the `Cache` if !self.should_trim { return; @@ -99,8 +100,12 @@ impl Cache { self.map.retain(|k, memory| { let retain = hits.contains(k); - if !retain && let Memory::Device(entry) = memory { - atlas.remove(entry); + if !retain && 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 +114,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()) - } } diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index e55ade38..9949cf39 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -94,6 +94,7 @@ impl Cache { &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, handle: &svg::Handle, color: Option, [width, height]: [f32; 2], @@ -167,8 +168,8 @@ 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}"); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 6b34889e..93733fc5 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -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 @@ -460,8 +458,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::::from(Rectangle::with_size( @@ -632,7 +628,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, @@ -910,6 +905,7 @@ impl renderer::Headless for Renderer { wgpu::TextureFormat::Rgba8Unorm }, Some(graphics::Antialiasing::MSAAx4), + Shell::headless(), ); Some(Self::new(engine, default_font, default_text_size)) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index c1ba7df2..8a0bb72a 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -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( settings: Settings, compatible_window: Option, + shell: Shell, ) -> Result { 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( settings: Settings, compatible_window: W, + shell: Shell, ) -> Result { - 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( settings: graphics::Settings, compatible_window: W, + shell: Shell, backend: Option<&str>, ) -> Result { 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", diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 75269d6a..cd163f16 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -49,7 +49,7 @@ 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::system; use crate::runtime::user_interface::{self, UserInterface}; use crate::runtime::{Action, Task}; @@ -587,8 +587,10 @@ async fn run_instance

( let default_fonts = default_fonts.clone(); async move { + let shell = Shell::new(proxy.clone()); + let mut compositor = - ::Compositor::new(graphics_settings, window).await; + ::Compositor::new(graphics_settings, window, shell).await; if let Ok(compositor) = &mut compositor { for font in default_fonts { @@ -824,7 +826,7 @@ async fn run_instance

( .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 +949,9 @@ async fn run_instance

( 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 +1650,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) => { diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index 92758c5f..5a56659f 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -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 Sink> for Proxy { Poll::Ready(Ok(())) } } + +impl shell::Notifier for Proxy +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)); + } +}