diff options
Diffstat (limited to '')
| -rw-r--r-- | wgpu/src/color.rs | 68 | ||||
| -rw-r--r-- | wgpu/src/engine.rs | 7 | ||||
| -rw-r--r-- | wgpu/src/geometry.rs | 13 | ||||
| -rw-r--r-- | wgpu/src/image/atlas.rs | 53 | ||||
| -rw-r--r-- | wgpu/src/image/atlas/allocator.rs | 4 | ||||
| -rw-r--r-- | wgpu/src/image/atlas/layer.rs | 8 | ||||
| -rw-r--r-- | wgpu/src/image/cache.rs | 43 | ||||
| -rw-r--r-- | wgpu/src/image/mod.rs | 91 | ||||
| -rw-r--r-- | wgpu/src/image/raster.rs | 17 | ||||
| -rw-r--r-- | wgpu/src/image/vector.rs | 8 | ||||
| -rw-r--r-- | wgpu/src/layer.rs | 13 | ||||
| -rw-r--r-- | wgpu/src/lib.rs | 42 | ||||
| -rw-r--r-- | wgpu/src/primitive.rs | 2 | ||||
| -rw-r--r-- | wgpu/src/settings.rs | 26 | ||||
| -rw-r--r-- | wgpu/src/shader/image.wgsl | 37 | ||||
| -rw-r--r-- | wgpu/src/text.rs | 178 | ||||
| -rw-r--r-- | wgpu/src/triangle.rs | 16 | ||||
| -rw-r--r-- | wgpu/src/window/compositor.rs | 26 | 
18 files changed, 462 insertions, 190 deletions
diff --git a/wgpu/src/color.rs b/wgpu/src/color.rs index 890f3f89..9d593d9c 100644 --- a/wgpu/src/color.rs +++ b/wgpu/src/color.rs @@ -1,5 +1,7 @@  use std::borrow::Cow; +use wgpu::util::DeviceExt; +  pub fn convert(      device: &wgpu::Device,      encoder: &mut wgpu::CommandEncoder, @@ -15,28 +17,58 @@ pub fn convert(          ..wgpu::SamplerDescriptor::default()      }); -    //sampler in 0 -    let sampler_layout = +    #[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)] +    #[repr(C)] +    struct Ratio { +        u: f32, +        v: f32, +    } + +    let ratio = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { +        label: Some("iced-wgpu::triangle::msaa ratio"), +        contents: bytemuck::bytes_of(&Ratio { u: 1.0, v: 1.0 }), +        usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, +    }); + +    let constant_layout =          device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {              label: Some("iced_wgpu.offscreen.blit.sampler_layout"), -            entries: &[wgpu::BindGroupLayoutEntry { -                binding: 0, -                visibility: wgpu::ShaderStages::FRAGMENT, -                ty: wgpu::BindingType::Sampler( -                    wgpu::SamplerBindingType::NonFiltering, -                ), -                count: None, -            }], +            entries: &[ +                wgpu::BindGroupLayoutEntry { +                    binding: 0, +                    visibility: wgpu::ShaderStages::FRAGMENT, +                    ty: wgpu::BindingType::Sampler( +                        wgpu::SamplerBindingType::NonFiltering, +                    ), +                    count: None, +                }, +                wgpu::BindGroupLayoutEntry { +                    binding: 1, +                    visibility: wgpu::ShaderStages::VERTEX, +                    ty: wgpu::BindingType::Buffer { +                        ty: wgpu::BufferBindingType::Uniform, +                        has_dynamic_offset: false, +                        min_binding_size: None, +                    }, +                    count: None, +                }, +            ],          }); -    let sampler_bind_group = +    let constant_bind_group =          device.create_bind_group(&wgpu::BindGroupDescriptor {              label: Some("iced_wgpu.offscreen.sampler.bind_group"), -            layout: &sampler_layout, -            entries: &[wgpu::BindGroupEntry { -                binding: 0, -                resource: wgpu::BindingResource::Sampler(&sampler), -            }], +            layout: &constant_layout, +            entries: &[ +                wgpu::BindGroupEntry { +                    binding: 0, +                    resource: wgpu::BindingResource::Sampler(&sampler), +                }, +                wgpu::BindGroupEntry { +                    binding: 1, +                    resource: ratio.as_entire_binding(), +                }, +            ],          });      let texture_layout = @@ -59,7 +91,7 @@ pub fn convert(      let pipeline_layout =          device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {              label: Some("iced_wgpu.offscreen.blit.pipeline_layout"), -            bind_group_layouts: &[&sampler_layout, &texture_layout], +            bind_group_layouts: &[&constant_layout, &texture_layout],              push_constant_ranges: &[],          }); @@ -152,7 +184,7 @@ pub fn convert(      });      pass.set_pipeline(&pipeline); -    pass.set_bind_group(0, &sampler_bind_group, &[]); +    pass.set_bind_group(0, &constant_bind_group, &[]);      pass.set_bind_group(1, &texture_bind_group, &[]);      pass.draw(0..6, 0..1); diff --git a/wgpu/src/engine.rs b/wgpu/src/engine.rs index 96cd6db8..782fd58c 100644 --- a/wgpu/src/engine.rs +++ b/wgpu/src/engine.rs @@ -59,8 +59,11 @@ impl Engine {      }      #[cfg(any(feature = "image", feature = "svg"))] -    pub fn image_cache(&self) -> &crate::image::cache::Shared { -        self.image_pipeline.cache() +    pub fn create_image_cache( +        &self, +        device: &wgpu::Device, +    ) -> crate::image::Cache { +        self.image_pipeline.create_cache(device)      }      pub fn submit( diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 60967082..f6213e1d 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -3,6 +3,7 @@ use crate::core::text::LineHeight;  use crate::core::{      Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,  }; +use crate::graphics::cache::{self, Cached};  use crate::graphics::color;  use crate::graphics::geometry::fill::{self, Fill};  use crate::graphics::geometry::{ @@ -10,7 +11,7 @@ use crate::graphics::geometry::{  };  use crate::graphics::gradient::{self, Gradient};  use crate::graphics::mesh::{self, Mesh}; -use crate::graphics::{self, Cached, Text}; +use crate::graphics::{self, Text};  use crate::text;  use crate::triangle; @@ -38,7 +39,11 @@ impl Cached for Geometry {          Geometry::Cached(cache.clone())      } -    fn cache(self, previous: Option<Self::Cache>) -> Self::Cache { +    fn cache( +        self, +        group: cache::Group, +        previous: Option<Self::Cache>, +    ) -> Self::Cache {          match self {              Self::Live { meshes, text } => {                  if let Some(mut previous) = previous { @@ -51,14 +56,14 @@ impl Cached for Geometry {                      if let Some(cache) = &mut previous.text {                          cache.update(text);                      } else { -                        previous.text = text::Cache::new(text); +                        previous.text = text::Cache::new(group, text);                      }                      previous                  } else {                      Cache {                          meshes: triangle::Cache::new(meshes), -                        text: text::Cache::new(text), +                        text: text::Cache::new(group, text),                      }                  }              } diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index ea36e06d..a1ec0d7b 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -15,15 +15,23 @@ pub const SIZE: u32 = 2048;  use crate::core::Size;  use crate::graphics::color; +use std::sync::Arc; +  #[derive(Debug)]  pub struct Atlas {      texture: wgpu::Texture,      texture_view: wgpu::TextureView, +    texture_bind_group: wgpu::BindGroup, +    texture_layout: Arc<wgpu::BindGroupLayout>,      layers: Vec<Layer>,  }  impl Atlas { -    pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self { +    pub fn new( +        device: &wgpu::Device, +        backend: wgpu::Backend, +        texture_layout: Arc<wgpu::BindGroupLayout>, +    ) -> Self {          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` @@ -60,15 +68,27 @@ impl Atlas {              ..Default::default()          }); +        let texture_bind_group = +            device.create_bind_group(&wgpu::BindGroupDescriptor { +                label: Some("iced_wgpu::image texture atlas bind group"), +                layout: &texture_layout, +                entries: &[wgpu::BindGroupEntry { +                    binding: 0, +                    resource: wgpu::BindingResource::TextureView(&texture_view), +                }], +            }); +          Atlas {              texture,              texture_view, +            texture_bind_group, +            texture_layout,              layers,          }      } -    pub fn view(&self) -> &wgpu::TextureView { -        &self.texture_view +    pub fn bind_group(&self) -> &wgpu::BindGroup { +        &self.texture_bind_group      }      pub fn layer_count(&self) -> usize { @@ -94,7 +114,7 @@ impl Atlas {              entry          }; -        log::info!("Allocated atlas entry: {entry:?}"); +        log::debug!("Allocated atlas entry: {entry:?}");          // It is a webgpu requirement that:          //   BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 @@ -147,13 +167,20 @@ impl Atlas {              }          } -        log::info!("Current atlas: {self:?}"); +        if log::log_enabled!(log::Level::Debug) { +            log::debug!( +                "Atlas layers: {} (busy: {}, allocations: {})", +                self.layer_count(), +                self.layers.iter().filter(|layer| !layer.is_empty()).count(), +                self.layers.iter().map(Layer::allocations).sum::<usize>(), +            ); +        }          Some(entry)      }      pub fn remove(&mut self, entry: &Entry) { -        log::info!("Removing atlas entry: {entry:?}"); +        log::debug!("Removing atlas entry: {entry:?}");          match entry {              Entry::Contiguous(allocation) => { @@ -266,7 +293,7 @@ impl Atlas {      }      fn deallocate(&mut self, allocation: &Allocation) { -        log::info!("Deallocating atlas: {allocation:?}"); +        log::debug!("Deallocating atlas: {allocation:?}");          match allocation {              Allocation::Full { layer } => { @@ -414,5 +441,17 @@ impl Atlas {                  dimension: Some(wgpu::TextureViewDimension::D2Array),                  ..Default::default()              }); + +        self.texture_bind_group = +            device.create_bind_group(&wgpu::BindGroupDescriptor { +                label: Some("iced_wgpu::image texture atlas bind group"), +                layout: &self.texture_layout, +                entries: &[wgpu::BindGroupEntry { +                    binding: 0, +                    resource: wgpu::BindingResource::TextureView( +                        &self.texture_view, +                    ), +                }], +            });      }  } diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs index 204a5c26..a51ac1f5 100644 --- a/wgpu/src/image/atlas/allocator.rs +++ b/wgpu/src/image/atlas/allocator.rs @@ -33,6 +33,10 @@ impl Allocator {      pub fn is_empty(&self) -> bool {          self.allocations == 0      } + +    pub fn allocations(&self) -> usize { +        self.allocations +    }  }  pub struct Region { diff --git a/wgpu/src/image/atlas/layer.rs b/wgpu/src/image/atlas/layer.rs index cf089601..fd6788d9 100644 --- a/wgpu/src/image/atlas/layer.rs +++ b/wgpu/src/image/atlas/layer.rs @@ -11,4 +11,12 @@ impl Layer {      pub fn is_empty(&self) -> bool {          matches!(self, Layer::Empty)      } + +    pub fn allocations(&self) -> usize { +        match self { +            Layer::Empty => 0, +            Layer::Busy(allocator) => allocator.allocations(), +            Layer::Full => 1, +        } +    }  } diff --git a/wgpu/src/image/cache.rs b/wgpu/src/image/cache.rs index 32118ed6..94f7071d 100644 --- a/wgpu/src/image/cache.rs +++ b/wgpu/src/image/cache.rs @@ -1,8 +1,7 @@  use crate::core::{self, Size};  use crate::image::atlas::{self, Atlas}; -use std::cell::{RefCell, RefMut}; -use std::rc::Rc; +use std::sync::Arc;  #[derive(Debug)]  pub struct Cache { @@ -14,9 +13,13 @@ pub struct Cache {  }  impl Cache { -    pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self { +    pub fn new( +        device: &wgpu::Device, +        backend: wgpu::Backend, +        layout: Arc<wgpu::BindGroupLayout>, +    ) -> Self {          Self { -            atlas: Atlas::new(device, backend), +            atlas: Atlas::new(device, backend, layout),              #[cfg(feature = "image")]              raster: crate::image::raster::Cache::default(),              #[cfg(feature = "svg")] @@ -24,6 +27,10 @@ impl Cache {          }      } +    pub fn bind_group(&self) -> &wgpu::BindGroup { +        self.atlas.bind_group() +    } +      pub fn layer_count(&self) -> usize {          self.atlas.layer_count()      } @@ -69,21 +76,6 @@ impl Cache {          )      } -    pub fn create_bind_group( -        &self, -        device: &wgpu::Device, -        layout: &wgpu::BindGroupLayout, -    ) -> wgpu::BindGroup { -        device.create_bind_group(&wgpu::BindGroupDescriptor { -            label: Some("iced_wgpu::image texture atlas bind group"), -            layout, -            entries: &[wgpu::BindGroupEntry { -                binding: 0, -                resource: wgpu::BindingResource::TextureView(self.atlas.view()), -            }], -        }) -    } -      pub fn trim(&mut self) {          #[cfg(feature = "image")]          self.raster.trim(&mut self.atlas); @@ -92,16 +84,3 @@ impl Cache {          self.vector.trim(&mut self.atlas);      }  } - -#[derive(Debug, Clone)] -pub struct Shared(Rc<RefCell<Cache>>); - -impl Shared { -    pub fn new(cache: Cache) -> Self { -        Self(Rc::new(RefCell::new(cache))) -    } - -    pub fn lock(&self) -> RefMut<'_, Cache> { -        self.0.borrow_mut() -    } -} diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index 86731cbf..daa2fe16 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -13,7 +13,9 @@ use crate::core::{Rectangle, Size, Transformation};  use crate::Buffer;  use bytemuck::{Pod, Zeroable}; +  use std::mem; +use std::sync::Arc;  pub use crate::graphics::Image; @@ -22,13 +24,11 @@ pub type Batch = Vec<Image>;  #[derive(Debug)]  pub struct Pipeline {      pipeline: wgpu::RenderPipeline, +    backend: wgpu::Backend,      nearest_sampler: wgpu::Sampler,      linear_sampler: wgpu::Sampler, -    texture: wgpu::BindGroup, -    texture_version: usize, -    texture_layout: wgpu::BindGroupLayout, +    texture_layout: Arc<wgpu::BindGroupLayout>,      constant_layout: wgpu::BindGroupLayout, -    cache: cache::Shared,      layers: Vec<Layer>,      prepare_layer: usize,  } @@ -135,14 +135,20 @@ impl Pipeline {                          attributes: &wgpu::vertex_attr_array!(                              // Position                              0 => Float32x2, -                            // Scale +                            // Center                              1 => Float32x2, -                            // Atlas position +                            // Scale                              2 => Float32x2, +                            // Rotation +                            3 => Float32, +                            // Opacity +                            4 => Float32, +                            // Atlas position +                            5 => Float32x2,                              // Atlas scale -                            3 => Float32x2, +                            6 => Float32x2,                              // Layer -                            4 => Sint32, +                            7 => Sint32,                          ),                      }],                  }, @@ -180,25 +186,20 @@ impl Pipeline {                  multiview: None,              }); -        let cache = Cache::new(device, backend); -        let texture = cache.create_bind_group(device, &texture_layout); -          Pipeline {              pipeline, +            backend,              nearest_sampler,              linear_sampler, -            texture, -            texture_version: cache.layer_count(), -            texture_layout, +            texture_layout: Arc::new(texture_layout),              constant_layout, -            cache: cache::Shared::new(cache),              layers: Vec::new(),              prepare_layer: 0,          }      } -    pub fn cache(&self) -> &cache::Shared { -        &self.cache +    pub fn create_cache(&self, device: &wgpu::Device) -> Cache { +        Cache::new(device, self.backend, self.texture_layout.clone())      }      pub fn prepare( @@ -206,6 +207,7 @@ impl Pipeline {          device: &wgpu::Device,          encoder: &mut wgpu::CommandEncoder,          belt: &mut wgpu::util::StagingBelt, +        cache: &mut Cache,          images: &Batch,          transformation: Transformation,          scale: f32, @@ -215,8 +217,6 @@ impl Pipeline {          let nearest_instances: &mut Vec<Instance> = &mut Vec::new();          let linear_instances: &mut Vec<Instance> = &mut Vec::new(); -        let mut cache = self.cache.lock(); -          for image in images {              match &image {                  #[cfg(feature = "image")] @@ -224,6 +224,8 @@ impl Pipeline {                      handle,                      filter_method,                      bounds, +                    rotation, +                    opacity,                  } => {                      if let Some(atlas_entry) =                          cache.upload_raster(device, encoder, handle) @@ -231,6 +233,8 @@ impl Pipeline {                          add_instances(                              [bounds.x, bounds.y],                              [bounds.width, bounds.height], +                            f32::from(*rotation), +                            *opacity,                              atlas_entry,                              match filter_method {                                  crate::core::image::FilterMethod::Nearest => { @@ -251,6 +255,8 @@ impl Pipeline {                      handle,                      color,                      bounds, +                    rotation, +                    opacity,                  } => {                      let size = [bounds.width, bounds.height]; @@ -260,6 +266,8 @@ impl Pipeline {                          add_instances(                              [bounds.x, bounds.y],                              size, +                            f32::from(*rotation), +                            *opacity,                              atlas_entry,                              nearest_instances,                          ); @@ -274,16 +282,6 @@ impl Pipeline {              return;          } -        let texture_version = cache.layer_count(); - -        if self.texture_version != texture_version { -            log::info!("Atlas has grown. Recreating bind group..."); - -            self.texture = -                cache.create_bind_group(device, &self.texture_layout); -            self.texture_version = texture_version; -        } -          if self.layers.len() <= self.prepare_layer {              self.layers.push(Layer::new(                  device, @@ -309,6 +307,7 @@ impl Pipeline {      pub fn render<'a>(          &'a self, +        cache: &'a Cache,          layer: usize,          bounds: Rectangle<u32>,          render_pass: &mut wgpu::RenderPass<'a>, @@ -323,14 +322,13 @@ impl Pipeline {                  bounds.height,              ); -            render_pass.set_bind_group(1, &self.texture, &[]); +            render_pass.set_bind_group(1, cache.bind_group(), &[]);              layer.render(render_pass);          }      }      pub fn end_frame(&mut self) { -        self.cache.lock().trim();          self.prepare_layer = 0;      }  } @@ -487,7 +485,10 @@ impl Data {  #[derive(Debug, Clone, Copy, Zeroable, Pod)]  struct Instance {      _position: [f32; 2], +    _center: [f32; 2],      _size: [f32; 2], +    _rotation: f32, +    _opacity: f32,      _position_in_atlas: [f32; 2],      _size_in_atlas: [f32; 2],      _layer: u32, @@ -506,12 +507,27 @@ struct Uniforms {  fn add_instances(      image_position: [f32; 2],      image_size: [f32; 2], +    rotation: f32, +    opacity: f32,      entry: &atlas::Entry,      instances: &mut Vec<Instance>,  ) { +    let center = [ +        image_position[0] + image_size[0] / 2.0, +        image_position[1] + image_size[1] / 2.0, +    ]; +      match entry {          atlas::Entry::Contiguous(allocation) => { -            add_instance(image_position, image_size, allocation, instances); +            add_instance( +                image_position, +                center, +                image_size, +                rotation, +                opacity, +                allocation, +                instances, +            );          }          atlas::Entry::Fragmented { fragments, size } => {              let scaling_x = image_size[0] / size.width as f32; @@ -537,7 +553,10 @@ fn add_instances(                      fragment_height as f32 * scaling_y,                  ]; -                add_instance(position, size, allocation, instances); +                add_instance( +                    position, center, size, rotation, opacity, allocation, +                    instances, +                );              }          }      } @@ -546,7 +565,10 @@ fn add_instances(  #[inline]  fn add_instance(      position: [f32; 2], +    center: [f32; 2],      size: [f32; 2], +    rotation: f32, +    opacity: f32,      allocation: &atlas::Allocation,      instances: &mut Vec<Instance>,  ) { @@ -556,7 +578,10 @@ fn add_instance(      let instance = Instance {          _position: position, +        _center: center,          _size: size, +        _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, diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 441b294f..4d3c3125 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -10,7 +10,7 @@ use rustc_hash::{FxHashMap, FxHashSet};  #[derive(Debug)]  pub enum Memory {      /// Image data on host -    Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, Vec<u8>>), +    Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>),      /// Storage entry      Device(atlas::Entry),      /// Image not found @@ -38,8 +38,9 @@ impl Memory {  /// Caches image raster data  #[derive(Debug, Default)]  pub struct Cache { -    map: FxHashMap<u64, Memory>, -    hits: FxHashSet<u64>, +    map: FxHashMap<image::Id, Memory>, +    hits: FxHashSet<image::Id>, +    should_trim: bool,  }  impl Cache { @@ -50,11 +51,13 @@ impl Cache {          }          let memory = match graphics::image::load(handle) { -            Ok(image) => Memory::Host(image.to_rgba8()), +            Ok(image) => Memory::Host(image),              Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound,              Err(_) => Memory::Invalid,          }; +        self.should_trim = true; +          self.insert(handle, memory);          self.get(handle).unwrap()      } @@ -86,6 +89,11 @@ impl Cache {      /// Trim cache misses from cache      pub fn trim(&mut self, atlas: &mut Atlas) { +        // Only trim if new entries have landed in the `Cache` +        if !self.should_trim { +            return; +        } +          let hits = &self.hits;          self.map.retain(|k, memory| { @@ -101,6 +109,7 @@ impl Cache {          });          self.hits.clear(); +        self.should_trim = false;      }      fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index d681b2e6..c6d829af 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -37,6 +37,7 @@ pub struct Cache {      rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,      svg_hits: FxHashSet<u64>,      rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>, +    should_trim: bool,  }  type ColorFilter = Option<[u8; 4]>; @@ -76,6 +77,8 @@ impl Cache {              }          } +        self.should_trim = true; +          let _ = self.svgs.insert(handle.id(), svg);          self.svgs.get(&handle.id()).unwrap()      } @@ -176,6 +179,10 @@ impl Cache {      /// Load svg and upload raster data      pub fn trim(&mut self, atlas: &mut Atlas) { +        if !self.should_trim { +            return; +        } +          let svg_hits = &self.svg_hits;          let rasterized_hits = &self.rasterized_hits; @@ -191,6 +198,7 @@ impl Cache {          });          self.svg_hits.clear();          self.rasterized_hits.clear(); +        self.should_trim = false;      }  } diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 9526c5a8..9551311d 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -1,5 +1,6 @@ -use crate::core::renderer; -use crate::core::{Background, Color, Point, Rectangle, Transformation}; +use crate::core::{ +    renderer, Background, Color, Point, Radians, Rectangle, Transformation, +};  use crate::graphics;  use crate::graphics::color;  use crate::graphics::layer; @@ -117,11 +118,15 @@ impl Layer {          filter_method: crate::core::image::FilterMethod,          bounds: Rectangle,          transformation: Transformation, +        rotation: Radians, +        opacity: f32,      ) {          let image = Image::Raster {              handle,              filter_method,              bounds: bounds * transformation, +            rotation, +            opacity,          };          self.images.push(image); @@ -133,11 +138,15 @@ impl Layer {          color: Option<Color>,          bounds: Rectangle,          transformation: Transformation, +        rotation: Radians, +        opacity: f32,      ) {          let svg = Image::Vector {              handle,              color,              bounds: bounds * transformation, +            rotation, +            opacity,          };          self.images.push(svg); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 178522de..1fbdbe9a 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -62,6 +62,7 @@ pub use geometry::Geometry;  use crate::core::{      Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, +    Vector,  };  use crate::graphics::text::{Editor, Paragraph};  use crate::graphics::Viewport; @@ -81,11 +82,12 @@ pub struct Renderer {      // TODO: Centralize all the image feature handling      #[cfg(any(feature = "svg", feature = "image"))] -    image_cache: image::cache::Shared, +    image_cache: std::cell::RefCell<image::Cache>,  }  impl Renderer {      pub fn new( +        _device: &wgpu::Device,          _engine: &Engine,          default_font: Font,          default_text_size: Pixels, @@ -99,7 +101,9 @@ impl Renderer {              text_storage: text::Storage::new(),              #[cfg(any(feature = "svg", feature = "image"))] -            image_cache: _engine.image_cache().clone(), +            image_cache: std::cell::RefCell::new( +                _engine.create_image_cache(_device), +            ),          }      } @@ -121,6 +125,9 @@ impl Renderer {          self.triangle_storage.trim();          self.text_storage.trim(); + +        #[cfg(any(feature = "svg", feature = "image"))] +        self.image_cache.borrow_mut().trim();      }      fn prepare( @@ -190,6 +197,7 @@ impl Renderer {                      device,                      encoder,                      &mut engine.staging_belt, +                    &mut self.image_cache.borrow_mut(),                      &layer.images,                      viewport.projection(),                      scale_factor, @@ -245,6 +253,8 @@ 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() as f32;          let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size( @@ -358,6 +368,7 @@ impl Renderer {              #[cfg(any(feature = "svg", feature = "image"))]              if !layer.images.is_empty() {                  engine.image_pipeline.render( +                    &image_cache,                      image_layer,                      scissor_rect,                      &mut render_pass, @@ -378,7 +389,6 @@ impl Renderer {          use crate::core::alignment;          use crate::core::text::Renderer as _;          use crate::core::Renderer as _; -        use crate::core::Vector;          self.with_layer(              Rectangle::with_size(viewport.logical_size()), @@ -509,7 +519,7 @@ impl core::image::Renderer for Renderer {      type Handle = core::image::Handle;      fn measure_image(&self, handle: &Self::Handle) -> Size<u32> { -        self.image_cache.lock().measure_image(handle) +        self.image_cache.borrow_mut().measure_image(handle)      }      fn draw_image( @@ -517,16 +527,25 @@ impl core::image::Renderer for Renderer {          handle: Self::Handle,          filter_method: core::image::FilterMethod,          bounds: Rectangle, +        rotation: core::Radians, +        opacity: f32,      ) {          let (layer, transformation) = self.layers.current_mut(); -        layer.draw_image(handle, filter_method, bounds, transformation); +        layer.draw_image( +            handle, +            filter_method, +            bounds, +            transformation, +            rotation, +            opacity, +        );      }  }  #[cfg(feature = "svg")]  impl core::svg::Renderer for Renderer {      fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> { -        self.image_cache.lock().measure_svg(handle) +        self.image_cache.borrow_mut().measure_svg(handle)      }      fn draw_svg( @@ -534,9 +553,18 @@ impl core::svg::Renderer for Renderer {          handle: core::svg::Handle,          color_filter: Option<Color>,          bounds: Rectangle, +        rotation: core::Radians, +        opacity: f32,      ) {          let (layer, transformation) = self.layers.current_mut(); -        layer.draw_svg(handle, color_filter, bounds, transformation); +        layer.draw_svg( +            handle, +            color_filter, +            bounds, +            transformation, +            rotation, +            opacity, +        );      }  } diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 1313e752..8641f27a 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -67,7 +67,7 @@ pub struct Storage {  impl Storage {      /// Returns `true` if `Storage` contains a type `T`.      pub fn has<T: 'static>(&self) -> bool { -        self.pipelines.get(&TypeId::of::<T>()).is_some() +        self.pipelines.contains_key(&TypeId::of::<T>())      }      /// Inserts the data `T` in to [`Storage`]. diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index a6aea0a5..b3c3cf6a 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -51,3 +51,29 @@ impl From<graphics::Settings> for Settings {          }      }  } + +/// Obtains a [`wgpu::PresentMode`] from the current environment +/// configuration, if set. +/// +/// The value returned by this function can be changed by setting +/// the `ICED_PRESENT_MODE` env variable. The possible values are: +/// +/// - `vsync` → [`wgpu::PresentMode::AutoVsync`] +/// - `no_vsync` → [`wgpu::PresentMode::AutoNoVsync`] +/// - `immediate` → [`wgpu::PresentMode::Immediate`] +/// - `fifo` → [`wgpu::PresentMode::Fifo`] +/// - `fifo_relaxed` → [`wgpu::PresentMode::FifoRelaxed`] +/// - `mailbox` → [`wgpu::PresentMode::Mailbox`] +pub fn present_mode_from_env() -> Option<wgpu::PresentMode> { +    let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?; + +    match present_mode.to_lowercase().as_str() { +        "vsync" => Some(wgpu::PresentMode::AutoVsync), +        "no_vsync" => Some(wgpu::PresentMode::AutoNoVsync), +        "immediate" => Some(wgpu::PresentMode::Immediate), +        "fifo" => Some(wgpu::PresentMode::Fifo), +        "fifo_relaxed" => Some(wgpu::PresentMode::FifoRelaxed), +        "mailbox" => Some(wgpu::PresentMode::Mailbox), +        _ => None, +    } +} diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl index 7b2e5238..accefc17 100644 --- a/wgpu/src/shader/image.wgsl +++ b/wgpu/src/shader/image.wgsl @@ -9,40 +9,55 @@ struct Globals {  struct VertexInput {      @builtin(vertex_index) vertex_index: u32,      @location(0) pos: vec2<f32>, -    @location(1) scale: vec2<f32>, -    @location(2) atlas_pos: vec2<f32>, -    @location(3) atlas_scale: vec2<f32>, -    @location(4) layer: i32, +    @location(1) center: vec2<f32>, +    @location(2) scale: vec2<f32>, +    @location(3) rotation: f32, +    @location(4) opacity: f32, +    @location(5) atlas_pos: vec2<f32>, +    @location(6) atlas_scale: vec2<f32>, +    @location(7) layer: i32,  }  struct VertexOutput {      @builtin(position) position: vec4<f32>,      @location(0) uv: vec2<f32>,      @location(1) layer: f32, // this should be an i32, but naga currently reads that as requiring interpolation. +    @location(2) opacity: f32,  }  @vertex  fn vs_main(input: VertexInput) -> VertexOutput {      var out: VertexOutput; -    let v_pos = vertex_position(input.vertex_index); +    // Generate a vertex position in the range [0, 1] from the vertex index. +    var v_pos = vertex_position(input.vertex_index); +    // Map the vertex position to the atlas texture.      out.uv = vec2<f32>(v_pos * input.atlas_scale + input.atlas_pos);      out.layer = f32(input.layer); +    out.opacity = input.opacity; -    var transform: mat4x4<f32> = mat4x4<f32>( -        vec4<f32>(input.scale.x, 0.0, 0.0, 0.0), -        vec4<f32>(0.0, input.scale.y, 0.0, 0.0), +    // Calculate the vertex position and move the center to the origin +    v_pos = input.pos + v_pos * input.scale - input.center; + +    // Apply the rotation around the center of the image +    let cos_rot = cos(input.rotation); +    let sin_rot = sin(input.rotation); +    let rotate = mat4x4<f32>( +        vec4<f32>(cos_rot, sin_rot, 0.0, 0.0), +        vec4<f32>(-sin_rot, cos_rot, 0.0, 0.0),          vec4<f32>(0.0, 0.0, 1.0, 0.0), -        vec4<f32>(input.pos, 0.0, 1.0) +        vec4<f32>(0.0, 0.0, 0.0, 1.0)      ); -    out.position = globals.transform * transform * vec4<f32>(v_pos, 0.0, 1.0); +    // Calculate the final position of the vertex +    out.position = globals.transform * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0));      return out;  }  @fragment  fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> { -    return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)); +    // Sample the texture at the given UV coordinate and layer. +    return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)) * vec4<f32>(1.0, 1.0, 1.0, input.opacity);  } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 0d01faca..7e683c77 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,12 +1,13 @@  use crate::core::alignment;  use crate::core::{Rectangle, Size, Transformation}; +use crate::graphics::cache;  use crate::graphics::color; -use crate::graphics::text::cache::{self, Cache as BufferCache}; +use crate::graphics::text::cache::{self as text_cache, Cache as BufferCache};  use crate::graphics::text::{font_system, to_color, Editor, Paragraph}; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashMap;  use std::collections::hash_map; -use std::rc::Rc; +use std::rc::{self, Rc};  use std::sync::atomic::{self, AtomicU64};  use std::sync::Arc; @@ -35,6 +36,7 @@ pub enum Item {  #[derive(Debug, Clone)]  pub struct Cache {      id: Id, +    group: cache::Group,      text: Rc<[Text]>,      version: usize,  } @@ -43,7 +45,7 @@ pub struct Cache {  pub struct Id(u64);  impl Cache { -    pub fn new(text: Vec<Text>) -> Option<Self> { +    pub fn new(group: cache::Group, text: Vec<Text>) -> Option<Self> {          static NEXT_ID: AtomicU64 = AtomicU64::new(0);          if text.is_empty() { @@ -52,12 +54,17 @@ impl Cache {          Some(Self {              id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), +            group,              text: Rc::from(text),              version: 0,          })      }      pub fn update(&mut self, text: Vec<Text>) { +        if self.text.is_empty() && text.is_empty() { +            return; +        } +          self.text = Rc::from(text);          self.version += 1;      } @@ -65,16 +72,25 @@ impl Cache {  struct Upload {      renderer: glyphon::TextRenderer, -    atlas: glyphon::TextAtlas,      buffer_cache: BufferCache,      transformation: Transformation,      version: usize, +    group_version: usize, +    text: rc::Weak<[Text]>, +    _atlas: rc::Weak<()>,  }  #[derive(Default)]  pub struct Storage { +    groups: FxHashMap<cache::Group, Group>,      uploads: FxHashMap<Id, Upload>, -    recently_used: FxHashSet<Id>, +} + +struct Group { +    atlas: glyphon::TextAtlas, +    version: usize, +    should_trim: bool, +    handle: Rc<()>, // Keeps track of active uploads  }  impl Storage { @@ -82,12 +98,15 @@ impl Storage {          Self::default()      } -    fn get(&self, cache: &Cache) -> Option<&Upload> { +    fn get(&self, cache: &Cache) -> Option<(&glyphon::TextAtlas, &Upload)> {          if cache.text.is_empty() {              return None;          } -        self.uploads.get(&cache.id) +        self.groups +            .get(&cache.group) +            .map(|group| &group.atlas) +            .zip(self.uploads.get(&cache.id))      }      fn prepare( @@ -101,41 +120,63 @@ impl Storage {          bounds: Rectangle,          target_size: Size<u32>,      ) { +        let group_count = self.groups.len(); + +        let group = self.groups.entry(cache.group).or_insert_with(|| { +            log::debug!( +                "New text atlas: {:?} (total: {})", +                cache.group, +                group_count + 1 +            ); + +            Group { +                atlas: glyphon::TextAtlas::with_color_mode( +                    device, queue, format, COLOR_MODE, +                ), +                version: 0, +                should_trim: false, +                handle: Rc::new(()), +            } +        }); +          match self.uploads.entry(cache.id) {              hash_map::Entry::Occupied(entry) => {                  let upload = entry.into_mut(); -                if !cache.text.is_empty() -                    && (upload.version != cache.version -                        || upload.transformation != new_transformation) +                if upload.version != cache.version +                    || upload.group_version != group.version +                    || upload.transformation != new_transformation                  { -                    let _ = prepare( -                        device, -                        queue, -                        encoder, -                        &mut upload.renderer, -                        &mut upload.atlas, -                        &mut upload.buffer_cache, -                        &cache.text, -                        bounds, -                        new_transformation, -                        target_size, -                    ); +                    if !cache.text.is_empty() { +                        let _ = prepare( +                            device, +                            queue, +                            encoder, +                            &mut upload.renderer, +                            &mut group.atlas, +                            &mut upload.buffer_cache, +                            &cache.text, +                            bounds, +                            new_transformation, +                            target_size, +                        ); +                    } + +                    // Only trim if glyphs have changed +                    group.should_trim = +                        group.should_trim || upload.version != cache.version; +                    upload.text = Rc::downgrade(&cache.text);                      upload.version = cache.version; +                    upload.group_version = group.version;                      upload.transformation = new_transformation;                      upload.buffer_cache.trim(); -                    upload.atlas.trim();                  }              }              hash_map::Entry::Vacant(entry) => { -                let mut atlas = glyphon::TextAtlas::with_color_mode( -                    device, queue, format, COLOR_MODE, -                ); -                  let mut renderer = glyphon::TextRenderer::new( -                    &mut atlas, +                    &mut group.atlas,                      device,                      wgpu::MultisampleState::default(),                      None, @@ -143,41 +184,76 @@ impl Storage {                  let mut buffer_cache = BufferCache::new(); -                let _ = prepare( -                    device, -                    queue, -                    encoder, -                    &mut renderer, -                    &mut atlas, -                    &mut buffer_cache, -                    &cache.text, -                    bounds, -                    new_transformation, -                    target_size, -                ); +                if !cache.text.is_empty() { +                    let _ = prepare( +                        device, +                        queue, +                        encoder, +                        &mut renderer, +                        &mut group.atlas, +                        &mut buffer_cache, +                        &cache.text, +                        bounds, +                        new_transformation, +                        target_size, +                    ); +                }                  let _ = entry.insert(Upload {                      renderer, -                    atlas,                      buffer_cache,                      transformation: new_transformation,                      version: 0, +                    group_version: group.version, +                    text: Rc::downgrade(&cache.text), +                    _atlas: Rc::downgrade(&group.handle),                  }); -                log::info!( +                group.should_trim = cache.group.is_singleton(); + +                log::debug!(                      "New text upload: {} (total: {})",                      cache.id.0,                      self.uploads.len()                  );              }          } - -        let _ = self.recently_used.insert(cache.id);      }      pub fn trim(&mut self) { -        self.uploads.retain(|id, _| self.recently_used.contains(id)); -        self.recently_used.clear(); +        self.uploads +            .retain(|_id, upload| upload.text.strong_count() > 0); + +        self.groups.retain(|id, group| { +            let active_uploads = Rc::weak_count(&group.handle); + +            if active_uploads == 0 { +                log::debug!("Dropping text atlas: {id:?}"); + +                return false; +            } + +            if group.should_trim { +                log::trace!("Trimming text atlas: {id:?}"); + +                group.atlas.trim(); +                group.should_trim = false; + +                // We only need to worry about glyph fighting +                // when the atlas may be shared by multiple +                // uploads. +                if !id.is_singleton() { +                    log::debug!( +                        "Invalidating text atlas: {id:?} \ +                        (uploads: {active_uploads})" +                    ); + +                    group.version += 1; +                } +            } + +            true +        });      }  } @@ -306,10 +382,10 @@ impl Pipeline {                      layer_count += 1;                  }                  Item::Cached { cache, .. } => { -                    if let Some(upload) = storage.get(cache) { +                    if let Some((atlas, upload)) = storage.get(cache) {                          upload                              .renderer -                            .render(&upload.atlas, render_pass) +                            .render(atlas, render_pass)                              .expect("Render cached text");                      }                  } @@ -345,7 +421,7 @@ fn prepare(      enum Allocation {          Paragraph(Paragraph),          Editor(Editor), -        Cache(cache::KeyHash), +        Cache(text_cache::KeyHash),          Raw(Arc<glyphon::Buffer>),      } @@ -369,7 +445,7 @@ fn prepare(              } => {                  let (key, _) = buffer_cache.allocate(                      font_system, -                    cache::Key { +                    text_cache::Key {                          content,                          size: f32::from(*size),                          line_height: f32::from(*line_height), diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 8470ea39..b0551f55 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -6,9 +6,9 @@ use crate::graphics::mesh::{self, Mesh};  use crate::graphics::Antialiasing;  use crate::Buffer; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashMap;  use std::collections::hash_map; -use std::rc::Rc; +use std::rc::{self, Rc};  use std::sync::atomic::{self, AtomicU64};  const INITIAL_INDEX_COUNT: usize = 1_000; @@ -64,12 +64,12 @@ struct Upload {      layer: Layer,      transformation: Transformation,      version: usize, +    batch: rc::Weak<[Mesh]>,  }  #[derive(Debug, Default)]  pub struct Storage {      uploads: FxHashMap<Id, Upload>, -    recently_used: FxHashSet<Id>,  }  impl Storage { @@ -113,6 +113,7 @@ impl Storage {                          new_transformation,                      ); +                    upload.batch = Rc::downgrade(&cache.batch);                      upload.version = cache.version;                      upload.transformation = new_transformation;                  } @@ -134,22 +135,21 @@ impl Storage {                      layer,                      transformation: new_transformation,                      version: 0, +                    batch: Rc::downgrade(&cache.batch),                  }); -                log::info!( +                log::debug!(                      "New mesh upload: {} (total: {})",                      cache.id.0,                      self.uploads.len()                  );              }          } - -        let _ = self.recently_used.insert(cache.id);      }      pub fn trim(&mut self) { -        self.uploads.retain(|id, _| self.recently_used.contains(id)); -        self.recently_used.clear(); +        self.uploads +            .retain(|_id, upload| upload.batch.strong_count() > 0);      }  } diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 095afd48..2e938c77 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -4,7 +4,8 @@ use crate::graphics::color;  use crate::graphics::compositor;  use crate::graphics::error;  use crate::graphics::{self, Viewport}; -use crate::{Engine, Renderer, Settings}; +use crate::settings::{self, Settings}; +use crate::{Engine, Renderer};  /// A window graphics backend for iced powered by `wgpu`.  #[allow(missing_debug_implementations)] @@ -270,15 +271,19 @@ impl graphics::Compositor for Compositor {          backend: Option<&str>,      ) -> Result<Self, graphics::Error> {          match backend { -            None | Some("wgpu") => Ok(new( -                Settings { -                    backends: wgpu::util::backend_bits_from_env() -                        .unwrap_or(wgpu::Backends::all()), -                    ..settings.into() -                }, -                compatible_window, -            ) -            .await?), +            None | Some("wgpu") => { +                let mut settings = Settings::from(settings); + +                if let Some(backends) = wgpu::util::backend_bits_from_env() { +                    settings.backends = backends; +                } + +                if let Some(present_mode) = settings::present_mode_from_env() { +                    settings.present_mode = present_mode; +                } + +                Ok(new(settings, compatible_window).await?) +            }              Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {                  backend: "wgpu",                  reason: error::Reason::DidNotMatch { @@ -290,6 +295,7 @@ impl graphics::Compositor for Compositor {      fn create_renderer(&self) -> Self::Renderer {          Renderer::new( +            &self.device,              &self.engine,              self.settings.default_font,              self.settings.default_text_size,  | 
