Skip to content

Commit

Permalink
Add buffer_map.rs; implement LayerBuffer recycling
Browse files Browse the repository at this point in the history
  • Loading branch information
eschweic committed Aug 20, 2013
1 parent 02ae7bd commit d92cbe6
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 67 deletions.
135 changes: 135 additions & 0 deletions src/components/gfx/buffer_map.rs
@@ -0,0 +1,135 @@
/* 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::hashmap::HashMap;
use std::to_bytes::Cb;
use geom::size::Size2D;
use servo_msg::compositor_msg::Tile;

/// This is a struct used to store buffers when they are not in use.
/// The render task can quickly query for a particular size of buffer when it
/// needs it.
pub struct BufferMap<T> {
/// A HashMap that stores the Buffers.
map: HashMap<BufferKey, BufferValue<T>>,
/// The current amount of memory stored by the BufferMap's buffers.
mem: uint,
/// The maximum allowed memory. Unused buffers willl be deleted
/// when this threshold is exceeded.
max_mem: uint,
/// A monotonically increasing counter to track how recently tile sizes were used.
counter: uint,
}

/// A key with which to store buffers. It is based on the size of the buffer.
struct BufferKey([uint, ..2]);

impl IterBytes for BufferKey {
fn iter_bytes(&self, lsb0: bool, f: Cb) -> bool {
let i = if lsb0 {0} else {1};
self[i].iter_bytes(lsb0, |x| f(x)) && self[1 - i].iter_bytes(lsb0, |x| f(x))
}
}

impl Eq for BufferKey {
fn eq(&self, other: &BufferKey) -> bool {
self[0] == other[0] && self[1] == other[1]
}
}

/// Create a key from a given size
impl BufferKey {
fn get(input: Size2D<uint>) -> BufferKey {
BufferKey([input.width, input.height])
}
}

/// A helper struct to keep track of buffers in the HashMap
struct BufferValue<T> {
/// An array of buffers, all the same size
buffers: ~[T],
/// The counter when this size was last requested
last_action: uint,
}

impl<T: Tile> BufferMap<T> {
// Creates a new BufferMap with a given buffer limit.
pub fn new(max_mem: uint) -> BufferMap<T> {
BufferMap {
map: HashMap::new(),
mem: 0u,
max_mem: max_mem,
counter: 0u,
}
}

// Insert a new buffer into the map.
pub fn insert(&mut self, new_buffer: T) {
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) {
return;
}

self.mem += new_buffer.get_mem();
// use lazy insertion function to prevent unnecessary allocation
self.map.find_or_insert_with(new_key, |_| BufferValue {
buffers: ~[],
last_action: self.counter
}).buffers.push(new_buffer);

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 => fail!("BufferMap: tried to delete with no elements in map"),
}
}
};
if {
let list = &mut self.map.get_mut(&old_key).buffers;
self.mem -= list.pop().get_mem();
list.is_empty()
}
{ // then
self.map.pop(&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<uint>) -> Option<T> {
let mut flag = false; // True if key needs to be popped after retrieval.
let key = BufferKey::get(size);
let ret = match self.map.find_mut(&key) {
Some(ref mut buffer_val) => {
buffer_val.last_action = self.counter;
self.counter += 1;

let buffer = buffer_val.buffers.pop();
self.mem -= buffer.get_mem();
if buffer_val.buffers.is_empty() {
flag = true;
}
Some(buffer)
}
None => None,
};

if flag {
self.map.pop(&key); // Don't store empty vectors!
}

ret
}
}
1 change: 1 addition & 0 deletions src/components/gfx/gfx.rc
Expand Up @@ -51,6 +51,7 @@ pub mod font_list;

// Misc.
pub mod opts;
mod buffer_map;

// Platform-specific implementations.
#[path="platform/mod.rs"]
Expand Down
41 changes: 30 additions & 11 deletions src/components/gfx/render_task.rs
Expand Up @@ -25,7 +25,7 @@ use servo_util::time::{ProfilerChan, profile};
use servo_util::time;

use extra::arc;

use buffer_map::BufferMap;


pub struct RenderLayer<T> {
Expand All @@ -36,6 +36,7 @@ pub struct RenderLayer<T> {
pub enum Msg<T> {
RenderMsg(RenderLayer<T>),
ReRenderMsg(~[BufferRequest], f32, Epoch),
UnusedBufferMsg(~[~LayerBuffer]),
PaintPermissionGranted,
PaintPermissionRevoked,
ExitMsg(Chan<()>),
Expand Down Expand Up @@ -94,6 +95,8 @@ struct RenderTask<C,T> {
last_paint_msg: Option<~LayerBufferSet>,
/// A counter for epoch messages
epoch: Epoch,
/// A data structure to store unused LayerBuffers
buffer_map: BufferMap<~LayerBuffer>,
}

impl<C: RenderListener + Send,T:Send+Freeze> RenderTask<C,T> {
Expand Down Expand Up @@ -129,6 +132,7 @@ impl<C: RenderListener + Send,T:Send+Freeze> RenderTask<C,T> {
paint_permission: false,
last_paint_msg: None,
epoch: Epoch(0),
buffer_map: BufferMap::new(10000000),
};

render_task.start();
Expand All @@ -154,6 +158,12 @@ impl<C: RenderListener + Send,T:Send+Freeze> RenderTask<C,T> {
debug!("renderer epoch mismatch: %? != %?", self.epoch, epoch);
}
}
UnusedBufferMsg(unused_buffers) => {
// move_rev_iter is more efficient
for buffer in unused_buffers.move_rev_iter() {
self.buffer_map.insert(buffer);
}
}
PaintPermissionGranted => {
self.paint_permission = true;
match self.render_layer {
Expand All @@ -169,7 +179,7 @@ impl<C: RenderListener + Send,T:Send+Freeze> RenderTask<C,T> {
// re-rendered redundantly.
match self.last_paint_msg {
Some(ref layer_buffer_set) => {
self.compositor.paint(self.id, layer_buffer_set.clone());
self.compositor.paint(self.id, layer_buffer_set.clone(), self.epoch);
}
None => {} // Nothing to do
}
Expand Down Expand Up @@ -206,15 +216,24 @@ impl<C: RenderListener + Send,T:Send+Freeze> RenderTask<C,T> {
let width = tile.screen_rect.size.width;
let height = tile.screen_rect.size.height;

let buffer = ~LayerBuffer {
draw_target: DrawTarget::new_with_fbo(self.opts.render_backend,
self.share_gl_context,
Size2D(width as i32, height as i32),
B8G8R8A8),
rect: tile.page_rect,
screen_pos: tile.screen_rect,
resolution: scale,
stride: (width * 4) as uint
let buffer = match self.buffer_map.find(tile.screen_rect.size) {
Some(buffer) => {
let mut buffer = buffer;
buffer.rect = tile.page_rect;
buffer.screen_pos = tile.screen_rect;
buffer.resolution = scale;
buffer
}
None => ~LayerBuffer {
draw_target: DrawTarget::new_with_fbo(self.opts.render_backend,
self.share_gl_context,
Size2D(width as i32, height as i32),
B8G8R8A8),
rect: tile.page_rect,
screen_pos: tile.screen_rect,
resolution: scale,
stride: (width * 4) as uint
}
};


Expand Down
24 changes: 16 additions & 8 deletions src/components/main/compositing/compositor_layer.rs
Expand Up @@ -7,7 +7,7 @@ use geom::point::Point2D;
use geom::size::Size2D;
use geom::rect::Rect;
use geom::matrix::identity;
use gfx::render_task::ReRenderMsg;
use gfx::render_task::{ReRenderMsg, UnusedBufferMsg};
use servo_msg::compositor_msg::{LayerBuffer, LayerBufferSet, Epoch};
use servo_msg::constellation_msg::PipelineId;
use script::dom::event::{ClickEvent, MouseDownEvent, MouseUpEvent};
Expand Down Expand Up @@ -208,9 +208,12 @@ impl CompositorLayer {
no quadtree initialized", self.pipeline.id),
Tree(ref mut quadtree) => quadtree,
};
let (request, r) = quadtree.get_tile_rects_page(rect, scale);
redisplay = r; // workaround to make redisplay visible outside block
if !request.is_empty() {
let (request, unused) = quadtree.get_tile_rects_page(rect, scale);
redisplay = !unused.is_empty(); // workaround to make redisplay visible outside block
if redisplay { // send back unused tiles
self.pipeline.render_chan.send(UnusedBufferMsg(unused));
}
if !request.is_empty() { // ask for tiles
self.pipeline.render_chan.send(ReRenderMsg(request, scale, self.epoch));
}
}
Expand Down Expand Up @@ -414,10 +417,15 @@ impl CompositorLayer {
Tree(ref mut quadtree) => quadtree,
};

for buffer in cell.take().buffers.consume_rev_iter() {
// TODO: This may return old buffers, which should be sent back to the renderer.
quadtree.add_tile_pixel(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y,
buffer.resolution, buffer);
let mut unused_tiles = ~[];
// move_rev_iter is more efficient
for buffer in cell.take().buffers.move_rev_iter() {
unused_tiles.push_all_move(quadtree.add_tile_pixel(buffer.screen_pos.origin.x,
buffer.screen_pos.origin.y,
buffer.resolution, buffer));
}
if !unused_tiles.is_empty() { // send back unused buffers
self.pipeline.render_chan.send(UnusedBufferMsg(unused_tiles));
}
}
self.build_layer_tree();
Expand Down

0 comments on commit d92cbe6

Please sign in to comment.