diff options
Diffstat (limited to 'components/compositing')
-rw-r--r-- | components/compositing/buffer_map.rs | 162 | ||||
-rw-r--r-- | components/compositing/compositor.rs | 86 | ||||
-rw-r--r-- | components/compositing/compositor_layer.rs | 35 | ||||
-rw-r--r-- | components/compositing/compositor_task.rs | 18 | ||||
-rw-r--r-- | components/compositing/headless.rs | 1 | ||||
-rw-r--r-- | components/compositing/lib.rs | 2 |
6 files changed, 252 insertions, 52 deletions
diff --git a/components/compositing/buffer_map.rs b/components/compositing/buffer_map.rs new file mode 100644 index 00000000000..8e514eab87c --- /dev/null +++ b/components/compositing/buffer_map.rs @@ -0,0 +1,162 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; +use std::collections::hash_map::Entry::{Occupied, Vacant}; +use euclid::size::Size2D; +use layers::platform::surface::NativeDisplay; +use layers::layers::LayerBuffer; +use std::hash::{Hash, Hasher}; + +/// This is a struct used to store buffers when they are not in use. +/// The paint task can quickly query for a particular size of buffer when it +/// needs it. +pub struct BufferMap { + /// A HashMap that stores the Buffers. + map: HashMap<BufferKey, BufferValue>, + /// The current amount of memory stored by the BufferMap's buffers. + mem: usize, + /// The maximum allowed memory. Unused buffers will be deleted + /// when this threshold is exceeded. + max_mem: usize, + /// A monotonically increasing counter to track how recently tile sizes were used. + counter: usize, +} + +/// A key with which to store buffers. It is based on the size of the buffer. +#[derive(Eq, Copy, Clone)] +struct BufferKey([usize; 2]); + +impl Hash for BufferKey { + fn hash<H: Hasher>(&self, state: &mut H) { + let BufferKey(ref bytes) = *self; + bytes.hash(state); + } +} + +impl PartialEq for BufferKey { + fn eq(&self, other: &BufferKey) -> bool { + let BufferKey(s) = *self; + let BufferKey(o) = *other; + s[0] == o[0] && s[1] == o[1] + } +} + +/// Create a key from a given size +impl BufferKey { + fn get(input: Size2D<usize>) -> BufferKey { + BufferKey([input.width, input.height]) + } +} + +/// A helper struct to keep track of buffers in the HashMap +struct BufferValue { + /// An array of buffers, all the same size + buffers: Vec<Box<LayerBuffer>>, + /// The counter when this size was last requested + last_action: usize, +} + +impl BufferMap { + // Creates a new BufferMap with a given buffer limit. + pub fn new(max_mem: usize) -> BufferMap { + BufferMap { + map: HashMap::new(), + mem: 0, + max_mem: max_mem, + counter: 0, + } + } + + pub fn insert_buffers(&mut self, display: &NativeDisplay, buffers: Vec<Box<LayerBuffer>>) { + for mut buffer in buffers.into_iter() { + buffer.mark_wont_leak(); + self.insert(display, buffer) + } + } + + /// Insert a new buffer into the map. + pub fn insert(&mut self, display: &NativeDisplay, new_buffer: Box<LayerBuffer>) { + let new_key = BufferKey::get(new_buffer.get_size_2d()); + + // If all our buffers are the same size and we're already at our + // memory limit, no need to store this new buffer; just let it drop. + if self.mem + new_buffer.get_mem() > self.max_mem && self.map.len() == 1 && + self.map.contains_key(&new_key) { + new_buffer.destroy(display); + return; + } + + self.mem += new_buffer.get_mem(); + // use lazy insertion function to prevent unnecessary allocation + let counter = &self.counter; + match self.map.entry(new_key) { + Occupied(entry) => { + entry.into_mut().buffers.push(new_buffer); + } + Vacant(entry) => { + entry.insert(BufferValue { + buffers: vec!(new_buffer), + last_action: *counter, + }); + } + } + + let mut opt_key: Option<BufferKey> = None; + while self.mem > self.max_mem { + let old_key = match opt_key { + Some(key) => key, + None => { + match self.map.iter().min_by(|&(_, x)| x.last_action) { + Some((k, _)) => *k, + None => panic!("BufferMap: tried to delete with no elements in map"), + } + } + }; + if { + let list = &mut self.map.get_mut(&old_key).unwrap().buffers; + let condemned_buffer = list.pop().take().unwrap(); + self.mem -= condemned_buffer.get_mem(); + condemned_buffer.destroy(display); + list.is_empty() + } + { // then + self.map.remove(&old_key); // Don't store empty vectors! + opt_key = None; + } else { + opt_key = Some(old_key); + } + } + } + + // Try to find a buffer for the given size. + pub fn find(&mut self, size: Size2D<usize>) -> Option<Box<LayerBuffer>> { + let mut flag = false; // True if key needs to be popped after retrieval. + let key = BufferKey::get(size); + let ret = match self.map.get_mut(&key) { + Some(ref mut buffer_val) => { + buffer_val.last_action = self.counter; + self.counter += 1; + + let buffer = buffer_val.buffers.pop().take().unwrap(); + self.mem -= buffer.get_mem(); + if buffer_val.buffers.is_empty() { + flag = true; + } + Some(buffer) + } + None => None, + }; + + if flag { + self.map.remove(&key); // Don't store empty vectors! + } + + ret + } + + pub fn mem(&self) -> usize { + self.mem + } +} diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index ace2a499b66..b02027e4672 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use buffer_map::BufferMap; use compositor_layer::{CompositorData, CompositorLayer, WantsScrollEventsFlag}; use compositor_task::{CompositorEventListener, CompositorProxy, CompositorReceiver}; use compositor_task::Msg; @@ -51,6 +52,8 @@ use url::Url; use util::geometry::{Au, PagePx, ScreenPx, ViewportPx}; use util::opts; +const BUFFER_MAP_SIZE : usize = 10000000; + /// Holds the state when running reftests that determines when it is /// safe to save the output image. #[derive(Copy, Clone, PartialEq)] @@ -154,6 +157,9 @@ pub struct IOCompositor<Window: WindowMethods> { /// Used by the logic that determines when it is safe to output an /// image for the reftest framework. ready_to_save_state: ReadyState, + + /// A data structure to store unused LayerBuffers. + buffer_map: BufferMap, } pub struct ScrollEvent { @@ -290,6 +296,7 @@ impl<Window: WindowMethods> IOCompositor<Window> { last_composite_time: 0, has_seen_quit_event: false, ready_to_save_state: ReadyState::Unknown, + buffer_map: BufferMap::new(BUFFER_MAP_SIZE), } } @@ -387,6 +394,11 @@ impl<Window: WindowMethods> IOCompositor<Window> { } } + (Msg::ReturnUnusedLayerBuffers(layer_buffers), + ShutdownState::NotShuttingDown) => { + self.cache_unused_buffers(layer_buffers); + } + (Msg::ScrollFragmentPoint(pipeline_id, layer_id, point), ShutdownState::NotShuttingDown) => { self.scroll_fragment_to_point(pipeline_id, layer_id, point); @@ -547,10 +559,11 @@ impl<Window: WindowMethods> IOCompositor<Window> { self.root_pipeline = Some(frame_tree.pipeline.clone()); // If we have an old root layer, release all old tiles before replacing it. - match self.scene.root { - Some(ref layer) => layer.clear_all_tiles(self), - None => { } + let old_root_layer = self.scene.root.take(); + if let Some(ref old_root_layer) = old_root_layer { + old_root_layer.clear_all_tiles(self) } + self.scene.root = Some(self.create_frame_tree_root_layers(frame_tree, None)); self.scene.set_root_layer_size(self.window_size.as_f32()); @@ -616,13 +629,15 @@ impl<Window: WindowMethods> IOCompositor<Window> { } fn remove_pipeline_root_layer(&mut self, pipeline_id: PipelineId) { - if let Some(ref root_layer) = self.scene.root { - // Remove all the compositor layers for this pipeline - // and send any owned buffers back to the paint task. - root_layer.remove_root_layer_with_pipeline_id(self, pipeline_id); + let root_layer = match self.scene.root { + Some(ref root_layer) => root_layer.clone(), + None => return, + }; - self.pipeline_details.remove(&pipeline_id); - } + // Remove all the compositor layers for this pipeline and recache + // any buffers that they owned. + root_layer.remove_root_layer_with_pipeline_id(self, pipeline_id); + self.pipeline_details.remove(&pipeline_id); } fn update_layer_if_exists(&mut self, pipeline_id: PipelineId, properties: LayerProperties) -> bool { @@ -787,9 +802,7 @@ impl<Window: WindowMethods> IOCompositor<Window> { } } - let pipeline = self.get_pipeline(pipeline_id); - let message = PaintMsg::UnusedBuffer(new_layer_buffer_set.buffers); - let _ = pipeline.paint_chan.send(message); + self.cache_unused_buffers(new_layer_buffer_set.buffers); } fn assign_painted_buffers_to_layer(&mut self, @@ -1141,7 +1154,29 @@ impl<Window: WindowMethods> IOCompositor<Window> { chan.send(ConstellationMsg::KeyEvent(key, state, modifiers)).unwrap() } - fn convert_buffer_requests_to_pipeline_requests_map(&self, + fn fill_paint_request_with_cached_layer_buffers(&mut self, paint_request: &mut PaintRequest) { + if opts::get().gpu_painting { + return; + } + + for buffer_request in paint_request.buffer_requests.iter_mut() { + if self.buffer_map.mem() == 0 { + return; + } + + if let Some(mut buffer) = self.buffer_map.find(buffer_request.screen_rect.size) { + buffer.rect = buffer_request.page_rect; + buffer.screen_pos = buffer_request.screen_rect; + buffer.resolution = paint_request.scale; + buffer.native_surface.mark_wont_leak(); + buffer.painted_with_cpu = true; + buffer.content_age = buffer_request.content_age; + buffer_request.layer_buffer = Some(buffer); + } + } + } + + fn convert_buffer_requests_to_pipeline_requests_map(&mut self, requests: Vec<(Rc<Layer<CompositorData>>, Vec<BufferRequest>)>) -> HashMap<PipelineId, Vec<PaintRequest>> { @@ -1173,29 +1208,20 @@ impl<Window: WindowMethods> IOCompositor<Window> { LayerKind::Layer2D }; - vec.push(PaintRequest { + let mut paint_request = PaintRequest { buffer_requests: layer_requests, scale: scale.get(), layer_id: layer.extra_data.borrow().id, epoch: layer.extra_data.borrow().requested_epoch, layer_kind: layer_kind, - }); + }; + self.fill_paint_request_with_cached_layer_buffers(&mut paint_request); + vec.push(paint_request); } results } - fn send_back_unused_buffers(&mut self, - unused_buffers: Vec<(Rc<Layer<CompositorData>>, - Vec<Box<LayerBuffer>>)>) { - for (layer, buffers) in unused_buffers.into_iter() { - if !buffers.is_empty() { - let pipeline = self.get_pipeline(layer.pipeline_id()); - let _ = pipeline.paint_chan.send_opt(PaintMsg::UnusedBuffer(buffers)); - } - } - } - fn send_viewport_rect_for_layer(&self, layer: Rc<Layer<CompositorData>>) { if layer.extra_data.borrow().id == LayerId::null() { let layer_rect = Rect::new(-layer.extra_data.borrow().scroll_offset.to_untyped(), @@ -1230,7 +1256,7 @@ impl<Window: WindowMethods> IOCompositor<Window> { self.scene.get_buffer_requests(&mut layers_and_requests, &mut unused_buffers); // Return unused tiles first, so that they can be reused by any new BufferRequests. - self.send_back_unused_buffers(unused_buffers); + self.cache_unused_buffers(unused_buffers); if layers_and_requests.len() == 0 { return false; @@ -1528,6 +1554,12 @@ impl<Window: WindowMethods> IOCompositor<Window> { None => None, } } + + pub fn cache_unused_buffers(&mut self, buffers: Vec<Box<LayerBuffer>>) { + if !buffers.is_empty() { + self.buffer_map.insert_buffers(&self.native_display, buffers); + } + } } fn find_layer_with_pipeline_and_layer_id_for_layer(layer: Rc<Layer<CompositorData>>, diff --git a/components/compositing/compositor_layer.rs b/components/compositing/compositor_layer.rs index 1be9df64ca9..707b7ce06b6 100644 --- a/components/compositing/compositor_layer.rs +++ b/components/compositing/compositor_layer.rs @@ -10,7 +10,6 @@ use euclid::length::Length; use euclid::point::{Point2D, TypedPoint2D}; use euclid::size::TypedSize2D; use euclid::rect::Rect; -use gfx::paint_task::Msg as PaintMsg; use layers::color::Color; use layers::geometry::LayerPixel; use layers::layers::{Layer, LayerBufferSet}; @@ -76,25 +75,25 @@ pub trait CompositorLayer { fn update_layer(&self, layer_properties: LayerProperties); fn add_buffers<Window>(&self, - compositor: &IOCompositor<Window>, + compositor: &mut IOCompositor<Window>, new_buffers: Box<LayerBufferSet>, epoch: Epoch) where Window: WindowMethods; /// Destroys all layer tiles, sending the buffers back to the painter to be destroyed or /// reused. - fn clear<Window>(&self, compositor: &IOCompositor<Window>) where Window: WindowMethods; + fn clear<Window>(&self, compositor: &mut IOCompositor<Window>) where Window: WindowMethods; /// Destroys tiles for this layer and all descendent layers, sending the buffers back to the /// painter to be destroyed or reused. - fn clear_all_tiles<Window>(&self, compositor: &IOCompositor<Window>) + fn clear_all_tiles<Window>(&self, compositor: &mut IOCompositor<Window>) where Window: WindowMethods; /// Removes the root layer (and any children) for a given pipeline from the /// compositor. Buffers that the compositor is holding are returned to the /// owning paint task. fn remove_root_layer_with_pipeline_id<Window>(&self, - compositor: &IOCompositor<Window>, + compositor: &mut IOCompositor<Window>, pipeline_id: PipelineId) where Window: WindowMethods; @@ -214,7 +213,7 @@ impl CompositorLayer for Layer<CompositorData> { // If the epoch of the message does not match the layer's epoch, the message is ignored, the // layer buffer set is consumed, and None is returned. fn add_buffers<Window>(&self, - compositor: &IOCompositor<Window>, + compositor: &mut IOCompositor<Window>, new_buffers: Box<LayerBufferSet>, epoch: Epoch) where Window: WindowMethods { @@ -225,33 +224,21 @@ impl CompositorLayer for Layer<CompositorData> { self.add_buffer(buffer); } - let unused_buffers = self.collect_unused_buffers(); - if !unused_buffers.is_empty() { // send back unused buffers - let pipeline = compositor.get_pipeline(self.pipeline_id()); - let _ = pipeline.paint_chan.send(PaintMsg::UnusedBuffer(unused_buffers)); - } + compositor.cache_unused_buffers(self.collect_unused_buffers()) } - fn clear<Window>(&self, compositor: &IOCompositor<Window>) where Window: WindowMethods { - let mut buffers = self.collect_buffers(); + fn clear<Window>(&self, compositor: &mut IOCompositor<Window>) where Window: WindowMethods { + let buffers = self.collect_buffers(); if !buffers.is_empty() { - // We have no way of knowing without a race whether the paint task is even up and - // running, but mark the buffers as not leaking. If the paint task died, then the - // buffers are going to be cleaned up. - for buffer in buffers.iter_mut() { - buffer.mark_wont_leak() - } - - let pipeline = compositor.get_pipeline(self.pipeline_id()); - let _ = pipeline.paint_chan.send(PaintMsg::UnusedBuffer(buffers)); + compositor.cache_unused_buffers(buffers); } } /// Destroys tiles for this layer and all descendent layers, sending the buffers back to the /// painter to be destroyed or reused. fn clear_all_tiles<Window>(&self, - compositor: &IOCompositor<Window>) + compositor: &mut IOCompositor<Window>) where Window: WindowMethods { self.clear(compositor); for kid in self.children().iter() { @@ -260,7 +247,7 @@ impl CompositorLayer for Layer<CompositorData> { } fn remove_root_layer_with_pipeline_id<Window>(&self, - compositor: &IOCompositor<Window>, + compositor: &mut IOCompositor<Window>, pipeline_id: PipelineId) where Window: WindowMethods { // Find the child that is the root layer for this pipeline. diff --git a/components/compositing/compositor_task.rs b/components/compositing/compositor_task.rs index 3c4521e1a81..15e5f59f513 100644 --- a/components/compositing/compositor_task.rs +++ b/components/compositing/compositor_task.rs @@ -14,7 +14,7 @@ use windowing::{WindowEvent, WindowMethods}; use euclid::point::Point2D; use euclid::rect::Rect; use layers::platform::surface::NativeDisplay; -use layers::layers::LayerBufferSet; +use layers::layers::{BufferRequest, LayerBuffer, LayerBufferSet}; use msg::compositor_msg::{Epoch, LayerId, LayerProperties, FrameTreeId}; use msg::compositor_msg::{PaintListener, ScriptListener}; use msg::constellation_msg::{AnimationState, ConstellationChan, PipelineId}; @@ -111,6 +111,18 @@ impl PaintListener for Box<CompositorProxy+'static+Send> { self.send(Msg::AssignPaintedBuffers(pipeline_id, epoch, replies, frame_tree_id)); } + fn ignore_buffer_requests(&mut self, buffer_requests: Vec<BufferRequest>) { + let mut layer_buffers = Vec::new(); + for request in buffer_requests.into_iter() { + if let Some(layer_buffer) = request.layer_buffer { + layer_buffers.push(layer_buffer); + } + } + if !layer_buffers.is_empty() { + self.send(Msg::ReturnUnusedLayerBuffers(layer_buffers)); + } + } + fn initialize_layers_for_pipeline(&mut self, pipeline_id: PipelineId, properties: Vec<LayerProperties>, @@ -184,6 +196,9 @@ pub enum Msg { NewFavicon(Url), /// <head> tag finished parsing HeadParsed, + /// Signal that the paint task ignored the paint requests that carried + /// these layer buffers, so that they can be re-added to the surface cache. + ReturnUnusedLayerBuffers(Vec<Box<LayerBuffer>>), } impl Debug for Msg { @@ -212,6 +227,7 @@ impl Debug for Msg { Msg::IsReadyToSaveImageReply(..) => write!(f, "IsReadyToSaveImageReply"), Msg::NewFavicon(..) => write!(f, "NewFavicon"), Msg::HeadParsed => write!(f, "HeadParsed"), + Msg::ReturnUnusedLayerBuffers(..) => write!(f, "ReturnUnusedLayerBuffers"), } } } diff --git a/components/compositing/headless.rs b/components/compositing/headless.rs index a92906d6756..c4a4368d49b 100644 --- a/components/compositing/headless.rs +++ b/components/compositing/headless.rs @@ -111,6 +111,7 @@ impl CompositorEventListener for NullCompositor { Msg::IsReadyToSaveImageReply(..) => {} Msg::NewFavicon(..) => {} Msg::HeadParsed => {} + Msg::ReturnUnusedLayerBuffers(..) => {} } true } diff --git a/components/compositing/lib.rs b/components/compositing/lib.rs index ba2934216e6..c3f64f8703f 100644 --- a/components/compositing/lib.rs +++ b/components/compositing/lib.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #![feature(box_syntax)] +#![feature(iter_cmp)] #![feature(slice_bytes)] #![feature(vec_push_all)] @@ -43,6 +44,7 @@ pub use constellation::Constellation; pub mod compositor_task; +mod buffer_map; mod compositor_layer; mod compositor; mod headless; |