aboutsummaryrefslogtreecommitdiffstats
path: root/components/compositing
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2015-06-30 16:32:14 -0700
committerMartin Robinson <mrobinson@igalia.com>2015-07-08 08:05:11 -0700
commite115c3d3c4996dd9014f0314be3f03f6ba106ee9 (patch)
treebd9983cb03eb5cab17fb0a475aa878a71a59ac1a /components/compositing
parent2e1c9785dc5725f809c56b7bafb7a3a68fb1dca0 (diff)
downloadservo-e115c3d3c4996dd9014f0314be3f03f6ba106ee9.tar.gz
servo-e115c3d3c4996dd9014f0314be3f03f6ba106ee9.zip
Move LayerBuffer cache to the compositor
Now that NativeDisplay can be shared between the compositor and the paint task, we can move the LayerBuffer cache to the compositor. This allows surfaces to be potentially reused between different paint tasks and will eventually allow OpenGL contexts to be preserved between instances of GL rasterization.
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;