From 6c9d605fd9ae7189dceee03148dcd8c3f5038455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 18 Jan 2020 08:30:53 +0100 Subject: [PATCH 1/2] Free images VRAM when upload was not requested --- wgpu/src/image.rs | 3 +- wgpu/src/image/raster.rs | 116 ++++++++++++++++++++++++--------------- 2 files changed, 74 insertions(+), 45 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 4558ffb060..90f608a646 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -252,9 +252,8 @@ impl Pipeline { let uploaded_texture = match &image.handle { Handle::Raster(handle) => { let mut cache = self.raster_cache.borrow_mut(); - let memory = cache.load(&handle); - memory.upload(device, encoder, &self.texture_layout) + cache.upload(handle, device, encoder, &self.texture_layout) } Handle::Vector(_handle) => { #[cfg(feature = "svg")] diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index fa10787977..3a99832315 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -4,10 +4,13 @@ use std::{ rc::Rc, }; +type ImageBuffer = ::image::ImageBuffer<::image::Bgra, Vec>; + #[derive(Debug)] pub enum Memory { - Host(::image::ImageBuffer<::image::Bgra, Vec>), + Host(Rc), Device { + image: Rc, bind_group: Rc, width: u32, height: u32, @@ -25,14 +28,64 @@ impl Memory { Memory::Invalid => (1, 1), } } +} + +#[derive(Debug)] +pub struct Cache { + map: HashMap, + load_hits: HashSet, + upload_hits: HashSet, +} + +impl Cache { + pub fn new() -> Self { + Self { + map: HashMap::new(), + load_hits: HashSet::new(), + upload_hits: HashSet::new(), + } + } + + pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { + if self.contains(handle) { + return self.get(handle).unwrap(); + } + + let memory = match handle.data() { + image::Data::Path(path) => { + if let Ok(image) = ::image::open(path) { + Memory::Host(Rc::new(image.to_bgra())) + } else { + Memory::NotFound + } + } + image::Data::Bytes(bytes) => { + if let Ok(image) = ::image::load_from_memory(&bytes) { + Memory::Host(Rc::new(image.to_bgra())) + } else { + Memory::Invalid + } + } + }; + + self.insert(handle, memory); + self.get(handle).unwrap() + } pub fn upload( &mut self, + handle: &image::Handle, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, texture_layout: &wgpu::BindGroupLayout, ) -> Option> { - match self { + let memory = { + let _ = self.upload_hits.insert(handle.id()); + + self.load(handle) + }; + + match memory { Memory::Host(image) => { let (width, height) = image.dimensions(); @@ -98,7 +151,8 @@ impl Memory { let bind_group = Rc::new(bind_group); - *self = Memory::Device { + *memory = Memory::Device { + image: image.clone(), bind_group: bind_group.clone(), width, height, @@ -111,57 +165,33 @@ impl Memory { Memory::Invalid => None, } } -} -#[derive(Debug)] -pub struct Cache { - map: HashMap, - hits: HashSet, -} + pub fn trim(&mut self) { + let load_hits = &self.load_hits; + let upload_hits = &self.upload_hits; -impl Cache { - pub fn new() -> Self { - Self { - map: HashMap::new(), - hits: HashSet::new(), - } - } + self.map.retain(|k, memory| { + if upload_hits.contains(k) { + return true; + } - pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { - if self.contains(handle) { - return self.get(handle).unwrap(); - } + let retain = load_hits.contains(k); - let memory = match handle.data() { - image::Data::Path(path) => { - if let Ok(image) = ::image::open(path) { - Memory::Host(image.to_bgra()) - } else { - Memory::NotFound + if let Memory::Device { image, .. } = memory { + if retain { + *memory = Memory::Host(image.clone()); } } - image::Data::Bytes(bytes) => { - if let Ok(image) = ::image::load_from_memory(&bytes) { - Memory::Host(image.to_bgra()) - } else { - Memory::Invalid - } - } - }; - self.insert(handle, memory); - self.get(handle).unwrap() - } - - pub fn trim(&mut self) { - let hits = &self.hits; + retain + }); - self.map.retain(|k, _| hits.contains(k)); - self.hits.clear(); + self.load_hits.clear(); + self.upload_hits.clear(); } fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { - let _ = self.hits.insert(handle.id()); + let _ = self.load_hits.insert(handle.id()); self.map.get_mut(&handle.id()) } From 9af3498fa6dc3b5f5da15e53a323cd8ad3a32e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 18 Jan 2020 10:04:41 +0100 Subject: [PATCH 2/2] Cull images and improve cache strategy The `image::Cache` takes a more optimistic approach; only trimming when new memory has been allocated. --- core/src/rectangle.rs | 12 ++++++++++++ wgpu/src/image/raster.rs | 14 ++++++++++++-- wgpu/src/renderer.rs | 21 ++++++++++++++++----- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index ee1e38079b..a206276bfc 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -16,6 +16,18 @@ pub struct Rectangle { pub height: T, } +impl + PartialOrd + Copy> Rectangle { + /// Returns true if the current [`Rectangle`] intersects with the given one. + /// + /// [`Rectangle`]: struct.Rectangle.html + pub fn intersects(&self, b: &Rectangle) -> bool { + self.x < b.x + b.width + && self.x + self.width > b.x + && self.y < b.y + b.height + && self.y + self.height > b.y + } +} + impl Rectangle { /// Returns true if the given [`Point`] is contained in the [`Rectangle`]. /// diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 3a99832315..e33de0d62b 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -35,6 +35,7 @@ pub struct Cache { map: HashMap, load_hits: HashSet, upload_hits: HashSet, + is_dirty: bool, } impl Cache { @@ -43,6 +44,7 @@ impl Cache { map: HashMap::new(), load_hits: HashSet::new(), upload_hits: HashSet::new(), + is_dirty: false, } } @@ -69,6 +71,8 @@ impl Cache { }; self.insert(handle, memory); + self.is_dirty = true; + self.get(handle).unwrap() } @@ -167,6 +171,10 @@ impl Cache { } pub fn trim(&mut self) { + if !self.is_dirty { + return; + } + let load_hits = &self.load_hits; let upload_hits = &self.upload_hits; @@ -177,8 +185,8 @@ impl Cache { let retain = load_hits.contains(k); - if let Memory::Device { image, .. } = memory { - if retain { + if retain { + if let Memory::Device { image, .. } = memory { *memory = Memory::Host(image.clone()); } } @@ -188,6 +196,8 @@ impl Cache { self.load_hits.clear(); self.upload_hits.clear(); + + self.is_dirty = false; } fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 6f35e2479b..e70cf92292 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -50,6 +50,15 @@ impl<'a> Layer<'a> { meshes: Vec::new(), } } + + pub fn visible_bounds(&self) -> Rectangle { + Rectangle { + x: (self.bounds.x + self.offset.x) as f32, + y: (self.bounds.y + self.offset.y) as f32, + width: self.bounds.width as f32, + height: self.bounds.height as f32, + } + } } impl Renderer { @@ -244,11 +253,13 @@ impl Renderer { }); } Primitive::Image { handle, bounds } => { - layer.images.push(Image { - handle: image::Handle::Raster(handle.clone()), - position: [bounds.x, bounds.y], - scale: [bounds.width, bounds.height], - }); + if bounds.intersects(&layer.visible_bounds()) { + layer.images.push(Image { + handle: image::Handle::Raster(handle.clone()), + position: [bounds.x, bounds.y], + scale: [bounds.width, bounds.height], + }); + } } Primitive::Svg { handle, bounds } => { layer.images.push(Image {