diff --git a/layers.rs b/layers.rs index 90c2039..12e8c13 100644 --- a/layers.rs +++ b/layers.rs @@ -8,6 +8,7 @@ // except according to those terms. use texturegl::Texture; +use tiling::TileGrid; use geom::matrix::{Matrix4, identity}; use geom::size::Size2D; @@ -15,10 +16,6 @@ use geom::rect::Rect; use platform::surface::{NativePaintingGraphicsContext, NativeSurfaceMethods, NativeSurface}; use std::cell::{RefCell, RefMut}; use std::rc::Rc; -use quadtree::{Quadtree, NodeStatus, Normal, Tile}; - -/// The amount of memory usage allowed per layer. -static MAX_TILE_MEMORY_PER_LAYER: uint = 10000000; pub enum Format { ARGB32Format, @@ -28,11 +25,11 @@ pub enum Format { pub struct Layer { pub children: RefCell>>>, pub tiles: RefCell>>, - pub quadtree: RefCell>>, pub transform: RefCell>, pub bounds: RefCell>, tile_size: uint, pub extra_data: RefCell, + tile_grid: RefCell, } impl Layer { @@ -40,14 +37,11 @@ impl Layer { Layer { children: RefCell::new(vec!()), tiles: RefCell::new(vec!()), - quadtree: RefCell::new(Quadtree::new(Size2D(bounds.size.width as uint, - bounds.size.height as uint), - tile_size, - Some(MAX_TILE_MEMORY_PER_LAYER))), transform: RefCell::new(identity()), bounds: RefCell::new(bounds), tile_size: tile_size, extra_data: RefCell::new(data), + tile_grid: RefCell::new(TileGrid::new(tile_size)), } } @@ -64,30 +58,36 @@ impl Layer { } pub fn get_tile_rects_page(this: Rc>, window: Rect, scale: f32) -> (Vec, Vec>) { - this.quadtree.borrow_mut().get_tile_rects_page(window, scale) - } - - pub fn set_status_page(this: Rc>, rect: Rect, _status: NodeStatus, _include_border: bool) { - this.quadtree.borrow_mut().set_status_page(rect, Normal, false); // Rect is unhidden + let mut tile_grid = this.tile_grid.borrow_mut(); + (tile_grid.get_buffer_requests_in_rect(window, scale), tile_grid.take_unused_tiles()) } - pub fn resize(this: Rc>, new_size: Size2D) -> Vec> { + pub fn resize(this: Rc>, new_size: Size2D) { this.bounds.borrow_mut().size = new_size; - this.quadtree.borrow_mut().resize(new_size.width as uint, new_size.height as uint) } pub fn do_for_all_tiles(this: Rc>, f: |&Box|) { - this.quadtree.borrow_mut().do_for_all_tiles(f); + this.tile_grid.borrow().do_for_all_tiles(f); + } + + pub fn add_tile_pixel(this: Rc>, tile: Box) { + this.tile_grid.borrow_mut().add_tile(tile); } - pub fn add_tile_pixel(this: Rc>, tile: Box) -> Vec> { - this.quadtree.borrow_mut().add_tile_pixel(tile.screen_pos.origin.x, - tile.screen_pos.origin.y, - tile.resolution, tile) + pub fn collect_unused_tiles(this: Rc>) -> Vec> { + this.tile_grid.borrow_mut().take_unused_tiles() } pub fn collect_tiles(this: Rc>) -> Vec> { - this.quadtree.borrow_mut().collect_tiles() + this.tile_grid.borrow_mut().collect_tiles() + } + + pub fn flush_pending_buffer_requests(&self) -> (Vec, f32) { + self.tile_grid.borrow_mut().flush_pending_buffer_requests() + } + + pub fn contents_changed(&self) { + self.tile_grid.borrow_mut().contents_changed() } } @@ -174,6 +174,25 @@ impl LayerBufferSet { } } +/// The interface used by the BufferMap to get info about layer buffers. +pub trait Tile { + /// Returns the amount of memory used by the tile + fn get_mem(&self) -> uint; + + /// Returns true if the tile is displayable at the given scale + fn is_valid(&self, f32) -> bool; + + /// Returns the Size2D of the tile + fn get_size_2d(&self) -> Size2D; + + /// Marks the layer buffer as not leaking. See comments on + /// `NativeSurfaceMethods::mark_wont_leak` for how this is used. + fn mark_wont_leak(&mut self); + + /// Destroys the layer buffer. Painting task only. + fn destroy(self, graphics_context: &NativePaintingGraphicsContext); +} + impl Tile for Box { fn get_mem(&self) -> uint { // This works for now, but in the future we may want a better heuristic diff --git a/lib.rs b/lib.rs index 5029916..bf8eb3b 100755 --- a/lib.rs +++ b/lib.rs @@ -41,7 +41,7 @@ pub mod rendergl; pub mod scene; pub mod texturegl; pub mod util; -pub mod quadtree; +pub mod tiling; pub mod platform { #[cfg(target_os="linux")] diff --git a/quadtree.rs b/quadtree.rs deleted file mode 100644 index 4c43551..0000000 --- a/quadtree.rs +++ /dev/null @@ -1,752 +0,0 @@ -// Copyright 2013 The Servo Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use geom::point::Point2D; -use geom::size::Size2D; -use geom::rect::Rect; -use platform::surface::{NativePaintingGraphicsContext}; -use std::cmp; -use std::mem::replace; -use std::num::next_power_of_two; -use layers::BufferRequest; - -/// The interface used by the quadtree and buffer map to get info about layer buffers. -pub trait Tile { - /// Returns the amount of memory used by the tile - fn get_mem(&self) -> uint; - - /// Returns true if the tile is displayable at the given scale - fn is_valid(&self, f32) -> bool; - - /// Returns the Size2D of the tile - fn get_size_2d(&self) -> Size2D; - - /// Marks the layer buffer as not leaking. See comments on - /// `NativeSurfaceMethods::mark_wont_leak` for how this is used. - fn mark_wont_leak(&mut self); - - /// Destroys the layer buffer. Painting task only. - fn destroy(self, graphics_context: &NativePaintingGraphicsContext); -} - -/// Parent to all quadtree nodes. Stores variables needed at all levels. All method calls -/// at this level are in pixel coordinates. -pub struct Quadtree { - // The root node of the quadtree - pub root: Box>, - // The size of the layer in pixels. Tiles will be clipped to this size. - // Note that the underlying quadtree has a potentailly larger size, since it is rounded - // to the next highest power of two. - pub clip_size: Size2D, - // The maximum size of the tiles requested in pixels. Tiles requested will be - // of a size anywhere between half this value and this value. - pub max_tile_size: uint, - // The maximum allowed total memory of tiles in the tree. If this limit is reached, tiles - // will be removed from the tree. Set this to None to prevent this behavior. - pub max_mem: Option, -} - -/// A node in the tree. All method calls at this level are in page coordinates. -pub struct QuadtreeNode { - /// The tile belonging to this node. Note that parent nodes can have tiles. - pub tile: Option, - /// The position of the node in page coordinates. - pub origin: Point2D, - /// The width and height of the node in page coordinates. - pub size: f32, - /// The node's children. - pub quadrants: [Option>>, ..4], - /// Combined size of self.tile and tiles of all descendants - pub tile_mem: uint, - /// The current status of this node. See below for details. - pub status: NodeStatus, -} - -/// The status of a QuadtreeNode. This determines the behavior of the node -/// when querying for tile requests. -#[deriving(PartialEq)] -pub enum NodeStatus { - /// If we have no valid tile, request one; otherwise, don't send a request. - Normal, - /// Render request has been sent; ignore this node until tile is inserted. - Rendering, - /// Do not send tile requests. Overrides Invalid. - Hidden, - /// Send tile requests, even if the node has (or child nodes have) a valid tile. - Invalid, -} - -enum Quadrant { - TL = 0, - TR = 1, - BL = 2, - BR = 3, -} - -fn div_ceil(x: uint, y: uint) -> uint { - let div = x / y; - if x % y == 0u { div } - else { div + 1u } -} - -impl Quadtree { - /// Public method to create a new Quadtree - /// Takes in the initial width and height of the space, a maximum tile size, and - /// a maximum amount of memory. Tiles will be deleted if this memory is exceeded. - /// Set max_mem to None to turn off automatic tile removal. - pub fn new(clip_size: Size2D, tile_size: uint, max_mem: Option) -> Quadtree { - // Spaces must be squares and powers of 2, so expand the space until it is - let longer = cmp::max(clip_size.width, clip_size.height); - let num_tiles = div_ceil(longer, tile_size); - let power_of_two = next_power_of_two(num_tiles); - let size = power_of_two * tile_size; - - Quadtree { - root: box QuadtreeNode { - tile: None, - origin: Point2D(0f32, 0f32), - size: size as f32, - quadrants: [None, None, None, None], - tile_mem: 0, - status: Normal, - }, - clip_size: clip_size, - max_tile_size: tile_size, - max_mem: max_mem, - } - } - - /// Add a tile associated with a given pixel position and scale. - /// If the tile pushes the total memory over its maximum, tiles will be removed - /// until total memory is below the maximum again. These tiles are returned. - pub fn add_tile_pixel(&mut self, x: uint, y: uint, scale: f32, tile: T) -> Vec { - let (_, tiles) = self.root.add_tile(x as f32 / scale, y as f32 / scale, tile, - self.max_tile_size as f32 / scale); - let mut tiles = tiles; - match self.max_mem { - Some(max) => { - while self.root.tile_mem > max { - let r = self.root.remove_tile(x as f32 / scale, y as f32 / scale); - match r { - (Some(tile), _, _) => tiles.push(tile), - _ => fail!("Quadtree: No valid tiles to remove"), - } - } - } - None => {} // Nothing to do - } - tiles - } - - /// Get all the tiles in the tree. - pub fn do_for_all_tiles(&mut self, f: |&T|) { - for tile in self.root.get_all_tiles().iter() { - f(*tile); - } - } - - /// Given a window rect in pixel coordinates, this function returns a list of BufferRequests for tiles that - /// need to be rendered. It also returns a vector of tiles if the window needs to be redisplayed, i.e. if - /// no tiles need to be rendered, but the display tree needs to be rebuilt. This can occur when the - /// user zooms out and cached tiles need to be displayed on top of higher resolution tiles. - /// When this happens, higher resolution tiles will be removed from the quadtree. - #[cfg(test)] - pub fn get_tile_rects_pixel(&mut self, window: Rect, scale: f32) -> (Vec, Vec) { - let (ret, unused, _) = self.root.get_tile_rects( - Rect(Point2D(window.origin.x as f32 / scale, window.origin.y as f32 / scale), - Size2D(window.size.width as f32 / scale, window.size.height as f32 / scale)), - Size2D(self.clip_size.width as f32, self.clip_size.height as f32), - scale, self.max_tile_size as f32 / scale, false); - (ret, unused) - } - - /// Same function as above, using page coordinates for the window. - pub fn get_tile_rects_page(&mut self, window: Rect, scale: f32) -> (Vec, Vec) { - let (ret, unused, _) = self.root.get_tile_rects( - window, - Size2D(self.clip_size.width as f32, self.clip_size.height as f32), - scale, self.max_tile_size as f32 / scale, false); - (ret, unused) - } - - /// Creates a new quadtree at the specified size. This should be called when the window changes size. - pub fn resize(&mut self, width: uint, height: uint) -> Vec { - // Spaces must be squares and powers of 2, so expand the space until it is - let longer = cmp::max(width, height); - let num_tiles = div_ceil(longer, self.max_tile_size); - let power_of_two = next_power_of_two(num_tiles); - let size = power_of_two * self.max_tile_size; - let ret = self.root.collect_tiles(); - self.root = box QuadtreeNode { - tile: None, - origin: Point2D(0f32, 0f32), - size: size as f32, - quadrants: [None, None, None, None], - status: Normal, - tile_mem: 0, - }; - self.clip_size = Size2D(width, height); - ret - } - - /// Resize the underlying quadtree without removing tiles already in place. - /// Might be useful later on, but resize() should be used for now. - /// TODO: return tiles after shrinking - #[cfg(test)] - pub fn bad_resize(&mut self, width: uint, height: uint) { - self.clip_size = Size2D(width, height); - let longer = cmp::max(width, height); - let new_num_tiles = div_ceil(longer, self.max_tile_size); - let new_size = next_power_of_two(new_num_tiles); - // difference here indicates the number of times the underlying size of the quadtree needs - // to be doubled or halved. It will recursively add a new root if it is positive, or - // recursivly make a child the new root if it is negative. - let difference = (new_size as f32 / self.root.size as f32).log2() as int; - if difference > 0 { // doubling - let difference = difference as uint; - for i in range(0, difference) { - let new_root = box QuadtreeNode { - tile: None, - origin: Point2D(0f32, 0f32), - size: new_size as f32 / ((difference - i - 1) as f32).exp2(), - quadrants: [None, None, None, None], - tile_mem: self.root.tile_mem, - status: Normal, - }; - self.root.quadrants[TL as uint] = Some(replace(&mut self.root, new_root)); - } - } else if difference < 0 { // halving - let difference = difference.abs() as uint; - for _ in range(0, difference) { - let remove = replace(&mut self.root.quadrants[TL as uint], None); - match remove { - Some(child) => self.root = child, - None => { - self.root = box QuadtreeNode { - tile: None, - origin: Point2D(0f32, 0f32), - size: new_size as f32, - quadrants: [None, None, None, None], - tile_mem: 0, - status: Normal, - }; - break; - } - } - } - } - } - - /// Set the status of all quadtree nodes within the given rect in page coordinates. If - /// include_border is true, then nodes on the edge of the rect will be included; otherwise, - /// only nodes completely occluded by the rect will be changed. - pub fn set_status_page(&mut self, rect: Rect, status: NodeStatus, include_border: bool) { - self.root.set_status(rect, status, include_border); - } - - /// Remove and return all tiles in the tree. Use this before deleting the quadtree to prevent - /// a GC pause. - pub fn collect_tiles(&mut self) -> Vec { - self.root.collect_tiles() - } -} - -impl QuadtreeNode { - /// Private method to create new children - fn new_child(x: f32, y: f32, size: f32) -> QuadtreeNode { - QuadtreeNode { - tile: None, - origin: Point2D(x, y), - size: size, - quadrants: [None, None, None, None], - tile_mem: 0, - status: Normal, - } - } - - /// Determine which child contains a given point in page coords. - fn get_quadrant(&self, x: f32, y: f32) -> Quadrant { - if x < self.origin.x + self.size / 2.0 { - if y < self.origin.y + self.size / 2.0 { - TL - } else { - BL - } - } else if y < self.origin.y + self.size / 2.0 { - TR - } else { - BR - } - } - - /// Get all tiles in the tree, parents first. - fn get_all_tiles<'r>(&'r self) -> Vec<&'r T> { - let mut ret = vec!(); - - match self.tile { - Some(ref tile) => ret.push(tile), - None => {} - } - - for quad in self.quadrants.iter() { - match *quad { - Some(ref child) => ret.push_all_move(child.get_all_tiles()), - None => {} - } - } - - return ret; - } - - /// Add a tile associated with a given position in page coords. If the tile size exceeds the maximum, - /// the node will be split and the method will recurse until the tile size is within limits. - /// Returns an the difference in tile memory between the new quadtree node and the old quadtree node, - /// along with any deleted tiles. - fn add_tile(&mut self, x: f32, y: f32, tile: T, tile_size: f32) -> (int, Vec) { - debug!("Quadtree: Adding: ({}, {}) size:{}px", self.origin.x, self.origin.y, self.size); - - if x >= self.origin.x + self.size || x < self.origin.x - || y >= self.origin.y + self.size || y < self.origin.y { - fail!("Quadtree: Tried to add tile to invalid region"); - } - - if self.size <= tile_size { // We are the child - let old_size = self.tile_mem; - self.tile_mem = tile.get_mem(); - let mut unused_tiles = match replace(&mut self.tile, Some(tile)) { - Some(old_tile) => vec!(old_tile), - None => vec!(), - }; - for child in self.quadrants.mut_iter() { - match *child { - Some(ref mut node) => { - unused_tiles.push_all_move(node.collect_tiles()); - } - None => {} // Nothing to do - } - *child = None; - } - self.status = Normal; - (self.tile_mem as int - old_size as int, unused_tiles) - } else { // Send tile to children - let quad = self.get_quadrant(x, y); - match self.quadrants[quad as uint] { - Some(ref mut child) => { - let (delta, unused) = child.add_tile(x, y, tile, tile_size); - self.tile_mem = (self.tile_mem as int + delta) as uint; - (delta, unused) - } - None => { // Make new child - let new_size = self.size / 2.0; - let new_x = match quad { - TL | BL => self.origin.x, - TR | BR => self.origin.x + new_size, - }; - let new_y = match quad { - TL | TR => self.origin.y, - BL | BR => self.origin.y + new_size, - }; - let mut c = box QuadtreeNode::new_child(new_x, new_y, new_size); - let (delta, unused) = c.add_tile(x, y, tile, tile_size); - self.tile_mem = (self.tile_mem as int + delta) as uint; - self.quadrants[quad as uint] = Some(c); - (delta, unused) - } - } - } - } - - /// Get a tile rect in screen and page coords for a given position in page coords - fn get_tile_rect(&mut self, x: f32, y: f32, clip_x: f32, clip_y: f32, scale: f32, - tile_size: f32) -> BufferRequest { - if x >= self.origin.x + self.size || x < self.origin.x - || y >= self.origin.y + self.size || y < self.origin.y { - fail!("Quadtree: Tried to query a tile rect outside of range"); - } - - if self.size <= tile_size { - let pix_x = (self.origin.x * scale).ceil() as uint; - let pix_y = (self.origin.y * scale).ceil() as uint; - let page_width = self.size.min(clip_x - self.origin.x); - let page_height = self.size.min(clip_y - self.origin.y); - let pix_width = (page_width * scale).ceil() as uint; - let pix_height = (page_height * scale).ceil() as uint; - self.status = Rendering; - return BufferRequest::new(Rect(Point2D(pix_x, pix_y), Size2D(pix_width, pix_height)), - Rect(Point2D(self.origin.x, self.origin.y), Size2D(page_width, page_height))); - } - - let quad = self.get_quadrant(x,y); - match self.quadrants[quad as uint] { - None => { - let new_size = self.size / 2.0; - let new_x = match quad { - TL | BL => self.origin.x, - TR | BR => self.origin.x + new_size, - }; - let new_y = match quad { - TL | TR => self.origin.y, - BL | BR => self.origin.y + new_size, - }; - let mut c = box QuadtreeNode::new_child(new_x, new_y, new_size); - let result = c.get_tile_rect(x, y, clip_x, clip_y, scale, tile_size); - self.quadrants[quad as uint] = Some(c); - result - } - Some(ref mut child) => child.get_tile_rect(x, y, clip_x, clip_y, scale, tile_size), - } - } - - /// Removes a tile that is far from the given input point in page coords. Returns the tile removed, - /// a bool that is true if the child has no tiles and needs to be deleted, and an integer showing the - /// amount of memory changed by the operation. Unfortunately, the tile has to be an option, because - /// there are occasionally leaves without tiles. However, the option will always be Some as long as - /// this quadtree node or at least one of its descendants is not empty. - fn remove_tile(&mut self, x: f32, y: f32) -> (Option, bool, int) { - if self.tile.is_some() { - let ret = replace(&mut(self.tile), None); - return match (ret, &self.quadrants) { - (Some(tile), &[None, None, None, None]) => { - let size = -(tile.get_mem() as int); - (Some(tile), true, size) - } - (Some(tile), _) => { - let size = -(tile.get_mem() as int); - (Some(tile), false, size) - } - _ => fail!("Quadtree: tile query failure in remove_tile"), - } - } - - // This is a hacky heuristic to find a tile that is "far away". There are better methods. - let quad = self.get_quadrant(x, y); - let queue = match quad { - TL => [BR, BL, TR, TL], - TR => [BL, BR, TL, TR], - BL => [TR, TL, BR, BL], - BR => [TL, TR, BL, BR], - }; - - let mut del_quad: Option = None; - let mut ret = (None, false, 0); - - for quad in queue.iter() { - match self.quadrants[*quad as uint] { - Some(ref mut child) => { - let (tile, flag, delta) = child.remove_tile(x, y); - match tile { - Some(_) => { - self.tile_mem = (self.tile_mem as int + delta) as uint; - if flag { - del_quad = Some(*quad); - } else { - return (tile, flag, delta); - } - - ret = (tile, flag, delta); - break; - } - None => {}, - } - } - None => {}, - } - } - - match del_quad { - Some(quad) => { - self.quadrants[quad as uint] = None; - let (tile, _, delta) = ret; - match (&self.tile, &self.quadrants) { - (&None, &[None, None, None, None]) => (tile, true, delta), - _ => (tile, false, delta) - } - } - None => ret, - } - } - - /// Given a window rect in page coordinates, returns a BufferRequest array, - /// an unused tile array, and the difference in tile memory between the new and old quadtree nodes. - /// The override bool will be true if a parent node was marked as invalid; child nodes will be - /// treated as invalid as well. - /// NOTE: this method will sometimes modify the tree by deleting tiles. - /// See the QuadTree function description for more details. - fn get_tile_rects(&mut self, - window: Rect, - clip: Size2D, - scale: f32, - tile_size: f32, - override: bool) - -> (Vec, Vec, int) { - let w_x = window.origin.x; - let w_y = window.origin.y; - let w_width = window.size.width; - let w_height = window.size.height; - let s_x = self.origin.x; - let s_y = self.origin.y; - let s_size = self.size; - - // if window is outside of visible region, nothing to do - if w_x + w_width < s_x || w_x > s_x + s_size - || w_y + w_height < s_y || w_y > s_y + s_size - || w_x >= clip.width || w_y >= clip.height { - return (vec!(), vec!(), 0); - } - - // clip window to visible region - let w_width = w_width.min(clip.width - w_x); - let w_height = w_height.min(clip.height - w_y); - - if s_size <= tile_size { // We are the child - return match self.tile { - _ if self.status == Rendering || self.status == Hidden => (vec!(), vec!(), 0), - Some(ref tile) if tile.is_valid(scale) && !override - && self.status != Invalid => { - let redisplay = match self.quadrants { - [None, None, None, None] => false, - _ => true, - }; - let mut delta = 0; - let mut unused_tiles = vec!(); - if redisplay { - let old_mem = self.tile_mem; - for child in self.quadrants.mut_iter() { - match *child { - Some(ref mut node) => { - unused_tiles.push_all_move(node.collect_tiles()); - } - None => {} // Nothing to do - } - *child = None; - } - self.tile_mem = tile.get_mem(); - delta = self.tile_mem as int - old_mem as int; - - } - (vec!(), unused_tiles, delta) - } - _ => (vec!(self.get_tile_rect(s_x, s_y, clip.width, clip.height, scale, tile_size)), vec!(), 0), - } - } - - // Otherwise, we either have children or will have children - let w_tl_quad = self.get_quadrant(w_x, w_y); - let w_br_quad = self.get_quadrant(w_x + w_width, w_y + w_height); - - // Figure out which quadrants the window is in - let mut quads_to_check = Vec::with_capacity(4); - match (w_tl_quad, w_br_quad) { - (tl, br) if tl as int == br as int => { - quads_to_check.push(tl); - } - (TL, br) => { - quads_to_check.push(TL); - quads_to_check.push(br); - match br { - BR => { - quads_to_check.push(TR); - quads_to_check.push(BL); - } - _ => {} - } - } - (tl, br) => { - quads_to_check.push(tl); - quads_to_check.push(br); - } - } - - let mut request = vec!(); - let mut unused = vec!(); - let mut delta = 0; - - for quad in quads_to_check.iter() { - // Recurse into child - let new_window = match *quad { - TL => Rect(window.origin, - Size2D(w_width.min(s_x + s_size / 2.0 - w_x), - w_height.min(s_y + s_size / 2.0 - w_y))), - TR => Rect(Point2D(w_x.max(s_x + s_size / 2.0), - w_y), - Size2D(w_width.min(w_x + w_width - (s_x + s_size / 2.0)), - w_height.min(s_y + s_size / 2.0 - w_y))), - BL => Rect(Point2D(w_x, - w_y.max(s_y + s_size / 2.0)), - Size2D(w_width.min(s_x + s_size / 2.0 - w_x), - w_height.min(w_y + w_height - (s_y + s_size / 2.0)))), - BR => Rect(Point2D(w_x.max(s_x + s_size / 2.0), - w_y.max(s_y + s_size / 2.0)), - Size2D(w_width.min(w_x + w_width - (s_x + s_size / 2.0)), - w_height.min(w_y + w_height - (s_y + s_size / 2.0)))), - - }; - - let override = override || self.status == Invalid; - self.status = Normal; - let (c_request, c_unused, c_delta) = match self.quadrants[*quad as uint] { - Some(ref mut child) => child.get_tile_rects(new_window, clip, scale, tile_size, override), - None => { - // Create new child - let new_size = self.size / 2.0; - let new_x = match *quad { - TL | BL => self.origin.x, - TR | BR => self.origin.x + new_size, - }; - let new_y = match *quad { - TL | TR => self.origin.y, - BL | BR => self.origin.y + new_size, - }; - let mut child = box QuadtreeNode::new_child(new_x, new_y, new_size); - let ret = child.get_tile_rects(new_window, clip, scale, tile_size, override); - self.quadrants[*quad as uint] = Some(child); - ret - } - }; - - delta = delta + c_delta; - request.push_all(c_request.as_slice()); - unused.push_all_move(c_unused); - } - self.tile_mem = (self.tile_mem as int + delta) as uint; - (request, unused, delta) - } - - /// Remove all tiles from the tree. Use this to collect all tiles before deleting a branch. - fn collect_tiles(&mut self) -> Vec { - let mut ret = match replace(&mut self.tile, None) { - Some(tile) => vec!(tile), - None => vec!(), - }; - for child in self.quadrants.mut_iter() { - match *child { - Some(ref mut node) => { - ret.push_all_move(node.collect_tiles()); - } - None => {} // Nothing to do - } - } - ret - } - - /// Set the status of nodes contained within the rect. See the quadtree method for - /// more info. - fn set_status(&mut self, rect: Rect, status: NodeStatus, borders: bool) { - let self_rect = Rect(self.origin, Size2D(self.size, self.size)); - let intersect = rect.intersection(&self_rect); - let intersect = match intersect { - None => return, // We do not intersect the rect, nothing to do - Some(rect) => rect, - }; - - if self_rect == intersect { // We are completely contained in the rect - if !(self.status == Hidden && status == Invalid) { // Hidden trumps Invalid - self.status = status; - } - return; // No need to recurse - } - - match self.quadrants { - [None, None, None, None] => { // We are a leaf - if borders && !(self.status == Hidden && status == Invalid) { - self.status = status; - } - } - _ => { // We are internal - for quad in self.quadrants.mut_iter() { - match *quad { - None => {} // Nothing to do - Some(ref mut child) => { - child.set_status(intersect, status, borders); - } - } - } - } - } - } -} - -#[test] -pub fn test_resize() { - struct T { - a: int, - } - - impl Tile for T { - fn get_mem(&self) -> uint { - 1 - } - - fn is_valid(&self, _: f32) -> bool { - true - } - fn get_size_2d(&self) -> Size2D { - Size2D(0u, 0u) - } - fn mark_wont_leak(&mut self) {} - fn destroy(self, _: &NativePaintingGraphicsContext) {} - } - - let mut q = Quadtree::new(Size2D(6u, 6), 1, None); - q.add_tile_pixel(0, 0, 1f32, T{a: 0}); - q.add_tile_pixel(5, 5, 1f32, T{a: 1}); - q.bad_resize(8, 1); - assert!(q.root.size == 8.0); - q.bad_resize(18, 1); - assert!(q.root.size == 32.0); - q.bad_resize(8, 1); - assert!(q.root.size == 8.0); - q.bad_resize(3, 1); - assert!(q.root.size == 4.0); -} - -#[test] -pub fn test() { - struct T { - a: int, - } - - impl Tile for T { - fn get_mem(&self) -> uint { - 1 - } - - fn is_valid(&self, _: f32) -> bool { - true - } - fn get_size_2d(&self) -> Size2D { - Size2D(0u, 0u) - } - fn mark_wont_leak(&mut self) {} - fn destroy(self, _: &NativePaintingGraphicsContext) {} - } - - let mut q = Quadtree::new(Size2D(8u, 8), 2, Some(4)); - q.add_tile_pixel(0, 0, 1f32, T{a: 0}); - q.add_tile_pixel(0, 0, 2f32, T{a: 1}); - q.add_tile_pixel(0, 0, 2f32, T{a: 2}); - q.add_tile_pixel(2, 0, 2f32, T{a: 3}); - assert!(q.root.tile_mem == 3); - q.add_tile_pixel(0, 2, 2f32, T{a: 4}); - q.add_tile_pixel(2, 2, 2f32, T{a: 5}); - assert!(q.root.tile_mem == 4); - - let (request, _) = q.get_tile_rects_pixel(Rect(Point2D(0, 0), Size2D(2, 2)), 2f32); - assert!(request.is_empty()); - let (request, _) = q.get_tile_rects_pixel(Rect(Point2D(0, 0), Size2D(2, 2)), 1.9); - assert!(request.is_empty()); - let (request, _) = q.get_tile_rects_pixel(Rect(Point2D(0, 0), Size2D(2, 2)), 1f32); - assert!(request.len() == 4); - - q.add_tile_pixel(0, 0, 0.5, T{a: 6}); - q.add_tile_pixel(0, 0, 1f32, T{a: 7}); - let (_, unused) = q.get_tile_rects_pixel(Rect(Point2D(0, 0), Size2D(2, 2)), 0.5); - assert!(!unused.is_empty()); - assert!(q.root.tile_mem == 1); -} diff --git a/tiling.rs b/tiling.rs new file mode 100644 index 0000000..760e673 --- /dev/null +++ b/tiling.rs @@ -0,0 +1,159 @@ +// Copyright 2014 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use geom::point::Point2D; +use geom::size::Size2D; +use geom::rect::Rect; +use layers::BufferRequest; +use layers::LayerBuffer; +use std::collections::hashmap::HashMap; +use std::iter::range_inclusive; +use std::mem; + +pub struct TileGrid { + pub tiles: HashMap, Box>, + + // The size of tiles in this grid in device pixels. + tile_size: uint, + + // Tiles that are currently unused or outside the last-known visible rectangle. + unused_tiles: Vec>, + + // Whether or not there are pending buffer requests. + waiting_on_tiles : bool, + + // Once we know that we are waiting for tiles, track any later buffer requests. + // FIXME: Replace with a per-tile state which better tracks epoch transitions. + pending_buffer_request: Option<(Rect, f32)>, +} + +pub fn rect_uint_as_rect_f32(rect: Rect) -> Rect { + Rect(Point2D(rect.origin.x as f32, rect.origin.y as f32), + Size2D(rect.size.width as f32, rect.size.height as f32)) +} + +impl TileGrid { + pub fn new(tile_size: uint) -> TileGrid { + TileGrid { + tiles: HashMap::new(), + tile_size: tile_size, + unused_tiles: Vec::new(), + waiting_on_tiles: false, + pending_buffer_request: None, + } + } + + pub fn get_tile_index_range_for_rect(&self, rect: Rect) -> (Point2D, Point2D) { + (Point2D((rect.origin.x / self.tile_size as f32) as uint, + (rect.origin.y / self.tile_size as f32) as uint), + Point2D(((rect.origin.x + rect.size.width) / self.tile_size as f32) as uint, + ((rect.origin.y + rect.size.height) / self.tile_size as f32) as uint)) + } + + pub fn get_rect_for_tile_index(&self, tile_index: Point2D) -> Rect { + Rect(Point2D(self.tile_size * tile_index.x, self.tile_size * tile_index.y), + Size2D(self.tile_size, self.tile_size)) + } + + pub fn take_unused_tiles(&mut self) -> Vec> { + let mut unused_tiles = Vec::new(); + mem::swap(&mut unused_tiles, &mut self.unused_tiles); + return unused_tiles; + } + + pub fn mark_tiles_outside_of_rect_as_unused(&mut self, rect: Rect) { + let mut tile_indexes_to_take = Vec::new(); + for tile_index in self.tiles.keys() { + if !rect_uint_as_rect_f32(self.get_rect_for_tile_index(*tile_index)).intersects(&rect) { + tile_indexes_to_take.push(tile_index.clone()); + } + } + + for tile_index in tile_indexes_to_take.iter() { + match self.tiles.pop(tile_index) { + Some(tile) => self.unused_tiles.push(tile), + None => {}, + } + } + } + + pub fn get_buffer_requests_in_rect(&mut self, screen_rect: Rect, scale: f32) -> Vec { + if self.waiting_on_tiles { + self.pending_buffer_request = Some((screen_rect, scale)); + return Vec::new(); + } + + let mut buffer_requests = Vec::new(); + let rect_in_layer_pixels = screen_rect * scale; + let (top_left_index, bottom_right_index) = + self.get_tile_index_range_for_rect(rect_in_layer_pixels); + + for x in range_inclusive(top_left_index.x, bottom_right_index.x) { + for y in range_inclusive(top_left_index.y, bottom_right_index.y) { + let tile_rect = self.get_rect_for_tile_index(Point2D(x, y)); + let tile_screen_rect = rect_uint_as_rect_f32(tile_rect) / scale; + buffer_requests.push(BufferRequest::new(tile_rect, tile_screen_rect)); + } + } + + self.mark_tiles_outside_of_rect_as_unused(rect_in_layer_pixels); + self.waiting_on_tiles = !buffer_requests.is_empty(); + return buffer_requests; + } + + pub fn get_tile_index_for_point(&self, point: Point2D) -> Point2D { + assert!(point.x % self.tile_size == 0); + assert!(point.y % self.tile_size == 0); + Point2D((point.x / self.tile_size) as uint, + (point.y / self.tile_size) as uint) + } + + pub fn add_tile(&mut self, tile: Box) { + self.waiting_on_tiles = false; + let index = self.get_tile_index_for_point(tile.screen_pos.origin.clone()); + match self.tiles.swap(index, tile) { + Some(tile) => self.unused_tiles.push(tile), + None => {}, + } + } + + pub fn do_for_all_tiles(&self, f: |&Box|) { + for tile in self.tiles.values() { + f(tile); + } + } + + pub fn collect_tiles(&mut self) -> Vec> { + let mut collected_tiles = Vec::new(); + + collected_tiles.push_all_move(self.take_unused_tiles()); + + // We need to replace the HashMap since it cannot be used again after move_iter(). + let mut tile_map = HashMap::new(); + mem::swap(&mut tile_map, &mut self.tiles); + + for (_, tile) in tile_map.move_iter() { + collected_tiles.push(tile); + } + + return collected_tiles; + } + + pub fn flush_pending_buffer_requests(&mut self) -> (Vec, f32) { + match self.pending_buffer_request.take() { + Some((rect, scale)) => (self.get_buffer_requests_in_rect(rect, scale), scale), + None => (Vec::new(), 0.0), + } + } + + pub fn contents_changed(&mut self) { + self.pending_buffer_request = None; + self.waiting_on_tiles = false; + } +}