diff options
| author | 2024-05-23 13:29:45 +0200 | |
|---|---|---|
| committer | 2024-05-23 13:29:45 +0200 | |
| commit | d8ba6b0673a33724a177f3a1ba59705527280142 (patch) | |
| tree | 89482c8d1e3a03e00b3a8151abbb81e30ae5898c /wgpu/src/image | |
| parent | 72ed8bcc8def9956e25f3720a3095fc96bb2eef0 (diff) | |
| parent | 468794d918eb06c1dbebb33c32b10017ad335f05 (diff) | |
| download | iced-d8ba6b0673a33724a177f3a1ba59705527280142.tar.gz iced-d8ba6b0673a33724a177f3a1ba59705527280142.tar.bz2 iced-d8ba6b0673a33724a177f3a1ba59705527280142.zip | |
Merge branch 'master' into feat/text-macro
Diffstat (limited to '')
| -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 | 86 | ||||
| -rw-r--r-- | wgpu/src/image/mod.rs (renamed from wgpu/src/image.rs) | 489 | ||||
| -rw-r--r-- | wgpu/src/image/null.rs | 10 | ||||
| -rw-r--r-- | wgpu/src/image/raster.rs | 19 | ||||
| -rw-r--r-- | wgpu/src/image/vector.rs | 18 | 
8 files changed, 412 insertions, 275 deletions
| 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 new file mode 100644 index 00000000..94f7071d --- /dev/null +++ b/wgpu/src/image/cache.rs @@ -0,0 +1,86 @@ +use crate::core::{self, Size}; +use crate::image::atlas::{self, Atlas}; + +use std::sync::Arc; + +#[derive(Debug)] +pub struct Cache { +    atlas: Atlas, +    #[cfg(feature = "image")] +    raster: crate::image::raster::Cache, +    #[cfg(feature = "svg")] +    vector: crate::image::vector::Cache, +} + +impl Cache { +    pub fn new( +        device: &wgpu::Device, +        backend: wgpu::Backend, +        layout: Arc<wgpu::BindGroupLayout>, +    ) -> Self { +        Self { +            atlas: Atlas::new(device, backend, layout), +            #[cfg(feature = "image")] +            raster: crate::image::raster::Cache::default(), +            #[cfg(feature = "svg")] +            vector: crate::image::vector::Cache::default(), +        } +    } + +    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<u32> { +        self.raster.load(handle).dimensions() +    } + +    #[cfg(feature = "svg")] +    pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> { +        self.vector.load(handle).viewport_dimensions() +    } + +    #[cfg(feature = "image")] +    pub fn upload_raster( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        handle: &core::image::Handle, +    ) -> Option<&atlas::Entry> { +        self.raster.upload(device, encoder, handle, &mut self.atlas) +    } + +    #[cfg(feature = "svg")] +    pub fn upload_vector( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        handle: &core::svg::Handle, +        color: Option<core::Color>, +        size: [f32; 2], +        scale: f32, +    ) -> Option<&atlas::Entry> { +        self.vector.upload( +            device, +            encoder, +            handle, +            color, +            size, +            scale, +            &mut self.atlas, +        ) +    } + +    pub fn trim(&mut self) { +        #[cfg(feature = "image")] +        self.raster.trim(&mut self.atlas); + +        #[cfg(feature = "svg")] +        self.vector.trim(&mut self.atlas); +    } +} diff --git a/wgpu/src/image.rs b/wgpu/src/image/mod.rs index c8e4a4c2..daa2fe16 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image/mod.rs @@ -1,3 +1,6 @@ +pub(crate) mod cache; +pub(crate) use cache::Cache; +  mod atlas;  #[cfg(feature = "image")] @@ -6,175 +9,30 @@ mod raster;  #[cfg(feature = "svg")]  mod vector; -use atlas::Atlas; -  use crate::core::{Rectangle, Size, Transformation}; -use crate::layer;  use crate::Buffer; -use std::cell::RefCell; -use std::mem; -  use bytemuck::{Pod, Zeroable}; -#[cfg(feature = "image")] -use crate::core::image; +use std::mem; +use std::sync::Arc; -#[cfg(feature = "svg")] -use crate::core::svg; +pub use crate::graphics::Image; -#[cfg(feature = "tracing")] -use tracing::info_span; +pub type Batch = Vec<Image>;  #[derive(Debug)]  pub struct Pipeline { -    #[cfg(feature = "image")] -    raster_cache: RefCell<raster::Cache>, -    #[cfg(feature = "svg")] -    vector_cache: RefCell<vector::Cache>, -      pipeline: wgpu::RenderPipeline, +    backend: wgpu::Backend,      nearest_sampler: wgpu::Sampler,      linear_sampler: wgpu::Sampler, -    texture: wgpu::BindGroup, -    texture_version: usize, -    texture_atlas: Atlas, -    texture_layout: wgpu::BindGroupLayout, +    texture_layout: Arc<wgpu::BindGroupLayout>,      constant_layout: wgpu::BindGroupLayout, -      layers: Vec<Layer>,      prepare_layer: usize,  } -#[derive(Debug)] -struct Layer { -    uniforms: wgpu::Buffer, -    nearest: Data, -    linear: Data, -} - -impl Layer { -    fn new( -        device: &wgpu::Device, -        constant_layout: &wgpu::BindGroupLayout, -        nearest_sampler: &wgpu::Sampler, -        linear_sampler: &wgpu::Sampler, -    ) -> Self { -        let uniforms = device.create_buffer(&wgpu::BufferDescriptor { -            label: Some("iced_wgpu::image uniforms buffer"), -            size: mem::size_of::<Uniforms>() as u64, -            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, -            mapped_at_creation: false, -        }); - -        let nearest = -            Data::new(device, constant_layout, nearest_sampler, &uniforms); - -        let linear = -            Data::new(device, constant_layout, linear_sampler, &uniforms); - -        Self { -            uniforms, -            nearest, -            linear, -        } -    } - -    fn prepare( -        &mut self, -        device: &wgpu::Device, -        queue: &wgpu::Queue, -        nearest_instances: &[Instance], -        linear_instances: &[Instance], -        transformation: Transformation, -    ) { -        queue.write_buffer( -            &self.uniforms, -            0, -            bytemuck::bytes_of(&Uniforms { -                transform: transformation.into(), -            }), -        ); - -        self.nearest.upload(device, queue, nearest_instances); -        self.linear.upload(device, queue, 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>, -    instance_count: usize, -} - -impl Data { -    pub fn new( -        device: &wgpu::Device, -        constant_layout: &wgpu::BindGroupLayout, -        sampler: &wgpu::Sampler, -        uniforms: &wgpu::Buffer, -    ) -> Self { -        let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { -            label: Some("iced_wgpu::image constants bind group"), -            layout: constant_layout, -            entries: &[ -                wgpu::BindGroupEntry { -                    binding: 0, -                    resource: wgpu::BindingResource::Buffer( -                        wgpu::BufferBinding { -                            buffer: uniforms, -                            offset: 0, -                            size: None, -                        }, -                    ), -                }, -                wgpu::BindGroupEntry { -                    binding: 1, -                    resource: wgpu::BindingResource::Sampler(sampler), -                }, -            ], -        }); - -        let instances = Buffer::new( -            device, -            "iced_wgpu::image instance buffer", -            Instance::INITIAL, -            wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, -        ); - -        Self { -            constants, -            instances, -            instance_count: 0, -        } -    } - -    fn upload( -        &mut self, -        device: &wgpu::Device, -        queue: &wgpu::Queue, -        instances: &[Instance], -    ) { -        let _ = self.instances.resize(device, instances.len()); -        let _ = self.instances.write(queue, 0, instances); - -        self.instance_count = instances.len(); -    } - -    fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { -        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); -    } -} -  impl Pipeline {      pub fn new(          device: &wgpu::Device, @@ -257,9 +115,9 @@ impl Pipeline {                  label: Some("iced_wgpu image shader"),                  source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(                      concat!( -                        include_str!("shader/vertex.wgsl"), +                        include_str!("../shader/vertex.wgsl"),                          "\n", -                        include_str!("shader/image.wgsl"), +                        include_str!("../shader/image.wgsl"),                      ),                  )),              }); @@ -277,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,                          ),                      }],                  }, @@ -322,137 +186,95 @@ impl Pipeline {                  multiview: None,              }); -        let texture_atlas = Atlas::new(device, backend); - -        let texture = 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_atlas.view(), -                ), -            }], -        }); -          Pipeline { -            #[cfg(feature = "image")] -            raster_cache: RefCell::new(raster::Cache::default()), - -            #[cfg(feature = "svg")] -            vector_cache: RefCell::new(vector::Cache::default()), -              pipeline, +            backend,              nearest_sampler,              linear_sampler, -            texture, -            texture_version: texture_atlas.layer_count(), -            texture_atlas, -            texture_layout, +            texture_layout: Arc::new(texture_layout),              constant_layout, -              layers: Vec::new(),              prepare_layer: 0,          }      } -    #[cfg(feature = "image")] -    pub fn dimensions(&self, handle: &image::Handle) -> Size<u32> { -        let mut cache = self.raster_cache.borrow_mut(); -        let memory = cache.load(handle); - -        memory.dimensions() -    } - -    #[cfg(feature = "svg")] -    pub fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32> { -        let mut cache = self.vector_cache.borrow_mut(); -        let svg = cache.load(handle); - -        svg.viewport_dimensions() +    pub fn create_cache(&self, device: &wgpu::Device) -> Cache { +        Cache::new(device, self.backend, self.texture_layout.clone())      }      pub fn prepare(          &mut self,          device: &wgpu::Device, -        queue: &wgpu::Queue,          encoder: &mut wgpu::CommandEncoder, -        images: &[layer::Image], +        belt: &mut wgpu::util::StagingBelt, +        cache: &mut Cache, +        images: &Batch,          transformation: Transformation, -        _scale: f32, +        scale: f32,      ) { -        #[cfg(feature = "tracing")] -        let _ = info_span!("Wgpu::Image", "PREPARE").entered(); - -        #[cfg(feature = "tracing")] -        let _ = info_span!("Wgpu::Image", "DRAW").entered(); +        let transformation = transformation * Transformation::scale(scale);          let nearest_instances: &mut Vec<Instance> = &mut Vec::new();          let linear_instances: &mut Vec<Instance> = &mut Vec::new(); -        #[cfg(feature = "image")] -        let mut raster_cache = self.raster_cache.borrow_mut(); - -        #[cfg(feature = "svg")] -        let mut vector_cache = self.vector_cache.borrow_mut(); -          for image in images {              match &image {                  #[cfg(feature = "image")] -                layer::Image::Raster { +                Image::Raster {                      handle,                      filter_method,                      bounds, +                    rotation, +                    opacity,                  } => { -                    if let Some(atlas_entry) = raster_cache.upload( -                        device, -                        encoder, -                        handle, -                        &mut self.texture_atlas, -                    ) { +                    if let Some(atlas_entry) = +                        cache.upload_raster(device, encoder, handle) +                    {                          add_instances(                              [bounds.x, bounds.y],                              [bounds.width, bounds.height], +                            f32::from(*rotation), +                            *opacity,                              atlas_entry,                              match filter_method { -                                image::FilterMethod::Nearest => { +                                crate::core::image::FilterMethod::Nearest => {                                      nearest_instances                                  } -                                image::FilterMethod::Linear => linear_instances, +                                crate::core::image::FilterMethod::Linear => { +                                    linear_instances +                                }                              },                          );                      }                  }                  #[cfg(not(feature = "image"))] -                layer::Image::Raster { .. } => {} +                Image::Raster { .. } => {}                  #[cfg(feature = "svg")] -                layer::Image::Vector { +                Image::Vector {                      handle,                      color,                      bounds, +                    rotation, +                    opacity,                  } => {                      let size = [bounds.width, bounds.height]; -                    if let Some(atlas_entry) = vector_cache.upload( -                        device, -                        encoder, -                        handle, -                        *color, -                        size, -                        _scale, -                        &mut self.texture_atlas, +                    if let Some(atlas_entry) = cache.upload_vector( +                        device, encoder, handle, *color, size, scale,                      ) {                          add_instances(                              [bounds.x, bounds.y],                              size, +                            f32::from(*rotation), +                            *opacity,                              atlas_entry,                              nearest_instances,                          );                      }                  }                  #[cfg(not(feature = "svg"))] -                layer::Image::Vector { .. } => {} +                Image::Vector { .. } => {}              }          } @@ -460,26 +282,6 @@ impl Pipeline {              return;          } -        let texture_version = self.texture_atlas.layer_count(); - -        if self.texture_version != texture_version { -            log::info!("Atlas has grown. Recreating bind group..."); - -            self.texture = -                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_atlas.view(), -                        ), -                    }], -                }); - -            self.texture_version = texture_version; -        } -          if self.layers.len() <= self.prepare_layer {              self.layers.push(Layer::new(                  device, @@ -493,7 +295,8 @@ impl Pipeline {          layer.prepare(              device, -            queue, +            encoder, +            belt,              nearest_instances,              linear_instances,              transformation, @@ -504,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>, @@ -518,20 +322,162 @@ 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) { -        #[cfg(feature = "image")] -        self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); +        self.prepare_layer = 0; +    } +} + +#[derive(Debug)] +struct Layer { +    uniforms: wgpu::Buffer, +    nearest: Data, +    linear: Data, +} -        #[cfg(feature = "svg")] -        self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); +impl Layer { +    fn new( +        device: &wgpu::Device, +        constant_layout: &wgpu::BindGroupLayout, +        nearest_sampler: &wgpu::Sampler, +        linear_sampler: &wgpu::Sampler, +    ) -> Self { +        let uniforms = device.create_buffer(&wgpu::BufferDescriptor { +            label: Some("iced_wgpu::image uniforms buffer"), +            size: mem::size_of::<Uniforms>() as u64, +            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, +            mapped_at_creation: false, +        }); -        self.prepare_layer = 0; +        let nearest = +            Data::new(device, constant_layout, nearest_sampler, &uniforms); + +        let linear = +            Data::new(device, constant_layout, linear_sampler, &uniforms); + +        Self { +            uniforms, +            nearest, +            linear, +        } +    } + +    fn prepare( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt, +        nearest_instances: &[Instance], +        linear_instances: &[Instance], +        transformation: Transformation, +    ) { +        let uniforms = Uniforms { +            transform: transformation.into(), +        }; + +        let bytes = bytemuck::bytes_of(&uniforms); + +        belt.write_buffer( +            encoder, +            &self.uniforms, +            0, +            (bytes.len() as u64).try_into().expect("Sized uniforms"), +            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>, +    instance_count: usize, +} + +impl Data { +    pub fn new( +        device: &wgpu::Device, +        constant_layout: &wgpu::BindGroupLayout, +        sampler: &wgpu::Sampler, +        uniforms: &wgpu::Buffer, +    ) -> Self { +        let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { +            label: Some("iced_wgpu::image constants bind group"), +            layout: constant_layout, +            entries: &[ +                wgpu::BindGroupEntry { +                    binding: 0, +                    resource: wgpu::BindingResource::Buffer( +                        wgpu::BufferBinding { +                            buffer: uniforms, +                            offset: 0, +                            size: None, +                        }, +                    ), +                }, +                wgpu::BindGroupEntry { +                    binding: 1, +                    resource: wgpu::BindingResource::Sampler(sampler), +                }, +            ], +        }); + +        let instances = Buffer::new( +            device, +            "iced_wgpu::image instance buffer", +            Instance::INITIAL, +            wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, +        ); + +        Self { +            constants, +            instances, +            instance_count: 0, +        } +    } + +    fn upload( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt, +        instances: &[Instance], +    ) { +        self.instance_count = instances.len(); + +        if self.instance_count == 0 { +            return; +        } + +        let _ = self.instances.resize(device, instances.len()); +        let _ = self.instances.write(device, encoder, belt, 0, instances); +    } + +    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);      }  } @@ -539,7 +485,10 @@ impl Pipeline {  #[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, @@ -558,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; @@ -589,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, +                );              }          }      } @@ -598,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>,  ) { @@ -608,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/null.rs b/wgpu/src/image/null.rs new file mode 100644 index 00000000..c06d56be --- /dev/null +++ b/wgpu/src/image/null.rs @@ -0,0 +1,10 @@ +pub use crate::graphics::Image; + +#[derive(Debug, Default)] +pub struct Batch; + +impl Batch { +    pub fn push(&mut self, _image: Image) {} + +    pub fn clear(&mut self) {} +} diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index a6cba76a..4d3c3125 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -4,13 +4,13 @@ use crate::graphics;  use crate::graphics::image::image_rs;  use crate::image::atlas::{self, Atlas}; -use std::collections::{HashMap, HashSet}; +use rustc_hash::{FxHashMap, FxHashSet};  /// Entry in cache corresponding to an image handle  #[derive(Debug)]  pub enum Memory {      /// Image data on host -    Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, 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: HashMap<u64, Memory>, -    hits: HashSet<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 d9be50d7..c6d829af 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -5,7 +5,7 @@ use crate::image::atlas::{self, Atlas};  use resvg::tiny_skia;  use resvg::usvg::{self, TreeTextToPath}; -use std::collections::{HashMap, HashSet}; +use rustc_hash::{FxHashMap, FxHashSet};  use std::fs;  /// Entry in cache corresponding to an svg handle @@ -33,10 +33,11 @@ impl Svg {  /// Caches svg vector and raster data  #[derive(Debug, Default)]  pub struct Cache { -    svgs: HashMap<u64, Svg>, -    rasterized: HashMap<(u64, u32, u32, ColorFilter), atlas::Entry>, -    svg_hits: HashSet<u64>, -    rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>, +    svgs: FxHashMap<u64, Svg>, +    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;      }  } | 
