diff options
author | Patrick Walton <pcwalton@mimiga.net> | 2014-10-19 09:23:18 -0700 |
---|---|---|
committer | Patrick Walton <pcwalton@mimiga.net> | 2014-11-04 13:51:56 -0800 |
commit | 10f7b49cf7dc8f77cf70750927a2c85650312033 (patch) | |
tree | 12f36afcf90ee22be0bfc41906bd628d0f07c6fa /components | |
parent | e483a189a3c24d0fe475cf2a8dedb11821f7ee21 (diff) | |
download | servo-10f7b49cf7dc8f77cf70750927a2c85650312033.tar.gz servo-10f7b49cf7dc8f77cf70750927a2c85650312033.zip |
Invert control flow, fix resizing, and improve checkerboarding
significantly by giving tiles some time to paint before we render
unrendered content.
Diffstat (limited to 'components')
-rw-r--r-- | components/compositing/compositor.rs | 457 | ||||
-rw-r--r-- | components/compositing/compositor_task.rs | 167 | ||||
-rw-r--r-- | components/compositing/constellation.rs | 158 | ||||
-rw-r--r-- | components/compositing/headless.rs | 123 | ||||
-rw-r--r-- | components/compositing/lib.rs | 5 | ||||
-rw-r--r-- | components/compositing/pipeline.rs | 8 | ||||
-rw-r--r-- | components/compositing/scrolling.rs | 73 | ||||
-rw-r--r-- | components/compositing/windowing.rs | 42 | ||||
-rw-r--r-- | components/gfx/display_list/mod.rs | 140 | ||||
-rw-r--r-- | components/gfx/lib.rs | 1 | ||||
-rw-r--r-- | components/gfx/render_context.rs | 136 | ||||
-rw-r--r-- | components/gfx/render_task.rs | 7 | ||||
-rw-r--r-- | components/msg/compositor_msg.rs | 22 | ||||
-rw-r--r-- | components/msg/constellation_msg.rs | 4 | ||||
-rw-r--r-- | components/script/dom/window.rs | 10 | ||||
-rw-r--r-- | components/script/page.rs | 5 | ||||
-rw-r--r-- | components/script/script_task.rs | 47 | ||||
-rw-r--r-- | components/script_traits/lib.rs | 28 | ||||
-rw-r--r-- | components/util/time.rs | 4 |
19 files changed, 890 insertions, 547 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index e11f66d1e34..eb0301e9208 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -7,21 +7,23 @@ use compositor_layer::{ScrollPositionChanged, WantsScrollEvents}; use compositor_task::{Msg, CompositorTask, Exit, ChangeReadyState, SetIds, LayerProperties}; use compositor_task::{GetGraphicsMetadata, CreateOrUpdateRootLayer, CreateOrUpdateDescendantLayer}; use compositor_task::{SetLayerOrigin, Paint, ScrollFragmentPoint, LoadComplete}; -use compositor_task::{ShutdownComplete, ChangeRenderState, RenderMsgDiscarded}; +use compositor_task::{ShutdownComplete, ChangeRenderState, RenderMsgDiscarded, ScrollTimeout}; +use compositor_task::{CompositorEventListener, CompositorProxy, CompositorReceiver}; use constellation::SendableFrameTree; use pipeline::CompositionPipeline; +use scrolling::ScrollingTimerProxy; use windowing; use windowing::{FinishedWindowEvent, IdleWindowEvent, LoadUrlWindowEvent, MouseWindowClickEvent}; use windowing::{MouseWindowEvent, MouseWindowEventClass, MouseWindowMouseDownEvent}; use windowing::{MouseWindowMouseUpEvent, MouseWindowMoveEventClass, NavigationWindowEvent}; use windowing::{QuitWindowEvent, RefreshWindowEvent, ResizeWindowEvent, ScrollWindowEvent}; use windowing::{WindowEvent, WindowMethods, WindowNavigateMsg, ZoomWindowEvent}; -use windowing::PinchZoomWindowEvent; +use windowing::{PinchZoomWindowEvent}; use azure::azure_hl; use std::cmp; +use std::mem; use std::num::Zero; -use std::time::duration::Duration; use geom::point::{Point2D, TypedPoint2D}; use geom::rect::{Rect, TypedRect}; use geom::size::TypedSize2D; @@ -45,20 +47,18 @@ use servo_util::memory::MemoryProfilerChan; use servo_util::opts; use servo_util::time::{profile, TimeProfilerChan}; use servo_util::{memory, time}; -use std::io::timer::sleep; use std::collections::hashmap::HashMap; use std::path::Path; use std::rc::Rc; -use time::precise_time_s; +use time::{precise_time_ns, precise_time_s}; use url::Url; - pub struct IOCompositor<Window: WindowMethods> { /// The application window. window: Rc<Window>, /// The port on which we receive messages. - port: Receiver<Msg>, + port: Box<CompositorReceiver>, /// The render context. context: RenderContext, @@ -82,20 +82,23 @@ pub struct IOCompositor<Window: WindowMethods> { /// The device pixel ratio for this window. hidpi_factor: ScaleFactor<ScreenPx, DevicePixel, f32>, - /// Tracks whether the renderer has finished its first rendering - composite_ready: bool, + /// A handle to the scrolling timer. + scrolling_timer: ScrollingTimerProxy, + + /// Tracks whether we should composite this frame. + composition_request: CompositionRequest, /// Tracks whether we are in the process of shutting down, or have shut down and should close /// the compositor. shutdown_state: ShutdownState, - /// Tracks whether we need to re-composite a page. - recomposite: bool, - /// Tracks outstanding render_msg's sent to the render tasks. outstanding_render_msgs: uint, - /// Tracks whether the zoom action has happend recently. + /// Tracks the last composite time. + last_composite_time: u64, + + /// Tracks whether the zoom action has happened recently. zoom_action: bool, /// The time of the last zoom action has started. @@ -112,6 +115,9 @@ pub struct IOCompositor<Window: WindowMethods> { /// many times for a single page. got_load_complete_message: bool, + /// Whether we have gotten a `SetIds` message. + got_set_ids_message: bool, + /// The channel on which messages can be sent to the constellation. constellation_chan: ConstellationChan, @@ -122,10 +128,25 @@ pub struct IOCompositor<Window: WindowMethods> { memory_profiler_chan: MemoryProfilerChan, /// Pending scroll to fragment event, if any - fragment_point: Option<Point2D<f32>> + fragment_point: Option<Point2D<f32>>, + + /// Pending scroll events. + pending_scroll_events: Vec<ScrollEvent>, +} + +pub struct ScrollEvent { + delta: TypedPoint2D<DevicePixel,f32>, + cursor: TypedPoint2D<DevicePixel,i32>, } #[deriving(PartialEq)] +enum CompositionRequest { + NoCompositingNecessary, + CompositeOnScrollTimeout(u64), + CompositeNow, +} + +#[deriving(PartialEq, Show)] enum ShutdownState { NotShuttingDown, ShuttingDown, @@ -139,11 +160,12 @@ struct HitTestResult { impl<Window: WindowMethods> IOCompositor<Window> { fn new(window: Rc<Window>, - port: Receiver<Msg>, + sender: Box<CompositorProxy+Send>, + receiver: Box<CompositorReceiver>, constellation_chan: ConstellationChan, time_profiler_chan: TimeProfilerChan, - memory_profiler_chan: MemoryProfilerChan) -> IOCompositor<Window> { - + memory_profiler_chan: MemoryProfilerChan) + -> IOCompositor<Window> { // Create an initial layer tree. // // TODO: There should be no initial layer tree until the renderer creates one from the @@ -155,7 +177,7 @@ impl<Window: WindowMethods> IOCompositor<Window> { let show_debug_borders = opts::get().show_debug_borders; IOCompositor { window: window, - port: port, + port: receiver, context: rendergl::RenderContext::new(context, show_debug_borders), root_pipeline: None, scene: Scene::new(Rect { @@ -164,9 +186,10 @@ impl<Window: WindowMethods> IOCompositor<Window> { }), window_size: window_size, hidpi_factor: hidpi_factor, - composite_ready: false, + scrolling_timer: ScrollingTimerProxy::new(sender), + composition_request: NoCompositingNecessary, + pending_scroll_events: Vec::new(), shutdown_state: NotShuttingDown, - recomposite: false, page_zoom: ScaleFactor(1.0), viewport_zoom: ScaleFactor(1.0), zoom_action: false, @@ -174,167 +197,129 @@ impl<Window: WindowMethods> IOCompositor<Window> { ready_states: HashMap::new(), render_states: HashMap::new(), got_load_complete_message: false, + got_set_ids_message: false, constellation_chan: constellation_chan, time_profiler_chan: time_profiler_chan, memory_profiler_chan: memory_profiler_chan, fragment_point: None, outstanding_render_msgs: 0, + last_composite_time: 0, } } pub fn create(window: Rc<Window>, - port: Receiver<Msg>, + sender: Box<CompositorProxy+Send>, + receiver: Box<CompositorReceiver>, constellation_chan: ConstellationChan, time_profiler_chan: TimeProfilerChan, - memory_profiler_chan: MemoryProfilerChan) { + memory_profiler_chan: MemoryProfilerChan) + -> IOCompositor<Window> { let mut compositor = IOCompositor::new(window, - port, + sender, + receiver, constellation_chan, time_profiler_chan, memory_profiler_chan); - compositor.update_zoom_transform(); - // Starts the compositor, which listens for messages on the specified port. - compositor.run(); - } + // Set the size of the root layer. + compositor.update_zoom_transform(); - fn run (&mut self) { // Tell the constellation about the initial window size. - self.send_window_size(); + compositor.send_window_size(); - // Enter the main event loop. - while self.shutdown_state != FinishedShuttingDown { - // Check for new messages coming from the rendering task. - self.handle_message(); - - if self.shutdown_state == FinishedShuttingDown { - // We have exited the compositor and passing window - // messages to script may crash. - debug!("Exiting the compositor due to a request from script."); - break; - } + compositor + } - // Check for messages coming from the windowing system. - let msg = self.window.recv(); - self.handle_window_message(msg); + fn handle_browser_message(&mut self, msg: Msg) -> bool { + match (msg, self.shutdown_state) { + (_, FinishedShuttingDown) => + fail!("compositor shouldn't be handling messages after shutting down"), - // If asked to recomposite and renderer has run at least once - if self.recomposite && self.composite_ready { - self.recomposite = false; - self.composite(); + (Exit(chan), _) => { + debug!("shutting down the constellation"); + let ConstellationChan(ref con_chan) = self.constellation_chan; + con_chan.send(ExitMsg); + chan.send(()); + self.shutdown_state = ShuttingDown; } - sleep(Duration::milliseconds(10)); - - // If a pinch-zoom happened recently, ask for tiles at the new resolution - if self.zoom_action && precise_time_s() - self.zoom_time > 0.3 { - self.zoom_action = false; - self.scene.mark_layer_contents_as_changed_recursively(); - self.send_buffer_requests_for_all_layers(); + (ShutdownComplete, _) => { + debug!("constellation completed shutdown"); + self.shutdown_state = FinishedShuttingDown; + return false; } - } - - // Clear out the compositor layers so that painting tasks can destroy the buffers. - match self.scene.root { - None => {} - Some(ref layer) => layer.forget_all_tiles(), - } - - // Drain compositor port, sometimes messages contain channels that are blocking - // another task from finishing (i.e. SetIds) - loop { - match self.port.try_recv() { - Err(_) => break, - Ok(_) => {}, + (ChangeReadyState(pipeline_id, ready_state), NotShuttingDown) => { + self.change_ready_state(pipeline_id, ready_state); } - } - - // Tell the profiler and memory profiler to shut down. - let TimeProfilerChan(ref time_profiler_chan) = self.time_profiler_chan; - time_profiler_chan.send(time::ExitMsg); - - let MemoryProfilerChan(ref memory_profiler_chan) = self.memory_profiler_chan; - memory_profiler_chan.send(memory::ExitMsg); - } - - fn handle_message(&mut self) { - loop { - match (self.port.try_recv(), self.shutdown_state) { - (_, FinishedShuttingDown) => - fail!("compositor shouldn't be handling messages after shutting down"), - (Err(_), _) => break, + (ChangeRenderState(pipeline_id, render_state), NotShuttingDown) => { + self.change_render_state(pipeline_id, render_state); + } - (Ok(Exit(chan)), _) => { - debug!("shutting down the constellation"); - let ConstellationChan(ref con_chan) = self.constellation_chan; - con_chan.send(ExitMsg); - chan.send(()); - self.shutdown_state = ShuttingDown; - } + (RenderMsgDiscarded, NotShuttingDown) => { + self.remove_outstanding_render_msg(); + } - (Ok(ShutdownComplete), _) => { - debug!("constellation completed shutdown"); - self.shutdown_state = FinishedShuttingDown; - break; - } + (SetIds(frame_tree, response_chan, new_constellation_chan), NotShuttingDown) => { + self.set_frame_tree(&frame_tree, + response_chan, + new_constellation_chan); + } - (Ok(ChangeReadyState(pipeline_id, ready_state)), NotShuttingDown) => { - self.change_ready_state(pipeline_id, ready_state); - } + (CreateOrUpdateRootLayer(layer_properties), NotShuttingDown) => { + self.create_or_update_root_layer(layer_properties); + } - (Ok(ChangeRenderState(pipeline_id, render_state)), NotShuttingDown) => { - self.change_render_state(pipeline_id, render_state); - } + (CreateOrUpdateDescendantLayer(layer_properties), NotShuttingDown) => { + self.create_or_update_descendant_layer(layer_properties); + } - (Ok(RenderMsgDiscarded), NotShuttingDown) => { - self.remove_outstanding_render_msg(); - } + (GetGraphicsMetadata(chan), NotShuttingDown) => { + chan.send(Some(self.window.native_metadata())); + } - (Ok(SetIds(frame_tree, response_chan, new_constellation_chan)), _) => { - self.set_frame_tree(&frame_tree, - response_chan, - new_constellation_chan); - } + (SetLayerOrigin(pipeline_id, layer_id, origin), NotShuttingDown) => { + self.set_layer_origin(pipeline_id, layer_id, origin); + } - (Ok(GetGraphicsMetadata(chan)), NotShuttingDown) => { - chan.send(Some(self.window.native_metadata())); + (Paint(pipeline_id, epoch, replies), NotShuttingDown) => { + for (layer_id, new_layer_buffer_set) in replies.into_iter() { + self.paint(pipeline_id, layer_id, new_layer_buffer_set, epoch); } + self.remove_outstanding_render_msg(); + } - (Ok(CreateOrUpdateRootLayer(layer_properties)), NotShuttingDown) => { - self.create_or_update_root_layer(layer_properties); - } + (ScrollFragmentPoint(pipeline_id, layer_id, point), NotShuttingDown) => { + self.scroll_fragment_to_point(pipeline_id, layer_id, point); + } - (Ok(CreateOrUpdateDescendantLayer(layer_properties)), NotShuttingDown) => { - self.create_or_update_descendant_layer(layer_properties); - } + (LoadComplete(..), NotShuttingDown) => { + self.got_load_complete_message = true; - (Ok(SetLayerOrigin(pipeline_id, layer_id, origin)), NotShuttingDown) => { - self.set_layer_origin(pipeline_id, layer_id, origin); + // If we're rendering in headless mode, schedule a recomposite. + if opts::get().output_file.is_some() { + self.composite_if_necessary(); } + } - (Ok(Paint(pipeline_id, epoch, replies)), NotShuttingDown) => { - for (layer_id, new_layer_buffer_set) in replies.into_iter() { - self.paint(pipeline_id, layer_id, new_layer_buffer_set, epoch); + (ScrollTimeout(timestamp), NotShuttingDown) => { + debug!("scroll timeout, drawing unrendered content!"); + match self.composition_request { + CompositeOnScrollTimeout(this_timestamp) if timestamp == this_timestamp => { + self.composition_request = CompositeNow } - self.remove_outstanding_render_msg(); - } - - (Ok(ScrollFragmentPoint(pipeline_id, layer_id, point)), NotShuttingDown) => { - self.scroll_fragment_to_point(pipeline_id, layer_id, point); - } - - (Ok(LoadComplete(..)), NotShuttingDown) => { - self.got_load_complete_message = true; + _ => {} } - - // When we are shutting_down, we need to avoid performing operations - // such as Paint that may crash because we have begun tearing down - // the rest of our resources. - (_, ShuttingDown) => { } } + + // When we are shutting_down, we need to avoid performing operations + // such as Paint that may crash because we have begun tearing down + // the rest of our resources. + (_, ShuttingDown) => { } } + + true } fn change_ready_state(&mut self, pipeline_id: PipelineId, ready_state: ReadyState) { @@ -342,6 +327,11 @@ impl<Window: WindowMethods> IOCompositor<Window> { ready_state, |_key, value| *value = ready_state); self.window.set_ready_state(self.get_earliest_pipeline_ready_state()); + + // If we're rendering in headless mode, schedule a recomposite. + if opts::get().output_file.is_some() { + self.composite_if_necessary() + } } fn get_earliest_pipeline_ready_state(&self) -> ReadyState { @@ -357,9 +347,6 @@ impl<Window: WindowMethods> IOCompositor<Window> { render_state, |_key, value| *value = render_state); self.window.set_render_state(render_state); - if render_state == IdleRenderState { - self.composite_ready = true; - } } fn all_pipelines_in_idle_render_state(&self) -> bool { @@ -417,6 +404,9 @@ impl<Window: WindowMethods> IOCompositor<Window> { // Initialize the new constellation channel by sending it the root window size. self.constellation_chan = new_constellation_chan; self.send_window_size(); + + self.got_set_ids_message = true; + self.composite_if_necessary(); } fn create_frame_tree_root_layers(&mut self, @@ -529,7 +519,6 @@ impl<Window: WindowMethods> IOCompositor<Window> { })); } - pub fn move_layer(&self, pipeline_id: PipelineId, layer_id: LayerId, @@ -555,10 +544,21 @@ impl<Window: WindowMethods> IOCompositor<Window> { fail!("Compositor: Tried to scroll to fragment with unknown layer."); } - self.recomposite = true; + self.start_scrolling_timer_if_necessary(); } None => {} - }; + } + } + + fn start_scrolling_timer_if_necessary(&mut self) { + match self.composition_request { + CompositeNow | CompositeOnScrollTimeout(_) => return, + NoCompositingNecessary => {} + } + + let timestamp = precise_time_ns(); + self.scrolling_timer.scroll_event_processed(timestamp); + self.composition_request = CompositeOnScrollTimeout(timestamp); } fn set_layer_origin(&mut self, @@ -580,7 +580,9 @@ impl<Window: WindowMethods> IOCompositor<Window> { layer_id: LayerId, new_layer_buffer_set: Box<LayerBufferSet>, epoch: Epoch) { - debug!("compositor received new frame"); + debug!("compositor received new frame at size {}x{}", + self.window_size.width.get(), + self.window_size.height.get()); // From now on, if we destroy the buffers, they will leak. let mut new_layer_buffer_set = new_layer_buffer_set; @@ -588,8 +590,10 @@ impl<Window: WindowMethods> IOCompositor<Window> { match self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) { Some(ref layer) => { + // FIXME(pcwalton): This is going to cause problems with inconsistent frames since + // we only composite one layer at a time. assert!(layer.add_buffers(new_layer_buffer_set, epoch)); - self.recomposite = true; + self.composite_if_necessary(); } None => { // FIXME: This may potentially be triggered by a race condition where a @@ -605,8 +609,9 @@ impl<Window: WindowMethods> IOCompositor<Window> { layer_id: LayerId, point: Point2D<f32>) { if self.move_layer(pipeline_id, layer_id, Point2D::from_untyped(&point)) { - self.recomposite = true; - self.send_buffer_requests_for_all_layers(); + if self.send_buffer_requests_for_all_layers() { + self.start_scrolling_timer_if_necessary(); + } } else { self.fragment_point = Some(point); } @@ -617,7 +622,7 @@ impl<Window: WindowMethods> IOCompositor<Window> { IdleWindowEvent => {} RefreshWindowEvent => { - self.recomposite = true; + self.composite_if_necessary() } ResizeWindowEvent(size) => { @@ -685,6 +690,7 @@ impl<Window: WindowMethods> IOCompositor<Window> { debug!("osmain: window resized to {:?}", new_size); self.window_size = new_size; + self.scene.set_root_layer_size(new_size.as_f32()); self.send_window_size(); } @@ -698,7 +704,8 @@ impl<Window: WindowMethods> IOCompositor<Window> { layers"), }; - let msg = LoadUrlMsg(root_pipeline_id, LoadData::new(Url::parse(url_string.as_slice()).unwrap())); + let msg = LoadUrlMsg(root_pipeline_id, + LoadData::new(Url::parse(url_string.as_slice()).unwrap())); let ConstellationChan(ref chan) = self.constellation_chan; chan.send(msg); } @@ -725,18 +732,29 @@ impl<Window: WindowMethods> IOCompositor<Window> { fn on_scroll_window_event(&mut self, delta: TypedPoint2D<DevicePixel, f32>, cursor: TypedPoint2D<DevicePixel, i32>) { - let delta = delta / self.scene.scale; - let cursor = cursor.as_f32() / self.scene.scale; + self.pending_scroll_events.push(ScrollEvent { + delta: delta, + cursor: cursor, + }); - let mut scroll = false; - match self.scene.root { - Some(ref mut layer) => { - scroll = layer.handle_scroll_event(delta, cursor) == ScrollPositionChanged; - } - None => { } + self.composite_if_necessary(); + } + + fn process_pending_scroll_events(&mut self) { + for scroll_event in mem::replace(&mut self.pending_scroll_events, Vec::new()).into_iter() { + let delta = scroll_event.delta / self.scene.scale; + let cursor = scroll_event.cursor.as_f32() / self.scene.scale; + + let scrolled = match self.scene.root { + Some(ref mut layer) => { + layer.handle_scroll_event(delta, cursor) == ScrollPositionChanged + } + None => false, + }; + + self.start_scrolling_timer_if_necessary(); + self.send_buffer_requests_for_all_layers(); } - self.recomposite_if(scroll); - self.send_buffer_requests_for_all_layers(); } fn device_pixels_per_screen_px(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> { @@ -768,6 +786,7 @@ impl<Window: WindowMethods> IOCompositor<Window> { self.send_window_size(); } + // TODO(pcwalton): I think this should go through the same queuing as scroll events do. fn on_pinch_zoom_window_event(&mut self, magnification: f32) { self.zoom_action = true; self.zoom_time = precise_time_s(); @@ -792,7 +811,7 @@ impl<Window: WindowMethods> IOCompositor<Window> { None => { } } - self.recomposite = true; + self.composite_if_necessary(); } fn on_navigation_window_event(&self, direction: WindowNavigateMsg) { @@ -840,9 +859,7 @@ impl<Window: WindowMethods> IOCompositor<Window> { match self.root_pipeline { Some(ref pipeline) => { let unused_buffers = self.scene.collect_unused_buffers(); - let have_unused_buffers = unused_buffers.len() > 0; - self.recomposite = self.recomposite || have_unused_buffers; - if have_unused_buffers { + if unused_buffers.len() != 0 { let message = UnusedBufferMsg(unused_buffers); let _ = pipeline.render_chan.send_opt(message); } @@ -851,7 +868,8 @@ impl<Window: WindowMethods> IOCompositor<Window> { } } - fn send_buffer_requests_for_all_layers(&mut self) { + /// Returns true if any buffer requests were sent or false otherwise. + fn send_buffer_requests_for_all_layers(&mut self) -> bool { let mut layers_and_requests = Vec::new(); self.scene.get_buffer_requests(&mut layers_and_requests, Rect(TypedPoint2D(0f32, 0f32), self.window_size.as_f32())); @@ -860,7 +878,7 @@ impl<Window: WindowMethods> IOCompositor<Window> { self.send_back_unused_buffers(); if layers_and_requests.len() == 0 { - return; + return false; } // We want to batch requests for each pipeline to avoid race conditions @@ -875,6 +893,7 @@ impl<Window: WindowMethods> IOCompositor<Window> { } self.add_outstanding_render_msg(num_render_msgs_sent); + true } fn is_ready_to_render_image_output(&self) -> bool { @@ -893,6 +912,11 @@ impl<Window: WindowMethods> IOCompositor<Window> { if !self.all_pipelines_in_idle_render_state() { return false; } + + if !self.got_set_ids_message { + return false; + } + return true; } @@ -939,10 +963,11 @@ impl<Window: WindowMethods> IOCompositor<Window> { }); if output_image { - let path = from_str::<Path>(opts::get().output_file.as_ref().unwrap().as_slice()).unwrap(); + let path = + from_str::<Path>(opts::get().output_file.as_ref().unwrap().as_slice()).unwrap(); let mut pixels = gl::read_pixels(0, 0, - width as GLsizei, - height as GLsizei, + width as gl::GLsizei, + height as gl::GLsizei, gl::RGB, gl::UNSIGNED_BYTE); gl::bind_framebuffer(gl::FRAMEBUFFER, 0); @@ -976,18 +1001,26 @@ impl<Window: WindowMethods> IOCompositor<Window> { self.shutdown_state = ShuttingDown; } + // Perform the page flip. This will likely block for a while. self.window.present(); + self.last_composite_time = precise_time_ns(); + let exit = opts::get().exit_after_load; if exit { debug!("shutting down the constellation for exit_after_load"); let ConstellationChan(ref chan) = self.constellation_chan; chan.send(ExitMsg); } + + self.composition_request = NoCompositingNecessary; + self.process_pending_scroll_events(); } - fn recomposite_if(&mut self, result: bool) { - self.recomposite = result || self.recomposite; + fn composite_if_necessary(&mut self) { + if self.composition_request == NoCompositingNecessary { + self.composition_request = CompositeNow + } } fn find_topmost_layer_at_point_for_layer(&self, @@ -1054,3 +1087,87 @@ fn find_layer_with_pipeline_and_layer_id_for_layer(layer: Rc<Layer<CompositorDat return None; } + +impl<Window> CompositorEventListener for IOCompositor<Window> where Window: WindowMethods { + fn handle_event(&mut self, msg: WindowEvent) -> bool { + // Check for new messages coming from the other tasks in the system. + loop { + match self.port.try_recv_compositor_msg() { + None => break, + Some(msg) => { + if !self.handle_browser_message(msg) { + break + } + } + } + } + + if self.shutdown_state == FinishedShuttingDown { + // We have exited the compositor and passing window + // messages to script may crash. + debug!("Exiting the compositor due to a request from script."); + return false; + } + + // Handle the message coming from the windowing system. + self.handle_window_message(msg); + + // If a pinch-zoom happened recently, ask for tiles at the new resolution + if self.zoom_action && precise_time_s() - self.zoom_time > 0.3 { + self.zoom_action = false; + self.scene.mark_layer_contents_as_changed_recursively(); + self.send_buffer_requests_for_all_layers(); + } + + match self.composition_request { + NoCompositingNecessary | CompositeOnScrollTimeout(_) => {} + CompositeNow => self.composite(), + } + + self.shutdown_state != FinishedShuttingDown + } + + /// Repaints and recomposites synchronously. You must be careful when calling this, as if a + /// paint is not scheduled the compositor will hang forever. + /// + /// This is used when resizing the window. + fn repaint_synchronously(&mut self) { + while self.shutdown_state != ShuttingDown { + let msg = self.port.recv_compositor_msg(); + let is_paint = match msg { + Paint(..) => true, + _ => false, + }; + let keep_going = self.handle_browser_message(msg); + if is_paint { + self.composite(); + break + } + if !keep_going { + break + } + } + } + + fn shutdown(&mut self) { + // Clear out the compositor layers so that painting tasks can destroy the buffers. + match self.scene.root { + None => {} + Some(ref layer) => layer.forget_all_tiles(), + } + + // Drain compositor port, sometimes messages contain channels that are blocking + // another task from finishing (i.e. SetIds) + while self.port.try_recv_compositor_msg().is_some() {} + + // Tell the profiler, memory profiler, and scrolling timer to shut down. + let TimeProfilerChan(ref time_profiler_chan) = self.time_profiler_chan; + time_profiler_chan.send(time::ExitMsg); + + let MemoryProfilerChan(ref memory_profiler_chan) = self.memory_profiler_chan; + memory_profiler_chan.send(memory::ExitMsg); + + self.scrolling_timer.shutdown(); + } +} + diff --git a/components/compositing/compositor_task.rs b/components/compositing/compositor_task.rs index dd215af28f7..5fe2c4bb57a 100644 --- a/components/compositing/compositor_task.rs +++ b/components/compositing/compositor_task.rs @@ -2,12 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +//! Communication with the compositor task. + pub use windowing; +pub use constellation::SendableFrameTree; use compositor; use headless; -pub use constellation::SendableFrameTree; -use windowing::WindowMethods; +use windowing::{WindowEvent, WindowMethods}; use azure::azure_hl::{SourceSurfaceMethods, Color}; use geom::point::Point2D; @@ -21,39 +23,64 @@ use servo_msg::constellation_msg::{ConstellationChan, PipelineId}; use servo_util::memory::MemoryProfilerChan; use servo_util::time::TimeProfilerChan; use std::comm::{channel, Sender, Receiver}; +use std::fmt::{FormatError, Formatter, Show}; use std::rc::Rc; - use url::Url; -/// The implementation of the layers-based compositor. -#[deriving(Clone)] -pub struct CompositorChan { - /// A channel on which messages can be sent to the compositor. - pub chan: Sender<Msg>, +/// Sends messages to the compositor. This is a trait supplied by the port because the method used +/// to communicate with the compositor may have to kick OS event loops awake, communicate cross- +/// process, and so forth. +pub trait CompositorProxy : 'static + Send { + /// Sends a message to the compositor. + fn send(&mut self, msg: Msg); + /// Clones the compositor proxy. + fn clone_compositor_proxy(&self) -> Box<CompositorProxy+'static+Send>; +} + +/// The port that the compositor receives messages on. As above, this is a trait supplied by the +/// Servo port. +pub trait CompositorReceiver for Sized? : 'static { + /// Receives the next message inbound for the compositor. This must not block. + fn try_recv_compositor_msg(&mut self) -> Option<Msg>; + /// Synchronously waits for, and returns, the next message inbound for the compositor. + fn recv_compositor_msg(&mut self) -> Msg; +} + +/// A convenience implementation of `CompositorReceiver` for a plain old Rust `Receiver`. +impl CompositorReceiver for Receiver<Msg> { + fn try_recv_compositor_msg(&mut self) -> Option<Msg> { + match self.try_recv() { + Ok(msg) => Some(msg), + Err(_) => None, + } + } + fn recv_compositor_msg(&mut self) -> Msg { + self.recv() + } } /// Implementation of the abstract `ScriptListener` interface. -impl ScriptListener for CompositorChan { - fn set_ready_state(&self, pipeline_id: PipelineId, ready_state: ReadyState) { +impl ScriptListener for Box<CompositorProxy+'static+Send> { + fn set_ready_state(&mut self, pipeline_id: PipelineId, ready_state: ReadyState) { let msg = ChangeReadyState(pipeline_id, ready_state); - self.chan.send(msg); + self.send(msg); } - fn scroll_fragment_point(&self, + fn scroll_fragment_point(&mut self, pipeline_id: PipelineId, layer_id: LayerId, point: Point2D<f32>) { - self.chan.send(ScrollFragmentPoint(pipeline_id, layer_id, point)); + self.send(ScrollFragmentPoint(pipeline_id, layer_id, point)); } - fn close(&self) { + fn close(&mut self) { let (chan, port) = channel(); - self.chan.send(Exit(chan)); + self.send(Exit(chan)); port.recv(); } - fn dup(&self) -> Box<ScriptListener+'static> { - box self.clone() as Box<ScriptListener+'static> + fn dup(&mut self) -> Box<ScriptListener+'static> { + box self.clone_compositor_proxy() as Box<ScriptListener+'static> } } @@ -83,21 +110,21 @@ impl LayerProperties { } /// Implementation of the abstract `RenderListener` interface. -impl RenderListener for CompositorChan { - fn get_graphics_metadata(&self) -> Option<NativeGraphicsMetadata> { +impl RenderListener for Box<CompositorProxy+'static+Send> { + fn get_graphics_metadata(&mut self) -> Option<NativeGraphicsMetadata> { let (chan, port) = channel(); - self.chan.send(GetGraphicsMetadata(chan)); + self.send(GetGraphicsMetadata(chan)); port.recv() } - fn paint(&self, + fn paint(&mut self, pipeline_id: PipelineId, epoch: Epoch, replies: Vec<(LayerId, Box<LayerBufferSet>)>) { - self.chan.send(Paint(pipeline_id, epoch, replies)); + self.send(Paint(pipeline_id, epoch, replies)); } - fn initialize_layers_for_pipeline(&self, + fn initialize_layers_for_pipeline(&mut self, pipeline_id: PipelineId, metadata: Vec<LayerMetadata>, epoch: Epoch) { @@ -108,36 +135,23 @@ impl RenderListener for CompositorChan { for metadata in metadata.iter() { let layer_properties = LayerProperties::new(pipeline_id, epoch, metadata); if first { - self.chan.send(CreateOrUpdateRootLayer(layer_properties)); + self.send(CreateOrUpdateRootLayer(layer_properties)); first = false } else { - self.chan.send(CreateOrUpdateDescendantLayer(layer_properties)); + self.send(CreateOrUpdateDescendantLayer(layer_properties)); } } } - fn render_msg_discarded(&self) { - self.chan.send(RenderMsgDiscarded); + fn render_msg_discarded(&mut self) { + self.send(RenderMsgDiscarded); } - fn set_render_state(&self, pipeline_id: PipelineId, render_state: RenderState) { - self.chan.send(ChangeRenderState(pipeline_id, render_state)) + fn set_render_state(&mut self, pipeline_id: PipelineId, render_state: RenderState) { + self.send(ChangeRenderState(pipeline_id, render_state)) } } -impl CompositorChan { - pub fn new() -> (Receiver<Msg>, CompositorChan) { - let (chan, port) = channel(); - let compositor_chan = CompositorChan { - chan: chan, - }; - (port, compositor_chan) - } - - pub fn send(&self, msg: Msg) { - self.chan.send(msg); - } -} /// Messages from the painting task and the constellation task to the compositor task. pub enum Msg { /// Requests that the compositor shut down. @@ -177,6 +191,30 @@ pub enum Msg { SetIds(SendableFrameTree, Sender<()>, ConstellationChan), /// The load of a page for a given URL has completed. LoadComplete(PipelineId, Url), + /// Indicates that the scrolling timeout with the given starting timestamp has happened and a + /// composite should happen. (See the `scrolling` module.) + ScrollTimeout(u64), +} + +impl Show for Msg { + fn fmt(&self, f: &mut Formatter) -> Result<(),FormatError> { + match *self { + Exit(..) => write!(f, "Exit"), + ShutdownComplete(..) => write!(f, "ShutdownComplete"), + GetGraphicsMetadata(..) => write!(f, "GetGraphicsMetadata"), + CreateOrUpdateRootLayer(..) => write!(f, "CreateOrUpdateRootLayer"), + CreateOrUpdateDescendantLayer(..) => write!(f, "CreateOrUpdateDescendantLayer"), + SetLayerOrigin(..) => write!(f, "SetLayerOrigin"), + ScrollFragmentPoint(..) => write!(f, "ScrollFragmentPoint"), + Paint(..) => write!(f, "Paint"), + ChangeReadyState(..) => write!(f, "ChangeReadyState"), + ChangeRenderState(..) => write!(f, "ChangeRenderState"), + RenderMsgDiscarded(..) => write!(f, "RenderMsgDiscarded"), + SetIds(..) => write!(f, "SetIds"), + LoadComplete(..) => write!(f, "LoadComplete"), + ScrollTimeout(..) => write!(f, "ScrollTimeout"), + } + } } pub struct CompositorTask; @@ -196,27 +234,38 @@ impl CompositorTask { NativeCompositingGraphicsContext::new() } - pub fn create<Window: WindowMethods>( - window: Option<Rc<Window>>, - port: Receiver<Msg>, - constellation_chan: ConstellationChan, - time_profiler_chan: TimeProfilerChan, - memory_profiler_chan: MemoryProfilerChan) { - + pub fn create<Window>(window: Option<Rc<Window>>, + sender: Box<CompositorProxy+Send>, + receiver: Box<CompositorReceiver>, + constellation_chan: ConstellationChan, + time_profiler_chan: TimeProfilerChan, + memory_profiler_chan: MemoryProfilerChan) + -> Box<CompositorEventListener + 'static> + where Window: WindowMethods + 'static { match window { Some(window) => { - compositor::IOCompositor::create(window, - port, - constellation_chan.clone(), - time_profiler_chan, - memory_profiler_chan) + box compositor::IOCompositor::create(window, + sender, + receiver, + constellation_chan.clone(), + time_profiler_chan, + memory_profiler_chan) + as Box<CompositorEventListener> } None => { - headless::NullCompositor::create(port, - constellation_chan.clone(), - time_profiler_chan, - memory_profiler_chan) + box headless::NullCompositor::create(receiver, + constellation_chan.clone(), + time_profiler_chan, + memory_profiler_chan) + as Box<CompositorEventListener> } - }; + } } } + +pub trait CompositorEventListener { + fn handle_event(&mut self, event: WindowEvent) -> bool; + fn repaint_synchronously(&mut self); + fn shutdown(&mut self); +} + diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs index 9b0446c9b0a..7a7204dfdaf 100644 --- a/components/compositing/constellation.rs +++ b/components/compositing/constellation.rs @@ -2,53 +2,79 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use compositor_task::{CompositorChan, LoadComplete, ShutdownComplete, SetLayerOrigin, SetIds}; +use pipeline::{Pipeline, CompositionPipeline}; + +use compositor_task::{CompositorProxy, LoadComplete, ShutdownComplete, SetLayerOrigin, SetIds}; use devtools_traits::DevtoolsControlChan; -use std::collections::hashmap::{HashMap, HashSet}; use geom::rect::{Rect, TypedRect}; use geom::scale_factor::ScaleFactor; +use gfx::font_cache_task::FontCacheTask; use gfx::render_task; -use libc; -use pipeline::{Pipeline, CompositionPipeline}; +use layers::geometry::DevicePixel; use layout_traits::{LayoutControlChan, LayoutTaskFactory, ExitNowMsg}; +use libc; use script_traits::{ResizeMsg, ResizeInactiveMsg, ExitPipelineMsg}; use script_traits::{ScriptControlChan, ScriptTaskFactory}; use servo_msg::compositor_msg::LayerId; use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, FailureMsg, Failure, FrameRectMsg}; use servo_msg::constellation_msg::{IFrameSandboxState, IFrameUnsandboxed, InitLoadUrlMsg}; -use servo_msg::constellation_msg::{LoadCompleteMsg, LoadIframeUrlMsg, LoadUrlMsg, Msg, NavigateMsg}; -use servo_msg::constellation_msg::{LoadData, NavigationType, PipelineId, RendererReadyMsg, ResizedWindowMsg}; -use servo_msg::constellation_msg::{SubpageId, WindowSizeData}; +use servo_msg::constellation_msg::{LoadCompleteMsg, LoadIframeUrlMsg, LoadUrlMsg, Msg}; +use servo_msg::constellation_msg::{LoadData, NavigateMsg, NavigationType, PipelineId}; +use servo_msg::constellation_msg::{RendererReadyMsg, ResizedWindowMsg, SubpageId, WindowSizeData}; use servo_msg::constellation_msg; use servo_net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient}; -use gfx::font_cache_task::FontCacheTask; use servo_net::resource_task::ResourceTask; use servo_net::resource_task; -use servo_util::geometry::PagePx; +use servo_util::geometry::{PagePx, ViewportPx}; use servo_util::opts; -use servo_util::time::TimeProfilerChan; use servo_util::task::spawn_named; +use servo_util::time::TimeProfilerChan; use std::cell::RefCell; -use std::mem::replace; +use std::collections::hashmap::{HashMap, HashSet}; use std::io; +use std::mem::replace; use std::rc::Rc; use url::Url; -/// Maintains the pipelines and navigation context and grants permission to composite +/// Maintains the pipelines and navigation context and grants permission to composite. pub struct Constellation<LTF, STF> { + /// A channel through which messages can be sent to this object. pub chan: ConstellationChan, + + /// Receives messages. pub request_port: Receiver<Msg>, - pub compositor_chan: CompositorChan, + + /// A channel (the implementation of which is port-specific) through which messages can be sent + /// to the compositor. + pub compositor_proxy: Box<CompositorProxy>, + + /// A channel through which messages can be sent to the resource task. pub resource_task: ResourceTask, + + /// A channel through which messages can be sent to the image cache task. pub image_cache_task: ImageCacheTask, + + /// A channel through which messages can be sent to the developer tools. devtools_chan: Option<DevtoolsControlChan>, + + /// A list of all the pipelines. (See the `pipeline` module for more details.) pipelines: HashMap<PipelineId, Rc<Pipeline>>, + + /// A channel through which messages can be sent to the font cache. font_cache_task: FontCacheTask, + navigation_context: NavigationContext, + + /// The next free ID to assign to a pipeline. next_pipeline_id: PipelineId, + pending_frames: Vec<FrameChange>, + pending_sizes: HashMap<(PipelineId, SubpageId), TypedRect<PagePx, f32>>, + + /// A channel through which messages can be sent to the time profiler. pub time_profiler_chan: TimeProfilerChan, + pub window_size: WindowSizeData, } @@ -86,7 +112,11 @@ impl FrameTree { fn to_sendable(&self) -> SendableFrameTree { let sendable_frame_tree = SendableFrameTree { pipeline: self.pipeline.to_sendable(), - children: self.children.borrow().iter().map(|frame_tree| frame_tree.to_sendable()).collect(), + children: self.children + .borrow() + .iter() + .map(|frame_tree| frame_tree.to_sendable()) + .collect(), }; sendable_frame_tree } @@ -109,8 +139,8 @@ impl FrameTreeTraversal for Rc<FrameTree> { self.iter().find(|frame_tree| id == frame_tree.pipeline.id) } - /// Replaces a node of the frame tree in place. Returns the node that was removed or the original node - /// if the node to replace could not be found. + /// Replaces a node of the frame tree in place. Returns the node that was removed or the + /// original node if the node to replace could not be found. fn replace_child(&self, id: PipelineId, new_child: Rc<FrameTree>) -> ReplaceResult { for frame_tree in self.iter() { let mut children = frame_tree.children.borrow_mut(); @@ -239,7 +269,7 @@ impl NavigationContext { } impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { - pub fn start(compositor_chan: CompositorChan, + pub fn start(compositor_proxy: Box<CompositorProxy+Send>, resource_task: ResourceTask, image_cache_task: ImageCacheTask, font_cache_task: FontCacheTask, @@ -249,10 +279,10 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { let (constellation_port, constellation_chan) = ConstellationChan::new(); let constellation_chan_clone = constellation_chan.clone(); spawn_named("Constellation", proc() { - let mut constellation : Constellation<LTF, STF> = Constellation { + let mut constellation: Constellation<LTF, STF> = Constellation { chan: constellation_chan_clone, request_port: constellation_port, - compositor_chan: compositor_chan, + compositor_proxy: compositor_proxy, devtools_chan: devtools_chan, resource_task: resource_task, image_cache_task: image_cache_task, @@ -293,7 +323,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { let pipe = Pipeline::create::<LTF, STF>(id, subpage_id, self.chan.clone(), - self.compositor_chan.clone(), + self.compositor_proxy.clone_compositor_proxy(), self.devtools_chan.clone(), self.image_cache_task.clone(), self.font_cache_task.clone(), @@ -367,7 +397,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { // script, and reflow messages have been sent. LoadCompleteMsg(pipeline_id, url) => { debug!("constellation got load complete message"); - self.compositor_chan.send(LoadComplete(pipeline_id, url)); + self.compositor_proxy.send(LoadComplete(pipeline_id, url)); } // Handle a forward or back request NavigateMsg(direction) => { @@ -387,14 +417,14 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { true } - fn handle_exit(&self) { + fn handle_exit(&mut self) { for (_id, ref pipeline) in self.pipelines.iter() { pipeline.exit(); } self.image_cache_task.exit(); self.resource_task.send(resource_task::Exit); self.font_cache_task.exit(); - self.compositor_chan.send(ShutdownComplete); + self.compositor_proxy.send(ShutdownComplete); } fn handle_failure_msg(&mut self, pipeline_id: PipelineId, subpage_id: Option<SubpageId>) { @@ -491,38 +521,23 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { let frames = self.find_all(pipeline_id); { - // Update a child's frame rect and inform its script task of the change, - // if it hasn't been already. Optionally inform the compositor if - // resize happens immediately. - let update_child_rect = |child_frame_tree: &mut ChildFrameTree, is_active: bool| { - child_frame_tree.rect = Some(rect); - // NOTE: work around borrowchk issues - let pipeline = &child_frame_tree.frame_tree.pipeline; - if !already_sent.contains(&pipeline.id) { - if is_active { - let ScriptControlChan(ref script_chan) = pipeline.script_chan; - script_chan.send(ResizeMsg(pipeline.id, WindowSizeData { - visible_viewport: rect.size, - initial_viewport: rect.size * ScaleFactor(1.0), - device_pixel_ratio: self.window_size.device_pixel_ratio, - })); - self.compositor_chan.send(SetLayerOrigin(pipeline.id, - LayerId::null(), - rect.to_untyped().origin)); - } else { - already_sent.insert(pipeline.id); - } - }; - }; - // If the subframe is in the current frame tree, the compositor needs the new size - for current_frame in self.current_frame().iter() { + for current_frame in self.navigation_context.current.iter() { debug!("Constellation: Sending size for frame in current frame tree."); let source_frame = current_frame.find(pipeline_id); for source_frame in source_frame.iter() { let mut children = source_frame.children.borrow_mut(); - let found_child = children.iter_mut().find(|child| subpage_eq(child)); - found_child.map(|child| update_child_rect(child, true)); + match children.iter_mut().find(|child| subpage_eq(child)) { + None => {} + Some(child) => { + update_child_rect(child, + rect, + true, + &mut already_sent, + &mut self.compositor_proxy, + self.window_size.device_pixel_ratio) + } + } } } @@ -530,7 +545,14 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { for frame_tree in frames.iter() { let mut children = frame_tree.children.borrow_mut(); let found_child = children.iter_mut().find(|child| subpage_eq(child)); - found_child.map(|child| update_child_rect(child, false)); + found_child.map(|child| { + update_child_rect(child, + rect, + false, + &mut already_sent, + &mut self.compositor_proxy, + self.window_size.device_pixel_ratio) + }); } } @@ -539,8 +561,38 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { if already_sent.len() == 0 { self.pending_sizes.insert((pipeline_id, subpage_id), rect); } + + // Update a child's frame rect and inform its script task of the change, + // if it hasn't been already. Optionally inform the compositor if + // resize happens immediately. + fn update_child_rect(child_frame_tree: &mut ChildFrameTree, + rect: TypedRect<PagePx,f32>, + is_active: bool, + already_sent: &mut HashSet<PipelineId>, + compositor_proxy: &mut Box<CompositorProxy>, + device_pixel_ratio: ScaleFactor<ViewportPx,DevicePixel,f32>) { + child_frame_tree.rect = Some(rect); + // NOTE: work around borrowchk issues + let pipeline = &child_frame_tree.frame_tree.pipeline; + if !already_sent.contains(&pipeline.id) { + if is_active { + let ScriptControlChan(ref script_chan) = pipeline.script_chan; + script_chan.send(ResizeMsg(pipeline.id, WindowSizeData { + visible_viewport: rect.size, + initial_viewport: rect.size * ScaleFactor(1.0), + device_pixel_ratio: device_pixel_ratio, + })); + compositor_proxy.send(SetLayerOrigin(pipeline.id, + LayerId::null(), + rect.to_untyped().origin)); + } else { + already_sent.insert(pipeline.id); + } + }; + } } + fn handle_load_iframe_url_msg(&mut self, url: Url, source_pipeline_id: PipelineId, @@ -851,10 +903,10 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { } } - fn set_ids(&self, frame_tree: &Rc<FrameTree>) { + fn set_ids(&mut self, frame_tree: &Rc<FrameTree>) { let (chan, port) = channel(); debug!("Constellation sending SetIds"); - self.compositor_chan.send(SetIds(frame_tree.to_sendable(), chan, self.chan.clone())); + self.compositor_proxy.send(SetIds(frame_tree.to_sendable(), chan, self.chan.clone())); match port.recv_opt() { Ok(()) => { let mut iter = frame_tree.iter(); diff --git a/components/compositing/headless.rs b/components/compositing/headless.rs index a1817560670..648b71034e0 100644 --- a/components/compositing/headless.rs +++ b/components/compositing/headless.rs @@ -2,10 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use compositor_task::{Msg, Exit, ChangeReadyState, SetIds}; use compositor_task::{GetGraphicsMetadata, CreateOrUpdateRootLayer, CreateOrUpdateDescendantLayer}; -use compositor_task::{SetLayerOrigin, Paint, ScrollFragmentPoint, LoadComplete}; -use compositor_task::{ShutdownComplete, ChangeRenderState, RenderMsgDiscarded}; +use compositor_task::{Exit, ChangeReadyState, LoadComplete, Paint, ScrollFragmentPoint, SetIds}; +use compositor_task::{SetLayerOrigin, ShutdownComplete, ChangeRenderState, RenderMsgDiscarded}; +use compositor_task::{CompositorEventListener, CompositorReceiver, ScrollTimeout}; +use windowing::WindowEvent; use geom::scale_factor::ScaleFactor; use geom::size::TypedSize2D; @@ -21,79 +22,97 @@ use servo_util::time; /// It's intended for headless testing. pub struct NullCompositor { /// The port on which we receive messages. - pub port: Receiver<Msg>, + pub port: Box<CompositorReceiver>, + /// A channel to the constellation. + constellation_chan: ConstellationChan, + /// A channel to the time profiler. + time_profiler_chan: TimeProfilerChan, + /// A channel to the memory profiler. + memory_profiler_chan: MemoryProfilerChan, } impl NullCompositor { - fn new(port: Receiver<Msg>) -> NullCompositor { + fn new(port: Box<CompositorReceiver>, + constellation_chan: ConstellationChan, + time_profiler_chan: TimeProfilerChan, + memory_profiler_chan: MemoryProfilerChan) + -> NullCompositor { NullCompositor { port: port, + constellation_chan: constellation_chan, + time_profiler_chan: time_profiler_chan, + memory_profiler_chan: memory_profiler_chan, } } - pub fn create(port: Receiver<Msg>, + pub fn create(port: Box<CompositorReceiver>, constellation_chan: ConstellationChan, time_profiler_chan: TimeProfilerChan, - memory_profiler_chan: MemoryProfilerChan) { - let compositor = NullCompositor::new(port); + memory_profiler_chan: MemoryProfilerChan) + -> NullCompositor { + let compositor = NullCompositor::new(port, + constellation_chan, + time_profiler_chan, + memory_profiler_chan); // Tell the constellation about the initial fake size. { - let ConstellationChan(ref chan) = constellation_chan; + let ConstellationChan(ref chan) = compositor.constellation_chan; chan.send(ResizedWindowMsg(WindowSizeData { initial_viewport: TypedSize2D(640_f32, 480_f32), visible_viewport: TypedSize2D(640_f32, 480_f32), device_pixel_ratio: ScaleFactor(1.0), })); } - compositor.handle_message(constellation_chan); - // Drain compositor port, sometimes messages contain channels that are blocking - // another task from finishing (i.e. SetIds) - loop { - match compositor.port.try_recv() { - Err(_) => break, - Ok(_) => {}, + compositor + } +} + +impl CompositorEventListener for NullCompositor { + fn handle_event(&mut self, _: WindowEvent) -> bool { + match self.port.recv_compositor_msg() { + Exit(chan) => { + debug!("shutting down the constellation"); + let ConstellationChan(ref con_chan) = self.constellation_chan; + con_chan.send(ExitMsg); + chan.send(()); } - } - time_profiler_chan.send(time::ExitMsg); - memory_profiler_chan.send(memory::ExitMsg); - } + ShutdownComplete => { + debug!("constellation completed shutdown"); + return false + } + + GetGraphicsMetadata(chan) => { + chan.send(None); + } - fn handle_message(&self, constellation_chan: ConstellationChan) { - loop { - match self.port.recv() { - Exit(chan) => { - debug!("shutting down the constellation"); - let ConstellationChan(ref con_chan) = constellation_chan; - con_chan.send(ExitMsg); - chan.send(()); - } - - ShutdownComplete => { - debug!("constellation completed shutdown"); - break - } - - GetGraphicsMetadata(chan) => { - chan.send(None); - } - - SetIds(_, response_chan, _) => { - response_chan.send(()); - } - - // Explicitly list ignored messages so that when we add a new one, - // we'll notice and think about whether it needs a response, like - // SetIds. - - CreateOrUpdateRootLayer(..) | - CreateOrUpdateDescendantLayer(..) | - SetLayerOrigin(..) | Paint(..) | - ChangeReadyState(..) | ChangeRenderState(..) | ScrollFragmentPoint(..) | - LoadComplete(..) | RenderMsgDiscarded(..) => () + SetIds(_, response_chan, _) => { + response_chan.send(()); } + + // Explicitly list ignored messages so that when we add a new one, + // we'll notice and think about whether it needs a response, like + // SetIds. + + CreateOrUpdateRootLayer(..) | + CreateOrUpdateDescendantLayer(..) | + SetLayerOrigin(..) | Paint(..) | + ChangeReadyState(..) | ChangeRenderState(..) | ScrollFragmentPoint(..) | + LoadComplete(..) | RenderMsgDiscarded(..) | ScrollTimeout(..) => () } + true + } + + fn repaint_synchronously(&mut self) {} + + fn shutdown(&mut self) { + // Drain compositor port, sometimes messages contain channels that are blocking + // another task from finishing (i.e. SetIds) + while self.port.try_recv_compositor_msg().is_some() {} + + self.time_profiler_chan.send(time::ExitMsg); + self.memory_profiler_chan.send(memory::ExitMsg); } } diff --git a/components/compositing/lib.rs b/components/compositing/lib.rs index e5af7de77ca..550b079f0f9 100644 --- a/components/compositing/lib.rs +++ b/components/compositing/lib.rs @@ -30,6 +30,7 @@ extern crate "util" as servo_util; extern crate gleam; extern crate libc; +extern crate native; extern crate time; extern crate url; @@ -38,12 +39,14 @@ extern crate core_graphics; #[cfg(target_os="macos")] extern crate core_text; -pub use compositor_task::{CompositorChan, CompositorTask}; +pub use compositor_task::{CompositorEventListener, CompositorProxy, CompositorTask}; pub use constellation::Constellation; pub mod compositor_task; mod compositor_layer; +mod scrolling; + mod compositor; mod headless; diff --git a/components/compositing/pipeline.rs b/components/compositing/pipeline.rs index 149fd0bffed..3fadfd90eaa 100644 --- a/components/compositing/pipeline.rs +++ b/components/compositing/pipeline.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use CompositorChan; +use CompositorProxy; use layout_traits::{LayoutTaskFactory, LayoutControlChan}; use script_traits::{ScriptControlChan, ScriptTaskFactory}; use script_traits::{AttachLayoutMsg, LoadMsg, NewLayoutInfo, ExitPipelineMsg}; @@ -47,7 +47,7 @@ impl Pipeline { id: PipelineId, subpage_id: Option<SubpageId>, constellation_chan: ConstellationChan, - compositor_chan: CompositorChan, + compositor_proxy: Box<CompositorProxy+'static+Send>, devtools_chan: Option<DevtoolsControlChan>, image_cache_task: ImageCacheTask, font_cache_task: FontCacheTask, @@ -73,7 +73,7 @@ impl Pipeline { let (script_chan, script_port) = channel(); ScriptTaskFactory::create(None::<&mut STF>, id, - box compositor_chan.clone(), + compositor_proxy.clone_compositor_proxy(), &layout_pair, ScriptControlChan(script_chan.clone()), script_port, @@ -101,7 +101,7 @@ impl Pipeline { RenderTask::create(id, render_port, - compositor_chan.clone(), + compositor_proxy, constellation_chan.clone(), font_cache_task.clone(), failure.clone(), diff --git a/components/compositing/scrolling.rs b/components/compositing/scrolling.rs new file mode 100644 index 00000000000..d3dfb6a8fdb --- /dev/null +++ b/components/compositing/scrolling.rs @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! A timer thread that gives the painting task a little time to catch up when the user scrolls. + +use compositor_task::{CompositorProxy, ScrollTimeout}; + +use native::task::NativeTaskBuilder; +use std::io::timer; +use std::task::TaskBuilder; +use std::time::duration::Duration; +use time; + +/// The amount of time in nanoseconds that we give to the painting thread to paint new tiles upon +/// processing a scroll event that caused new tiles to be revealed. When this expires, we give up +/// and composite anyway (showing a "checkerboard") to avoid dropping the frame. +static TIMEOUT: i64 = 12_000_000; + +pub struct ScrollingTimerProxy { + sender: Sender<ToScrollingTimerMsg>, +} + +pub struct ScrollingTimer { + compositor_proxy: Box<CompositorProxy>, + receiver: Receiver<ToScrollingTimerMsg>, +} + +enum ToScrollingTimerMsg { + ExitMsg, + ScrollEventProcessedMsg(u64), +} + +impl ScrollingTimerProxy { + pub fn new(compositor_proxy: Box<CompositorProxy+Send>) -> ScrollingTimerProxy { + let (to_scrolling_timer_sender, to_scrolling_timer_receiver) = channel(); + TaskBuilder::new().native().spawn(proc() { + let mut scrolling_timer = ScrollingTimer { + compositor_proxy: compositor_proxy, + receiver: to_scrolling_timer_receiver, + }; + scrolling_timer.run(); + }); + ScrollingTimerProxy { + sender: to_scrolling_timer_sender, + } + } + + pub fn scroll_event_processed(&mut self, timestamp: u64) { + self.sender.send(ScrollEventProcessedMsg(timestamp)) + } + + pub fn shutdown(&mut self) { + self.sender.send(ExitMsg); + } +} + +impl ScrollingTimer { + pub fn run(&mut self) { + loop { + match self.receiver.recv_opt() { + Ok(ScrollEventProcessedMsg(timestamp)) => { + let target = timestamp as i64 + TIMEOUT; + let delta = target - (time::precise_time_ns() as i64); + timer::sleep(Duration::nanoseconds(delta)); + self.compositor_proxy.send(ScrollTimeout(timestamp)); + } + Ok(ExitMsg) | Err(_) => break, + } + } + } +} + diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs index 915a15d77b8..5815de6e2b5 100644 --- a/components/compositing/windowing.rs +++ b/components/compositing/windowing.rs @@ -4,6 +4,8 @@ //! Abstract windowing methods. The concrete implementations of these can be found in `platform/`. +use compositor_task::{CompositorProxy, CompositorReceiver}; + use geom::point::TypedPoint2D; use geom::scale_factor::ScaleFactor; use geom::size::TypedSize2D; @@ -11,6 +13,8 @@ use layers::geometry::DevicePixel; use layers::platform::surface::NativeGraphicsMetadata; use servo_msg::compositor_msg::{ReadyState, RenderState}; use servo_util::geometry::ScreenPx; +use std::fmt::{FormatError, Formatter, Show}; +use std::rc::Rc; pub enum MouseWindowEvent { MouseWindowClickEvent(uint, TypedPoint2D<DevicePixel, f32>), @@ -25,10 +29,12 @@ pub enum WindowNavigateMsg { /// Events that the windowing system sends to Servo. pub enum WindowEvent { - /// Sent when no message has arrived. + /// Sent when no message has arrived, but the event loop was kicked for some reason (perhaps + /// by another Servo subsystem). /// - /// FIXME: This is a bogus event and is only used because we don't have the new - /// scheduler integrated with the platform event loop. + /// FIXME(pcwalton): This is kind of ugly and may not work well with multiprocess Servo. + /// It's possible that this should be something like + /// `CompositorMessageWindowEvent(compositor_task::Msg)` instead. IdleWindowEvent, /// Sent when part of the window is marked dirty and needs to be redrawn. RefreshWindowEvent, @@ -54,6 +60,25 @@ pub enum WindowEvent { QuitWindowEvent, } +impl Show for WindowEvent { + fn fmt(&self, f: &mut Formatter) -> Result<(),FormatError> { + match *self { + IdleWindowEvent => write!(f, "Idle"), + RefreshWindowEvent => write!(f, "Refresh"), + ResizeWindowEvent(..) => write!(f, "Resize"), + LoadUrlWindowEvent(..) => write!(f, "LoadUrl"), + MouseWindowEventClass(..) => write!(f, "Mouse"), + MouseWindowMoveEventClass(..) => write!(f, "MouseMove"), + ScrollWindowEvent(..) => write!(f, "Scroll"), + ZoomWindowEvent(..) => write!(f, "Zoom"), + PinchZoomWindowEvent(..) => write!(f, "PinchZoom"), + NavigationWindowEvent(..) => write!(f, "Navigation"), + FinishedWindowEvent => write!(f, "Finished"), + QuitWindowEvent => write!(f, "Quit"), + } + } +} + pub trait WindowMethods { /// Returns the size of the window in hardware pixels. fn framebuffer_size(&self) -> TypedSize2D<DevicePixel, uint>; @@ -62,9 +87,6 @@ pub trait WindowMethods { /// Presents the window to the screen (perhaps by page flipping). fn present(&self); - /// Spins the event loop and returns the next event. - fn recv(&self) -> WindowEvent; - /// Sets the ready state of the current page. fn set_ready_state(&self, ready_state: ReadyState); /// Sets the render state of the current page. @@ -75,5 +97,13 @@ pub trait WindowMethods { /// Gets the OS native graphics information for this window. fn native_metadata(&self) -> NativeGraphicsMetadata; + + /// Creates a channel to the compositor. The dummy parameter is needed because we don't have + /// UFCS in Rust yet. + /// + /// This is part of the windowing system because its implementation often involves OS-specific + /// magic to wake the up window's event loop. + fn create_compositor_channel(_: &Option<Rc<Self>>) + -> (Box<CompositorProxy+Send>, Box<CompositorReceiver>); } diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index bdc136f3730..7caa3835486 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -19,6 +19,7 @@ use render_context::RenderContext; use text::glyph::CharIndex; use text::TextRun; +use azure::azure::AzFloat; use collections::Deque; use collections::dlist::{mod, DList}; use geom::{Point2D, Rect, SideOffsets2D, Size2D, Matrix2D}; @@ -26,18 +27,11 @@ use libc::uintptr_t; use servo_net::image::base::Image; use servo_util::dlist as servo_dlist; use servo_util::geometry::Au; -use servo_util::opts; use servo_util::range::Range; use std::fmt; use std::slice::Items; use style::computed_values::border_style; use sync::Arc; -use std::num::Zero; -use std::ptr; - -use azure::AzFloat; -use azure::scaled_font::ScaledFont; -use azure::azure_hl::ColorPattern; pub mod optimizer; @@ -58,88 +52,6 @@ impl OpaqueNode { } } -trait ScaledFontExtensionMethods { - fn draw_text_into_context(&self, - rctx: &RenderContext, - run: &Box<TextRun>, - range: &Range<CharIndex>, - baseline_origin: Point2D<Au>, - color: Color, - antialias: bool); -} - -impl ScaledFontExtensionMethods for ScaledFont { - fn draw_text_into_context(&self, - rctx: &RenderContext, - run: &Box<TextRun>, - range: &Range<CharIndex>, - baseline_origin: Point2D<Au>, - color: Color, - antialias: bool) { - use libc::types::common::c99::uint32_t; - use azure::{struct__AzDrawOptions, - struct__AzGlyph, - struct__AzGlyphBuffer, - struct__AzPoint}; - use azure::azure::{AzDrawTargetFillGlyphs}; - - let target = rctx.get_draw_target(); - let pattern = ColorPattern::new(color); - let azure_pattern = pattern.azure_color_pattern; - assert!(azure_pattern.is_not_null()); - - let fields = if antialias { - 0x0200 - } else { - 0 - }; - - let mut options = struct__AzDrawOptions { - mAlpha: 1f64 as AzFloat, - fields: fields, - }; - - let mut origin = baseline_origin.clone(); - let mut azglyphs = vec!(); - azglyphs.reserve(range.length().to_uint()); - - for (glyphs, _offset, slice_range) in run.iter_slices_for_range(range) { - for (_i, glyph) in glyphs.iter_glyphs_for_char_range(&slice_range) { - let glyph_advance = glyph.advance(); - let glyph_offset = glyph.offset().unwrap_or(Zero::zero()); - - let azglyph = struct__AzGlyph { - mIndex: glyph.id() as uint32_t, - mPosition: struct__AzPoint { - x: (origin.x + glyph_offset.x).to_subpx() as AzFloat, - y: (origin.y + glyph_offset.y).to_subpx() as AzFloat - } - }; - origin = Point2D(origin.x + glyph_advance, origin.y); - azglyphs.push(azglyph) - }; - } - - let azglyph_buf_len = azglyphs.len(); - if azglyph_buf_len == 0 { return; } // Otherwise the Quartz backend will assert. - - let mut glyphbuf = struct__AzGlyphBuffer { - mGlyphs: azglyphs.as_mut_ptr(), - mNumGlyphs: azglyph_buf_len as uint32_t - }; - - unsafe { - // TODO(Issue #64): this call needs to move into azure_hl.rs - AzDrawTargetFillGlyphs(target.azure_draw_target, - self.get_ref(), - &mut glyphbuf, - azure_pattern, - &mut options, - ptr::null_mut()); - } - } -} - /// "Steps" as defined by CSS 2.1 § E.2. #[deriving(Clone, PartialEq, Show)] pub enum StackingLevel { @@ -543,55 +455,7 @@ impl DisplayItem { TextDisplayItemClass(ref text) => { debug!("Drawing text at {}.", text.base.bounds); - - // Optimization: Don’t set a transform matrix for upright text, - // and pass a strart point to `draw_text_into_context`. - // For sideways text, it’s easier to do the rotation such that its center - // (the baseline’s start point) is at (0, 0) coordinates. - let baseline_origin = match text.orientation { - Upright => text.baseline_origin, - SidewaysLeft => { - let x = text.baseline_origin.x.to_nearest_px() as AzFloat; - let y = text.baseline_origin.y.to_nearest_px() as AzFloat; - render_context.draw_target.set_transform(¤t_transform.mul( - &Matrix2D::new( - 0., -1., - 1., 0., - x, y - ) - )); - Zero::zero() - }, - SidewaysRight => { - let x = text.baseline_origin.x.to_nearest_px() as AzFloat; - let y = text.baseline_origin.y.to_nearest_px() as AzFloat; - render_context.draw_target.set_transform(¤t_transform.mul( - &Matrix2D::new( - 0., 1., - -1., 0., - x, y - ) - )); - Zero::zero() - } - }; - - render_context.font_ctx.get_render_font_from_template( - &text.text_run.font_template, - text.text_run.actual_pt_size - ).borrow().draw_text_into_context( - render_context, - &*text.text_run, - &text.range, - baseline_origin, - text.text_color, - opts::get().enable_text_antialiasing - ); - - // Undo the transform, only when we did one. - if text.orientation != Upright { - render_context.draw_target.set_transform(current_transform) - } + render_context.draw_text(&**text, current_transform); } ImageDisplayItemClass(ref image_item) => { diff --git a/components/gfx/lib.rs b/components/gfx/lib.rs index 28312a09440..b5f8c3e961d 100644 --- a/components/gfx/lib.rs +++ b/components/gfx/lib.rs @@ -30,6 +30,7 @@ extern crate "util" as servo_util; extern crate "msg" as servo_msg; extern crate style; extern crate sync; +extern crate time; extern crate url; // Eventually we would like the shaper to be pluggable, as many operating systems have their own diff --git a/components/gfx/render_context.rs b/components/gfx/render_context.rs index e2c893890c1..b682b8f5f02 100644 --- a/components/gfx/render_context.rs +++ b/components/gfx/render_context.rs @@ -2,23 +2,32 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use display_list::{SidewaysLeft, SidewaysRight, TextDisplayItem, Upright}; use font_context::FontContext; use style::computed_values::border_style; use azure::azure_hl::{B8G8R8A8, A8, Color, ColorPattern, ColorPatternRef, DrawOptions}; -use azure::azure_hl::{DrawSurfaceOptions, DrawTarget, Linear, SourceOp, StrokeOptions}; -use azure::AZ_CAP_BUTT; -use azure::AzFloat; +use azure::azure_hl::{DrawSurfaceOptions,DrawTarget, Linear, SourceOp, StrokeOptions}; +use azure::scaled_font::ScaledFont; +use azure::{AZ_CAP_BUTT, AzDrawTargetFillGlyphs, AzFloat, struct__AzDrawOptions, struct__AzGlyph}; +use azure::{struct__AzGlyphBuffer, struct__AzPoint}; +use geom::matrix2d::Matrix2D; use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; use geom::side_offsets::SideOffsets2D; -use libc::types::common::c99::uint16_t; +use libc::types::common::c99::{uint16_t, uint32_t}; use libc::size_t; use png::{RGB8, RGBA8, K8, KA8}; use servo_net::image::base::Image; use servo_util::geometry::Au; +use servo_util::opts; +use servo_util::range::Range; +use std::num::Zero; +use std::ptr; use sync::Arc; +use text::glyph::CharIndex; +use text::TextRun; pub struct RenderContext<'a> { pub draw_target: &'a DrawTarget, @@ -390,6 +399,50 @@ impl<'a> RenderContext<'a> { self.draw_border_path(original_bounds, direction, border, scaled_color); } + pub fn draw_text(&mut self, + text: &TextDisplayItem, + current_transform: &Matrix2D<AzFloat>) { + // Optimization: Don’t set a transform matrix for upright text, and pass a start point to + // `draw_text_into_context`. + // + // For sideways text, it’s easier to do the rotation such that its center (the baseline’s + // start point) is at (0, 0) coordinates. + let baseline_origin = match text.orientation { + Upright => text.baseline_origin, + SidewaysLeft => { + let x = text.baseline_origin.x.to_subpx() as AzFloat; + let y = text.baseline_origin.y.to_subpx() as AzFloat; + self.draw_target.set_transform(¤t_transform.mul(&Matrix2D::new(0., -1., + 1., 0., + x, y))); + Zero::zero() + } + SidewaysRight => { + let x = text.baseline_origin.x.to_subpx() as AzFloat; + let y = text.baseline_origin.y.to_subpx() as AzFloat; + self.draw_target.set_transform(¤t_transform.mul(&Matrix2D::new(0., 1., + -1., 0., + x, y))); + Zero::zero() + } + }; + + self.font_ctx + .get_render_font_from_template(&text.text_run.font_template, + text.text_run.actual_pt_size) + .borrow() + .draw_text_into_context(self, + &*text.text_run, + &text.range, + baseline_origin, + text.text_color, + opts::get().enable_text_antialiasing); + + // Undo the transform, only when we did one. + if text.orientation != Upright { + self.draw_target.set_transform(current_transform) + } + } } trait ToAzureRect { @@ -417,3 +470,78 @@ impl ToSideOffsetsPx for SideOffsets2D<Au> { self.left.to_nearest_px() as AzFloat) } } + +trait ScaledFontExtensionMethods { + fn draw_text_into_context(&self, + rctx: &RenderContext, + run: &Box<TextRun>, + range: &Range<CharIndex>, + baseline_origin: Point2D<Au>, + color: Color, + antialias: bool); +} + +impl ScaledFontExtensionMethods for ScaledFont { + fn draw_text_into_context(&self, + rctx: &RenderContext, + run: &Box<TextRun>, + range: &Range<CharIndex>, + baseline_origin: Point2D<Au>, + color: Color, + antialias: bool) { + let target = rctx.get_draw_target(); + let pattern = ColorPattern::new(color); + let azure_pattern = pattern.azure_color_pattern; + assert!(azure_pattern.is_not_null()); + + let fields = if antialias { + 0x0200 + } else { + 0 + }; + + let mut options = struct__AzDrawOptions { + mAlpha: 1f64 as AzFloat, + fields: fields, + }; + + let mut origin = baseline_origin.clone(); + let mut azglyphs = vec!(); + azglyphs.reserve(range.length().to_uint()); + + for (glyphs, _offset, slice_range) in run.iter_slices_for_range(range) { + for (_i, glyph) in glyphs.iter_glyphs_for_char_range(&slice_range) { + let glyph_advance = glyph.advance(); + let glyph_offset = glyph.offset().unwrap_or(Zero::zero()); + let azglyph = struct__AzGlyph { + mIndex: glyph.id() as uint32_t, + mPosition: struct__AzPoint { + x: (origin.x + glyph_offset.x).to_subpx() as AzFloat, + y: (origin.y + glyph_offset.y).to_subpx() as AzFloat + } + }; + origin = Point2D(origin.x + glyph_advance, origin.y); + azglyphs.push(azglyph) + }; + } + + let azglyph_buf_len = azglyphs.len(); + if azglyph_buf_len == 0 { return; } // Otherwise the Quartz backend will assert. + + let mut glyphbuf = struct__AzGlyphBuffer { + mGlyphs: azglyphs.as_mut_ptr(), + mNumGlyphs: azglyph_buf_len as uint32_t + }; + + unsafe { + // TODO(Issue #64): this call needs to move into azure_hl.rs + AzDrawTargetFillGlyphs(target.azure_draw_target, + self.get_ref(), + &mut glyphbuf, + azure_pattern, + &mut options, + ptr::null_mut()); + } + } +} + diff --git a/components/gfx/render_task.rs b/components/gfx/render_task.rs index 0662eef3d99..9eeeed51000 100644 --- a/components/gfx/render_task.rs +++ b/components/gfx/render_task.rs @@ -7,6 +7,7 @@ use buffer_map::BufferMap; use display_list::optimizer::DisplayListOptimizer; use display_list::DisplayList; +use font_cache_task::FontCacheTask; use font_context::FontContext; use render_context::RenderContext; @@ -37,7 +38,6 @@ use std::comm::{Receiver, Sender, channel}; use std::mem; use std::task::TaskBuilder; use sync::Arc; -use font_cache_task::FontCacheTask; /// Information about a layer that layout sends to the painting task. #[deriving(Clone)] @@ -153,7 +153,10 @@ impl<C> RenderTask<C> where C: RenderListener + Send { shutdown_chan: Sender<()>) { let ConstellationChan(c) = constellation_chan.clone(); spawn_named_with_send_on_failure("RenderTask", task_state::Render, proc() { - { // Ensures RenderTask and graphics context are destroyed before shutdown msg + { + // Ensures that the render task and graphics context are destroyed before the + // shutdown message. + let mut compositor = compositor; let native_graphics_context = compositor.get_graphics_metadata().map( |md| NativePaintingGraphicsContext::from_metadata(&md)); let worker_threads = WorkerThreadProxy::spawn(compositor.get_graphics_metadata(), diff --git a/components/msg/compositor_msg.rs b/components/msg/compositor_msg.rs index c8713d7a119..a4cc14a12ea 100644 --- a/components/msg/compositor_msg.rs +++ b/components/msg/compositor_msg.rs @@ -84,36 +84,36 @@ pub struct LayerMetadata { /// The interface used by the renderer to acquire draw targets for each render frame and /// submit them to be drawn to the display. -pub trait RenderListener { - fn get_graphics_metadata(&self) -> Option<NativeGraphicsMetadata>; +pub trait RenderListener for Sized? { + fn get_graphics_metadata(&mut self) -> Option<NativeGraphicsMetadata>; /// Informs the compositor of the layers for the given pipeline. The compositor responds by /// creating and/or destroying render layers as necessary. - fn initialize_layers_for_pipeline(&self, + fn initialize_layers_for_pipeline(&mut self, pipeline_id: PipelineId, metadata: Vec<LayerMetadata>, epoch: Epoch); /// Sends new tiles for the given layer to the compositor. - fn paint(&self, + fn paint(&mut self, pipeline_id: PipelineId, epoch: Epoch, replies: Vec<(LayerId, Box<LayerBufferSet>)>); - fn render_msg_discarded(&self); - fn set_render_state(&self, PipelineId, RenderState); + fn render_msg_discarded(&mut self); + fn set_render_state(&mut self, PipelineId, RenderState); } /// The interface used by the script task to tell the compositor to update its ready state, /// which is used in displaying the appropriate message in the window's title. -pub trait ScriptListener : Clone { - fn set_ready_state(&self, PipelineId, ReadyState); - fn scroll_fragment_point(&self, +pub trait ScriptListener { + fn set_ready_state(&mut self, PipelineId, ReadyState); + fn scroll_fragment_point(&mut self, pipeline_id: PipelineId, layer_id: LayerId, point: Point2D<f32>); - fn close(&self); - fn dup(&self) -> Box<ScriptListener+'static>; + fn close(&mut self); + fn dup(&mut self) -> Box<ScriptListener+'static>; } impl<E, S: Encoder<E>> Encodable<S, E> for Box<ScriptListener+'static> { diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index 737ef3f4d98..00357752ee1 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -2,8 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -//! The high-level interface from script to constellation. Using this abstract interface helps reduce -//! coupling between these two components +//! The high-level interface from script to constellation. Using this abstract interface helps +//! reduce coupling between these two components. use geom::rect::Rect; use geom::size::TypedSize2D; diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index c9eeedc2a2a..90c18f5dca5 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use dom::bindings::cell::{DOMRefCell, Ref}; +use dom::bindings::cell::{DOMRefCell, Ref, RefMut}; use dom::bindings::codegen::Bindings::EventHandlerBinding::{OnErrorEventHandlerNonNull, EventHandlerNonNull}; use dom::bindings::codegen::Bindings::WindowBinding; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; @@ -53,7 +53,7 @@ pub struct Window { location: MutNullableJS<Location>, navigator: MutNullableJS<Navigator>, image_cache_task: ImageCacheTask, - compositor: Box<ScriptListener+'static>, + compositor: DOMRefCell<Box<ScriptListener+'static>>, browser_context: DOMRefCell<Option<BrowserContext>>, page: Rc<Page>, performance: MutNullableJS<Performance>, @@ -81,8 +81,8 @@ impl Window { &self.image_cache_task } - pub fn compositor<'a>(&'a self) -> &'a ScriptListener+'static { - &*self.compositor + pub fn compositor(&self) -> RefMut<Box<ScriptListener+'static>> { + self.compositor.borrow_mut() } pub fn browser_context(&self) -> Ref<Option<BrowserContext>> { @@ -398,7 +398,7 @@ impl Window { script_chan: script_chan, control_chan: control_chan, console: Default::default(), - compositor: compositor, + compositor: DOMRefCell::new(compositor), page: page, location: Default::default(), navigator: Default::default(), diff --git a/components/script/page.rs b/components/script/page.rs index 01b396126ad..4e87ebe9050 100644 --- a/components/script/page.rs +++ b/components/script/page.rs @@ -182,7 +182,7 @@ impl Page { if force_reflow { let frame = self.frame(); let window = frame.as_ref().unwrap().window.root(); - self.reflow(reflow_goal, window.control_chan().clone(), window.compositor(), query); + self.reflow(reflow_goal, window.control_chan().clone(), &mut **window.compositor(), query); } else { self.avoided_reflows.set(self.avoided_reflows.get() + 1); } @@ -328,9 +328,8 @@ impl Page { pub fn reflow(&self, goal: ReflowGoal, script_chan: ScriptControlChan, - compositor: &ScriptListener, + compositor: &mut ScriptListener, query_type: ReflowQueryType) { - let root = match *self.frame() { None => return, Some(ref frame) => { diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 5dd6f7c2d02..8595ebf0737 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -168,7 +168,7 @@ pub struct ScriptTask { /// For communicating load url messages to the constellation constellation_chan: ConstellationChan, /// A handle to the compositor for communicating ready state messages. - compositor: Box<ScriptListener+'static>, + compositor: DOMRefCell<Box<ScriptListener+'static>>, /// For providing instructions to an optional devtools server. devtools_chan: Option<DevtoolsControlChan>, @@ -248,25 +248,25 @@ impl ScriptTaskFactory for ScriptTask { box pair.sender() as Box<Any+Send> } - fn create<C:ScriptListener + Send + 'static>( - _phantom: Option<&mut ScriptTask>, - id: PipelineId, - compositor: Box<C>, - layout_chan: &OpaqueScriptLayoutChannel, - control_chan: ScriptControlChan, - control_port: Receiver<ConstellationControlMsg>, - constellation_chan: ConstellationChan, - failure_msg: Failure, - resource_task: ResourceTask, - image_cache_task: ImageCacheTask, - devtools_chan: Option<DevtoolsControlChan>, - window_size: WindowSizeData) { + fn create<C>(_phantom: Option<&mut ScriptTask>, + id: PipelineId, + compositor: C, + layout_chan: &OpaqueScriptLayoutChannel, + control_chan: ScriptControlChan, + control_port: Receiver<ConstellationControlMsg>, + constellation_chan: ConstellationChan, + failure_msg: Failure, + resource_task: ResourceTask, + image_cache_task: ImageCacheTask, + devtools_chan: Option<DevtoolsControlChan>, + window_size: WindowSizeData) + where C: ScriptListener + Send + 'static { let ConstellationChan(const_chan) = constellation_chan.clone(); let (script_chan, script_port) = channel(); let layout_chan = LayoutChan(layout_chan.sender()); spawn_named_with_send_on_failure("ScriptTask", task_state::Script, proc() { let script_task = ScriptTask::new(id, - compositor as Box<ScriptListener>, + box compositor as Box<ScriptListener>, layout_chan, script_port, ScriptChan(script_chan), @@ -350,7 +350,7 @@ impl ScriptTask { control_chan: control_chan, control_port: control_port, constellation_chan: constellation_chan, - compositor: compositor, + compositor: DOMRefCell::new(compositor), devtools_chan: devtools_chan, devtools_port: devtools_receiver, @@ -671,7 +671,7 @@ impl ScriptTask { *layout_join_port = None; } - self.compositor.set_ready_state(pipeline_id, FinishedLoading); + self.compositor.borrow_mut().set_ready_state(pipeline_id, FinishedLoading); if page.pending_reflows.get() > 0 { page.pending_reflows.set(0); @@ -709,7 +709,7 @@ impl ScriptTask { // TODO(tkuehn): currently there is only one window, // so this can afford to be naive and just shut down the // compositor. In the future it'll need to be smarter. - self.compositor.close(); + self.compositor.borrow_mut().close(); } /// Handles a request to exit the script task and shut down layout. @@ -771,7 +771,7 @@ impl ScriptTask { page.clone(), self.chan.clone(), self.control_chan.clone(), - self.compositor.dup(), + self.compositor.borrow_mut().dup(), self.image_cache_task.clone()).root(); let doc_url = if is_javascript { let doc_url = last_url.unwrap_or_else(|| { @@ -787,7 +787,7 @@ impl ScriptTask { window.init_browser_context(*document); - self.compositor.set_ready_state(pipeline_id, Loading); + self.compositor.borrow_mut().set_ready_state(pipeline_id, Loading); let parser_input = if !is_javascript { InputUrl(url.clone()) @@ -858,7 +858,7 @@ impl ScriptTask { // Really what needs to happen is that this needs to go through layout to ask which // layer the element belongs to, and have it send the scroll message to the // compositor. - self.compositor.scroll_fragment_point(pipeline_id, LayerId::null(), point); + self.compositor.borrow_mut().scroll_fragment_point(pipeline_id, LayerId::null(), point); } fn force_reflow(&self, page: &Page) { @@ -873,7 +873,10 @@ impl ScriptTask { } page.damage(); - page.reflow(ReflowForDisplay, self.control_chan.clone(), &*self.compositor, NoQuery); + page.reflow(ReflowForDisplay, + self.control_chan.clone(), + &mut **self.compositor.borrow_mut(), + NoQuery); } /// This is the main entry point for receiving and dispatching DOM events. diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 918739d2b88..0caf54ba341 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -89,18 +89,20 @@ impl<S: Encoder<E>, E> Encodable<S, E> for ScriptControlChan { } pub trait ScriptTaskFactory { - fn create<C: ScriptListener + Send>(_phantom: Option<&mut Self>, - id: PipelineId, - compositor: Box<C>, - layout_chan: &OpaqueScriptLayoutChannel, - control_chan: ScriptControlChan, - control_port: Receiver<ConstellationControlMsg>, - constellation_msg: ConstellationChan, - failure_msg: Failure, - resource_task: ResourceTask, - image_cache_task: ImageCacheTask, - devtools_chan: Option<DevtoolsControlChan>, - window_size: WindowSizeData); + fn create<C>(_phantom: Option<&mut Self>, + id: PipelineId, + compositor: C, + layout_chan: &OpaqueScriptLayoutChannel, + control_chan: ScriptControlChan, + control_port: Receiver<ConstellationControlMsg>, + constellation_msg: ConstellationChan, + failure_msg: Failure, + resource_task: ResourceTask, + image_cache_task: ImageCacheTask, + devtools_chan: Option<DevtoolsControlChan>, + window_size: WindowSizeData) + where C: ScriptListener + Send; fn create_layout_channel(_phantom: Option<&mut Self>) -> OpaqueScriptLayoutChannel; - fn clone_layout_channel(_phantom: Option<&mut Self>, pair: &OpaqueScriptLayoutChannel) -> Box<Any+Send>; + fn clone_layout_channel(_phantom: Option<&mut Self>, pair: &OpaqueScriptLayoutChannel) + -> Box<Any+Send>; } diff --git a/components/util/time.rs b/components/util/time.rs index 232e73b3802..b4a60da36ef 100644 --- a/components/util/time.rs +++ b/components/util/time.rs @@ -4,13 +4,13 @@ //! Timing functions. -use std_time::precise_time_ns; use collections::treemap::TreeMap; use std::comm::{Sender, channel, Receiver}; use std::f64; -use std::iter::AdditiveIterator; use std::io::timer::sleep; +use std::iter::AdditiveIterator; use std::time::duration::Duration; +use std_time::precise_time_ns; use task::{spawn_named}; use url::Url; |