diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index 05cbd0bc079..4bb2b2a45a2 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; @@ -19,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; @@ -33,12 +31,28 @@ pub struct RenderLayer { pub enum Msg { RenderMsg(RenderLayer), - ReRenderMsg(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, @@ -119,18 +133,19 @@ impl RenderTask { loop { match self.port.recv() { RenderMsg(render_layer) => { + if self.paint_permission { + 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; - 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,83 +161,69 @@ impl RenderTask { } } - fn render(&mut self, scale: f32) { - debug!("render_task: rendering"); - + fn render(&mut self, tiles: ~[BufferRequest], scale: f32) { 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); 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 = ~[]; // 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.iter().advance |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: tile.page_rect, + screen_pos: tile.screen_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..3033afbb56d 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; 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; +use extra::time::precise_time_s; +use compositing::quadtree::Quadtree; +mod quadtree; + /// The implementation of the layers-based compositor. #[deriving(Clone)] pub struct CompositorChan { @@ -70,6 +75,16 @@ impl RenderListener for CompositorChan { self.chan.send(Paint(id, layer_buffer_set, new_size)) } + 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 +117,15 @@ pub enum Msg { GetSize(Chan>), /// Requests the compositors GL context. GetGLContext(Chan), + + // 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. + 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 +223,106 @@ 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; + + // Keeps track of if we have performed a zoom event and how recently. + 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 { + 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) => { + chan.send(ReRenderMsg(tile_request, *world_zoom)); + } + _ => { + println("Warning: Compositor: Cannot send tile request, no render chan initialized"); + } + } + } else if redisplay { + build_layer_tree(quad); + } + } + _ => { + fail!("Compositor: Tried to ask for tiles without an initialized quadtree"); + } + } + }; + let update_layout_callbacks: @fn(LayoutChan) = |layout_chan: LayoutChan| { let layout_chan_clone = layout_chan.clone(); do window.set_navigation_callback |direction| { @@ -247,18 +368,9 @@ impl CompositorTask { } WindowMouseDownEvent(button, layer_mouse_point) => { event = MouseDownEvent(button, world_mouse_point(layer_mouse_point)); + } 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 - } - event = MouseUpEvent(button, world_mouse_point(layer_mouse_point)); } } @@ -266,6 +378,7 @@ impl CompositorTask { } }; + let check_for_messages: @fn(&Port) = |port: &Port| { // Handle messages while port.peek() { @@ -291,78 +404,49 @@ impl CompositorTask { } GetGLContext(chan) => chan.send(current_gl_context()), + + 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 { Some(pipeline_id) => if id != pipeline_id { loop; }, None => { loop; }, } - - debug!("osmain: received new frame"); - - *page_size = Size2D(new_size.width as f32, new_size.height as f32); + + 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"), + } + 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; - for new_layer_buffer_set.buffers.iter().advance |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 { - 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.screen_pos.origin; - 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); - texture_layer.common.set_transform(transform); + // FIXME: Don't copy the buffers here + quad.add_tile(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y, + *world_zoom, ~buffer.clone()); } - - // 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)); + + *page_size = Size2D(new_size.width as f32, new_size.height as f32); + + build_layer_tree(quad); // TODO: Recycle the old buffers; send them back to the renderer to reuse if // it wishes. - - *recomposite = true; } } } @@ -409,6 +493,11 @@ 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; } @@ -416,6 +505,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 @@ -465,6 +556,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 1eb7a29655b..0a86b39f6dc 100644 --- a/src/components/main/compositing/quadtree.rs +++ b/src/components/main/compositing/quadtree.rs @@ -8,11 +8,29 @@ 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; -priv enum Quadtype { - Empty, - Base, - Branch, +/// Parent to all quadtree nodes. Stores variables needed at all levels. All method calls +/// at this level are in pixel coordinates. +pub struct Quadtree { + 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 position of the node in page coordinates. + origin: Point2D, + /// 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 { @@ -22,206 +40,488 @@ priv enum Quadrant { BR = 3, } +impl 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 = div_ceil(longer, tile_size); + let power_of_two = next_power_of_two(num_tiles); + let size = power_of_two * tile_size; + + Quadtree { + root: QuadtreeNode { + tile: None, + 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, + } + } + + /// 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) + } + /// 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); + } + /// Get the tile rect in screen and page coordinates for a given pixel position + 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 + 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. For debugging purposes only. + pub fn get_html(&self) -> ~str { + static HEADER: &'static str = ""; + fmt!("%s%s", HEADER, self.root.get_html()) + } -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 { - Quadtree { - quadtype: Empty, - rect: Rect { - origin: Point2D(x, y), - size: Size2D(width, height), - }, - +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], + render_flag: false, } } - /// 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 - } + /// 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 } } - - /// 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); - - 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 + /// 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"); } - // 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; - 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), + } + } + + /// Get all tiles in the tree, parents first. + fn get_all_tiles<'r>(&'r self) -> ~[&'r T] { + let mut ret = ~[]; + + match self.tile { + Some(ref tile) => ret = ~[tile], + None => {} + } + + for self.quadrants.iter().advance |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) { + 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"); + } - // 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 <= tile_size { // We are the child + self.tile = Some(tile); + // 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; } - (&[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; + self.render_flag = false; + } 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 + 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.add_tile(x, y, tile, tile_size); + self.quadrants[quad as int] = Some(c); + + // 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(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, + _ => {} + } } - _ => {} // 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) } } } } + + /// 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, 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 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 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 quad = self.get_quadrant(x,y); + match self.quadrants[quad as int] { + 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 = ~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 mut 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); + } + None if self.render_flag => { + return(~[], false); + } + _ => { + 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 { + 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); + // 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()); + } + None => { + ret = fmt!("%s", ret); + } + } + match *quad { + TR => ret = fmt!("%s", ret), + _ => {} + } + } + ret = fmt!("%s
%sEMPTY CHILD
\n", ret); + } + } + return ret; + } + } #[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 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 diff --git a/src/components/msg/compositor_msg.rs b/src/components/msg/compositor_msg.rs index d9dd02f2671..49d98e383f2 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, + // 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); + 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); }