From 58f0d569e6714b50d307fc233891851778746180 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Sun, 22 Jun 2014 21:44:40 -0700 Subject: [PATCH] Move logic for tiling from Servo into rust-layers Pull in the quadtree implementation from Servo and rework the way that tiles are stored in the layer tree. Instead of storing tiles in-tree, simply hang them off of ContainerLayer separately. Also have ContainerLayer templatized to hold arbitrary data, which will prevent Servo from having to mirror the tree using Servo CompositorLayers. --- layers.rs | 359 ++++++++++++++---------- lib.rs | 1 + quadtree.rs | 773 ++++++++++++++++++++++++++++++++++++++++++++++++++++ rendergl.rs | 40 +-- scene.rs | 13 +- 5 files changed, 1016 insertions(+), 170 deletions(-) create mode 100644 quadtree.rs diff --git a/layers.rs b/layers.rs index 58b188b..1211e31 100644 --- a/layers.rs +++ b/layers.rs @@ -12,87 +12,69 @@ use texturegl::Texture; use geom::matrix::{Matrix4, identity}; use geom::size::Size2D; use geom::rect::Rect; -use std::cell::RefCell; +use geom::point::Point2D; +use platform::surface::{NativeSurfaceMethods, NativeSurface}; +use std::cell::{RefCell, RefMut}; +use std::num::Zero; use std::rc::Rc; +use quadtree::{Quadtree, NodeStatus, Normal}; + +/// The amount of memory usage allowed per layer. +static MAX_TILE_MEMORY_PER_LAYER: uint = 10000000; pub enum Format { ARGB32Format, RGB24Format } -#[deriving(Clone)] -pub enum Layer { - ContainerLayerKind(Rc), - TextureLayerKind(Rc), -} - -impl Layer { - pub fn with_common(&self, f: |&mut CommonLayer| -> T) -> T { - match *self { - ContainerLayerKind(ref container_layer) => { - f(&mut *container_layer.common.borrow_mut()) - }, - TextureLayerKind(ref texture_layer) => { - f(&mut *texture_layer.common.borrow_mut()) - }, - } - } -} - -pub struct CommonLayer { - pub parent: Option, - pub prev_sibling: Option, - pub next_sibling: Option, +pub struct CommonLayer { + pub parent: Option>>, + pub prev_sibling: Option>>, + pub next_sibling: Option>>, pub transform: Matrix4, + pub origin: Point2D, } -impl CommonLayer { +impl CommonLayer { // FIXME: Workaround for cross-crate bug regarding mutability of class fields pub fn set_transform(&mut self, new_transform: Matrix4) { self.transform = new_transform; } -} -pub fn CommonLayer() -> CommonLayer { - CommonLayer { - parent: None, - prev_sibling: None, - next_sibling: None, - transform: identity(), + pub fn new() -> CommonLayer { + CommonLayer { + parent: None, + prev_sibling: None, + next_sibling: None, + transform: identity(), + origin: Zero::zero(), + } } } +pub struct ContainerLayer { + pub common: RefCell>, + pub first_child: RefCell>>>, + pub last_child: RefCell>>>, + pub tiles: RefCell>>, + pub quadtree: RefCell, -pub struct ContainerLayer { - pub common: RefCell, - pub first_child: RefCell>, - pub last_child: RefCell>, - pub scissor: RefCell>>, -} - - -pub fn ContainerLayer() -> ContainerLayer { - ContainerLayer { - common: RefCell::new(CommonLayer()), - first_child: RefCell::new(None), - last_child: RefCell::new(None), - scissor: RefCell::new(None), - } + tile_size: uint, + pub extra_data: RefCell, } -pub struct ChildIterator { - current: Option, +pub struct ChildIterator { + current: Option>>, } -impl Iterator for ChildIterator { - fn next(&mut self) -> Option { +impl Iterator>> for ChildIterator { + fn next(&mut self) -> Option>> { let (new_current, result) = match self.current { None => (None, None), Some(ref child) => { - (child.with_common(|x| x.next_sibling.clone()), - Some(child.clone())) + (child.common().next_sibling.clone(), Some(child.clone())) } }; self.current = new_current; @@ -100,113 +82,164 @@ impl Iterator for ChildIterator { } } -impl ContainerLayer { - pub fn children(&self) -> ChildIterator { +impl ContainerLayer { + pub fn new(page_size: Option>, tile_size: uint, data: T) -> ContainerLayer { + ContainerLayer { + common: RefCell::new(CommonLayer::new()), + first_child: RefCell::new(None), + last_child: RefCell::new(None), + quadtree: match page_size { + None => { + RefCell::new(Quadtree::new(Size2D(tile_size, tile_size), + tile_size, + Some(MAX_TILE_MEMORY_PER_LAYER))) + } + Some(page_size) => { + RefCell::new(Quadtree::new(Size2D(page_size.width as uint, page_size.height as uint), + tile_size, + Some(MAX_TILE_MEMORY_PER_LAYER))) + } + }, + tiles: RefCell::new(vec!()), + tile_size: tile_size, + extra_data: RefCell::new(data), + } + } + + pub fn children(&self) -> ChildIterator { ChildIterator { current: self.first_child.borrow().clone(), } } + pub fn common<'a>(&'a self) -> RefMut<'a,CommonLayer> { + self.common.borrow_mut() + } + /// Adds a child to the beginning of the list. /// Only works when the child is disconnected from the layer tree. - pub fn add_child_start(pseudo_self: Rc, new_child: Layer) { - new_child.with_common(|new_child_common| { - assert!(new_child_common.parent.is_none()); - assert!(new_child_common.prev_sibling.is_none()); - assert!(new_child_common.next_sibling.is_none()); - - new_child_common.parent = Some(ContainerLayerKind(pseudo_self.clone())); - - match *pseudo_self.first_child.borrow() { - None => {} - Some(ref first_child) => { - first_child.with_common(|first_child_common| { - assert!(first_child_common.prev_sibling.is_none()); - first_child_common.prev_sibling = Some(new_child.clone()); - new_child_common.next_sibling = Some(first_child.clone()); - }); - } + pub fn add_child_start(this: Rc>, new_child: Rc>) { + let mut new_child_common = new_child.common(); + assert!(new_child_common.parent.is_none()); + assert!(new_child_common.prev_sibling.is_none()); + assert!(new_child_common.next_sibling.is_none()); + + new_child_common.parent = Some(this.clone()); + + match *this.first_child.borrow() { + None => {} + Some(ref first_child) => { + let mut first_child_common = first_child.common(); + assert!(first_child_common.prev_sibling.is_none()); + first_child_common.prev_sibling = Some(new_child.clone()); + new_child_common.next_sibling = Some(first_child.clone()); } + } - *pseudo_self.first_child.borrow_mut() = Some(new_child.clone()); + *this.first_child.borrow_mut() = Some(new_child.clone()); - let should_set = pseudo_self.last_child.borrow().is_none(); - if should_set { - *pseudo_self.last_child.borrow_mut() = Some(new_child.clone()); - } - }); + let should_set = this.last_child.borrow().is_none(); + if should_set { + *this.last_child.borrow_mut() = Some(new_child.clone()); + } } /// Adds a child to the end of the list. /// Only works when the child is disconnected from the layer tree. - pub fn add_child_end(pseudo_self: Rc, new_child: Layer) { - new_child.with_common(|new_child_common| { - assert!(new_child_common.parent.is_none()); - assert!(new_child_common.prev_sibling.is_none()); - assert!(new_child_common.next_sibling.is_none()); - - new_child_common.parent = Some(ContainerLayerKind(pseudo_self.clone())); - - - match *pseudo_self.last_child.borrow() { - None => {} - Some(ref last_child) => { - last_child.with_common(|last_child_common| { - assert!(last_child_common.next_sibling.is_none()); - last_child_common.next_sibling = Some(new_child.clone()); - new_child_common.prev_sibling = Some(last_child.clone()); - }); - } + pub fn add_child_end(this: Rc>, new_child: Rc>) { + let mut new_child_common = new_child.common(); + assert!(new_child_common.parent.is_none()); + assert!(new_child_common.prev_sibling.is_none()); + assert!(new_child_common.next_sibling.is_none()); + + new_child_common.parent = Some(this.clone()); + + + match *this.last_child.borrow() { + None => {} + Some(ref last_child) => { + let mut last_child_common = last_child.common(); + assert!(last_child_common.next_sibling.is_none()); + last_child_common.next_sibling = Some(new_child.clone()); + new_child_common.prev_sibling = Some(last_child.clone()); } + } - *pseudo_self.last_child.borrow_mut() = Some(new_child.clone()); + *this.last_child.borrow_mut() = Some(new_child.clone()); - let mut child = pseudo_self.first_child.borrow_mut(); - match *child { - Some(_) => {}, - None => *child = Some(new_child.clone()), - } - }); - } - - pub fn remove_child(pseudo_self: Rc, child: Layer) { - child.with_common(|child_common| { - assert!(child_common.parent.is_some()); - match child_common.parent { - Some(ContainerLayerKind(ref container)) => { - assert!(container.deref() as *ContainerLayer == - pseudo_self.deref() as *ContainerLayer); - }, - _ => fail!("Invalid parent of child in layer tree"), + let mut child = this.first_child.borrow_mut(); + match *child { + Some(_) => {}, + None => *child = Some(new_child.clone()), + } + } + + pub fn remove_child(this: Rc>, child: Rc>) { + let mut child_common = child.common(); + assert!(child_common.parent.is_some()); + match child_common.parent { + Some(ref container) => { + assert!(container.deref() as *ContainerLayer == + this.deref() as *ContainerLayer); + }, + _ => fail!("Invalid parent of child in layer tree"), + } + + let previous_sibling = child_common.prev_sibling.clone(); + match child_common.next_sibling { + None => { // this is the last child + *this.last_child.borrow_mut() = previous_sibling; + }, + Some(ref sibling) => { + sibling.common().prev_sibling = previous_sibling; } + } - match child_common.next_sibling { - None => { // this is the last child - *pseudo_self.last_child.borrow_mut() = child_common.prev_sibling.clone(); - }, - Some(ref sibling) => { - sibling.with_common(|sibling_common| { - sibling_common.prev_sibling = child_common.prev_sibling.clone(); - }); - } + let next_sibling = child_common.next_sibling.clone(); + match child_common.prev_sibling { + None => { // this is the first child + *this.first_child.borrow_mut() = next_sibling; + }, + Some(ref sibling) => { + sibling.common().next_sibling = next_sibling; } - match child_common.prev_sibling { - None => { // this is the first child - *pseudo_self.first_child.borrow_mut() = child_common.next_sibling.clone(); - }, - Some(ref sibling) => { - sibling.with_common(|sibling_common| { - sibling_common.next_sibling = child_common.next_sibling.clone(); - }); - } - } - }); + } } pub fn remove_all_children(&self) { *self.first_child.borrow_mut() = None; *self.last_child.borrow_mut() = None; } + + pub fn tile_size(this: Rc>) -> uint { + this.tile_size + } + + 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 + } + + pub fn resize(this: Rc>, new_size: Size2D) -> Vec> { + 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); + } + + 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_tiles(this: Rc>) -> Vec> { + this.quadtree.borrow_mut().collect_tiles() + } } /// Whether a texture should be flipped. @@ -219,23 +252,75 @@ pub enum Flip { } pub struct TextureLayer { - pub common: RefCell, /// A handle to the GPU texture. pub texture: Texture, /// The size of the texture in pixels. size: Size2D, /// Whether this texture is flipped vertically. pub flip: Flip, + + pub transform: Matrix4, } impl TextureLayer { - pub fn new(texture: Texture, size: Size2D, flip: Flip) -> TextureLayer { + pub fn new(texture: Texture, size: Size2D, flip: Flip, transform: Matrix4) -> TextureLayer { TextureLayer { - common: RefCell::new(CommonLayer()), texture: texture, size: size, flip: flip, + transform: transform, } } } +/// A request from the compositor to the renderer for tiles that need to be (re)displayed. +#[deriving(Clone)] +pub struct BufferRequest { + // The rect in pixels that will be drawn to the screen + pub screen_rect: Rect, + + // The rect in page coordinates that this tile represents + pub page_rect: Rect, +} + +impl BufferRequest { + pub fn new(screen_rect: Rect, page_rect: Rect) -> BufferRequest { + BufferRequest { + screen_rect: screen_rect, + page_rect: page_rect, + } + } +} + +pub struct LayerBuffer { + /// The native surface which can be shared between threads or processes. On Mac this is an + /// `IOSurface`; on Linux this is an X Pixmap; on Android this is an `EGLImageKHR`. + pub native_surface: NativeSurface, + + /// The rect in the containing RenderLayer that this represents. + pub rect: Rect, + + /// The rect in pixels that will be drawn to the screen. + pub screen_pos: Rect, + + /// The scale at which this tile is rendered + pub resolution: f32, + + /// NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH. + pub stride: uint, +} + +/// A set of layer buffers. This is an atomic unit used to switch between the front and back +/// buffers. +pub struct LayerBufferSet { + pub buffers: Vec> +} + +impl LayerBufferSet { + /// Notes all buffer surfaces will leak if not destroyed via a call to `destroy`. + pub fn mark_will_leak(&mut self) { + for buffer in self.buffers.mut_iter() { + buffer.native_surface.mark_will_leak() + } + } +} diff --git a/lib.rs b/lib.rs index a950464..5029916 100755 --- a/lib.rs +++ b/lib.rs @@ -41,6 +41,7 @@ pub mod rendergl; pub mod scene; pub mod texturegl; pub mod util; +pub mod quadtree; pub mod platform { #[cfg(target_os="linux")] diff --git a/quadtree.rs b/quadtree.rs new file mode 100644 index 0000000..cf237ce --- /dev/null +++ b/quadtree.rs @@ -0,0 +1,773 @@ +// 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, NativeSurfaceMethods}; +use std::cmp; +use std::mem::replace; +use std::num::next_power_of_two; +use layers::{BufferRequest, LayerBuffer}; + +/// 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); +} + +impl Tile for Box { + fn get_mem(&self) -> uint { + // This works for now, but in the future we may want a better heuristic + self.screen_pos.size.width * self.screen_pos.size.height + } + fn is_valid(&self, scale: f32) -> bool { + (self.resolution - scale).abs() < 1.0e-6 + } + fn get_size_2d(&self) -> Size2D { + self.screen_pos.size + } + fn mark_wont_leak(&mut self) { + self.native_surface.mark_wont_leak() + } + fn destroy(self, graphics_context: &NativePaintingGraphicsContext) { + let mut this = self; + this.native_surface.destroy(graphics_context) + } +} + + +/// 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: Box) -> 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: |&Box|) { + 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 Box> { + 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: Box, 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/rendergl.rs b/rendergl.rs index 4c226c2..d588133 100755 --- a/rendergl.rs +++ b/rendergl.rs @@ -7,7 +7,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use layers::{ContainerLayerKind, Flip, NoFlip, TextureLayerKind, VerticalFlip}; +use layers::{ContainerLayer, TextureLayer, Flip, NoFlip, VerticalFlip}; use layers; use scene::Scene; use texturegl::{Texture, TextureTarget2D, TextureTargetRectangle}; @@ -26,6 +26,7 @@ use opengles::gl2::{gen_buffers, get_attrib_location, get_error, get_program_iv} use opengles::gl2::{get_shader_info_log, get_shader_iv, get_uniform_location}; use opengles::gl2::{link_program, shader_source, uniform_1i, uniform_2f}; use opengles::gl2::{uniform_matrix_4fv, use_program, vertex_attrib_pointer_f32, viewport}; +use std::rc::Rc; static FRAGMENT_2D_SHADER_SOURCE: &'static str = " #ifdef GL_ES @@ -361,18 +362,18 @@ pub trait Render { scene_size: Size2D); } -impl Render for layers::ContainerLayer { +impl Render for layers::ContainerLayer { fn render(&self, render_context: RenderContext, transform: Matrix4, scene_size: Size2D) { - // NOTE: work around borrowchk - { - let tmp = self.common.borrow(); - let transform = transform.mul(&tmp.transform); - for child in self.children() { - render_layer(render_context, transform, scene_size, child); - } + let tmp = self.common.borrow(); + let transform = transform.translate(tmp.origin.x, tmp.origin.y, 0.0).mul(&tmp.transform); + for tile in self.tiles.borrow().iter() { + tile.render(render_context, transform, scene_size) + } + for child in self.children() { + child.render(render_context, transform, scene_size) } } } @@ -382,27 +383,12 @@ impl Render for layers::TextureLayer { render_context: RenderContext, transform: Matrix4, scene_size: Size2D) { - let tmp = self.common.borrow(); - let transform = transform.mul(&tmp.transform); + let transform = transform.mul(&self.transform); bind_and_render_quad(render_context, &self.texture, self.flip, &transform, scene_size); } } -fn render_layer(render_context: RenderContext, - transform: Matrix4, - scene_size: Size2D, - layer: layers::Layer) { - match layer { - ContainerLayerKind(container_layer) => { - container_layer.render(render_context, transform, scene_size) - } - TextureLayerKind(texture_layer) => { - texture_layer.render(render_context, transform, scene_size) - } - } -} - -pub fn render_scene(render_context: RenderContext, scene: &Scene) { +pub fn render_scene(root_layer: Rc>, render_context: RenderContext, scene: &Scene) { // Set the viewport. viewport(0 as GLint, 0 as GLint, scene.size.width as GLsizei, scene.size.height as GLsizei); @@ -417,5 +403,5 @@ pub fn render_scene(render_context: RenderContext, scene: &Scene) { let transform = scene.transform; // Render the root layer. - render_layer(render_context, transform, scene.size, scene.root.clone()); + root_layer.render(render_context, transform, scene.size); } diff --git a/scene.rs b/scene.rs index 7737b55..c75fb07 100755 --- a/scene.rs +++ b/scene.rs @@ -7,21 +7,22 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use layers::Layer; use color::Color; use geom::size::Size2D; use geom::matrix::Matrix4; +use layers::ContainerLayer; +use std::rc::Rc; -pub struct Scene { - pub root: Layer, +pub struct Scene { + pub root: Option>>, pub size: Size2D, pub transform: Matrix4, pub background_color: Color } -pub fn Scene(root: Layer, size: Size2D, transform: Matrix4) -> Scene { +pub fn Scene(size: Size2D, transform: Matrix4) -> Scene { Scene { - root: root, + root: None, size: size, transform: transform, background_color: Color { @@ -33,7 +34,7 @@ pub fn Scene(root: Layer, size: Size2D, transform: Matrix4) -> Scene { } } -impl Scene { +impl Scene { // FIXME: Workaround for cross-crate bug regarding mutability of class fields pub fn set_transform(&mut self, new_transform: Matrix4) { self.transform = new_transform;