diff options
author | eschweic <eschweickart@mozilla.com> | 2013-07-01 15:20:29 -0700 |
---|---|---|
committer | eschweic <eschweickart@mozilla.com> | 2013-07-10 17:12:52 -0700 |
commit | 6bebda4f26c97c2204d23ce6340962a65cb727ad (patch) | |
tree | bf2ddefed3ddc5b44839678c4551e04a6b21ca54 /src | |
parent | ad7dc32fc8a52a3f1a93bc02446a0fb58ec9a716 (diff) | |
download | servo-6bebda4f26c97c2204d23ce6340962a65cb727ad.tar.gz servo-6bebda4f26c97c2204d23ce6340962a65cb727ad.zip |
Implement progressive rendering
Diffstat (limited to 'src')
-rw-r--r-- | src/components/gfx/render_task.rs | 113 | ||||
-rw-r--r-- | src/components/main/compositing/mod.rs | 152 | ||||
-rw-r--r-- | src/components/main/compositing/quadtree.rs | 132 | ||||
-rw-r--r-- | src/components/msg/compositor_msg.rs | 9 |
4 files changed, 321 insertions, 85 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<uint>], f32), PaintPermissionGranted, PaintPermissionRevoked, ExitMsg(Chan<()>), @@ -119,11 +119,11 @@ impl<C: RenderListener + Send> RenderTask<C> { 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<C: RenderListener + Send> RenderTask<C> { } } - fn render(&mut self, scale: f32) { + fn render(&mut self, tiles: ~[Rect<uint>], 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<C: RenderListener + Send> RenderTask<C> { // 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<AzFloat> = 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<AzFloat> = 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<uint>, tile_size: uint) { + self.chan.send(NewLayer(page_size, tile_size)) + } + fn resize_layer(&self, page_size: Size2D<uint>) { + 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<Size2D<int>>), /// Requests the compositors GL context. GetGLContext(Chan<AzGLContext>), + + //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>, uint), + /// Alerts the compositor that the current layer has changed size. + ResizeLayer(Size2D<uint>), + /// 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<LayerBufferSet>, Size2D<uint>), /// 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<RenderChan> = @mut None; let pipeline_id: @mut Option<uint> = @mut None; + // Quadtree for this layer + // FIXME: This should be one-per-layer + let quadtree: @mut Option<Quadtree<LayerBuffer>> = @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)); - } - 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)); + //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 => {} // Nothing to do + None => {} } + } + WindowMouseUpEvent(button, layer_mouse_point) => { + + //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<Msg>) = |port: &Port<Msg>| { // 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<T> Quadtree<T> { 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<T> { self.root.get_tile(x as f32 / scale, y as f32 / scale) @@ -61,12 +65,24 @@ impl<T> Quadtree<T> { 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>, 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 = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> <html xmlns=\"http://www.w3.org/1999/xhtml\">"; + fmt!("%s<body>%s</body></html>", header, self.root.get_html()) + } } impl<T> QuadtreeNode<T> { - // Private method to create new children + /// Private method to create new children fn new_child(x: f32, y: f32, size: f32) -> QuadtreeNode<T> { QuadtreeNode { tile: None, @@ -105,6 +121,26 @@ impl<T> QuadtreeNode<T> { } } + /// 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<T> QuadtreeNode<T> { 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<T> QuadtreeNode<T> { 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>, 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<table border=1><tr>", ret); + for [TL, TR, BL, BR].each |quad| { + match self.quadrants[*quad as int] { + Some(ref child) => { + ret = fmt!("%s<td>%s</td>", ret, child.get_html()); + } + None => { + ret = fmt!("%s<td>EMPTY CHILD</td>", ret); + } + } + match *quad { + TR => ret = fmt!("%s</tr><tr>", ret), + _ => {} + } } + ret = fmt!("%s</table>\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<uint>, + // 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>, uint); //eschweic + fn resize_layer(&self, Size2D<uint>); + fn delete_layer(&self); fn paint(&self, id: uint, layer_buffer_set: arc::ARC<LayerBufferSet>, new_size: Size2D<uint>); fn set_render_state(&self, render_state: RenderState); } |