From 974ed79144db35d13673101f34c433aaf18c274a Mon Sep 17 00:00:00 2001 From: eschweic Date: Mon, 29 Jul 2013 09:46:19 -0700 Subject: [PATCH] Refactor compositor; implement compositor iframe support --- src/components/gfx/render_task.rs | 16 +- .../main/compositing/compositor_layer.rs | 316 +++++++++++++--- src/components/main/compositing/mod.rs | 346 ++++++------------ src/components/main/compositing/quadtree.rs | 68 +++- .../main/platform/common/glfw_windowing.rs | 24 +- src/components/main/windowing.rs | 7 +- src/components/msg/compositor_msg.rs | 8 +- 7 files changed, 482 insertions(+), 303 deletions(-) diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index af80d384b69..dab31cdc2f4 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -32,7 +32,7 @@ pub struct RenderLayer { pub enum Msg { RenderMsg(RenderLayer), - ReRenderMsg(~[BufferRequest], f32), + ReRenderMsg(~[BufferRequest], f32, PipelineId), PaintPermissionGranted, PaintPermissionRevoked, ExitMsg(Chan<()>), @@ -135,18 +135,18 @@ impl RenderTask { match self.port.recv() { RenderMsg(render_layer) => { if self.paint_permission { - self.compositor.new_layer(render_layer.size, self.opts.tile_size); + self.compositor.new_layer(self.id, render_layer.size); } self.render_layer = Some(render_layer); } - ReRenderMsg(tiles, scale) => { - self.render(tiles, scale); + ReRenderMsg(tiles, scale, id) => { + self.render(tiles, scale, id); } PaintPermissionGranted => { self.paint_permission = true; match self.render_layer { Some(ref render_layer) => { - self.compositor.new_layer(render_layer.size, self.opts.tile_size); + self.compositor.new_layer(self.id, render_layer.size); } None => {} } @@ -162,7 +162,7 @@ impl RenderTask { } } - fn render(&mut self, tiles: ~[BufferRequest], scale: f32) { + fn render(&mut self, tiles: ~[BufferRequest], scale: f32, id: PipelineId) { let render_layer; match self.render_layer { Some(ref r_layer) => { @@ -202,7 +202,7 @@ impl RenderTask { 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); @@ -234,7 +234,7 @@ impl RenderTask { debug!("render_task: returning surface"); if self.paint_permission { - self.compositor.paint(self.id, layer_buffer_set.clone(), render_layer.size); + self.compositor.paint(id, layer_buffer_set.clone()); } debug!("caching paint msg"); self.last_paint_msg = Some((layer_buffer_set, render_layer.size)); diff --git a/src/components/main/compositing/compositor_layer.rs b/src/components/main/compositing/compositor_layer.rs index 55724aeed4e..c15356b8a44 100644 --- a/src/components/main/compositing/compositor_layer.rs +++ b/src/components/main/compositing/compositor_layer.rs @@ -6,24 +6,55 @@ use geom::point::Point2D; use geom::size::Size2D; use geom::rect::Rect; use geom::matrix::identity; -use gfx::render_task::BufferRequest; +use gfx::render_task::ReRenderMsg; use servo_msg::compositor_msg::{LayerBuffer, LayerBufferSet}; +use servo_msg::constellation_msg::PipelineId; +use script::dom::event::{ClickEvent, MouseDownEvent, MouseUpEvent}; +use script::script_task::SendEventMsg; +use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent}; use compositing::quadtree::Quadtree; -use layers::layers::{ContainerLayer, TextureLayerKind, TextureLayer, TextureManager}; +use layers::layers::{ContainerLayerKind, ContainerLayer, TextureLayerKind, TextureLayer, TextureManager}; +use pipeline::Pipeline; +/// The CompositorLayer represents an element on a page that has a unique scroll +/// or animation behavior. This can include absolute positioned elements, iframes, etc. +/// Each layer can also have child elements. pub struct CompositorLayer { - id: uint, + /// This layer's pipeline. BufferRequests will be sent through this. + pipeline: Pipeline, + /// The rect that this layer occupies in page coordinates. The offset + /// is blind to any parents this layer may have; it only refers to the + /// scroll position of the page. page_rect: Rect, - z_order: int, + /// This layer's children. These could be iframes or any element which + /// differs in scroll behavior from its parent. Each is associated with a + /// ContainerLayer which determines its position relative to its parent and + /// clipping rect. Children are stored in the order in which they are drawn. + children: ~[CompositorLayerChild], + /// This layer's quadtree. This is where all buffers are stored for this layer. quadtree: Quadtree<~LayerBuffer>, + /// The root layer of this CompositorLayer's layer tree. Buffers are collected + /// from the quadtree and inserted here when the layer is painted to the screen. root_layer: @mut ContainerLayer, + + // TODO: Eventually, it may be useful to have an ID associated with each layer. } -pub fn CompositorLayer(id: uint, page_size: Size2D, init_offset: Point2D, z_order: int, tile_size: uint, max_mem: Option) -> CompositorLayer { +/// Helper struct for keeping CompositorLayer children organized. +struct CompositorLayerChild { + /// The child itself. + child: ~CompositorLayer, + /// A ContainerLayer managed by the parent node. This deals with clipping and + /// positioning, and is added above the child's layer tree. + container: @mut ContainerLayer, +} + +pub fn CompositorLayer(pipeline: Pipeline, page_size: Size2D, tile_size: uint, + max_mem: Option) -> CompositorLayer { CompositorLayer { - id: id, - page_rect: Rect(init_offset, page_size), - z_order: z_order, + pipeline: pipeline, + page_rect: Rect(Point2D(0f32, 0f32), page_size), + children: ~[], quadtree: Quadtree::new(page_size.width as uint, page_size.height as uint, tile_size, max_mem), @@ -31,39 +62,172 @@ pub fn CompositorLayer(id: uint, page_size: Size2D, init_offset: Point2D, scale: f32) -> (~[BufferRequest], bool) { - let rect = Rect(Point2D(-(self.page_rect.origin.x as int), - -(self.page_rect.origin.y as int)), - window_size); - self.quadtree.get_tile_rects(rect, scale) - } - - // Move the layer by as relative specified amount. Called during a scroll event. - fn translate(&mut self, delta: Point2D) { +impl CompositorLayer { + // Move the layer by as relative specified amount in page coordinates. Does not change + // the position of the layer relative to its parent. This also takes in a cursor position + // to see if the mouse is over child layers first. If a layer successfully scrolled, returns + // true; otherwise returns false, so a parent layer can scroll instead. + pub fn scroll(&mut self, delta: Point2D, cursor: Point2D, window_size: Size2D) -> bool { + let cursor = cursor - self.page_rect.origin; + for self.children.mut_iter().advance |child| { + match child.container.scissor { + None => { + error!("CompositorLayer: unable to perform cursor hit test for layer"); + } + Some(rect) => { + if cursor.x >= rect.origin.x && cursor.x < rect.origin.x + rect.size.width + && cursor.y >= rect.origin.y && cursor.y < rect.origin.y + rect.size.height + && child.child.scroll(delta, cursor - rect.origin, rect.size) { + return true; + } + } + } + } + + let old_origin = self.page_rect.origin; self.page_rect.origin = self.page_rect.origin + delta; - } - - // Move the layer to an absolute position. - fn set_offset(&mut self, new_offset: Point2D) { - self.page_rect.origin = new_offset; + + // bounds checking + let min_x = (window_size.width - self.page_rect.size.width).min(&0.0); + self.page_rect.origin.x = self.page_rect.origin.x.clamp(&min_x, &0.0); + let min_y = (window_size.height - self.page_rect.size.height).min(&0.0); + self.page_rect.origin.y = self.page_rect.origin.y.clamp(&min_y, &0.0); + + // check to see if we scrolled + if old_origin - self.page_rect.origin == Point2D(0f32, 0f32) { + return false; + } + + self.root_layer.common.set_transform(identity().translate(self.page_rect.origin.x, + self.page_rect.origin.y, + 0.0)); + true } - // Called when the layer changes size (NOT as a result of a zoom event). - fn resize(&mut self, new_size: Size2D) { - self.page_rect.size = new_size; - // TODO: might get buffers back here - self.quadtree.resize(new_size.width as uint, new_size.height as uint); + // Takes in a MouseWindowEvent, determines if it should be passed to children, and + // sends the event off to the appropriate pipeline. NB: the cursor position is in + // page coordinates. + pub fn send_mouse_event(&self, event: MouseWindowEvent, cursor: Point2D) { + let cursor = cursor - self.page_rect.origin; + // FIXME: maybe we want rev_iter() instead? Depends on draw order + for self.children.iter().advance |child| { + match child.container.scissor { + None => { + error!("CompositorLayer: unable to perform cursor hit test for layer"); + } + Some(rect) => { + if cursor.x >= rect.origin.x && cursor.x < rect.origin.x + rect.size.width + && cursor.y >= rect.origin.y && cursor.y < rect.origin.y + rect.size.height { + child.child.send_mouse_event(event, cursor - rect.origin); + return; + } + } + } + } + + // This mouse event is mine! + let message = match event { + MouseWindowClickEvent(button, _) => ClickEvent(button, cursor), + MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, cursor), + MouseWindowMouseUpEvent(button, _) => MouseUpEvent(button, cursor), + }; + + self.pipeline.script_chan.send(SendEventMsg(self.pipeline.id.clone(), message)); } - fn get_layer_tree(&mut self, scale: f32) -> @mut ContainerLayer { + // Given the current window size, determine which tiles need to be redisplayed + // and sends them off the the appropriate renderer. + // Returns a bool that is true if the scene should be repainted. + pub fn get_buffer_request(&mut self, window_rect: Rect, scale: f32) -> bool { + let rect = Rect(Point2D(-self.page_rect.origin.x + window_rect.origin.x, + -self.page_rect.origin.y + window_rect.origin.y), + window_rect.size); + let (request, redisplay) = self.quadtree.get_tile_rects_page(rect, scale); + if !request.is_empty() { + self.pipeline.render_chan.send(ReRenderMsg(request, scale, self.pipeline.id.clone())); + } + if redisplay { + self.build_layer_tree(); + } + let transform = |x: &mut CompositorLayerChild| -> bool { + match x.container.scissor { + Some(scissor) => { + let new_rect = window_rect.intersection(&scissor); + match new_rect { + Some(new_rect) => { + x.child.get_buffer_request(new_rect, scale) + } + None => { + false //Layer is offscreen + } + } + } + None => { + fail!("CompositorLayer: Child layer not clipped"); + } + } + }; + self.children.mut_iter() + .transform(transform) + .fold(false, |a, b| a || b) || redisplay + } + + + // Move the sublayer to an absolute position in page coordinates relative to its parent, + // and clip the layer to the specified size in page coordinates. + // This method returns false if the specified layer is not found. + pub fn set_clipping_rect(&mut self, pipeline_id: PipelineId, new_rect: Rect) -> bool { + for self.children.iter().advance |child_node| { + if pipeline_id != child_node.child.pipeline.id { + loop; + } + let con = child_node.container; + con.common.set_transform(identity().translate(new_rect.origin.x, + new_rect.origin.y, + 0.0)); + con.scissor = Some(new_rect); + return true; + } + + // ID does not match any of our immediate children, so recurse on descendents. + self.children.mut_iter().transform(|x| &mut x.child).any(|x| x.set_clipping_rect(pipeline_id, new_rect)) + } + + + // Called when the layer changes size (NOT as a result of a zoom event). + // This method returns false if the specified layer is not found. + pub fn resize(&mut self, pipeline_id: PipelineId, new_size: Size2D, window_size: Size2D) -> bool { + if self.pipeline.id == pipeline_id { + self.page_rect.size = new_size; + // TODO: might get buffers back here + self.quadtree.resize(new_size.width as uint, new_size.height as uint); + // Call scroll for bounds checking of the page shrunk. + self.scroll(Point2D(0f32, 0f32), Point2D(-1f32, -1f32), window_size); + return true; + } + + // ID does not match ours, so recurse on descendents. + let transform = |x: &mut CompositorLayerChild| -> bool { + match x.container.scissor { + Some(scissor) => { + x.child.resize(pipeline_id, new_size, scissor.size) + } + None => { + fail!("CompositorLayer: Child layer not clipped"); + } + } + }; + + self.children.mut_iter().any(transform) + } + + // Collect buffers from the quadtree. This method IS NOT recursive, so child CompositorLayers + // are not rebuilt directly from this method. + pub fn build_layer_tree(&mut self) { // Iterate over the children of the container layer. let mut current_layer_child = self.root_layer.first_child; - // Delete old layer + // Delete old layer. while current_layer_child.is_some() { let trash = current_layer_child.get(); do current_layer_child.get().with_common |common| { @@ -72,10 +236,25 @@ impl CompositorLayer { self.root_layer.remove_child(trash); } + // Add child layers. + for self.children.mut_iter().advance |child| { + current_layer_child = match current_layer_child { + None => { + child.container.common.parent = None; + child.container.common.prev_sibling = None; + child.container.common.next_sibling = None; + self.root_layer.add_child(ContainerLayerKind(child.container)); + None + } + Some(_) => { + fail!("CompositorLayer: Layer tree failed to delete"); + } + }; + } + + // Add new tiles. let all_tiles = self.quadtree.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. @@ -100,28 +279,61 @@ impl CompositorLayer { Some(_) => fail!(~"found unexpected layer kind"), }; - let origin = buffer.rect.origin; - let origin = Point2D(origin.x as f32, origin.y as f32); - + + let rect = buffer.rect; // Set the layer's transform. - let transform = identity().translate(origin.x * scale + self.page_rect.origin.x, origin.y * scale + self.page_rect.origin.y, 0.0); - let transform = transform.scale(width as f32 * scale / buffer.resolution, height as f32 * scale / buffer.resolution, 1.0); + let transform = identity().translate(rect.origin.x, rect.origin.y, 0.0); + let transform = transform.scale(rect.size.width, rect.size.height, 1.0); texture_layer.common.set_transform(transform); } - - self.root_layer - } + + } - // Add LayerBuffers to this layer. - // TODO: This may return old buffers, which should be sent back to the renderer. - fn add_buffers(&mut self, new_buffers: &mut LayerBufferSet) { - for new_buffers.buffers.iter().advance |buffer| { - self.quadtree.add_tile(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y, - buffer.resolution, ~buffer.clone()); + // Add LayerBuffers to the specified layer. Returns false if the layer is not found. + pub fn add_buffers(&mut self, pipeline_id: PipelineId, new_buffers: &LayerBufferSet) -> bool { + if self.pipeline.id == pipeline_id { + for new_buffers.buffers.iter().advance |buffer| { + // TODO: This may return old buffers, which should be sent back to the renderer. + self.quadtree.add_tile_pixel(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y, + buffer.resolution, ~buffer.clone()); + } + self.build_layer_tree(); + return true; } - + // ID does not match ours, so recurse on descendents. + self.children.mut_iter().transform(|x| &mut x.child).any(|x| x.add_buffers(pipeline_id, new_buffers)) } - // TODO: send buffers back to renderer when later is deleted + // Deletes a specified sublayer. Returns false if the layer is not found. + pub fn delete(&mut self, pipeline_id: PipelineId) -> bool { + match self.children.rposition(|x| x.child.pipeline.id == pipeline_id) { + Some(index) => { + // TODO: send buffers back to renderer when layer is deleted + self.children.remove(index); + self.build_layer_tree(); + true + } + None => { + self.children.mut_iter().transform(|x| &mut x.child).any(|x| x.delete(pipeline_id)) + } + } + } -} \ No newline at end of file + + // Adds a child. + pub fn add_child(&mut self, pipeline: Pipeline, page_size: Size2D, tile_size: uint, + max_mem: Option, clipping_rect: Rect) { + let container = @mut ContainerLayer(); + container.scissor = Some(clipping_rect); + container.common.set_transform(identity().translate(clipping_rect.origin.x, + clipping_rect.origin.y, + 0.0)); + let child = ~CompositorLayer(pipeline, page_size, tile_size, max_mem); + container.add_child(ContainerLayerKind(child.root_layer)); + self.children.push(CompositorLayerChild { + child: child, + container: container, + }); + + } +} diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 3bb05937ca2..3822d4f420f 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -3,19 +3,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use platform::{Application, Window}; -use script::dom::event::{Event_, ClickEvent, MouseDownEvent, MouseUpEvent, ResizeEvent}; +use script::dom::event::ResizeEvent; use script::script_task::{LoadMsg, NavigateMsg, SendEventMsg}; +pub use windowing; use windowing::{ApplicationMethods, WindowEvent, WindowMethods}; use windowing::{IdleWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent, MouseWindowEventClass}; use windowing::{ScrollWindowEvent, ZoomWindowEvent, NavigationWindowEvent, FinishedWindowEvent}; use windowing::{QuitWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent}; -use servo_msg::compositor_msg::{RenderListener, LayerBuffer, LayerBufferSet, RenderState}; +use servo_msg::compositor_msg::{RenderListener, LayerBufferSet, RenderState}; use servo_msg::compositor_msg::{ReadyState, ScriptListener}; use servo_msg::constellation_msg::PipelineId; use servo_msg::constellation_msg; -use gfx::render_task::ReRenderMsg; use gfx::opts::Opts; use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods, current_gl_context}; @@ -35,7 +35,6 @@ 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}; use layers::rendergl; use layers::scene::Scene; use opengles::gl2; @@ -44,17 +43,17 @@ use servo_util::{time, url}; use servo_util::time::profile; use servo_util::time::ProfilerChan; -use extra::arc; -pub use windowing; - use extra::time::precise_time_s; -use compositing::quadtree::Quadtree; +use extra::arc; + use constellation::SendableFrameTree; use pipeline::Pipeline; use compositing::compositor_layer::CompositorLayer; + mod quadtree; mod compositor_layer; + /// The implementation of the layers-based compositor. #[deriving(Clone)] pub struct CompositorChan { @@ -81,18 +80,18 @@ impl RenderListener for CompositorChan { port.recv() } - fn paint(&self, id: PipelineId, layer_buffer_set: arc::ARC, new_size: Size2D) { - self.chan.send(Paint(id, layer_buffer_set, new_size)) + fn paint(&self, id: PipelineId, layer_buffer_set: arc::ARC) { + self.chan.send(Paint(id, layer_buffer_set)) } - fn new_layer(&self, page_size: Size2D, tile_size: uint) { - self.chan.send(NewLayer(page_size, tile_size)) + fn new_layer(&self, id: PipelineId, page_size: Size2D) { + self.chan.send(NewLayer(id, page_size)) } - fn resize_layer(&self, page_size: Size2D) { - self.chan.send(ResizeLayer(page_size)) + fn resize_layer(&self, id: PipelineId, page_size: Size2D) { + self.chan.send(ResizeLayer(id, page_size)) } - fn delete_layer(&self) { - self.chan.send(DeleteLayer) + fn delete_layer(&self, id: PipelineId) { + self.chan.send(DeleteLayer(id)) } fn set_render_state(&self, render_state: RenderState) { @@ -128,16 +127,16 @@ pub enum Msg { /// Requests the compositors GL context. GetGLContext(Chan), - // TODO: Attach layer ids and epochs to these messages + // TODO: Attach 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, + NewLayer(PipelineId, Size2D), + /// Alerts the compositor that the specified layer has changed size. + ResizeLayer(PipelineId, Size2D), + /// Alerts the compositor that the specified layer has been deleted. + DeleteLayer(PipelineId), /// Requests that the compositor paint the given layer buffer set for the given page size. - Paint(PipelineId, arc::ARC, Size2D), + Paint(PipelineId, arc::ARC), /// Alerts the compositor to the current status of page loading. ChangeReadyState(ReadyState), /// Alerts the compositor to the current status of rendering. @@ -222,119 +221,30 @@ impl CompositorTask { let root_layer = @mut ContainerLayer(); let window_size = window.size(); let mut scene = Scene(ContainerLayerKind(root_layer), window_size, identity()); + let mut window_size = Size2D(window_size.width as int, window_size.height as int); let mut done = false; let mut recomposite = false; - // FIXME: This should not be a separate offset applied after the fact but rather should be - // applied to the layers themselves on a per-layer basis. However, this won't work until scroll - // positions are sent to content. - let mut world_offset = Point2D(0f32, 0f32); - let mut page_size = Size2D(0f32, 0f32); - let mut window_size = Size2D(window_size.width as int, - window_size.height as int); - // Keeps track of the current zoom factor let mut world_zoom = 1f32; - // Keeps track of local zoom factor. Reset to 1 after a rerender event. - let mut local_zoom = 1f32; - // Channel to the outermost frame's pipeline. - // FIXME: Compositor currently only asks for tiles to composite from this pipeline, - // Subframes need to be handled, as well. Additionally, events are only forwarded - // to this pipeline, but they should be routed to the appropriate pipeline via - // the constellation. - let mut pipeline: Option = None; - - // Quadtree for this layer - // FIXME: This should be one-per-layer - let mut quadtree: Option> = None; - - // Keeps track of if we have performed a zoom event and how recently. let mut zoom_action = false; let mut zoom_time = 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; - - // Delete old layer - 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); - } + // Channel to the outermost frame's pipeline. + // FIXME: Events are only forwarded to this pipeline, but they should be + // routed to the appropriate pipeline via the constellation. + let mut pipeline: Option = None; - 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); - - } - - // Reset zoom - local_zoom = 1f32; - root_layer.common.set_transform(identity().translate(-world_offset.x, - -world_offset.y, - 0.0)); - recomposite = true; - }; + // The root CompositorLayer + let mut compositor_layer: Option = None; + // Get BufferRequests from each layer. let ask_for_tiles = || { - match quadtree { - Some(ref mut quad) => { - let (tile_request, redisplay) = quad.get_tile_rects(Rect(Point2D(world_offset.x as int, - world_offset.y as int), - window_size), world_zoom); - - if !tile_request.is_empty() { - match pipeline { - Some(ref pipeline) => { - pipeline.render_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 window_size_page = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + for compositor_layer.mut_iter().advance |layer| { + recomposite = layer.get_buffer_request(Rect(Point2D(0f32, 0f32), window_size_page), + world_zoom) || recomposite; } }; @@ -359,46 +269,66 @@ 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(new_size.width.max(&(window_size.width as uint)), - new_size.height.max(&(window_size.height as uint)), - tile_size, Some(10000000u))); + NewLayer(_id, new_size) => { + // FIXME: This should create an additional layer instead of replacing the current one. + // Once ResizeLayer messages are set up, we can switch to the new functionality. + + let p = match pipeline { + Some(ref pipeline) => pipeline, + None => fail!("Compositor: Received new layer without initialized pipeline"), + }; + let page_size = Size2D(new_size.width as f32, new_size.height as f32); + let new_layer = CompositorLayer(p.clone(), page_size, + self.opts.tile_size, Some(10000000u)); + + let current_child = root_layer.first_child; + // This assumes there is at most one child, which should be the case. + match current_child { + Some(old_layer) => root_layer.remove_child(old_layer), + None => {} + } + root_layer.add_child(ContainerLayerKind(new_layer.root_layer)); + compositor_layer = Some(new_layer); + 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 { - Some(ref pipeline) => if id != pipeline.id { loop; }, - None => { loop; }, + ResizeLayer(id, new_size) => { + match compositor_layer { + Some(ref mut layer) => { + let page_window = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + assert!(layer.resize(id, Size2D(new_size.width as f32, + new_size.height as f32), + page_window)); + ask_for_tiles(); + } + None => {} } - + } + + DeleteLayer(id) => { + match compositor_layer { + Some(ref mut layer) => { + assert!(layer.delete(id)); + ask_for_tiles(); + } + None => {} + } + } + + Paint(id, new_layer_buffer_set) => { 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"), + match compositor_layer { + Some(ref mut layer) => { + assert!(layer.add_buffers(id, new_layer_buffer_set.get())); + recomposite = true; + } + None => { + fail!("Compositor: given paint command with no CompositorLayer initialized"); + } } - - 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 - quad.add_tile(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y, - buffer.resolution, ~buffer.clone()); - } - - 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. } @@ -433,56 +363,27 @@ impl CompositorTask { } MouseWindowEventClass(mouse_window_event) => { - let event: Event_; - let world_mouse_point = |layer_mouse_point: Point2D| { - layer_mouse_point + world_offset + let point = match mouse_window_event { + MouseWindowClickEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom), + MouseWindowMouseDownEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom), + MouseWindowMouseUpEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom), }; - match mouse_window_event { - MouseWindowClickEvent(button, layer_mouse_point) => { - event = ClickEvent(button, world_mouse_point(layer_mouse_point)); - } - MouseWindowMouseDownEvent(button, layer_mouse_point) => { - event = MouseDownEvent(button, world_mouse_point(layer_mouse_point)); - } - MouseWindowMouseUpEvent(button, layer_mouse_point) => { - event = MouseUpEvent(button, world_mouse_point(layer_mouse_point)); - } - } - match pipeline { - Some(ref pipeline) => pipeline.script_chan.send(SendEventMsg(pipeline.id.clone(), event)), - None => error!("Compositor: Recieved mouse event without initialized layout chan"), + for compositor_layer.iter().advance |layer| { + layer.send_mouse_event(mouse_window_event, point); } } - ScrollWindowEvent(delta) => { - // FIXME (Rust #2528): Can't use `-=`. - let world_offset_copy = world_offset; - world_offset = world_offset_copy - delta; - - // Clamp the world offset to the screen size. - let max_x = (page_size.width * world_zoom - window_size.width as f32).max(&0.0); - world_offset.x = world_offset.x.clamp(&0.0, &max_x).round(); - let max_y = (page_size.height * world_zoom - window_size.height as f32).max(&0.0); - world_offset.y = world_offset.y.clamp(&0.0, &max_y).round(); - - debug!("compositor: scrolled to %?", world_offset); - - - let mut scroll_transform = identity(); - - scroll_transform = scroll_transform.translate(window_size.width as f32 / 2f32 * local_zoom - world_offset.x, - window_size.height as f32 / 2f32 * local_zoom - world_offset.y, - 0.0); - scroll_transform = scroll_transform.scale(local_zoom, local_zoom, 1f32); - scroll_transform = scroll_transform.translate(window_size.width as f32 / -2f32, - window_size.height as f32 / -2f32, - 0.0); - - root_layer.common.set_transform(scroll_transform); - + ScrollWindowEvent(delta, cursor) => { + // TODO: modify delta to snap scroll to pixels. + let page_delta = Point2D(delta.x as f32 / world_zoom, delta.y as f32 / world_zoom); + let page_cursor: Point2D = Point2D(cursor.x as f32 / world_zoom, + cursor.y as f32 / world_zoom); + let page_window = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + for compositor_layer.mut_iter().advance |layer| { + recomposite = layer.scroll(page_delta, page_cursor, page_window) || recomposite; + } ask_for_tiles(); - - recomposite = true; } ZoomWindowEvent(magnification) => { @@ -492,34 +393,19 @@ impl CompositorTask { // Determine zoom amount world_zoom = (world_zoom * magnification).max(&1.0); - local_zoom = local_zoom * world_zoom/old_world_zoom; - - // Update world offset - let corner_to_center_x = world_offset.x + window_size.width as f32 / 2f32; - let new_corner_to_center_x = corner_to_center_x * world_zoom / old_world_zoom; - world_offset.x = world_offset.x + new_corner_to_center_x - corner_to_center_x; - - let corner_to_center_y = world_offset.y + window_size.height as f32 / 2f32; - let new_corner_to_center_y = corner_to_center_y * world_zoom / old_world_zoom; - world_offset.y = world_offset.y + new_corner_to_center_y - corner_to_center_y; - - // Clamp to page bounds when zooming out - let max_x = (page_size.width * world_zoom - window_size.width as f32).max(&0.0); - world_offset.x = world_offset.x.clamp(&0.0, &max_x).round(); - let max_y = (page_size.height * world_zoom - window_size.height as f32).max(&0.0); - world_offset.y = world_offset.y.clamp(&0.0, &max_y).round(); - - // Apply transformations - let mut zoom_transform = identity(); - zoom_transform = zoom_transform.translate(window_size.width as f32 / 2f32 * local_zoom - world_offset.x, - window_size.height as f32 / 2f32 * local_zoom - world_offset.y, - 0.0); - zoom_transform = zoom_transform.scale(local_zoom, local_zoom, 1f32); - zoom_transform = zoom_transform.translate(window_size.width as f32 / -2f32, - window_size.height as f32 / -2f32, - 0.0); - root_layer.common.set_transform(zoom_transform); + root_layer.common.set_transform(identity().scale(world_zoom, world_zoom, 1f32)); + // Scroll as needed + let page_delta = Point2D(window_size.width as f32 * (1.0 / world_zoom - 1.0 / old_world_zoom) * 0.5, + window_size.height as f32 * (1.0 / world_zoom - 1.0 / old_world_zoom) * 0.5); + // TODO: modify delta to snap scroll to pixels. + let page_cursor = Point2D(-1f32, -1f32); // Make sure this hits the base layer + let page_window = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + for compositor_layer.mut_iter().advance |layer| { + layer.scroll(page_delta, page_cursor, page_window); + } + recomposite = true; } diff --git a/src/components/main/compositing/quadtree.rs b/src/components/main/compositing/quadtree.rs index 314835fcef6..0aa2eaa68d2 100644 --- a/src/components/main/compositing/quadtree.rs +++ b/src/components/main/compositing/quadtree.rs @@ -85,13 +85,19 @@ impl Quadtree { 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 { + pub fn get_tile_pixel<'r>(&'r self, x: uint, y: uint, scale: f32) -> &'r Option { self.root.get_tile(x as f32 / scale, y as f32 / scale) } + + /// Get a tile at a given page position. + pub fn get_tile_page<'r>(&'r self, x: f32, y: f32) -> &'r Option { + self.root.get_tile(x, y) + } + /// Add a tile associtated with a given pixel position and scale. /// If the tile pushes the total memory over its maximum, tiles will be removed /// until total memory is below the maximum again. - pub fn add_tile(&mut self, x: uint, y: uint, scale: f32, tile: T) { + pub fn add_tile_pixel(&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); match self.max_mem { Some(max) => { @@ -106,32 +112,73 @@ impl Quadtree { None => {} } } + + /// Add a tile associtated with a given page position. + /// If the tile pushes the total memory over its maximum, tiles will be removed + /// until total memory is below the maximum again. + pub fn add_tile_page(&mut self, x: f32, y: f32, scale: f32, tile: T) { + self.root.add_tile(x, y, tile, self.max_tile_size as f32 / scale); + match self.max_mem { + Some(max) => { + while self.root.tile_mem > max { + let r = self.root.remove_tile(x, y); + match r { + (Some(_), _, _) => {} + _ => fail!("Quadtree: No valid tiles to remove"), + } + } + } + None => {} + } + } + /// 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 { + pub fn get_tile_rect_pixel(&mut self, x: uint, y: uint, scale: f32) -> BufferRequest { self.root.get_tile_rect(x as f32 / scale, y as f32 / scale, self.clip_size.width as f32, self.clip_size.height as f32, scale, self.max_tile_size as f32 / scale) } + + /// Get the tile rect in screen and page coordinates for a given page position + pub fn get_tile_rect_page(&mut self, x: f32, y: f32, scale: f32) -> BufferRequest { + self.root.get_tile_rect(x, y, + self.clip_size.width as f32, + self.clip_size.height as f32, + 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) -> T { + pub fn remove_tile_pixel(&mut self, x: uint, y: uint, scale: f32) -> T { let r = self.root.remove_tile(x as f32 / scale, y as f32 / scale); match r { (Some(tile), _, _) => tile, _ => fail!("Quadtree: No valid tiles to remove"), } } + + /// Ask a tile to be deleted from the quadtree. This tries to delete a tile that is far from the + /// given point in page coordinates. + pub fn remove_tile_page(&mut self, x: f32, y: f32) -> T { + let r = self.root.remove_tile(x, y); + match r { + (Some(tile), _, _) => tile, + _ => fail!("Quadtree: No valid tiles to remove"), + } + } + /// Given a window rect in pixel coordinates, this function returns a list of BufferRequests for tiles that /// need to be rendered. It also returns a 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. /// When this happens, higher resolution tiles will be removed from the quadtree. - pub fn get_tile_rects(&mut self, window: Rect, scale: f32) -> (~[BufferRequest], bool) { + pub fn get_tile_rects_pixel(&mut self, window: Rect, scale: f32) -> (~[BufferRequest], bool) { let (ret, redisplay, _) = 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)), @@ -139,6 +186,17 @@ impl Quadtree { scale, self.max_tile_size as f32 / scale); (ret, redisplay) } + + /// Same function as above, using page coordinates for the window + pub fn get_tile_rects_page(&mut self, window: Rect, scale: f32) -> (~[BufferRequest], bool) { + let (ret, redisplay, _) = self.root.get_tile_rects( + window, + Size2D(self.clip_size.width as f32, self.clip_size.height as f32), + scale, self.max_tile_size as f32 / scale); + (ret, redisplay) + } + + /// Resize the quadtree. This can add more space, changing the root node, or it can shrink, making /// an internal node the new root. /// TODO: return tiles after shrinking diff --git a/src/components/main/platform/common/glfw_windowing.rs b/src/components/main/platform/common/glfw_windowing.rs index 10be6d5b2e8..327d7423540 100644 --- a/src/components/main/platform/common/glfw_windowing.rs +++ b/src/components/main/platform/common/glfw_windowing.rs @@ -89,15 +89,29 @@ impl WindowMethods for Window { } do window.glfw_window.set_mouse_button_callback |win, button, action, _mods| { let (x, y) = win.get_cursor_pos(); + //handle hidpi displays, since GLFW returns non-hi-def coordinates. + let (backing_size, _) = win.get_framebuffer_size(); + let (window_size, _) = win.get_size(); + let hidpi = (backing_size as f32) / (window_size as f32); + let x = x as f32 * hidpi; + let y = y as f32 * hidpi; if button < 3 { window.handle_mouse(button, action, x as i32, y as i32); } } - do window.glfw_window.set_scroll_callback |_win, x_offset, y_offset| { + do window.glfw_window.set_scroll_callback |win, x_offset, y_offset| { let dx = (x_offset as f32) * 30.0; let dy = (y_offset as f32) * 30.0; - event_queue.push(ScrollWindowEvent(Point2D(dx, dy))); + let (x, y) = win.get_cursor_pos(); + //handle hidpi displays, since GLFW returns non-hi-def coordinates. + let (backing_size, _) = win.get_framebuffer_size(); + let (window_size, _) = win.get_size(); + let hidpi = (backing_size as f32) / (window_size as f32); + let x = x as f32 * hidpi; + let y = y as f32 * hidpi; + + event_queue.push(ScrollWindowEvent(Point2D(dx, dy), Point2D(x as i32, y as i32))); } window @@ -148,6 +162,12 @@ impl WindowMethods for Window { self.render_state = render_state; self.update_window_title() } + + fn hidpi_factor(@mut self) -> f32 { + let (backing_size, _) = self.glfw_window.get_framebuffer_size(); + let (window_size, _) = self.glfw_window.get_size(); + (backing_size as f32) / (window_size as f32) + } } impl Window { diff --git a/src/components/main/windowing.rs b/src/components/main/windowing.rs index 00439009d34..f4fbca5d58c 100644 --- a/src/components/main/windowing.rs +++ b/src/components/main/windowing.rs @@ -32,8 +32,8 @@ pub enum WindowEvent { LoadUrlWindowEvent(~str), /// Sent when a mouse hit test is to be performed. MouseWindowEventClass(MouseWindowEvent), - /// Sent when the user scrolls. - ScrollWindowEvent(Point2D), + /// Sent when the user scrolls. Includes the current cursor position. + ScrollWindowEvent(Point2D, Point2D), /// Sent when the user zooms. ZoomWindowEvent(f32), /// Sent when the user uses chrome navigation (i.e. backspace or shift-backspace). @@ -64,5 +64,8 @@ pub trait WindowMethods { pub fn set_ready_state(@mut self, ready_state: ReadyState); /// Sets the render state of the current page. pub fn set_render_state(@mut self, render_state: RenderState); + + /// Returns the hidpi factor of the monitor. + pub fn hidpi_factor(@mut self) -> f32; } diff --git a/src/components/msg/compositor_msg.rs b/src/components/msg/compositor_msg.rs index 5da0ddf204f..80f0c0703d3 100644 --- a/src/components/msg/compositor_msg.rs +++ b/src/components/msg/compositor_msg.rs @@ -58,10 +58,10 @@ 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: PipelineId, layer_buffer_set: arc::ARC, new_size: Size2D); + fn new_layer(&self, PipelineId, Size2D); + fn resize_layer(&self, PipelineId, Size2D); + fn delete_layer(&self, PipelineId); + fn paint(&self, id: PipelineId, layer_buffer_set: arc::ARC); fn set_render_state(&self, render_state: RenderState); }