aboutsummaryrefslogtreecommitdiffstats
path: root/components/compositing
diff options
context:
space:
mode:
Diffstat (limited to 'components/compositing')
-rw-r--r--components/compositing/buffer_map.rs162
-rw-r--r--components/compositing/compositor.rs86
-rw-r--r--components/compositing/compositor_layer.rs35
-rw-r--r--components/compositing/compositor_task.rs18
-rw-r--r--components/compositing/headless.rs1
-rw-r--r--components/compositing/lib.rs2
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;