From d590a7b45aaf353c389019022519d46ead5755dc Mon Sep 17 00:00:00 2001 From: eschweic Date: Fri, 21 Jun 2013 17:08:24 -0700 Subject: [PATCH 1/9] Make quadtrees generic --- src/components/main/compositing/quadtree.rs | 299 ++++++++------------ 1 file changed, 120 insertions(+), 179 deletions(-) diff --git a/src/components/main/compositing/quadtree.rs b/src/components/main/compositing/quadtree.rs index 1eb7a29655b..89cd8398405 100644 --- a/src/components/main/compositing/quadtree.rs +++ b/src/components/main/compositing/quadtree.rs @@ -6,13 +6,14 @@ // been rasterized and which have not. use geom::point::Point2D; -use geom::size::Size2D; -use geom::rect::Rect; -priv enum Quadtype { - Empty, - Base, - Branch, +pub struct Quadtree { + tile: Option, + origin: Point2D, + size: f32, + quadrants: [Option<~Quadtree>, ..4], + scale: @mut f32, + max_tile_size: uint, } priv enum Quadrant { @@ -22,206 +23,146 @@ priv enum Quadrant { BR = 3, } - -pub struct Quadtree { - quadtype: Quadtype, - rect: Rect, - quadrants: [Option<~Quadtree>, ..4], -} - - -impl Quadtree { - pub fn new(x: uint, y: uint, width: uint, height: uint) -> Quadtree { +impl Quadtree { + // Public method to create a new Quadtree + pub fn new(x: uint, y: uint, width: uint, height: uint, tile_size: uint, scale: @mut f32) -> Quadtree { + if(*scale != 1.0) { + println("Warning: Quadtree: Quadtree initialized while zoomed; this action is unsupported."); + println("Please set zoom to 1.0 before creating the Quadtree."); + } + + // Spaces must be squares and powers of 2, so expand the space until it is + let longer = width.max(&height); + let num_tiles = uint::div_ceil(longer, tile_size); + let power_of_two = uint::next_power_of_two(num_tiles); + let size = power_of_two * tile_size; + Quadtree { - quadtype: Empty, - rect: Rect { - origin: Point2D(x, y), - size: Size2D(width, height), - }, - + tile: None, + origin: Point2D(x as f32, y as f32), + size: size as f32, quadrants: [None, None, None, None], + scale: scale, + max_tile_size: tile_size, + } + } + + // Private method to create new children + fn new_child(&self, x: f32, y: f32, size: f32) -> Quadtree { + Quadtree { + tile: None, + origin: Point2D(x, y), + size: size, + quadrants: [None, None, None, None], + scale: self.scale, + max_tile_size: self.max_tile_size, } } /// Determine which child contains a given point - priv fn get_quadrant(&self, x: uint, y: uint) -> Quadrant { - let self_width = self.rect.size.width; - let self_height = self.rect.size.height; - let self_x = self.rect.origin.x; - let self_y = self.rect.origin.y; - match (self_width, self_height) { - (1, _) => { - if y < self_y + self_height / 2 { - TL - } else { - BR - } - } - (_, 1) => { - if x < self_x + self_width / 2 { - TL - } else { - BR - } - } - _ => { - if x < self_x + self_width / 2 { - if y < self_y + self_height / 2 { - TL - } else { - BL - } - } else if y < self_y + self_height / 2 { - TR - } else { - BR - } + fn get_quadrant(&self, x: uint, y: uint) -> Quadrant { + let self_x = (self.origin.x * *(self.scale)).ceil() as uint; + let self_y = (self.origin.y * *(self.scale)).ceil() as uint; + let self_size = (self.size * *(self.scale)).ceil() as uint; + + if x < self_x + self_size / 2 { + if y < self_y + self_size / 2 { + TL + } else { + BL } + } else if y < self_y + self_size / 2 { + TR + } else { + BR } } - - /// Change a point from Empty to Base - pub fn add_region(&mut self, x: uint, y: uint) { - let self_x = self.rect.origin.x; - let self_y = self.rect.origin.y; - let self_width = self.rect.size.width; - let self_height = self.rect.size.height; - debug!("Quadtree: adding: (%?, %?) w:%?, h:%?", self_x, self_y, self_width, self_height); + /// Get the lowest-level (highest resolution) tile associated with a certain pixel + pub fn get_tile<'r> (&'r self, x: uint, y: uint) -> &'r Option { + let self_x = (self.origin.x * *(self.scale)).ceil() as uint; + let self_y = (self.origin.y * *(self.scale)).ceil() as uint; + let self_size = (self.size * *(self.scale)).ceil() as uint; - if x >= self_x + self_width || x < self_x - || y >= self_y + self_height || y < self_y { - return; // Out of bounds - } - match self.quadtype { - Base => return, - Empty => { - if self_width == 1 && self_height == 1 { - self.quadtype = Base; - return; - } - self.quadtype = Branch; - - // Initialize children - self.quadrants[TL as int] = Some(~Quadtree::new(self_x, - self_y, - (self_width / 2).max(&1), - (self_height / 2).max(&1))); - if self_width > 1 && self_height > 1 { - self.quadrants[TR as int] = Some(~Quadtree::new(self_x + self_width / 2, - self_y, - self_width - self_width / 2, - self_height / 2)); - self.quadrants[BL as int] = Some(~Quadtree::new(self_x, - self_y + self_height / 2, - self_width / 2, - self_height - self_height / 2)); - } - self.quadrants[BR as int] = Some(~Quadtree::new(self_x + self_width / 2, - self_y + self_height / 2, - self_width - self_width / 2, - self_height - self_height / 2)); - } - Branch => {} // Fall through + if x >= self_x + self_size || x < self_x + || y >= self_y + self_size || y < self_y { + fail!("Quadtree: Tried to get a tile outside of range"); } - // If we've made it this far, we know we are a branch and therefore have children - let index = self.get_quadrant(x, y) as int; - + let index = self.get_quadrant(x,y) as int; match self.quadrants[index] { - None => fail!("Quadtree: child query failure"), - Some(ref mut region) => { - // Recurse if necessary - match region.quadtype { - Empty | Branch => { - region.add_region(x, y); - } - Base => {} // nothing to do - } - } + None => &'r self.tile, + Some(ref child) => child.get_tile(x, y), + } + } + + + /// Add a tile + pub fn add_tile(&mut self, x: uint, y: uint, tile: T) { + let self_x = (self.origin.x * *(self.scale)).ceil() as uint; + let self_y = (self.origin.y * *(self.scale)).ceil() as uint; + let self_size = (self.size * *(self.scale)).ceil() as uint; + + debug!("Quadtree: Adding: (%?, %?) size:%?px", self_x, self_y, self_size); + + if x >= self_x + self_size || x < self_x + || y >= self_y + self_size || y < self_y { + fail!("Quadtree: Tried to add tile to invalid region"); } - // FIXME: ideally we could make the assignments in the match, - // but borrowed pointers prevent that. So here's a flag instead. - let mut base_flag = 0; - - // If all children are Bases, convert self to Base - match (&self.quadrants, self_width, self_height) { - (&[Some(ref tl_q), _, _, Some(ref br_q)], 1, _) | - (&[Some(ref tl_q), _, _, Some(ref br_q)], _, 1) => { - match(tl_q.quadtype, br_q.quadtype) { - (Base, Base) => { - base_flag = 1; - } - _ => {} // nothing to do - } + if self_size <= self.max_tile_size { // We are the child + self.tile = Some(tile); + for vec::each([TL, TR, BL, BR]) |quad| { + self.quadrants[*quad as int] = None; } - (&[Some(ref tl_q), Some(ref tr_q), Some(ref bl_q), Some(ref br_q)], _, _) => { - match (tl_q.quadtype, tr_q.quadtype, bl_q.quadtype, br_q.quadtype) { - (Base, Base, Base, Base) => { - base_flag = 2; + } else { //send tile to children + let quad = self.get_quadrant(x, y); + match self.quadrants[quad as int] { + Some(ref mut child) => child.add_tile(x, y, tile), + 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 = ~self.new_child(new_x, new_y, new_size); + c.add_tile(x, y, tile); + self.quadrants[quad as int] = Some(c); + + // If we have 4 children, we probably shouldn't be hanging onto a tile + // Though this isn't always true if we have grandchildren + match self.quadrants { + [Some(_), Some(_), Some(_), Some(_)] => { + self.tile = None; } - _ => {} // nothing to do + _ => {} } - } - _ => {} // nothing to do - } - - match base_flag { - 0 => {} - 1 => { - self.quadtype = Base; - self.quadrants[TL as int] = None; - self.quadrants[BR as int] = None; - } - 2 => { - self.quadtype = Base; - self.quadrants[TL as int] = None; - self.quadrants[TR as int] = None; - self.quadrants[BL as int] = None; - self.quadrants[BR as int] = None; - } - _ => fail!("Quadtree: Unknown flag type"), - } - } - - /// Check if a point is a Base or Empty. - pub fn check_region(&self, x: uint, y: uint) -> bool { - let self_x = self.rect.origin.x; - let self_y = self.rect.origin.y; - let self_width = self.rect.size.width; - let self_height = self.rect.size.height; - if x >= self_x + self_width || x < self_x - || y >= self_y + self_height || y < self_y { - return false; // out of bounds - } - - match self.quadtype { - Empty => false, - Base => true, - Branch => { - let index = self.get_quadrant(x,y) as int; - match self.quadrants[index] { - None => fail!("Quadtree: child query failed"), - Some(ref region) => region.check_region(x, y) } } } } + } #[test] -fn test_add_region() { - let mut t = Quadtree::new(50, 50, 3, 4); - assert!(!t.check_region(50, 50)); - t.add_region(50, 50); - assert!(t.check_region(50, 50)); - assert!(!t.check_region(51, 50)); - assert!(!t.check_region(50, 51)); - t.add_region(53, 50); - assert!(!t.check_region(53, 50)); - +fn test_add_tile() { + let scale = @mut 1.0; + let mut t = Quadtree::new(50, 30, 20, 20, 10, scale); + assert!(t.get_tile(50, 30).is_none()); + t.add_tile(50, 30, 1); + assert!(t.get_tile(50, 30).get() == 1); + assert!(t.get_tile(59, 39).get() == 1); + assert!(t.get_tile(60, 40).is_none()); + *scale = 2.0; + assert!(t.get_tile(110, 70).get() == 1); + t.add_tile(100, 60, 2); + assert!(t.get_tile(109, 69).get() == 2); + assert!(t.get_tile(110, 70).get() == 1); } \ No newline at end of file From ad7dc32fc8a52a3f1a93bc02446a0fb58ec9a716 Mon Sep 17 00:00:00 2001 From: eschweic Date: Sun, 23 Jun 2013 15:19:40 -0700 Subject: [PATCH 2/9] Address pcwalton's comments --- src/components/main/compositing/quadtree.rs | 138 ++++++++++---------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/src/components/main/compositing/quadtree.rs b/src/components/main/compositing/quadtree.rs index 89cd8398405..8c3e426e5b6 100644 --- a/src/components/main/compositing/quadtree.rs +++ b/src/components/main/compositing/quadtree.rs @@ -7,15 +7,25 @@ use geom::point::Point2D; +/// Parent to all quadtree nodes. Stores variables needed at all levels. All method calls +/// at this level are in pixel coordinates. pub struct Quadtree { - tile: Option, - origin: Point2D, - size: f32, - quadrants: [Option<~Quadtree>, ..4], - scale: @mut f32, + root: QuadtreeNode, max_tile_size: uint, } +/// A node in the tree. All method calls at this level are in page coordinates. +struct QuadtreeNode { + /// The tile belonging to this node. Note that parent nodes can have tiles. + tile: Option, + /// The positiong of the node in page coordinates. + origin: Point2D, + /// The width and hight of the node in page coordinates. + size: f32, + /// The node's children. + quadrants: [Option<~QuadtreeNode>, ..4], +} + priv enum Quadrant { TL = 0, TR = 1, @@ -24,13 +34,8 @@ priv enum Quadrant { } impl Quadtree { - // Public method to create a new Quadtree - pub fn new(x: uint, y: uint, width: uint, height: uint, tile_size: uint, scale: @mut f32) -> Quadtree { - if(*scale != 1.0) { - println("Warning: Quadtree: Quadtree initialized while zoomed; this action is unsupported."); - println("Please set zoom to 1.0 before creating the Quadtree."); - } - + /// Public method to create a new Quadtree + pub fn new(x: uint, y: uint, width: uint, height: uint, tile_size: uint) -> Quadtree { // Spaces must be squares and powers of 2, so expand the space until it is let longer = width.max(&height); let num_tiles = uint::div_ceil(longer, tile_size); @@ -38,79 +43,79 @@ impl Quadtree { let size = power_of_two * tile_size; Quadtree { - tile: None, - origin: Point2D(x as f32, y as f32), - size: size as f32, - quadrants: [None, None, None, None], - scale: scale, + root: QuadtreeNode { + tile: None, + origin: Point2D(x as f32, y as f32), + size: size as f32, + quadrants: [None, None, None, None], + }, max_tile_size: tile_size, } } + + /// Get a tile at a given pixel position and scale. + pub fn get_tile<'r>(&'r self, x: uint, y: uint, scale: f32) -> &'r Option { + self.root.get_tile(x as f32 / scale, y as f32 / scale) + } + /// Add a tile associtated with a given pixel position and scale. + pub fn add_tile(&mut self, x: uint, y: uint, scale: f32, tile: T) { + self.root.add_tile(x as f32 / scale, y as f32 / scale, tile, self.max_tile_size as f32 / scale); + } + +} + +impl QuadtreeNode { // Private method to create new children - fn new_child(&self, x: f32, y: f32, size: f32) -> Quadtree { - Quadtree { + fn new_child(x: f32, y: f32, size: f32) -> QuadtreeNode { + QuadtreeNode { tile: None, origin: Point2D(x, y), size: size, quadrants: [None, None, None, None], - scale: self.scale, - max_tile_size: self.max_tile_size, } } - /// Determine which child contains a given point - fn get_quadrant(&self, x: uint, y: uint) -> Quadrant { - let self_x = (self.origin.x * *(self.scale)).ceil() as uint; - let self_y = (self.origin.y * *(self.scale)).ceil() as uint; - let self_size = (self.size * *(self.scale)).ceil() as uint; - - if x < self_x + self_size / 2 { - if y < self_y + self_size / 2 { + /// 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_y + self_size / 2 { + } else if y < self.origin.y + self.size / 2.0 { TR } else { BR } } - /// Get the lowest-level (highest resolution) tile associated with a certain pixel - pub fn get_tile<'r> (&'r self, x: uint, y: uint) -> &'r Option { - let self_x = (self.origin.x * *(self.scale)).ceil() as uint; - let self_y = (self.origin.y * *(self.scale)).ceil() as uint; - let self_size = (self.size * *(self.scale)).ceil() as uint; - - if x >= self_x + self_size || x < self_x - || y >= self_y + self_size || y < self_y { + /// Get the lowest-level (highest resolution) tile associated with a given position in page coords. + fn get_tile<'r> (&'r self, x: f32, y: f32) -> &'r Option { + 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 get a tile outside of range"); } - let index = self.get_quadrant(x,y) as int; + let index = self.get_quadrant(x, y) as int; match self.quadrants[index] { None => &'r self.tile, Some(ref child) => child.get_tile(x, y), } - } + } - - /// Add a tile - pub fn add_tile(&mut self, x: uint, y: uint, tile: T) { - let self_x = (self.origin.x * *(self.scale)).ceil() as uint; - let self_y = (self.origin.y * *(self.scale)).ceil() as uint; - let self_size = (self.size * *(self.scale)).ceil() as uint; + /// 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. + fn add_tile(&mut self, x: f32, y: f32, tile: T, tile_size: f32) { + debug!("Quadtree: Adding: (%?, %?) size:%?px", self.origin.x, self.origin.y, self.size); - debug!("Quadtree: Adding: (%?, %?) size:%?px", self_x, self_y, self_size); - - if x >= self_x + self_size || x < self_x - || y >= self_y + self_size || y < self_y { + 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 <= self.max_tile_size { // We are the child + if self.size <= tile_size { // We are the child self.tile = Some(tile); for vec::each([TL, TR, BL, BR]) |quad| { self.quadrants[*quad as int] = None; @@ -118,7 +123,7 @@ impl Quadtree { } else { //send tile to children let quad = self.get_quadrant(x, y); match self.quadrants[quad as int] { - Some(ref mut child) => child.add_tile(x, y, tile), + Some(ref mut child) => child.add_tile(x, y, tile, tile_size), None => { //make new child let new_size = self.size / 2.0; let new_x = match quad { @@ -129,8 +134,8 @@ impl Quadtree { TL | TR => self.origin.y, BL | BR => self.origin.y + new_size, }; - let mut c = ~self.new_child(new_x, new_y, new_size); - c.add_tile(x, y, tile); + let mut c = ~QuadtreeNode::new_child(new_x, new_y, new_size); + c.add_tile(x, y, tile, tile_size); self.quadrants[quad as int] = Some(c); // If we have 4 children, we probably shouldn't be hanging onto a tile @@ -146,23 +151,18 @@ impl Quadtree { } } } - - } - #[test] fn test_add_tile() { - let scale = @mut 1.0; - let mut t = Quadtree::new(50, 30, 20, 20, 10, scale); - assert!(t.get_tile(50, 30).is_none()); - t.add_tile(50, 30, 1); - assert!(t.get_tile(50, 30).get() == 1); - assert!(t.get_tile(59, 39).get() == 1); - assert!(t.get_tile(60, 40).is_none()); - *scale = 2.0; - assert!(t.get_tile(110, 70).get() == 1); - t.add_tile(100, 60, 2); - assert!(t.get_tile(109, 69).get() == 2); - assert!(t.get_tile(110, 70).get() == 1); + let mut t = Quadtree::new(50, 30, 20, 20, 10); + assert!(t.get_tile(50, 30, 1.0).is_none()); + t.add_tile(50, 30, 1.0, 1); + assert!(t.get_tile(50, 30, 1.0).get() == 1); + assert!(t.get_tile(59, 39, 1.0).get() == 1); + assert!(t.get_tile(60, 40, 1.0).is_none()); + assert!(t.get_tile(110, 70, 2.0).get() == 1); + t.add_tile(100, 60, 2.0, 2); + assert!(t.get_tile(109, 69, 2.0).get() == 2); + assert!(t.get_tile(110, 70, 2.0).get() == 1); } \ No newline at end of file From 6bebda4f26c97c2204d23ce6340962a65cb727ad Mon Sep 17 00:00:00 2001 From: eschweic Date: Mon, 1 Jul 2013 15:20:29 -0700 Subject: [PATCH 3/9] Implement progressive rendering --- src/components/gfx/render_task.rs | 113 +++++++-------- src/components/main/compositing/mod.rs | 152 ++++++++++++++++++-- src/components/main/compositing/quadtree.rs | 134 +++++++++++++++-- src/components/msg/compositor_msg.rs | 9 +- 4 files changed, 322 insertions(+), 86 deletions(-) diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index 05cbd0bc079..1fdbc91aa74 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -33,7 +33,7 @@ pub struct RenderLayer { pub enum Msg { RenderMsg(RenderLayer), - ReRenderMsg(f32), + ReRenderMsg(~Rect], f32), PaintPermissionGranted, PaintPermissionRevoked, ExitMsg(Chan<()>), @@ -119,11 +119,11 @@ impl RenderTask { loop { match self.port.recv() { RenderMsg(render_layer) => { + self.compositor.new_layer(render_layer.size, self.opts.tile_size); self.render_layer = Some(render_layer); - self.render(1.0); } - ReRenderMsg(scale) => { - self.render(scale); + ReRenderMsg(tiles, scale) => { + self.render(tiles, scale); } PaintPermissionGranted => { self.paint_permission = true; @@ -146,15 +146,15 @@ impl RenderTask { } } - fn render(&mut self, scale: f32) { + fn render(&mut self, tiles: ~[Rect], scale: f32) { debug!("render_task: rendering"); let render_layer; - match (self.render_layer) { - None => return, + match self.render_layer { Some(ref r_layer) => { render_layer = r_layer; } + _ => return, // nothing to do } self.compositor.set_render_state(RenderingRenderState); @@ -166,63 +166,56 @@ impl RenderTask { // Divide up the layer into tiles. do time::profile(time::RenderingPrepBuffCategory, self.profiler_chan.clone()) { - let mut y = 0; - while y < (render_layer.size.height as f32 * scale).ceil() as uint { - let mut x = 0; - while x < (render_layer.size.width as f32 * scale).ceil() as uint { - // Figure out the dimension of this tile. - let right = uint::min(x + tile_size, (render_layer.size.width as f32 * scale).ceil() as uint); - let bottom = uint::min(y + tile_size, (render_layer.size.height as f32 * scale).ceil() as uint); - let width = right - x; - let height = bottom - y; - - let tile_rect = Rect(Point2D(x as f32 / scale, y as f32 / scale), Size2D(width as f32, height as f32)); - let screen_rect = Rect(Point2D(x, y), Size2D(width, 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_rect, - screen_pos: screen_rect, - stride: (width * 4) as uint + for tiles.each |tile_rect| { + let x = tile_rect.origin.x; + let y = tile_rect.origin.y; + let width = tile_rect.size.width; + let height = tile_rect.size.height; + + let rect = Rect(Point2D(x as f32 / scale, y as f32 / scale), Size2D(width as f32, height as f32)); + + 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: rect, + screen_pos: *tile_rect, + resolution: scale, + stride: (width * 4) as uint + }; + + + { + // Build the render context. + let ctx = RenderContext { + canvas: &buffer, + font_ctx: self.font_ctx, + opts: &self.opts }; - - { - // Build the render context. - let ctx = RenderContext { - canvas: &buffer, - font_ctx: self.font_ctx, - opts: &self.opts - }; - - // Apply the translation to render the tile we want. - let matrix: Matrix2D = Matrix2D::identity(); - let matrix = matrix.scale(scale as AzFloat, scale as AzFloat); - let matrix = matrix.translate(-(buffer.rect.origin.x) as AzFloat, - -(buffer.rect.origin.y) as AzFloat); - - ctx.canvas.draw_target.set_transform(&matrix); - - // Clear the buffer. - ctx.clear(); - - // Draw the display list. - do profile(time::RenderingDrawingCategory, self.profiler_chan.clone()) { - render_layer.display_list.draw_into_context(&ctx); - ctx.canvas.draw_target.flush(); - } + + // Apply the translation to render the tile we want. + let matrix: Matrix2D = Matrix2D::identity(); + let matrix = matrix.scale(scale as AzFloat, scale as AzFloat); + let matrix = matrix.translate(-(buffer.rect.origin.x) as AzFloat, + -(buffer.rect.origin.y) as AzFloat); + + ctx.canvas.draw_target.set_transform(&matrix); + + // Clear the buffer. + ctx.clear(); + + // Draw the display list. + do profile(time::RenderingDrawingCategory, self.profiler_chan.clone()) { + render_layer.display_list.draw_into_context(&ctx); + ctx.canvas.draw_target.flush(); } - - new_buffers.push(buffer); - - x += tile_size; } - - y += tile_size; + + new_buffers.push(buffer); + } + } let layer_buffer_set = LayerBufferSet { diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 2dc4794a420..efb089e92e6 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -10,7 +10,7 @@ use windowing::{ApplicationMethods, WindowMethods, WindowMouseEvent, WindowClick use windowing::{WindowMouseDownEvent, WindowMouseUpEvent}; -use servo_msg::compositor_msg::{RenderListener, LayerBufferSet, RenderState}; +use servo_msg::compositor_msg::{RenderListener, LayerBuffer, LayerBufferSet, RenderState}; use servo_msg::compositor_msg::{ReadyState, ScriptListener}; use servo_msg::constellation_msg::{CompositorAck, ConstellationChan}; use servo_msg::constellation_msg; @@ -28,6 +28,7 @@ use extra::timer; use geom::matrix::identity; use geom::point::Point2D; use geom::size::Size2D; +use geom::rect::Rect; //eschweic use layers::layers::{ARGB32Format, ContainerLayer, ContainerLayerKind, Format}; use layers::layers::{ImageData, WithDataFn}; use layers::layers::{TextureLayerKind, TextureLayer, TextureManager}; @@ -40,6 +41,10 @@ use servo_util::time::ProfilerChan; use extra::arc; pub use windowing; +//eschweic +use compositing::quadtree::Quadtree; +mod quadtree; + /// The implementation of the layers-based compositor. #[deriving(Clone)] pub struct CompositorChan { @@ -70,6 +75,17 @@ impl RenderListener for CompositorChan { self.chan.send(Paint(id, layer_buffer_set, new_size)) } + //eschweic + fn new_layer(&self, page_size: Size2D, tile_size: uint) { + self.chan.send(NewLayer(page_size, tile_size)) + } + fn resize_layer(&self, page_size: Size2D) { + self.chan.send(ResizeLayer(page_size)) + } + fn delete_layer(&self) { + self.chan.send(DeleteLayer) + } + fn set_render_state(&self, render_state: RenderState) { self.chan.send(ChangeRenderState(render_state)) } @@ -102,6 +118,16 @@ pub enum Msg { GetSize(Chan>), /// Requests the compositors GL context. GetGLContext(Chan), + + //eschweic + // FIXME: Attach layer ids and epochs to these messages + /// Alerts the compositor that there is a new layer to be rendered. + NewLayer(Size2D, uint), + /// Alerts the compositor that the current layer has changed size. + ResizeLayer(Size2D), + /// Alerts the compositor that the current layer has been deleted. + DeleteLayer, + /// Requests that the compositor paint the given layer buffer set for the given page size. Paint(uint, arc::ARC, Size2D), /// Alerts the compositor to the current status of page loading. @@ -199,9 +225,61 @@ impl CompositorTask { let local_zoom = @mut 1f32; // Channel to the current renderer. // FIXME: This probably shouldn't be stored like this. + let render_chan: @mut Option = @mut None; let pipeline_id: @mut Option = @mut None; + // Quadtree for this layer + // FIXME: This should be one-per-layer + let quadtree: @mut Option> = @mut None; + + + let ask_for_tiles: @fn() = || { + match *quadtree { + Some(ref quad) => { + let mut tile_size = quad.get_tile_size(); // temporary solution + let mut tile_request = ~[]; //FIXME: try not to allocate if possible + + let mut y = world_offset.y as uint; + while y < world_offset.y as uint + window_size.height + tile_size { + let mut x = world_offset.x as uint; + while x < world_offset.x as uint + window_size.width + tile_size { + match *(quad.get_tile(x, y, *world_zoom)) { + Some(ref current_tile) => { + if current_tile.resolution == *world_zoom { + x += tile_size; + loop; // we already have this tile + } + } + None => {} // fall through + } + let (tile_pos, new_tile_size) = quad.get_tile_rect(x, y, *world_zoom); + tile_size = new_tile_size; + x = tile_pos.x; + y = tile_pos.y; + + // TODO: clamp tiles to page bounds + // TODO: add null buffer/checkerboard tile to stop a flood of requests + println(fmt!("requesting tile: (%?, %?): %?", x, y, tile_size)); + tile_request.push(Rect(Point2D(x, y), Size2D(tile_size, tile_size))); + + x += tile_size; + } + y += tile_size; + } + if !tile_request.is_empty() { + match *render_chan { + Some(ref chan) => { + chan.send(ReRenderMsg(tile_request, *world_zoom)); + } + _ => {} + } + } + } + _ => {} + } + }; + let update_layout_callbacks: @fn(LayoutChan) = |layout_chan: LayoutChan| { let layout_chan_clone = layout_chan.clone(); do window.set_navigation_callback |direction| { @@ -247,18 +325,32 @@ impl CompositorTask { } WindowMouseDownEvent(button, layer_mouse_point) => { event = MouseDownEvent(button, world_mouse_point(layer_mouse_point)); + + //eschweic + match *quadtree { + Some(ref quad) => { + +/* let wmp = world_mouse_point(layer_mouse_point); + println(fmt!("mouse: (%?, %?):", wmp.x as uint, wmp.y as uint)); + let buffer = quad.get_tile(wmp.x as uint, wmp.y as uint, *world_zoom); + match *buffer { + None => println("None"), + Some(ref buffer) => println(fmt!("Some: (%?, %?), %?, %?", buffer.screen_pos.origin.x, buffer.screen_pos.origin.y, buffer.screen_pos.size.width, buffer.resolution)), + + } */ + + println(quad.get_html()); + } + None => {} + } } WindowMouseUpEvent(button, layer_mouse_point) => { - // rerender layer at new zoom level - // FIXME: this should happen when the user stops zooming, definitely not here - match *render_chan { - Some(ref r_chan) => { - r_chan.send(ReRenderMsg(*world_zoom)); - } - None => {} // Nothing to do - } + //FIXME: this should not be here eschweic + ask_for_tiles(); + + event = MouseUpEvent(button, world_mouse_point(layer_mouse_point)); } } @@ -266,6 +358,7 @@ impl CompositorTask { } }; + let check_for_messages: @fn(&Port) = |port: &Port| { // Handle messages while port.peek() { @@ -291,6 +384,21 @@ impl CompositorTask { } GetGLContext(chan) => chan.send(current_gl_context()), + + //eschweic + NewLayer(new_size, tile_size) => { + *page_size = Size2D(new_size.width as f32, new_size.height as f32); + *quadtree = Some(Quadtree::new(0, 0, new_size.width, new_size.height, tile_size)); + ask_for_tiles(); + + } + ResizeLayer(new_size) => { + *page_size = Size2D(new_size.width as f32, new_size.height as f32); + // TODO: update quadtree, ask for tiles + } + DeleteLayer => { + // TODO: create secondary layer tree, keep displaying until new tiles come in + } Paint(id, new_layer_buffer_set, new_size) => { match *pipeline_id { @@ -300,6 +408,12 @@ impl CompositorTask { debug!("osmain: received new frame"); + let quad; + match *quadtree { + Some(ref mut q) => quad = q, + None => fail!("Compositor: given paint command with no quadtree initialized"), + } + *page_size = Size2D(new_size.width as f32, new_size.height as f32); let new_layer_buffer_set = new_layer_buffer_set.get(); @@ -307,12 +421,20 @@ impl CompositorTask { // Iterate over the children of the container layer. let mut current_layer_child = root_layer.first_child; - for new_layer_buffer_set.buffers.iter().advance |buffer| { + // Replace the image layer data with the buffer data. + let buffers = util::replace(&mut new_layer_buffer_set.buffers, ~[]); + + do vec::consume(buffers) |_, buffer| { + quad.add_tile(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y, + *world_zoom, buffer); + } + + for quad.get_all_tiles().each |buffer| { let width = buffer.rect.size.width as uint; let height = buffer.rect.size.height as uint; debug!("osmain: compositing buffer rect %?", &buffer.rect); - + // Find or create a texture layer. let texture_layer; current_layer_child = match current_layer_child { @@ -339,9 +461,10 @@ impl CompositorTask { let origin = Point2D(origin.x as f32, origin.y as f32); // Set the layer's transform. - let transform = identity().translate(origin.x, origin.y, 0.0); - let transform = transform.scale(width as f32, height as f32, 1.0); + let transform = identity().translate(origin.x * *world_zoom / buffer.resolution, origin.y * *world_zoom / buffer.resolution, 0.0); + let transform = transform.scale(width as f32 * *world_zoom / buffer.resolution, height as f32 * *world_zoom / buffer.resolution, 1.0); texture_layer.common.set_transform(transform); + } // Delete leftover layers @@ -409,6 +532,8 @@ impl CompositorTask { root_layer.common.set_transform(scroll_transform); +// ask_for_tiles(); + *recomposite = true; } @@ -447,6 +572,7 @@ impl CompositorTask { window_size.height as f32 / -2f32, 0.0); root_layer.common.set_transform(zoom_transform); +// ask_for_tiles(); *recomposite = true; } diff --git a/src/components/main/compositing/quadtree.rs b/src/components/main/compositing/quadtree.rs index 8c3e426e5b6..e9fc4e65d85 100644 --- a/src/components/main/compositing/quadtree.rs +++ b/src/components/main/compositing/quadtree.rs @@ -52,7 +52,11 @@ impl Quadtree { max_tile_size: tile_size, } } - + + /// Return the maximum allowed tile size + pub fn get_tile_size(&self) -> uint { + self.max_tile_size + } /// Get a tile at a given pixel position and scale. pub fn get_tile<'r>(&'r self, x: uint, y: uint, scale: f32) -> &'r Option { self.root.get_tile(x as f32 / scale, y as f32 / scale) @@ -61,12 +65,24 @@ impl Quadtree { pub fn add_tile(&mut self, x: uint, y: uint, scale: f32, tile: T) { self.root.add_tile(x as f32 / scale, y as f32 / scale, tile, self.max_tile_size as f32 / scale); } - + /// Get the tile size/offset for a given pixel position + pub fn get_tile_rect(&self, x: uint, y: uint, scale: f32) -> (Point2D, uint) { + self.root.get_tile_rect(x as f32 / scale, y as f32 / scale, scale, self.max_tile_size as f32 / scale) + } + /// Get all the tiles in the tree + pub fn get_all_tiles<'r>(&'r self) -> ~[&'r T] { + self.root.get_all_tiles() + } + /// Generate html to visualize the tree + pub fn get_html(&self) -> ~str { + let header = " "; + fmt!("%s%s", header, self.root.get_html()) + } } impl QuadtreeNode { - // Private method to create new children + /// Private method to create new children fn new_child(x: f32, y: f32, size: f32) -> QuadtreeNode { QuadtreeNode { tile: None, @@ -105,6 +121,26 @@ impl QuadtreeNode { } } + /// Get all tiles in the tree, parents first. + /// FIXME: this could probably be more efficient + fn get_all_tiles<'r>(&'r self) -> ~[&'r T] { + let mut ret = ~[]; + + match self.tile { + Some (ref tile) => ret = ~[tile], + None => {} + } + + for self.quadrants.each |quad| { + match *quad { + Some(ref child) => ret = ret + 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. fn add_tile(&mut self, x: f32, y: f32, tile: T, tile_size: f32) { @@ -115,16 +151,16 @@ impl QuadtreeNode { fail!("Quadtree: Tried to add tile to invalid region"); } - if self.size <= tile_size { // We are the child + if self.size <= tile_size { // We are the child self.tile = Some(tile); - for vec::each([TL, TR, BL, BR]) |quad| { + for [TL, TR, BL, BR].each |quad| { self.quadrants[*quad as int] = None; } - } else { //send tile to children + } else { // Send tile to children let quad = self.get_quadrant(x, y); match self.quadrants[quad as int] { Some(ref mut child) => child.add_tile(x, y, tile, tile_size), - None => { //make new child + None => { // Make new child let new_size = self.size / 2.0; let new_x = match quad { TL | BL => self.origin.x, @@ -138,19 +174,93 @@ impl QuadtreeNode { c.add_tile(x, y, tile, tile_size); self.quadrants[quad as int] = Some(c); - // If we have 4 children, we probably shouldn't be hanging onto a tile - // Though this isn't always true if we have grandchildren + // If my tile is completely occluded, get rid of it. + // FIXME: figure out a better way to determine if a tile is completely occluded + // e.g. this alg doesn't work if a tile is covered by its grandchildren match self.quadrants { - [Some(_), Some(_), Some(_), Some(_)] => { - self.tile = None; + [Some(ref tl_child), Some(ref tr_child), Some(ref bl_child), Some(ref br_child)] => { + match (&tl_child.tile, &tr_child.tile, &bl_child.tile, &br_child.tile) { + (&Some(_), &Some(_), &Some(_), &Some(_)) => self.tile = None, + _ => {} + } } _ => {} } - } } } } + + /// Get an origin and a width/height for a future tile for a given position in page coords + fn get_tile_rect(&self, x: f32, y: f32, scale: f32, tile_size: f32) -> (Point2D, uint) { + 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 self_x = (self.origin.x * scale).ceil() as uint; + let self_y = (self.origin.y * scale).ceil() as uint; + let self_size = (self.size * scale).ceil() as uint; + return (Point2D(self_x, self_y), self_size); + } + + let index = self.get_quadrant(x,y) as int; + match self.quadrants[index] { + None => { + // calculate where the new tile should go + let factor = self.size / tile_size; + let divisor = uint::next_power_of_two(factor.ceil() as uint); + let new_size_page = self.size / (divisor as f32); + let new_size_pixel = (new_size_page * scale).ceil() as uint; + + let new_x_page = self.origin.x + new_size_page * ((x - self.origin.x) / new_size_page).floor(); + let new_y_page = self.origin.y + new_size_page * ((y - self.origin.y) / new_size_page).floor(); + let new_x_pixel = (new_x_page * scale).ceil() as uint; + let new_y_pixel = (new_y_page * scale).ceil() as uint; + + (Point2D(new_x_pixel, new_y_pixel), new_size_pixel) + } + Some(ref child) => child.get_tile_rect(x, y, scale, tile_size), + } + } + + /// Generate html to visualize the tree. + /// This is really inefficient, but it's for testing only. + fn get_html(&self) -> ~str { + let mut ret = ~""; + match self.tile { + Some(ref tile) => { + ret = fmt!("%s%?", ret, tile); + } + None => { + ret = fmt!("%sNO TILE", ret); + } + } + match self.quadrants { + [None, None, None, None] => {} + _ => { + ret = fmt!("%s", ret); + for [TL, TR, BL, BR].each |quad| { + match self.quadrants[*quad as int] { + Some(ref child) => { + ret = fmt!("%s", ret, child.get_html()); + } + None => { + ret = fmt!("%s", ret); + } + } + match *quad { + TR => ret = fmt!("%s", ret), + _ => {} + } + } + ret = fmt!("%s
%sEMPTY CHILD
\n", ret); + } + } + return ret; + } + } #[test] diff --git a/src/components/msg/compositor_msg.rs b/src/components/msg/compositor_msg.rs index d9dd02f2671..0763833357c 100644 --- a/src/components/msg/compositor_msg.rs +++ b/src/components/msg/compositor_msg.rs @@ -19,8 +19,12 @@ pub struct LayerBuffer { // The rect in pixels that will be drawn to the screen. screen_pos: Rect, + // The scale at which this tile is rendered + resolution: f32, //eschweic + // NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH. - stride: uint + stride: uint, + } /// A set of layer buffers. This is an atomic unit used to switch between the front and back @@ -49,6 +53,9 @@ pub enum ReadyState { /// submit them to be drawn to the display. pub trait RenderListener { fn get_gl_context(&self) -> AzGLContext; + fn new_layer(&self, Size2D, uint); //eschweic + fn resize_layer(&self, Size2D); + fn delete_layer(&self); fn paint(&self, id: uint, layer_buffer_set: arc::ARC, new_size: Size2D); fn set_render_state(&self, render_state: RenderState); } From 1948280aec04d6c0e2e73c3ddf3d18be8a3fe61b Mon Sep 17 00:00:00 2001 From: eschweic Date: Tue, 2 Jul 2013 09:33:19 -0700 Subject: [PATCH 4/9] Clean up and annotations --- src/components/gfx/render_task.rs | 20 ++++---- src/components/main/compositing/mod.rs | 51 +++++++-------------- src/components/main/compositing/quadtree.rs | 16 ++++--- src/components/msg/compositor_msg.rs | 2 +- 4 files changed, 35 insertions(+), 54 deletions(-) diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index 1fdbc91aa74..8d62a57664a 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -11,7 +11,6 @@ use servo_msg::compositor_msg::{RenderListener, IdleRenderState, RenderingRender use servo_msg::compositor_msg::{LayerBufferSet}; use font_context::FontContext; use geom::matrix2d::Matrix2D; -use geom::point::Point2D; use geom::size::Size2D; use geom::rect::Rect; use opts::Opts; @@ -33,7 +32,7 @@ pub struct RenderLayer { pub enum Msg { RenderMsg(RenderLayer), - ReRenderMsg(~Rect], f32), + ReRenderMsg(~[(Rect, Rect)], f32), PaintPermissionGranted, PaintPermissionRevoked, ExitMsg(Chan<()>), @@ -146,7 +145,7 @@ impl RenderTask { } } - fn render(&mut self, tiles: ~[Rect], scale: f32) { + fn render(&mut self, tiles: ~[(Rect, Rect)], scale: f32) { debug!("render_task: rendering"); let render_layer; @@ -166,21 +165,18 @@ impl RenderTask { // Divide up the layer into tiles. do time::profile(time::RenderingPrepBuffCategory, self.profiler_chan.clone()) { - for tiles.each |tile_rect| { - let x = tile_rect.origin.x; - let y = tile_rect.origin.y; - let width = tile_rect.size.width; - let height = tile_rect.size.height; - - let rect = Rect(Point2D(x as f32 / scale, y as f32 / scale), Size2D(width as f32, height as f32)); + for tiles.each |tile_rects| { + let (screen_rect, page_rect) = *tile_rects; + let width = screen_rect.size.width; + let height = 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: rect, - screen_pos: *tile_rect, + rect: page_rect, + screen_pos: screen_rect, resolution: scale, stride: (width * 4) as uint }; diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index efb089e92e6..543d2e564cd 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -28,7 +28,6 @@ use extra::timer; use geom::matrix::identity; use geom::point::Point2D; use geom::size::Size2D; -use geom::rect::Rect; //eschweic use layers::layers::{ARGB32Format, ContainerLayer, ContainerLayerKind, Format}; use layers::layers::{ImageData, WithDataFn}; use layers::layers::{TextureLayerKind, TextureLayer, TextureManager}; @@ -119,8 +118,7 @@ pub enum Msg { /// Requests the compositors GL context. GetGLContext(Chan), - //eschweic - // FIXME: Attach layer ids and epochs to these messages + // TODO: Attach layer ids and epochs to these messages /// Alerts the compositor that there is a new layer to be rendered. NewLayer(Size2D, uint), /// Alerts the compositor that the current layer has changed size. @@ -237,7 +235,9 @@ impl CompositorTask { let ask_for_tiles: @fn() = || { match *quadtree { Some(ref quad) => { - let mut tile_size = quad.get_tile_size(); // temporary solution + // FIXME: find a better way to get the tile size + // currently tiles that need to be updated can be missed + let mut tile_size = quad.get_tile_size(); let mut tile_request = ~[]; //FIXME: try not to allocate if possible let mut y = world_offset.y as uint; @@ -253,15 +253,15 @@ impl CompositorTask { } None => {} // fall through } - let (tile_pos, new_tile_size) = quad.get_tile_rect(x, y, *world_zoom); - tile_size = new_tile_size; - x = tile_pos.x; - y = tile_pos.y; + let (tile_screen_pos, tile_page_pos) = quad.get_tile_rect(x, y, *world_zoom); + tile_size = tile_screen_pos.size.width; + x = tile_screen_pos.origin.x; + y = tile_screen_pos.origin.y; // TODO: clamp tiles to page bounds // TODO: add null buffer/checkerboard tile to stop a flood of requests - println(fmt!("requesting tile: (%?, %?): %?", x, y, tile_size)); - tile_request.push(Rect(Point2D(x, y), Size2D(tile_size, tile_size))); + debug!("requesting tile: (%?, %?): %?", x, y, tile_size); + tile_request.push((tile_screen_pos, tile_page_pos)); x += tile_size; } @@ -325,32 +325,14 @@ impl CompositorTask { } WindowMouseDownEvent(button, layer_mouse_point) => { event = MouseDownEvent(button, world_mouse_point(layer_mouse_point)); - - //eschweic - match *quadtree { - Some(ref quad) => { -/* let wmp = world_mouse_point(layer_mouse_point); - println(fmt!("mouse: (%?, %?):", wmp.x as uint, wmp.y as uint)); - let buffer = quad.get_tile(wmp.x as uint, wmp.y as uint, *world_zoom); - match *buffer { - None => println("None"), - Some(ref buffer) => println(fmt!("Some: (%?, %?), %?, %?", buffer.screen_pos.origin.x, buffer.screen_pos.origin.y, buffer.screen_pos.size.width, buffer.resolution)), - - } */ - - println(quad.get_html()); - } - None => {} - } } WindowMouseUpEvent(button, layer_mouse_point) => { - //FIXME: this should not be here eschweic + // FIXME: this should happen on a scroll/zoom event instead, + // but is here temporarily to prevent request floods to the renderer ask_for_tiles(); - - event = MouseUpEvent(button, world_mouse_point(layer_mouse_point)); } } @@ -385,7 +367,6 @@ impl CompositorTask { GetGLContext(chan) => chan.send(current_gl_context()), - //eschweic NewLayer(new_size, tile_size) => { *page_size = Size2D(new_size.width as f32, new_size.height as f32); *quadtree = Some(Quadtree::new(0, 0, new_size.width, new_size.height, tile_size)); @@ -430,8 +411,8 @@ impl CompositorTask { } for quad.get_all_tiles().each |buffer| { - let width = buffer.rect.size.width as uint; - let height = buffer.rect.size.height as uint; + let width = buffer.screen_pos.size.width as uint; + let height = buffer.screen_pos.size.height as uint; debug!("osmain: compositing buffer rect %?", &buffer.rect); @@ -457,11 +438,11 @@ impl CompositorTask { Some(_) => fail!(~"found unexpected layer kind"), }; - let origin = buffer.screen_pos.origin; + let origin = buffer.rect.origin; let origin = Point2D(origin.x as f32, origin.y as f32); // Set the layer's transform. - let transform = identity().translate(origin.x * *world_zoom / buffer.resolution, origin.y * *world_zoom / buffer.resolution, 0.0); + let transform = identity().translate(origin.x * *world_zoom, origin.y * *world_zoom, 0.0); let transform = transform.scale(width as f32 * *world_zoom / buffer.resolution, height as f32 * *world_zoom / buffer.resolution, 1.0); texture_layer.common.set_transform(transform); diff --git a/src/components/main/compositing/quadtree.rs b/src/components/main/compositing/quadtree.rs index e9fc4e65d85..2fda821406d 100644 --- a/src/components/main/compositing/quadtree.rs +++ b/src/components/main/compositing/quadtree.rs @@ -6,6 +6,8 @@ // been rasterized and which have not. use geom::point::Point2D; +use geom::size::Size2D; +use geom::rect::Rect; /// Parent to all quadtree nodes. Stores variables needed at all levels. All method calls /// at this level are in pixel coordinates. @@ -65,8 +67,8 @@ impl Quadtree { pub fn add_tile(&mut self, x: uint, y: uint, scale: f32, tile: T) { self.root.add_tile(x as f32 / scale, y as f32 / scale, tile, self.max_tile_size as f32 / scale); } - /// Get the tile size/offset for a given pixel position - pub fn get_tile_rect(&self, x: uint, y: uint, scale: f32) -> (Point2D, uint) { + /// Get the tile rect in screen and page coordinates for a given pixel position + pub fn get_tile_rect(&self, x: uint, y: uint, scale: f32) -> (Rect, Rect) { self.root.get_tile_rect(x as f32 / scale, y as f32 / scale, scale, self.max_tile_size as f32 / scale) } /// Get all the tiles in the tree @@ -191,8 +193,8 @@ impl QuadtreeNode { } } - /// Get an origin and a width/height for a future tile for a given position in page coords - fn get_tile_rect(&self, x: f32, y: f32, scale: f32, tile_size: f32) -> (Point2D, uint) { + /// Get a tile rect in screen and page coords for a given position in page coords + fn get_tile_rect(&self, x: f32, y: f32, scale: f32, tile_size: f32) -> (Rect, Rect) { 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"); @@ -202,7 +204,8 @@ impl QuadtreeNode { let self_x = (self.origin.x * scale).ceil() as uint; let self_y = (self.origin.y * scale).ceil() as uint; let self_size = (self.size * scale).ceil() as uint; - return (Point2D(self_x, self_y), self_size); + return (Rect(Point2D(self_x, self_y), Size2D(self_size, self_size)), + Rect(Point2D(self.origin.x, self.origin.y), Size2D(self.size, self.size))); } let index = self.get_quadrant(x,y) as int; @@ -219,7 +222,8 @@ impl QuadtreeNode { let new_x_pixel = (new_x_page * scale).ceil() as uint; let new_y_pixel = (new_y_page * scale).ceil() as uint; - (Point2D(new_x_pixel, new_y_pixel), new_size_pixel) + (Rect(Point2D(new_x_pixel, new_y_pixel), Size2D(new_size_pixel, new_size_pixel)), + Rect(Point2D(new_x_page, new_y_page), Size2D(new_size_page, new_size_page))) } Some(ref child) => child.get_tile_rect(x, y, scale, tile_size), } diff --git a/src/components/msg/compositor_msg.rs b/src/components/msg/compositor_msg.rs index 0763833357c..4a5ba488f1b 100644 --- a/src/components/msg/compositor_msg.rs +++ b/src/components/msg/compositor_msg.rs @@ -20,7 +20,7 @@ pub struct LayerBuffer { screen_pos: Rect, // The scale at which this tile is rendered - resolution: f32, //eschweic + resolution: f32, // NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH. stride: uint, From 1b225fbad2c44ddd9fbc7e9bd05cb8e99b9f2d29 Mon Sep 17 00:00:00 2001 From: eschweic Date: Wed, 3 Jul 2013 09:33:02 -0700 Subject: [PATCH 5/9] Cleanup after merge --- src/components/main/compositing/mod.rs | 17 +++++++++-------- src/components/main/compositing/quadtree.rs | 9 +++++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 543d2e564cd..4f8c4267acd 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -74,7 +74,6 @@ impl RenderListener for CompositorChan { self.chan.send(Paint(id, layer_buffer_set, new_size)) } - //eschweic fn new_layer(&self, page_size: Size2D, tile_size: uint) { self.chan.send(NewLayer(page_size, tile_size)) } @@ -229,7 +228,7 @@ impl CompositorTask { // Quadtree for this layer // FIXME: This should be one-per-layer - let quadtree: @mut Option> = @mut None; + let quadtree: @mut Option> = @mut None; let ask_for_tiles: @fn() = || { @@ -241,9 +240,11 @@ impl CompositorTask { let mut tile_request = ~[]; //FIXME: try not to allocate if possible let mut y = world_offset.y as uint; - while y < world_offset.y as uint + window_size.height + tile_size { + while y < world_offset.y as uint + window_size.height && + y <= (page_size.height * *world_zoom) as uint { let mut x = world_offset.x as uint; - while x < world_offset.x as uint + window_size.width + tile_size { + while x < world_offset.x as uint + window_size.width && + x <= (page_size.width * *world_zoom) as uint { match *(quad.get_tile(x, y, *world_zoom)) { Some(ref current_tile) => { if current_tile.resolution == *world_zoom { @@ -293,7 +294,7 @@ impl CompositorTask { let layout_chan_clone = layout_chan.clone(); // Hook the windowing system's resize callback up to the resize rate limiter. do window.set_resize_callback |width, height| { - let new_size = Size2D(width as int, height as int); + let new_size = Size2D(width as uint, height as uint); if *window_size != new_size { debug!("osmain: window resized to %ux%u", width, height); *window_size = new_size; @@ -403,11 +404,11 @@ impl CompositorTask { let mut current_layer_child = root_layer.first_child; // Replace the image layer data with the buffer data. - let buffers = util::replace(&mut new_layer_buffer_set.buffers, ~[]); +// let buffers = util::replace(&mut new_layer_buffer_set.buffers, ~[]); - do vec::consume(buffers) |_, buffer| { + for new_layer_buffer_set.buffers.iter().advance |buffer| { quad.add_tile(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y, - *world_zoom, buffer); + *world_zoom, ~buffer.clone()); } for quad.get_all_tiles().each |buffer| { diff --git a/src/components/main/compositing/quadtree.rs b/src/components/main/compositing/quadtree.rs index 2fda821406d..b43beafd828 100644 --- a/src/components/main/compositing/quadtree.rs +++ b/src/components/main/compositing/quadtree.rs @@ -8,6 +8,7 @@ use geom::point::Point2D; use geom::size::Size2D; use geom::rect::Rect; +use std::uint::{div_ceil, next_power_of_two}; /// Parent to all quadtree nodes. Stores variables needed at all levels. All method calls /// at this level are in pixel coordinates. @@ -40,8 +41,8 @@ impl Quadtree { pub fn new(x: uint, y: uint, width: uint, height: uint, tile_size: uint) -> Quadtree { // Spaces must be squares and powers of 2, so expand the space until it is let longer = width.max(&height); - let num_tiles = uint::div_ceil(longer, tile_size); - let power_of_two = uint::next_power_of_two(num_tiles); + 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 { @@ -211,9 +212,9 @@ impl QuadtreeNode { let index = self.get_quadrant(x,y) as int; match self.quadrants[index] { None => { - // calculate where the new tile should go + // Calculate where the new tile should go let factor = self.size / tile_size; - let divisor = uint::next_power_of_two(factor.ceil() as uint); + let divisor = next_power_of_two(factor.ceil() as uint); let new_size_page = self.size / (divisor as f32); let new_size_pixel = (new_size_page * scale).ceil() as uint; From d40086048cfdccd06dcfec6aaba9cc5de31f2875 Mon Sep 17 00:00:00 2001 From: eschweic Date: Mon, 8 Jul 2013 12:01:52 -0700 Subject: [PATCH 6/9] Fix paint permissions issue, clean up --- src/components/gfx/render_task.rs | 15 ++++++--------- src/components/main/compositing/mod.rs | 9 ++++++--- src/components/msg/compositor_msg.rs | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index 8d62a57664a..fcc21acce51 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -18,7 +18,6 @@ use render_context::RenderContext; use std::cell::Cell; use std::comm::{Chan, Port, SharedChan}; -use std::uint; use servo_util::time::{ProfilerChan, profile}; use servo_util::time; @@ -118,7 +117,9 @@ impl RenderTask { loop { match self.port.recv() { RenderMsg(render_layer) => { - self.compositor.new_layer(render_layer.size, self.opts.tile_size); + if self.paint_permission { + self.compositor.new_layer(render_layer.size, self.opts.tile_size); + } self.render_layer = Some(render_layer); } ReRenderMsg(tiles, scale) => { @@ -126,10 +127,9 @@ impl RenderTask { } PaintPermissionGranted => { self.paint_permission = true; - match self.last_paint_msg { - Some((ref layer_buffer_set, layer_size)) => { - self.compositor.paint(self.id, layer_buffer_set.clone(), layer_size); - self.compositor.set_render_state(IdleRenderState); + match self.render_layer { + Some(ref render_layer) => { + self.compositor.new_layer(render_layer.size, self.opts.tile_size); } None => {} } @@ -146,8 +146,6 @@ impl RenderTask { } fn render(&mut self, tiles: ~[(Rect, Rect)], scale: f32) { - debug!("render_task: rendering"); - let render_layer; match self.render_layer { Some(ref r_layer) => { @@ -158,7 +156,6 @@ impl RenderTask { self.compositor.set_render_state(RenderingRenderState); do time::profile(time::RenderingCategory, self.profiler_chan.clone()) { - let tile_size = self.opts.tile_size; // FIXME: Try not to create a new array here. let mut new_buffers = ~[]; diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 4f8c4267acd..24f95b1b23a 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -40,7 +40,6 @@ use servo_util::time::ProfilerChan; use extra::arc; pub use windowing; -//eschweic use compositing::quadtree::Quadtree; mod quadtree; @@ -273,11 +272,15 @@ impl CompositorTask { Some(ref chan) => { chan.send(ReRenderMsg(tile_request, *world_zoom)); } - _ => {} + _ => { + println("Warning: Compositor: Cannot send tile request, no render chan initialized"); + } } } } - _ => {} + _ => { + fail!("Compositor: Tried to ask for tiles without an initialized quadtree"); + } } }; diff --git a/src/components/msg/compositor_msg.rs b/src/components/msg/compositor_msg.rs index 4a5ba488f1b..49d98e383f2 100644 --- a/src/components/msg/compositor_msg.rs +++ b/src/components/msg/compositor_msg.rs @@ -53,7 +53,7 @@ pub enum ReadyState { /// submit them to be drawn to the display. pub trait RenderListener { fn get_gl_context(&self) -> AzGLContext; - fn new_layer(&self, Size2D, uint); //eschweic + fn new_layer(&self, Size2D, uint); fn resize_layer(&self, Size2D); fn delete_layer(&self); fn paint(&self, id: uint, layer_buffer_set: arc::ARC, new_size: Size2D); From 435941e93238ac0494a92a82ceecc9d68b868b8f Mon Sep 17 00:00:00 2001 From: eschweic Date: Mon, 8 Jul 2013 13:17:46 -0700 Subject: [PATCH 7/9] Address metajack's comments --- src/components/gfx/render_task.rs | 31 +++++++++++++++++++------- src/components/main/compositing/mod.rs | 11 +++++++-- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index fcc21acce51..5e327441aaa 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -31,12 +31,28 @@ pub struct RenderLayer { pub enum Msg { RenderMsg(RenderLayer), - ReRenderMsg(~[(Rect, Rect)], f32), + ReRenderMsg(~[BufferRequest], f32), PaintPermissionGranted, PaintPermissionRevoked, ExitMsg(Chan<()>), } +/// A request from the compositor to the renderer for tiles that need to be (re)displayed. +pub struct BufferRequest { + // The rect in pixels that will be drawn to the screen + screen_rect: Rect, + + // The rect in page coordinates that this tile represents + page_rect: Rect, +} + +pub fn BufferRequest(screen_rect: Rect, page_rect: Rect) -> BufferRequest { + BufferRequest { + screen_rect: screen_rect, + page_rect: page_rect, + } +} + #[deriving(Clone)] pub struct RenderChan { chan: SharedChan, @@ -145,7 +161,7 @@ impl RenderTask { } } - fn render(&mut self, tiles: ~[(Rect, Rect)], scale: f32) { + fn render(&mut self, tiles: ~[BufferRequest], scale: f32) { let render_layer; match self.render_layer { Some(ref r_layer) => { @@ -162,18 +178,17 @@ impl RenderTask { // Divide up the layer into tiles. do time::profile(time::RenderingPrepBuffCategory, self.profiler_chan.clone()) { - for tiles.each |tile_rects| { - let (screen_rect, page_rect) = *tile_rects; - let width = screen_rect.size.width; - let height = screen_rect.size.height; + for tiles.each |tile| { + 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: page_rect, - screen_pos: screen_rect, + rect: tile.page_rect, + screen_pos: tile.screen_rect, resolution: scale, stride: (width * 4) as uint }; diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 24f95b1b23a..50139134523 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -14,7 +14,7 @@ use servo_msg::compositor_msg::{RenderListener, LayerBuffer, LayerBufferSet, Ren use servo_msg::compositor_msg::{ReadyState, ScriptListener}; use servo_msg::constellation_msg::{CompositorAck, ConstellationChan}; use servo_msg::constellation_msg; -use gfx::render_task::{RenderChan, ReRenderMsg}; +use gfx::render_task::{RenderChan, ReRenderMsg, BufferRequest}; use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods, current_gl_context}; use azure::azure::AzGLContext; @@ -261,7 +261,7 @@ impl CompositorTask { // TODO: clamp tiles to page bounds // TODO: add null buffer/checkerboard tile to stop a flood of requests debug!("requesting tile: (%?, %?): %?", x, y, tile_size); - tile_request.push((tile_screen_pos, tile_page_pos)); + tile_request.push(BufferRequest(tile_screen_pos, tile_page_pos)); x += tile_size; } @@ -517,6 +517,9 @@ impl CompositorTask { root_layer.common.set_transform(scroll_transform); + // FIXME: ask_for_tiles() should be called here, but currently this sends a flood of requests + // to the renderer, which slows the application dramatically. Instead, ask_for_tiles() is only + // called on a click event. // ask_for_tiles(); *recomposite = true; @@ -557,6 +560,10 @@ impl CompositorTask { window_size.height as f32 / -2f32, 0.0); root_layer.common.set_transform(zoom_transform); + + // FIXME: ask_for_tiles() should be called here, but currently this sends a flood of requests + // to the renderer, which slows the application dramatically. Instead, ask_for_tiles() is only + // called on a click event. // ask_for_tiles(); *recomposite = true; From cc816c04b92be034282ac72454b92765b99fe1fe Mon Sep 17 00:00:00 2001 From: eschweic Date: Wed, 10 Jul 2013 11:19:14 -0700 Subject: [PATCH 8/9] Move buffer requests to quadtree; add remove_tile() to quadtree; automatic rerendering after pinch-zoom --- src/components/gfx/render_task.rs | 2 +- src/components/main/compositing/mod.rs | 85 +++---- src/components/main/compositing/quadtree.rs | 257 +++++++++++++++++++- 3 files changed, 281 insertions(+), 63 deletions(-) diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index 5e327441aaa..4bb2b2a45a2 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -178,7 +178,7 @@ impl RenderTask { // Divide up the layer into tiles. do time::profile(time::RenderingPrepBuffCategory, self.profiler_chan.clone()) { - for tiles.each |tile| { + for tiles.iter().advance |tile| { let width = tile.screen_rect.size.width; let height = tile.screen_rect.size.height; diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 50139134523..f794446e299 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -14,7 +14,7 @@ use servo_msg::compositor_msg::{RenderListener, LayerBuffer, LayerBufferSet, Ren use servo_msg::compositor_msg::{ReadyState, ScriptListener}; use servo_msg::constellation_msg::{CompositorAck, ConstellationChan}; use servo_msg::constellation_msg; -use gfx::render_task::{RenderChan, ReRenderMsg, BufferRequest}; +use gfx::render_task::{RenderChan, ReRenderMsg}; use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods, current_gl_context}; use azure::azure::AzGLContext; @@ -28,6 +28,7 @@ use extra::timer; use geom::matrix::identity; use geom::point::Point2D; use geom::size::Size2D; +use geom::rect::Rect; use layers::layers::{ARGB32Format, ContainerLayer, ContainerLayerKind, Format}; use layers::layers::{ImageData, WithDataFn}; use layers::layers::{TextureLayerKind, TextureLayer, TextureManager}; @@ -40,6 +41,7 @@ use servo_util::time::ProfilerChan; use extra::arc; pub use windowing; +use extra::time::precise_time_s; use compositing::quadtree::Quadtree; mod quadtree; @@ -229,44 +231,21 @@ impl CompositorTask { // FIXME: This should be one-per-layer let quadtree: @mut Option> = @mut None; + // Keeps track of if we have performed a zoom event and how recently. + let zoom_action = @mut false; + let zoom_time = @mut 0f; + let ask_for_tiles: @fn() = || { match *quadtree { - Some(ref quad) => { - // FIXME: find a better way to get the tile size - // currently tiles that need to be updated can be missed - let mut tile_size = quad.get_tile_size(); - let mut tile_request = ~[]; //FIXME: try not to allocate if possible - - let mut y = world_offset.y as uint; - while y < world_offset.y as uint + window_size.height && - y <= (page_size.height * *world_zoom) as uint { - let mut x = world_offset.x as uint; - while x < world_offset.x as uint + window_size.width && - x <= (page_size.width * *world_zoom) as uint { - match *(quad.get_tile(x, y, *world_zoom)) { - Some(ref current_tile) => { - if current_tile.resolution == *world_zoom { - x += tile_size; - loop; // we already have this tile - } - } - None => {} // fall through - } - let (tile_screen_pos, tile_page_pos) = quad.get_tile_rect(x, y, *world_zoom); - tile_size = tile_screen_pos.size.width; - x = tile_screen_pos.origin.x; - y = tile_screen_pos.origin.y; - - // TODO: clamp tiles to page bounds - // TODO: add null buffer/checkerboard tile to stop a flood of requests - debug!("requesting tile: (%?, %?): %?", x, y, tile_size); - tile_request.push(BufferRequest(tile_screen_pos, tile_page_pos)); - - x += tile_size; - } - y += tile_size; - } + Some(ref mut quad) => { + let valid = |tile: &~LayerBuffer| -> bool { + tile.resolution == *world_zoom + }; + let (tile_request, redisplay) = quad.get_tile_rects(Rect(Point2D(world_offset.x as int, + world_offset.y as int), + *window_size), valid, *world_zoom); + if !tile_request.is_empty() { match *render_chan { Some(ref chan) => { @@ -276,6 +255,8 @@ impl CompositorTask { println("Warning: Compositor: Cannot send tile request, no render chan initialized"); } } + } else if redisplay { + // TODO: move display code to its own closure and call that here } } _ => { @@ -297,7 +278,7 @@ impl CompositorTask { let layout_chan_clone = layout_chan.clone(); // Hook the windowing system's resize callback up to the resize rate limiter. do window.set_resize_callback |width, height| { - let new_size = Size2D(width as uint, height as uint); + let new_size = Size2D(width as int, height as int); if *window_size != new_size { debug!("osmain: window resized to %ux%u", width, height); *window_size = new_size; @@ -402,22 +383,20 @@ impl CompositorTask { *page_size = Size2D(new_size.width as f32, new_size.height as f32); let new_layer_buffer_set = new_layer_buffer_set.get(); - - // Iterate over the children of the container layer. - let mut current_layer_child = root_layer.first_child; - - // Replace the image layer data with the buffer data. -// let buffers = util::replace(&mut new_layer_buffer_set.buffers, ~[]); - for new_layer_buffer_set.buffers.iter().advance |buffer| { + // FIXME: Don't copy the buffers here quad.add_tile(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y, *world_zoom, ~buffer.clone()); } - for quad.get_all_tiles().each |buffer| { + + // Iterate over the children of the container layer. + let mut current_layer_child = root_layer.first_child; + + let all_tiles = quad.get_all_tiles(); + for all_tiles.iter().advance |buffer| { let width = buffer.screen_pos.size.width as uint; let height = buffer.screen_pos.size.height as uint; - debug!("osmain: compositing buffer rect %?", &buffer.rect); // Find or create a texture layer. @@ -529,6 +508,8 @@ impl CompositorTask { // When the user pinch-zooms, scale the layer do window.set_zoom_callback |magnification| { + *zoom_action = true; + *zoom_time = precise_time_s(); let old_world_zoom = *world_zoom; // Determine zoom amount @@ -560,11 +541,6 @@ impl CompositorTask { window_size.height as f32 / -2f32, 0.0); root_layer.common.set_transform(zoom_transform); - - // FIXME: ask_for_tiles() should be called here, but currently this sends a flood of requests - // to the renderer, which slows the application dramatically. Instead, ask_for_tiles() is only - // called on a click event. -// ask_for_tiles(); *recomposite = true; } @@ -583,6 +559,13 @@ impl CompositorTask { } timer::sleep(&uv_global_loop::get(), 100); + + // If a pinch-zoom happened recently, ask for tiles at the new resolution + if *zoom_action && precise_time_s() - *zoom_time > 0.3 { + *zoom_action = false; + ask_for_tiles(); + } + } self.shutdown_chan.send(()) diff --git a/src/components/main/compositing/quadtree.rs b/src/components/main/compositing/quadtree.rs index b43beafd828..300c3ccff09 100644 --- a/src/components/main/compositing/quadtree.rs +++ b/src/components/main/compositing/quadtree.rs @@ -9,6 +9,8 @@ use geom::point::Point2D; use geom::size::Size2D; use geom::rect::Rect; use std::uint::{div_ceil, next_power_of_two}; +use std::vec::build_sized; +use gfx::render_task::BufferRequest; /// Parent to all quadtree nodes. Stores variables needed at all levels. All method calls /// at this level are in pixel coordinates. @@ -69,13 +71,31 @@ impl Quadtree { self.root.add_tile(x as f32 / scale, y as f32 / scale, tile, self.max_tile_size as f32 / scale); } /// Get the tile rect in screen and page coordinates for a given pixel position - pub fn get_tile_rect(&self, x: uint, y: uint, scale: f32) -> (Rect, Rect) { + pub fn get_tile_rect(&self, x: uint, y: uint, scale: f32) -> BufferRequest { self.root.get_tile_rect(x as f32 / scale, y as f32 / scale, scale, self.max_tile_size as f32 / scale) } /// Get all the tiles in the tree pub fn get_all_tiles<'r>(&'r self) -> ~[&'r T] { self.root.get_all_tiles() } + /// Ask a tile to be deleted from the quadtree. This tries to delete a tile that is far from the + /// given point in pixel coordinates. + pub fn remove_tile(&mut self, x: uint, y: uint, scale: f32) { + self.root.remove_tile(x as f32 / scale, y as f32 / scale); + } + /// Given a window rect in page coordinates and a function to check if an existing tile is "valid" + /// (i.e. is the correct resolution), this function returns a list of BufferRequests for tiles that + /// need to be rendered. It also returns a boolean 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. + pub fn get_tile_rects(&mut self, window: Rect, valid: &fn(&T) -> bool, scale: f32) -> + (~[BufferRequest], bool) { + + 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)), + valid, scale, self.max_tile_size as f32 / scale) + } + /// Generate html to visualize the tree pub fn get_html(&self) -> ~str { let header = " "; @@ -125,7 +145,6 @@ impl QuadtreeNode { } /// Get all tiles in the tree, parents first. - /// FIXME: this could probably be more efficient fn get_all_tiles<'r>(&'r self) -> ~[&'r T] { let mut ret = ~[]; @@ -134,7 +153,7 @@ impl QuadtreeNode { None => {} } - for self.quadrants.each |quad| { + for self.quadrants.iter().advance |quad| { match *quad { Some(ref child) => ret = ret + child.get_all_tiles(), None => {} @@ -154,9 +173,11 @@ impl QuadtreeNode { fail!("Quadtree: Tried to add tile to invalid region"); } - if self.size <= tile_size { // We are the child + if self.size <= tile_size { // We are the child self.tile = Some(tile); - for [TL, TR, BL, BR].each |quad| { + // FIXME: This should be inline, but currently won't compile + let quads = [TL, TR, BL, BR]; + for quads.iter().advance |quad| { self.quadrants[*quad as int] = None; } } else { // Send tile to children @@ -195,7 +216,7 @@ impl QuadtreeNode { } /// Get a tile rect in screen and page coords for a given position in page coords - fn get_tile_rect(&self, x: f32, y: f32, scale: f32, tile_size: f32) -> (Rect, Rect) { + fn get_tile_rect(&self, x: f32, 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"); @@ -205,8 +226,8 @@ impl QuadtreeNode { let self_x = (self.origin.x * scale).ceil() as uint; let self_y = (self.origin.y * scale).ceil() as uint; let self_size = (self.size * scale).ceil() as uint; - return (Rect(Point2D(self_x, self_y), Size2D(self_size, self_size)), - Rect(Point2D(self.origin.x, self.origin.y), Size2D(self.size, self.size))); + return BufferRequest(Rect(Point2D(self_x, self_y), Size2D(self_size, self_size)), + Rect(Point2D(self.origin.x, self.origin.y), Size2D(self.size, self.size))); } let index = self.get_quadrant(x,y) as int; @@ -223,13 +244,224 @@ impl QuadtreeNode { let new_x_pixel = (new_x_page * scale).ceil() as uint; let new_y_pixel = (new_y_page * scale).ceil() as uint; - (Rect(Point2D(new_x_pixel, new_y_pixel), Size2D(new_size_pixel, new_size_pixel)), - Rect(Point2D(new_x_page, new_y_page), Size2D(new_size_page, new_size_page))) + BufferRequest(Rect(Point2D(new_x_pixel, new_y_pixel), Size2D(new_size_pixel, new_size_pixel)), + Rect(Point2D(new_x_page, new_y_page), Size2D(new_size_page, new_size_page))) } Some(ref child) => child.get_tile_rect(x, y, scale, tile_size), } } + /// Removes a tile that is far from the given input point in page coords. Returns true if the child + /// has no tiles and needs to be deleted. + fn remove_tile(&mut self, x: f32, y: f32) -> bool { + match (&self.tile, &self.quadrants) { + (&Some(_), &[None, None, None, None]) => { + self.tile = None; + return true; + } + (&Some(_), _) => { + self.tile = None; + return false; + } + _ => {} + } + + // 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 my_child = match quad { + TL => { + match (&self.quadrants[BR as int], &self.quadrants[BL as int], &self.quadrants[TR as int]) { + (&Some(_), _, _) => BR, + (&None, &Some(_), _) => BL, + (&None, &None, &Some(_)) => TR, + _ => TL, + } + } + TR => { + match (&self.quadrants[BL as int], &self.quadrants[BR as int], &self.quadrants[TL as int]) { + (&Some(_), _, _) => BL, + (&None, &Some(_), _) => BR, + (&None, &None, &Some(_)) => TL, + _ => TR, + } + } + BL => { + match (&self.quadrants[TR as int], &self.quadrants[TL as int], &self.quadrants[BR as int]) { + (&Some(_), _, _) => TR, + (&None, &Some(_), _) => TL, + (&None, &None, &Some(_)) => BR, + _ => BL, + } + } + BR => { + match (&self.quadrants[TL as int], &self.quadrants[TR as int], &self.quadrants[BL as int]) { + (&Some(_), _, _) => TL, + (&None, &Some(_), _) => TR, + (&None, &None, &Some(_)) => BL, + _ => BR, + } + } + }; + + match self.quadrants[my_child as int] { + Some(ref mut child) if !child.remove_tile(x, y) => { + return false; + } + Some(_) => {} // fall through + None => fail!("Quadtree: child query failure"), + } + + // child.remove_tile() returned true + self.quadrants[my_child as int] = None; + match self.quadrants { + [None, None, None, None] => true, + _ => false, + } + } + + /// Given a window rect in page coordinates and a tile validation function, returns a BufferRequest array + /// and a redisplay boolean. See QuadTree function description for more details. + fn get_tile_rects(&mut self, window: Rect, valid: &fn(&T) -> bool, scale: f32, tile_size: f32) -> + (~[BufferRequest], bool) { + + 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 w_x < s_x || w_x + w_width > s_x + s_size + || w_y < s_y || w_y + w_height > s_y + s_size { + println(fmt!("window: %?, %?, %?, %?; self: %?, %?, %?", w_x, w_y, w_width, w_height, s_x, s_y, s_size)); + fail!("Quadtree: tried to query an invalid tile rect"); + } + + if s_size <= tile_size { // We are the child + match self.tile { + Some(ref tile) if valid(tile) => { + let redisplay = match self.quadrants { + [None, None, None, None] => false, + _ => true, + }; + if redisplay { + // FIXME: This should be inline, but currently won't compile + let quads = [TL, TR, BL, BR]; + for quads.iter().advance |quad| { + self.quadrants[*quad as int] = None; + } + } + return (~[], redisplay); + } + _ => { + return (~[self.get_tile_rect(s_x, s_y, scale, tile_size)], false); + } + } + } + + // 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 builder = |push: &fn(Quadrant)| { + match (w_tl_quad, w_br_quad) { + (tl, br) if tl as int == br as int => { + push(tl); + } + (TL, br) => { + push(TL); + push(br); + match br { + BR => { + push(TR); + push(BL); + } + _ => {} + } + } + (tl, br) => { + push(tl); + push(br); + } + } + }; + + let quads_to_check = build_sized(4, builder); + + let mut ret = ~[]; + let mut redisplay = false; + + for quads_to_check.iter().advance |quad| { + match self.quadrants[*quad as int] { + Some(ref mut child) => { + // 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 (c_ret, c_redisplay) = child.get_tile_rects(new_window, |x| valid(x), scale, tile_size); + ret = ret + c_ret; + redisplay = redisplay || c_redisplay; + } + None => { + // Figure out locations of future children + let (x_start, y_start, x_end, y_end) = match *quad { + TL => (w_x, + w_y, + (w_x + w_width).min(&(s_x + s_size / 2.0)), + (w_y + w_height).min(&(s_y + s_size / 2.0))), + TR => (w_x.max(&(s_x + s_size / 2.0)), + w_y, + (w_x + w_width + tile_size).min(&(s_x + s_size)), + (w_y + w_height).min(&(s_y + s_size / 2.0))), + BL => (w_x, + w_y.max(&(s_y + s_size / 2.0)), + (w_x + w_width).min(&(s_x + s_size / 2.0)), + (w_y + w_height + tile_size).min(&(s_y + s_size))), + BR => (w_x.max(&(s_x + s_size / 2.0)), + w_y.max(&(s_y + s_size / 2.0)), + (w_x + w_width + tile_size).min(&(s_x + s_size)), + (w_y + w_height + tile_size).min(&(s_y + s_size))), + }; + let size = (((x_end - x_start) / tile_size).ceil() * + ((y_end - y_start) / tile_size).ceil()) as uint; + + let builder = |push: &fn(BufferRequest)| { + let mut y = y_start; + while y < y_end { + let mut x = x_start; + while x < x_end { + push(self.get_tile_rect(x, y, scale, tile_size)); + x = x + tile_size; + } + y = y + tile_size; + } + }; + ret = ret + build_sized(size, builder); + } + } + } + + (ret, redisplay) + } + + /// Generate html to visualize the tree. /// This is really inefficient, but it's for testing only. fn get_html(&self) -> ~str { @@ -246,7 +478,9 @@ impl QuadtreeNode { [None, None, None, None] => {} _ => { ret = fmt!("%s", ret); - for [TL, TR, BL, BR].each |quad| { + // FIXME: This should be inline, but currently won't compile + let quads = [TL, TR, BL, BR]; + for quads.iter().advance |quad| { match self.quadrants[*quad as int] { Some(ref child) => { ret = fmt!("%s", ret, child.get_html()); @@ -268,6 +502,7 @@ impl QuadtreeNode { } + #[test] fn test_add_tile() { let mut t = Quadtree::new(50, 30, 20, 20, 10); From 8a0878a41c53405af77ea33b08a34fbcd1b8c74b Mon Sep 17 00:00:00 2001 From: eschweic Date: Wed, 10 Jul 2013 17:08:09 -0700 Subject: [PATCH 9/9] Address pcwalton's nits; automatic rendering after scrolling --- src/components/main/compositing/mod.rs | 135 ++++++++++---------- src/components/main/compositing/quadtree.rs | 57 +++++---- 2 files changed, 99 insertions(+), 93 deletions(-) diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index f794446e299..3033afbb56d 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -235,6 +235,64 @@ impl CompositorTask { let zoom_action = @mut false; let zoom_time = @mut 0f; + // Extract tiles from the given quadtree and build and display the render tree. + let build_layer_tree: @fn(&Quadtree<~LayerBuffer>) = |quad: &Quadtree<~LayerBuffer>| { + // Iterate over the children of the container layer. + let mut current_layer_child = root_layer.first_child; + + let all_tiles = quad.get_all_tiles(); + for all_tiles.iter().advance |buffer| { + let width = buffer.screen_pos.size.width as uint; + let height = buffer.screen_pos.size.height as uint; + debug!("osmain: compositing buffer rect %?", &buffer.rect); + + // Find or create a texture layer. + let texture_layer; + current_layer_child = match current_layer_child { + None => { + debug!("osmain: adding new texture layer"); + texture_layer = @mut TextureLayer::new(@buffer.draw_target.clone() as @TextureManager, + buffer.screen_pos.size); + root_layer.add_child(TextureLayerKind(texture_layer)); + None + } + Some(TextureLayerKind(existing_texture_layer)) => { + texture_layer = existing_texture_layer; + texture_layer.manager = @buffer.draw_target.clone() as @TextureManager; + + // Move on to the next sibling. + do current_layer_child.get().with_common |common| { + common.next_sibling + } + } + Some(_) => fail!(~"found unexpected layer kind"), + }; + + let origin = buffer.rect.origin; + let origin = Point2D(origin.x as f32, origin.y as f32); + + // Set the layer's transform. + let transform = identity().translate(origin.x * *world_zoom, origin.y * *world_zoom, 0.0); + let transform = transform.scale(width as f32 * *world_zoom / buffer.resolution, height as f32 * *world_zoom / buffer.resolution, 1.0); + texture_layer.common.set_transform(transform); + + } + + // Delete leftover layers + while current_layer_child.is_some() { + let trash = current_layer_child.get(); + do current_layer_child.get().with_common |common| { + current_layer_child = common.next_sibling; + } + root_layer.remove_child(trash); + } + // Reset zoom + *local_zoom = 1f32; + root_layer.common.set_transform(identity().translate(-world_offset.x, + -world_offset.y, + 0.0)); + *recomposite = true; + }; let ask_for_tiles: @fn() = || { match *quadtree { @@ -256,7 +314,7 @@ impl CompositorTask { } } } else if redisplay { - // TODO: move display code to its own closure and call that here + build_layer_tree(quad); } } _ => { @@ -313,11 +371,6 @@ impl CompositorTask { } WindowMouseUpEvent(button, layer_mouse_point) => { - - // FIXME: this should happen on a scroll/zoom event instead, - // but is here temporarily to prevent request floods to the renderer - ask_for_tiles(); - event = MouseUpEvent(button, world_mouse_point(layer_mouse_point)); } } @@ -371,17 +424,16 @@ impl CompositorTask { Some(pipeline_id) => if id != pipeline_id { loop; }, None => { loop; }, } - - debug!("osmain: received new frame"); + + debug!("osmain: received new frame"); + let quad; match *quadtree { Some(ref mut q) => quad = q, None => fail!("Compositor: given paint command with no quadtree initialized"), } - - *page_size = Size2D(new_size.width as f32, new_size.height as f32); - + let new_layer_buffer_set = new_layer_buffer_set.get(); for new_layer_buffer_set.buffers.iter().advance |buffer| { // FIXME: Don't copy the buffers here @@ -389,67 +441,12 @@ impl CompositorTask { *world_zoom, ~buffer.clone()); } - - // Iterate over the children of the container layer. - let mut current_layer_child = root_layer.first_child; + *page_size = Size2D(new_size.width as f32, new_size.height as f32); - let all_tiles = quad.get_all_tiles(); - for all_tiles.iter().advance |buffer| { - let width = buffer.screen_pos.size.width as uint; - let height = buffer.screen_pos.size.height as uint; - debug!("osmain: compositing buffer rect %?", &buffer.rect); - - // Find or create a texture layer. - let texture_layer; - current_layer_child = match current_layer_child { - None => { - debug!("osmain: adding new texture layer"); - texture_layer = @mut TextureLayer::new(@buffer.draw_target.clone() as @TextureManager, - buffer.screen_pos.size); - root_layer.add_child(TextureLayerKind(texture_layer)); - None - } - Some(TextureLayerKind(existing_texture_layer)) => { - texture_layer = existing_texture_layer; - texture_layer.manager = @buffer.draw_target.clone() as @TextureManager; - - // Move on to the next sibling. - do current_layer_child.get().with_common |common| { - common.next_sibling - } - } - Some(_) => fail!(~"found unexpected layer kind"), - }; - - let origin = buffer.rect.origin; - let origin = Point2D(origin.x as f32, origin.y as f32); - - // Set the layer's transform. - let transform = identity().translate(origin.x * *world_zoom, origin.y * *world_zoom, 0.0); - let transform = transform.scale(width as f32 * *world_zoom / buffer.resolution, height as f32 * *world_zoom / buffer.resolution, 1.0); - texture_layer.common.set_transform(transform); - - } - - // Delete leftover layers - while current_layer_child.is_some() { - let trash = current_layer_child.get(); - do current_layer_child.get().with_common |common| { - current_layer_child = common.next_sibling; - } - root_layer.remove_child(trash); - } - - // Reset zoom - *local_zoom = 1f32; - root_layer.common.set_transform(identity().translate(-world_offset.x, - -world_offset.y, - 0.0)); + build_layer_tree(quad); // TODO: Recycle the old buffers; send them back to the renderer to reuse if // it wishes. - - *recomposite = true; } } } @@ -499,7 +496,7 @@ impl CompositorTask { // FIXME: ask_for_tiles() should be called here, but currently this sends a flood of requests // to the renderer, which slows the application dramatically. Instead, ask_for_tiles() is only // called on a click event. -// ask_for_tiles(); + ask_for_tiles(); *recomposite = true; } diff --git a/src/components/main/compositing/quadtree.rs b/src/components/main/compositing/quadtree.rs index 300c3ccff09..0a86b39f6dc 100644 --- a/src/components/main/compositing/quadtree.rs +++ b/src/components/main/compositing/quadtree.rs @@ -23,12 +23,14 @@ pub struct Quadtree { struct QuadtreeNode { /// The tile belonging to this node. Note that parent nodes can have tiles. tile: Option, - /// The positiong of the node in page coordinates. + /// The position of the node in page coordinates. origin: Point2D, - /// The width and hight of the node in page coordinates. + /// The width and height of the node in page coordinates. size: f32, /// The node's children. quadrants: [Option<~QuadtreeNode>, ..4], + /// If this node is marked for rendering + render_flag: bool, } priv enum Quadrant { @@ -53,6 +55,7 @@ impl Quadtree { origin: Point2D(x as f32, y as f32), size: size as f32, quadrants: [None, None, None, None], + render_flag: false, }, max_tile_size: tile_size, } @@ -71,7 +74,7 @@ impl Quadtree { self.root.add_tile(x as f32 / scale, y as f32 / scale, tile, self.max_tile_size as f32 / scale); } /// Get the tile rect in screen and page coordinates for a given pixel position - pub fn get_tile_rect(&self, x: uint, y: uint, scale: f32) -> BufferRequest { + pub fn get_tile_rect(&mut self, x: uint, y: uint, scale: f32) -> BufferRequest { self.root.get_tile_rect(x as f32 / scale, y as f32 / scale, scale, self.max_tile_size as f32 / scale) } /// Get all the tiles in the tree @@ -96,10 +99,10 @@ impl Quadtree { valid, scale, self.max_tile_size as f32 / scale) } - /// Generate html to visualize the tree + /// Generate html to visualize the tree. For debugging purposes only. pub fn get_html(&self) -> ~str { - let header = " "; - fmt!("%s%s", header, self.root.get_html()) + static HEADER: &'static str = ""; + fmt!("%s%s", HEADER, self.root.get_html()) } } @@ -112,6 +115,7 @@ impl QuadtreeNode { origin: Point2D(x, y), size: size, quadrants: [None, None, None, None], + render_flag: false, } } @@ -149,7 +153,7 @@ impl QuadtreeNode { let mut ret = ~[]; match self.tile { - Some (ref tile) => ret = ~[tile], + Some(ref tile) => ret = ~[tile], None => {} } @@ -180,6 +184,7 @@ impl QuadtreeNode { for quads.iter().advance |quad| { self.quadrants[*quad as int] = None; } + self.render_flag = false; } else { // Send tile to children let quad = self.get_quadrant(x, y); match self.quadrants[quad as int] { @@ -216,7 +221,7 @@ impl QuadtreeNode { } /// Get a tile rect in screen and page coords for a given position in page coords - fn get_tile_rect(&self, x: f32, y: f32, scale: f32, tile_size: f32) -> BufferRequest { + fn get_tile_rect(&mut self, x: f32, 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"); @@ -230,24 +235,25 @@ impl QuadtreeNode { Rect(Point2D(self.origin.x, self.origin.y), Size2D(self.size, self.size))); } - let index = self.get_quadrant(x,y) as int; - match self.quadrants[index] { + let quad = self.get_quadrant(x,y); + match self.quadrants[quad as int] { None => { - // Calculate where the new tile should go - let factor = self.size / tile_size; - let divisor = next_power_of_two(factor.ceil() as uint); - let new_size_page = self.size / (divisor as f32); - let new_size_pixel = (new_size_page * scale).ceil() as uint; - - let new_x_page = self.origin.x + new_size_page * ((x - self.origin.x) / new_size_page).floor(); - let new_y_page = self.origin.y + new_size_page * ((y - self.origin.y) / new_size_page).floor(); - let new_x_pixel = (new_x_page * scale).ceil() as uint; - let new_y_pixel = (new_y_page * scale).ceil() as uint; - - BufferRequest(Rect(Point2D(new_x_pixel, new_y_pixel), Size2D(new_size_pixel, new_size_pixel)), - Rect(Point2D(new_x_page, new_y_page), Size2D(new_size_page, new_size_page))) + 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 = ~QuadtreeNode::new_child(new_x, new_y, new_size); + c.render_flag = true; + let result = c.get_tile_rect(x, y, scale, tile_size); + self.quadrants[quad as int] = Some(c); + result } - Some(ref child) => child.get_tile_rect(x, y, scale, tile_size), + Some(ref mut child) => child.get_tile_rect(x, y, scale, tile_size), } } @@ -354,6 +360,9 @@ impl QuadtreeNode { } return (~[], redisplay); } + None if self.render_flag => { + return(~[], false); + } _ => { return (~[self.get_tile_rect(s_x, s_y, scale, tile_size)], false); }
%s