diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 3e786e94d937..991ed3676c75 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -378,7 +378,14 @@ impl IOCompositor { self.get_title_for_main_frame(); } - (Msg::InitializeLayersForPipeline(pipeline_id, epoch, properties), ShutdownState::NotShuttingDown) => { + (Msg::InitializeLayersForPipeline(pipeline_id, epoch, properties), + ShutdownState::NotShuttingDown) => { + if let Some(ref root_layer) = self.scene.root { + root_layer.update_transform_state(&Matrix4::identity(), + &Matrix4::identity(), + &Point2D::zero()); + } + self.get_or_create_pipeline_details(pipeline_id).current_epoch = epoch; for (index, layer_properties) in properties.iter().enumerate() { if index == 0 { @@ -387,7 +394,6 @@ impl IOCompositor { self.create_or_update_descendant_layer(pipeline_id, *layer_properties); } } - self.send_buffer_requests_for_all_layers(); } (Msg::GetNativeDisplay(chan), ShutdownState::NotShuttingDown) => { @@ -552,7 +558,8 @@ impl IOCompositor { self.get_or_create_pipeline_details(pipeline_id).animations_running = false; } AnimationState::NoAnimationCallbacksPresent => { - self.get_or_create_pipeline_details(pipeline_id).animation_callbacks_running = false; + self.get_or_create_pipeline_details(pipeline_id).animation_callbacks_running = + false; } } } @@ -628,6 +635,7 @@ impl IOCompositor { rect: Rect::zero(), background_color: color::transparent(), scroll_policy: ScrollPolicy::Scrollable, + invalid_rect: Rect::zero(), transform: Matrix4::identity(), perspective: Matrix4::identity(), establishes_3d_context: true, @@ -683,7 +691,8 @@ impl IOCompositor { self.pipeline_details.remove(&pipeline_id); } - fn update_layer_if_exists(&mut self, pipeline_id: PipelineId, properties: LayerProperties) -> bool { + fn update_layer_if_exists(&mut self, pipeline_id: PipelineId, properties: LayerProperties) + -> bool { match self.find_layer_with_pipeline_and_layer_id(pipeline_id, properties.id) { Some(existing_layer) => { existing_layer.update_layer(properties); @@ -693,7 +702,9 @@ impl IOCompositor { } } - fn create_or_update_base_layer(&mut self, pipeline_id: PipelineId, layer_properties: LayerProperties) { + fn create_or_update_base_layer(&mut self, + pipeline_id: PipelineId, + layer_properties: LayerProperties) { debug_assert!(layer_properties.parent_id.is_none()); let root_layer = match self.find_pipeline_root_layer(pipeline_id) { @@ -723,18 +734,38 @@ impl IOCompositor { root_layer.children().insert(0, base_layer); } - self.scroll_layer_to_fragment_point_if_necessary(pipeline_id, - layer_properties.id); - } + self.scroll_layer_to_fragment_point_if_necessary(pipeline_id, layer_properties.id); - fn create_or_update_descendant_layer(&mut self, pipeline_id: PipelineId, layer_properties: LayerProperties) { - debug_assert!(layer_properties.parent_id.is_some()); + let invalid_rect: Rect = { + let layer = self.find_layer_with_pipeline_and_layer_id(pipeline_id, + layer_properties.id); + let layer = layer.expect("create_or_update_base_layer(): didn't find layer!"); + let extra_data = layer.extra_data.borrow(); + extra_data.invalid_rect.clone() + }; + self.send_buffer_requests_for_layer_with_invalid_rect(pipeline_id, + layer_properties.id, + &Rect::from_untyped(&invalid_rect)); + } + fn create_or_update_descendant_layer(&mut self, + pipeline_id: PipelineId, + layer_properties: LayerProperties) { if !self.update_layer_if_exists(pipeline_id, layer_properties) { self.create_descendant_layer(pipeline_id, layer_properties); } - self.scroll_layer_to_fragment_point_if_necessary(pipeline_id, - layer_properties.id); + self.scroll_layer_to_fragment_point_if_necessary(pipeline_id, layer_properties.id); + + let invalid_rect: Rect = { + let layer = self.find_layer_with_pipeline_and_layer_id(pipeline_id, + layer_properties.id); + let layer = layer.expect("create_or_update_descendant_layer(): didn't find layer!"); + let extra_data = layer.extra_data.borrow(); + extra_data.invalid_rect.clone() + }; + self.send_buffer_requests_for_layer_with_invalid_rect(pipeline_id, + layer_properties.id, + &Rect::from_untyped(&invalid_rect)); } fn create_descendant_layer(&self, pipeline_id: PipelineId, layer_properties: LayerProperties) { @@ -830,7 +861,8 @@ impl IOCompositor { // loaded and the frame tree changes. This can result in the compositor thinking it // has already drawn the most recently painted buffer, and missing a frame. if frame_tree_id == self.frame_tree_id { - if let Some(layer) = self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) { + if let Some(layer) = self.find_layer_with_pipeline_and_layer_id(pipeline_id, + layer_id) { let requested_epoch = layer.extra_data.borrow().requested_epoch; if requested_epoch == epoch { self.assign_painted_buffers_to_layer(layer, new_layer_buffer_set, epoch); @@ -1282,17 +1314,11 @@ impl IOCompositor { } /// Returns true if any buffer requests were sent or false otherwise. - fn send_buffer_requests_for_all_layers(&mut self) -> bool { - if let Some(ref root_layer) = self.scene.root { - root_layer.update_transform_state(&Matrix4::identity(), - &Matrix4::identity(), - &Point2D::zero()); - } - - let mut layers_and_requests = Vec::new(); - let mut unused_buffers = Vec::new(); - self.scene.get_buffer_requests(&mut layers_and_requests, &mut unused_buffers); - + fn send_buffer_requests(&mut self, + layers_and_requests: Vec<(Rc>, + Vec)>, + unused_buffers: Vec>) + -> bool { // Return unused tiles first, so that they can be reused by any new BufferRequests. self.cache_unused_buffers(unused_buffers); @@ -1313,18 +1339,77 @@ impl IOCompositor { true } + /// Returns true if any buffer requests were sent or false otherwise. + /// + /// NB: This function invalidates all tiles! Use with caution! + fn send_buffer_requests_for_all_layers(&mut self) -> bool { + if let Some(ref root_layer) = self.scene.root { + root_layer.update_transform_state(&Matrix4::identity(), + &Matrix4::identity(), + &Point2D::zero()); + } + + let mut layers_and_requests = Vec::new(); + let mut unused_buffers = Vec::new(); + self.scene.get_buffer_requests(&mut layers_and_requests, &mut unused_buffers); + self.send_buffer_requests(layers_and_requests, unused_buffers) + } + + /// Returns true if any buffer requests were sent or false otherwise. + fn send_buffer_requests_for_layer_with_invalid_rect(&mut self, + pipeline_id: PipelineId, + layer_id: LayerId, + invalid_rect: &TypedRect) + -> bool { + let (mut layers_and_requests, mut unused_buffers) = (Vec::new(), Vec::new()); + let root_bounds = + *self.scene + .root + .as_ref() + .expect("send_buffer_requests_for_layer_with_invalid_rect: no root layer!") + .bounds + .borrow(); + if let Some(ref layer) = self.find_layer_with_pipeline_and_layer_id(pipeline_id, + layer_id) { + let invalid_rect = invalid_rect.translate(&layer.bounds.borrow().origin); + self.scene.get_buffer_requests_for_layer((*layer).clone(), + invalid_rect, + root_bounds, + &mut layers_and_requests, + &mut unused_buffers) + } + + let scale_factor: ScaleFactor = + ScaleFactor::new(self.device_pixels_per_screen_px().get()); + for &mut (_, ref mut buffer_requests) in layers_and_requests.iter_mut() { + buffer_requests.retain(|buffer_request| { + let invalid_rect = (*invalid_rect * scale_factor).to_untyped(); + buffer_request.page_rect.intersects(&invalid_rect) + }); + } + + self.send_buffer_requests(layers_and_requests, unused_buffers) + } + /// Check if a layer (or its children) have any outstanding paint /// results to arrive yet. - fn does_layer_have_outstanding_paint_messages(&self, layer: &Rc>) -> bool { + fn does_layer_have_outstanding_paint_messages(&self, layer: &Rc>) + -> bool { let layer_data = layer.extra_data.borrow(); - let current_epoch = self.pipeline_details.get(&layer_data.pipeline_id).unwrap().current_epoch; + let current_epoch = + self.pipeline_details.get(&layer_data.pipeline_id).unwrap().current_epoch; + // Don't check the root layer (the one with a null ID); we set it up and it's never + // painted. + // // Only check layers that have requested the current epoch, as there may be // layers that are not visible in the current viewport, and therefore // have not requested a paint of the current epoch. // If a layer has sent a request for the current epoch, but it hasn't // arrived yet then this layer is waiting for a paint message. - if layer_data.requested_epoch == current_epoch && layer_data.painted_epoch != current_epoch { + if layer_data.id != LayerId::null() && + layer_data.requested_epoch == current_epoch && + layer_data.painted_epoch != current_epoch { return true; } diff --git a/components/compositing/compositor_layer.rs b/components/compositing/compositor_layer.rs index 707b7ce06b69..e333c1119bdd 100644 --- a/components/compositing/compositor_layer.rs +++ b/components/compositing/compositor_layer.rs @@ -42,6 +42,9 @@ pub struct CompositorData { /// The scroll offset originating from this scrolling root. This allows scrolling roots /// to track their current scroll position even while their content_offset does not change. pub scroll_offset: TypedPoint2D, + + /// The invalid rectangle for this layer. + pub invalid_rect: Rect, } impl CompositorData { @@ -58,6 +61,7 @@ impl CompositorData { requested_epoch: Epoch(0), painted_epoch: Epoch(0), scroll_offset: Point2D::typed(0., 0.), + invalid_rect: layer_properties.invalid_rect, }; Rc::new(Layer::new(Rect::from_untyped(&layer_properties.rect), @@ -188,12 +192,16 @@ pub enum ScrollEventResult { impl CompositorLayer for Layer { fn update_layer_except_bounds(&self, layer_properties: LayerProperties) { - self.extra_data.borrow_mut().scroll_policy = layer_properties.scroll_policy; *self.transform.borrow_mut() = layer_properties.transform; *self.perspective.borrow_mut() = layer_properties.perspective; - *self.background_color.borrow_mut() = to_layers_color(&layer_properties.background_color); + { + let mut extra_data = self.extra_data.borrow_mut(); + extra_data.scroll_policy = layer_properties.scroll_policy; + extra_data.invalid_rect = extra_data.invalid_rect.union(&layer_properties.invalid_rect); + } + *self.background_color.borrow_mut() = to_layers_color(&layer_properties.background_color); self.contents_changed(); } @@ -224,7 +232,10 @@ impl CompositorLayer for Layer { self.add_buffer(buffer); } - compositor.cache_unused_buffers(self.collect_unused_buffers()) + compositor.cache_unused_buffers(self.collect_unused_buffers()); + + // This layer is now up-to-date. + self.extra_data.borrow_mut().invalid_rect = Rect::zero(); } fn clear(&self, compositor: &mut IOCompositor) where Window: WindowMethods { diff --git a/components/gfx/display_list/invalidation.rs b/components/gfx/display_list/invalidation.rs new file mode 100644 index 000000000000..05850b25bf94 --- /dev/null +++ b/components/gfx/display_list/invalidation.rs @@ -0,0 +1,565 @@ +/* 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/. */ + +//! Computes the differences between two display lists. + +use display_list::{BaseDisplayItem, BorderDisplayItem, BoxShadowDisplayItem, DisplayItem}; +use display_list::{DisplayItemIterator, DisplayList, GradientDisplayItem, GradientStop}; +use display_list::{ImageDisplayItem, LineDisplayItem, SolidColorDisplayItem, StackingContext}; +use display_list::{TextDisplayItem}; +use text::TextRun; + +use euclid::point::Point2D; +use euclid::rect::Rect; +use fnv::FnvHasher; +use msg::compositor_msg::{LayerId, LayerKind}; +use net_traits::image::base::Image; +use std::collections::HashMap; +use std::collections::hash_map::Entry; +use std::collections::hash_state::DefaultState; +use std::collections::linked_list::{self, LinkedList}; +use std::sync::Arc; +use util::geometry::{Au, ZERO_RECT}; + +pub fn compute_invalid_regions(old_stacking_context: &StackingContext, + new_stacking_context: &StackingContext, + layer_kind: LayerKind) + -> HashMap, DefaultState> { + // TODO(pcwalton): This is a hack to avoid running DLBI on 3d transformed tiles. We should have + // a better solution than just disabling DLBI here. + if layer_kind == LayerKind::Layer3D { + return invalidate_entire_stacking_context(new_stacking_context) + } + + let mut invalidation_computer = DisplayListInvalidationComputer { + invalid_rects: HashMap::with_hash_state(Default::default()), + }; + let position_in_layer = old_stacking_context.overflow.origin - + old_stacking_context.bounds.origin; + invalidation_computer.compute_invalid_regions(old_stacking_context, + new_stacking_context, + old_stacking_context.layer.as_ref().unwrap().id, + &position_in_layer); + invalidation_computer.invalid_rects +} + +pub fn invalidate_entire_stacking_context(stacking_context: &StackingContext) + -> HashMap, DefaultState> { + let mut invalidation_computer = DisplayListInvalidationComputer { + invalid_rects: HashMap::with_hash_state(Default::default()), + }; + let position_in_layer = stacking_context.overflow.origin - stacking_context.bounds.origin; + invalidation_computer.invalidate_entire_stacking_context( + stacking_context, + stacking_context.layer.as_ref().unwrap().id, + &position_in_layer); + invalidation_computer.invalid_rects +} + +struct DisplayListInvalidationComputer { + invalid_rects: HashMap, DefaultState>, +} + +impl DisplayListInvalidationComputer { + /// This function assumes that the two stacking contexts are positioned at the same position in + /// the parent layer. + fn compute_invalid_regions(&mut self, + old_stacking_context: &StackingContext, + new_stacking_context: &StackingContext, + layer_id: LayerId, + position_in_layer: &Point2D) { + let mut invalid_rect = ZERO_RECT; + + // Compute differences between display items. + compute_differences(&*old_stacking_context.display_list, + &*new_stacking_context.display_list, + |result| { + if let DisplayItemComparisonResult::Different(display_item) = + result { + display_item.invalidate(&mut invalid_rect); + } + }); + + // Write in the resulting invalid rect. + self.invalidate_rect(&invalid_rect, layer_id, position_in_layer); + + // Compute differences between child stacking contexts. + compute_differences(&*old_stacking_context.display_list.children, + &*new_stacking_context.display_list.children, + |result| { + match result { + DisplayItemComparisonResult::Same(old_child, new_child) => { + // This child stacking context is in the same position. + // Perform DLBI on its display list. + let (layer_id_for_child, position_in_layer_for_child) = + layer_id_and_position_in_layer_for_stacking_context_child( + old_child, + layer_id, + position_in_layer); + self.compute_invalid_regions(old_child, + new_child, + layer_id_for_child, + &position_in_layer_for_child); + } + DisplayItemComparisonResult::Different( + child_stacking_context) => { + // This child stacking context is in a different position + // entirely. Invalidate the entire thing. + let (layer_id_for_child, position_in_layer_for_child) = + layer_id_and_position_in_layer_for_stacking_context_child( + child_stacking_context, + layer_id, + position_in_layer); + self.invalidate_entire_stacking_context( + child_stacking_context, + layer_id_for_child, + &position_in_layer_for_child) + } + } + }); + } + + fn invalidate_entire_stacking_context(&mut self, + stacking_context: &StackingContext, + layer_id: LayerId, + position_in_layer: &Point2D) { + self.invalidate_rect(&stacking_context.overflow.translate(&stacking_context.bounds.origin), + layer_id, + position_in_layer); + + for child_stacking_context in stacking_context.display_list.children.iter() { + let (layer_id_for_child, position_in_layer_for_child) = + layer_id_and_position_in_layer_for_stacking_context_child(child_stacking_context, + layer_id, + position_in_layer); + self.invalidate_entire_stacking_context(child_stacking_context, + layer_id_for_child, + &position_in_layer_for_child); + } + } + + fn invalidate_rect(&mut self, + invalid_rect: &Rect, + layer_id: LayerId, + position_in_layer: &Point2D) { + let invalid_rect = invalid_rect.translate(position_in_layer); + + match self.invalid_rects.entry(layer_id) { + Entry::Vacant(entry) => { + entry.insert(invalid_rect); + } + Entry::Occupied(ref mut entry) => { + *entry.get_mut() = entry.get().union(&invalid_rect) + } + } + } +} + +fn layer_id_and_position_in_layer_for_stacking_context_child( + child_stacking_context: &StackingContext, + parent_layer_id: LayerId, + parent_position_in_layer: &Point2D) + -> (LayerId, Point2D) { + match child_stacking_context.layer { + Some(ref layer) => { + (layer.id, + child_stacking_context.overflow.origin - child_stacking_context.bounds.origin) + } + None => { + (parent_layer_id, + *parent_position_in_layer + child_stacking_context.overflow.origin - + child_stacking_context.bounds.origin) + } + } +} + +/// A simple O(n) approximation algorithm to compute the differences between two sets of objects. +/// We simply throw out equivalent items from the front and back of each list and invalidate the +/// remainder. The full diff(1) algorithm (longest common subsequence) is O(n^2), which is likely +/// too expensive for lots of display items. +fn compute_differences<'a,T,I,B,F>(old_objects: B, new_objects: B, mut callback: F) + where T: DisplayItemComparison + DisplayItemPrint + 'a, + I: Iterator, + B: Iterable + Copy, + F: FnMut(DisplayItemComparisonResult<&'a T>) { + // Find the first display item that differs. + let (mut old_objects_iter, mut new_objects_iter) = (old_objects.iter(), new_objects.iter()); + let (mut old_object_list, mut new_object_list) = (Vec::new(), Vec::new()); + loop { + let old_object = match old_objects_iter.next() { + Some(old_object) => old_object, + None => break, + }; + let new_object = match new_objects_iter.next() { + Some(new_object) => new_object, + None => break, + }; + if old_object.must_be_identical_to(new_object) { + callback(DisplayItemComparisonResult::Same(old_object, new_object)); + continue + } + old_object_list.push(old_object); + new_object_list.push(new_object); + break + } + + // Gather up the remaining old objects and new objects into a list. + for old_object in old_objects_iter { + old_object_list.push(old_object) + } + for new_object in new_objects_iter { + new_object_list.push(new_object) + } + + // Throw out identical old and new objects from the end of the list. + loop { + match (old_object_list.last(), new_object_list.last()) { + (Some(old_object), Some(new_object)) if + old_object.must_be_identical_to(new_object) => { + callback(DisplayItemComparisonResult::Same(old_object, new_object)); + } + _ => break, + } + old_object_list.pop(); + new_object_list.pop(); + } + + + // Invalidate differences. + for old_object in old_object_list.iter() { + callback(DisplayItemComparisonResult::Different(old_object)); + } + for new_object in old_object_list.iter() { + callback(DisplayItemComparisonResult::Different(new_object)); + } +} + +enum DisplayItemComparisonResult { + Same(T, T), + Different(T), +} + +trait Iterable { + type ConcreteIterator; + + fn iter(self) -> Self::ConcreteIterator; +} + +impl<'a> Iterable for &'a DisplayList { + type ConcreteIterator = DisplayItemIterator<'a>; + + fn iter(self) -> DisplayItemIterator<'a> { + self.iter() + } +} + +impl<'a,T> Iterable for &'a LinkedList { + type ConcreteIterator = linked_list::Iter<'a,T>; + + fn iter(self) -> linked_list::Iter<'a,T> { + self.iter() + } +} + +/// A trait for marking areas as invalid. +trait DisplayItemInvalidation { + /// Modifies the given rectangle to include the area that this display item occupies. + fn invalidate(&self, invalid_rect: &mut Rect); +} + +impl DisplayItemInvalidation for DisplayItem { + fn invalidate(&self, invalid_rect: &mut Rect) { + *invalid_rect = invalid_rect.union(&self.base().bounds) + } +} + +/// A trait for quick display item comparison. +trait DisplayItemComparison { + /// Returns true if this display item must have precisely the same appearance as the given + /// display item. This is optimized for speed above all else; the comparison can have false + /// negatives. + fn must_be_identical_to(&self, other: &Self) -> bool; +} + +impl DisplayItemComparison for DisplayItem { + fn must_be_identical_to(&self, other: &DisplayItem) -> bool { + match (self, other) { + (&DisplayItem::SolidColorClass(ref this_solid_color_display_item), + &DisplayItem::SolidColorClass(ref other_solid_color_display_item)) => { + this_solid_color_display_item.must_be_identical_to(other_solid_color_display_item) + } + (&DisplayItem::TextClass(ref this_text_display_item), + &DisplayItem::TextClass(ref other_text_display_item)) => { + this_text_display_item.must_be_identical_to(other_text_display_item) + } + (&DisplayItem::ImageClass(ref this_image_display_item), + &DisplayItem::ImageClass(ref other_image_display_item)) => { + this_image_display_item.must_be_identical_to(other_image_display_item) + } + (&DisplayItem::BorderClass(ref this_border_display_item), + &DisplayItem::BorderClass(ref other_border_display_item)) => { + this_border_display_item.must_be_identical_to(other_border_display_item) + } + (&DisplayItem::GradientClass(ref this_gradient_display_item), + &DisplayItem::GradientClass(ref other_gradient_display_item)) => { + this_gradient_display_item.must_be_identical_to(other_gradient_display_item) + } + (&DisplayItem::LineClass(ref this_line_display_item), + &DisplayItem::LineClass(ref other_line_display_item)) => { + this_line_display_item.must_be_identical_to(other_line_display_item) + } + (&DisplayItem::BoxShadowClass(ref this_box_shadow_display_item), + &DisplayItem::BoxShadowClass(ref other_box_shadow_display_item)) => { + this_box_shadow_display_item.must_be_identical_to(other_box_shadow_display_item) + } + _ => false, + } + } +} + +impl DisplayItemComparison for BaseDisplayItem { + fn must_be_identical_to(&self, other: &BaseDisplayItem) -> bool { + self.bounds == other.bounds && self.clip == other.clip + } +} + +impl DisplayItemComparison for SolidColorDisplayItem { + fn must_be_identical_to(&self, other: &SolidColorDisplayItem) -> bool { + // We use destructuring here so that if any new structure fields are added the compiler + // will require that we update this method. + let &SolidColorDisplayItem { + base: ref this_base, + color: ref this_color, + } = self; + let &SolidColorDisplayItem { + base: ref other_base, + color: ref other_color + } = other; + this_base.must_be_identical_to(other_base) && this_color == other_color + } +} + +impl DisplayItemComparison for TextDisplayItem { + fn must_be_identical_to(&self, other: &TextDisplayItem) -> bool { + // We use destructuring here so that if any new structure fields are added the compiler + // will require that we update this method. + let &TextDisplayItem { + base: ref this_base, + text_run: ref this_text_run, + range: ref this_range, + text_color: ref this_text_color, + baseline_origin: ref this_baseline_origin, + orientation: ref this_orientation, + blur_radius: ref this_blur_radius, + } = self; + let &TextDisplayItem { + base: ref other_base, + text_run: ref other_text_run, + range: ref other_range, + text_color: ref other_text_color, + baseline_origin: ref other_baseline_origin, + orientation: ref other_orientation, + blur_radius: ref other_blur_radius, + } = other; + this_base.must_be_identical_to(other_base) && + (&***this_text_run as *const TextRun) == (&***other_text_run as *const TextRun) && + this_range == other_range && + this_text_color == other_text_color && + this_baseline_origin == other_baseline_origin && + this_orientation == other_orientation && + this_blur_radius == other_blur_radius + } +} + +impl DisplayItemComparison for ImageDisplayItem { + fn must_be_identical_to(&self, other: &ImageDisplayItem) -> bool { + // We use destructuring here so that if any new structure fields are added the compiler + // will require that we update this method. + let &ImageDisplayItem { + base: ref this_base, + image: ref this_image, + stretch_size: ref this_stretch_size, + image_rendering: ref this_image_rendering, + } = self; + let &ImageDisplayItem { + base: ref other_base, + image: ref other_image, + stretch_size: ref other_stretch_size, + image_rendering: ref other_image_rendering, + } = other; + this_base.must_be_identical_to(other_base) && + (&**this_image as *const Image) == (&**other_image as *const Image) && + this_stretch_size == other_stretch_size && + this_image_rendering == other_image_rendering + } +} + +impl DisplayItemComparison for GradientDisplayItem { + fn must_be_identical_to(&self, other: &GradientDisplayItem) -> bool { + // We use destructuring here so that if any new structure fields are added the compiler + // will require that we update this method. + let &GradientDisplayItem { + base: ref this_base, + start_point: ref this_start_point, + end_point: ref this_end_point, + stops: ref this_stops, + } = self; + let &GradientDisplayItem { + base: ref other_base, + start_point: ref other_start_point, + end_point: ref other_end_point, + stops: ref other_stops, + } = other; + this_base.must_be_identical_to(other_base) && + this_start_point == other_start_point && + this_end_point == other_end_point && + this_stops.len() == other_stops.len() && + this_stops.iter().zip(other_stops.iter()).all(|(this_stop, other_stop)| { + this_stop.must_be_identical_to(other_stop) + }) + } +} + +impl DisplayItemComparison for BorderDisplayItem { + fn must_be_identical_to(&self, other: &BorderDisplayItem) -> bool { + // We use destructuring here so that if any new structure fields are added the compiler + // will require that we update this method. + let &BorderDisplayItem { + base: ref this_base, + border_widths: ref this_border_widths, + color: ref this_color, + style: ref this_style, + radius: ref this_radius, + } = self; + let &BorderDisplayItem { + base: ref other_base, + border_widths: ref other_border_widths, + color: ref other_color, + style: ref other_style, + radius: ref other_radius, + } = other; + this_base.must_be_identical_to(other_base) && + this_border_widths == other_border_widths && + this_color == other_color && + this_style == other_style && + this_radius == other_radius + } +} + +impl DisplayItemComparison for LineDisplayItem { + fn must_be_identical_to(&self, other: &LineDisplayItem) -> bool { + // We use destructuring here so that if any new structure fields are added the compiler + // will require that we update this method. + let &LineDisplayItem { + base: ref this_base, + color: ref this_color, + style: ref this_style, + } = self; + let &LineDisplayItem { + base: ref other_base, + color: ref other_color, + style: ref other_style, + } = other; + this_base.must_be_identical_to(other_base) && + this_color == other_color && + this_style == other_style + } +} + +impl DisplayItemComparison for BoxShadowDisplayItem { + fn must_be_identical_to(&self, other: &BoxShadowDisplayItem) -> bool { + // We use destructuring here so that if any new structure fields are added the compiler + // will require that we update this method. + let &BoxShadowDisplayItem { + base: ref this_base, + box_bounds: ref this_box_bounds, + offset: ref this_offset, + color: ref this_color, + blur_radius: ref this_blur_radius, + spread_radius: ref this_spread_radius, + clip_mode: ref this_clip_mode, + } = self; + let &BoxShadowDisplayItem { + base: ref other_base, + box_bounds: ref other_box_bounds, + offset: ref other_offset, + color: ref other_color, + blur_radius: ref other_blur_radius, + spread_radius: ref other_spread_radius, + clip_mode: ref other_clip_mode, + } = other; + this_base.must_be_identical_to(other_base) && + this_box_bounds == other_box_bounds && + this_offset == other_offset && + this_color == other_color && + this_blur_radius == other_blur_radius && + this_spread_radius == other_spread_radius && + this_clip_mode == other_clip_mode + } +} + +impl DisplayItemComparison for GradientStop { + fn must_be_identical_to(&self, other: &GradientStop) -> bool { + self.offset == other.offset && self.color == other.color + } +} + +impl DisplayItemComparison for Arc { + fn must_be_identical_to(&self, other: &Arc) -> bool { + // We use destructuring here so that if any new structure fields are added the compiler + // will require that we update this method. + // + // NB: This one is not actually "must-be-identical-to", but it's OK because we only use + // differences here to determine which stacking contexts to perform full DLBI on. + let &StackingContext { + bounds: ref this_bounds, + z_index: this_z_index, + filters: ref this_filters, + blend_mode: ref this_blend_mode, + transform: ref this_transform, + perspective: ref this_perspective, + establishes_3d_context: ref this_establishes_3d_context, + display_list: _, + layer: _, + overflow: _, + } = &**self; + let &StackingContext { + bounds: ref other_bounds, + z_index: other_z_index, + filters: ref other_filters, + blend_mode: ref other_blend_mode, + transform: ref other_transform, + perspective: ref other_perspective, + establishes_3d_context: ref other_establishes_3d_context, + display_list: _, + layer: _, + overflow: _, + } = &**other; + this_bounds == other_bounds && + this_z_index == other_z_index && + this_filters == other_filters && + this_blend_mode == other_blend_mode && + this_transform == other_transform && + this_perspective == other_perspective && + this_establishes_3d_context == other_establishes_3d_context + } +} + +/// A trait for debug printing of display items. +trait DisplayItemPrint { + fn print(&self, indentation: &str); +} + +impl DisplayItemPrint for DisplayItem { + fn print(&self, indentation: &str) { + DisplayItem::print(self, indentation) + } +} + +impl DisplayItemPrint for Arc { + fn print(&self, indentation: &str) { + self.display_list.print_items(indentation.to_owned()) + } +} + diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index 9610c78b80f3..ddf3e466f4c2 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -19,7 +19,6 @@ use display_list::optimizer::DisplayListOptimizer; use paint_context::{PaintContext, ToAzureRect}; use self::DisplayItem::*; -use self::DisplayItemIterator::*; use text::glyph::CharIndex; use text::TextRun; @@ -51,6 +50,7 @@ use util::range::Range; // layout to use. pub use azure::azure_hl::GradientStop; +pub mod invalidation; pub mod optimizer; /// The factor that we multiply the blur radius by in order to inflate the boundaries of display @@ -151,61 +151,25 @@ impl DisplayList { /// Returns a list of all items in this display list concatenated together. This is extremely /// inefficient and should only be used for debugging. pub fn all_display_items(&self) -> Vec { - let mut result = Vec::new(); - for display_item in self.background_and_borders.iter() { - result.push((*display_item).clone()) - } - for display_item in self.block_backgrounds_and_borders.iter() { - result.push((*display_item).clone()) - } - for display_item in self.floats.iter() { - result.push((*display_item).clone()) - } - for display_item in self.content.iter() { - result.push((*display_item).clone()) - } - for display_item in self.positioned_content.iter() { - result.push((*display_item).clone()) - } - for display_item in self.outlines.iter() { - result.push((*display_item).clone()) + self.iter().map(|display_item| (*display_item).clone()).collect() + } + + /// Returns an iterator over all display items in this display list. + pub fn iter<'a>(&'a self) -> DisplayItemIterator<'a> { + DisplayItemIterator { + display_list: self, + iterator: self.background_and_borders.iter(), + step: Some(DisplayStep::BackgroundAndBorders), } - result } - // Print the display list. Only makes sense to call it after performing reflow. + /// Print the display list. Only makes sense to call it after performing reflow. pub fn print_items(&self, indentation: String) { - // Closures are so nice! - let doit = |items: &Vec| { - for item in items.iter() { - match *item { - DisplayItem::SolidColorClass(ref solid_color) => { - println!("{:?} SolidColor. {:?}", indentation, solid_color.base.bounds) - } - DisplayItem::TextClass(ref text) => { - println!("{:?} Text. {:?}", indentation, text.base.bounds) - } - DisplayItem::ImageClass(ref image) => { - println!("{:?} Image. {:?}", indentation, image.base.bounds) - } - DisplayItem::BorderClass(ref border) => { - println!("{:?} Border. {:?}", indentation, border.base.bounds) - } - DisplayItem::GradientClass(ref gradient) => { - println!("{:?} Gradient. {:?}", indentation, gradient.base.bounds) - } - DisplayItem::LineClass(ref line) => { - println!("{:?} Line. {:?}", indentation, line.base.bounds) - } - DisplayItem::BoxShadowClass(ref box_shadow) => { - println!("{:?} Box_shadow. {:?}", indentation, box_shadow.base.bounds) - } - } - } - println!("\n"); - }; + for item in self.all_display_items().iter() { + item.print(&*indentation) + } + println!("\n"); - doit(&(self.all_display_items())); if self.children.len() != 0 { println!("{} Children stacking contexts list length: {}", indentation, @@ -218,8 +182,66 @@ impl DisplayList { } } -#[derive(HeapSizeOf, Deserialize, Serialize)] +/// Steps as defined in CSS 2.1 Appendix E. +#[derive(HeapSizeOf, Copy, Clone, Deserialize, Serialize)] +enum DisplayStep { + BackgroundAndBorders, + BlockBackgroundsAndBorders, + Floats, + Content, + PositionedContent, + Outlines, +} + +pub struct DisplayItemIterator<'a> { + display_list: &'a DisplayList, + iterator: linked_list::Iter<'a,DisplayItem>, + step: Option, +} + +impl<'a> Iterator for DisplayItemIterator<'a> { + type Item = &'a DisplayItem; + + fn next(&mut self) -> Option<&'a DisplayItem> { + loop { + let step = match self.step { + None => return None, + Some(step) => step, + }; + + if let Some(item) = self.iterator.next() { + return Some(item) + } + + match step { + DisplayStep::BackgroundAndBorders => { + self.step = Some(DisplayStep::BlockBackgroundsAndBorders); + self.iterator = self.display_list.block_backgrounds_and_borders.iter() + } + DisplayStep::BlockBackgroundsAndBorders => { + self.step = Some(DisplayStep::Floats); + self.iterator = self.display_list.floats.iter() + } + DisplayStep::Floats => { + self.step = Some(DisplayStep::Content); + self.iterator = self.display_list.content.iter() + } + DisplayStep::Content => { + self.step = Some(DisplayStep::PositionedContent); + self.iterator = self.display_list.positioned_content.iter() + } + DisplayStep::PositionedContent => { + self.step = Some(DisplayStep::Outlines); + self.iterator = self.display_list.outlines.iter() + } + DisplayStep::Outlines => self.step = None, + } + } + } +} + /// Represents one CSS stacking context, which may or may not have a hardware layer. +#[derive(HeapSizeOf, Serialize, Deserialize)] pub struct StackingContext { /// The display items that make up this stacking context. pub display_list: Box, @@ -1033,22 +1055,6 @@ pub enum BoxShadowClipMode { Inset, } -pub enum DisplayItemIterator<'a> { - Empty, - Parent(linked_list::Iter<'a,DisplayItem>), -} - -impl<'a> Iterator for DisplayItemIterator<'a> { - type Item = &'a DisplayItem; - #[inline] - fn next(&mut self) -> Option<&'a DisplayItem> { - match *self { - DisplayItemIterator::Empty => None, - DisplayItemIterator::Parent(ref mut subiterator) => subiterator.next(), - } - } -} - impl DisplayItem { /// Paints this display item into the given painting context. fn draw_into_context(&self, paint_context: &mut PaintContext) { @@ -1145,6 +1151,32 @@ impl DisplayItem { } println!("{}+ {:?}", indent, self); } + + pub fn print(&self, indentation: &str) { + match *self { + DisplayItem::SolidColorClass(ref solid_color) => { + println!("{}SolidColor. {:?}", indentation, solid_color.base.bounds) + } + DisplayItem::TextClass(ref text) => { + println!("{}Text. {:?}", indentation, text.base.bounds) + } + DisplayItem::ImageClass(ref image) => { + println!("{}Image. {:?}", indentation, image.base.bounds) + } + DisplayItem::BorderClass(ref border) => { + println!("{}Border. {:?}", indentation, border.base.bounds) + } + DisplayItem::GradientClass(ref gradient) => { + println!("{}Gradient. {:?}", indentation, gradient.base.bounds) + } + DisplayItem::LineClass(ref line) => { + println!("{}Line. {:?}", indentation, line.base.bounds) + } + DisplayItem::BoxShadowClass(ref box_shadow) => { + println!("{}Box_shadow. {:?}", indentation, box_shadow.base.bounds) + } + } + } } impl fmt::Debug for DisplayItem { diff --git a/components/gfx/paint_task.rs b/components/gfx/paint_task.rs index ba9f73a154dc..d00e9cd40ac9 100644 --- a/components/gfx/paint_task.rs +++ b/components/gfx/paint_task.rs @@ -4,6 +4,7 @@ //! The task that handles all painting. +use display_list::invalidation; use display_list::{self, StackingContext}; use font_cache_task::FontCacheTask; use font_context::FontContext; @@ -16,6 +17,7 @@ use euclid::Matrix4; use euclid::point::Point2D; use euclid::rect::Rect; use euclid::size::Size2D; +use fnv::FnvHasher; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use layers::platform::surface::{NativeDisplay, NativeSurface}; @@ -30,6 +32,7 @@ use profile_traits::time::{self, profile}; use rand::{self, Rng}; use skia::gl_context::GLContext; use std::borrow::ToOwned; +use std::collections::hash_state::DefaultState; use std::mem as std_mem; use std::sync::Arc; use std::sync::mpsc::{Receiver, Sender, channel}; @@ -210,7 +213,23 @@ impl PaintTask where C: PaintListener + Send + 'static { loop { match self.port.recv().unwrap() { Msg::PaintInit(epoch, stacking_context) => { + let layer_kind = if stacking_context.perspective == Matrix4::identity() { + LayerKind::Layer2D + } else { + LayerKind::Layer3D + }; + self.current_epoch = Some(epoch); + let invalid_rects = match self.root_stacking_context { + Some(ref old_stacking_context) => { + invalidation::compute_invalid_regions(&**old_stacking_context, + &*stacking_context, + layer_kind) + } + None => { + invalidation::invalidate_entire_stacking_context(&*stacking_context) + } + }; self.root_stacking_context = Some(stacking_context.clone()); if !self.paint_permission { @@ -220,7 +239,7 @@ impl PaintTask where C: PaintListener + Send + 'static { continue; } - self.initialize_layers(); + self.initialize_layers(&invalid_rects); } // Inserts a new canvas renderer to the layer map Msg::CanvasLayer(layer_id, canvas_renderer) => { @@ -258,7 +277,7 @@ impl PaintTask where C: PaintListener + Send + 'static { self.paint_permission = true; if self.root_stacking_context.is_some() { - self.initialize_layers(); + self.initialize_layers(&HashMap::with_hash_state(Default::default())); } } Msg::PaintPermissionRevoked => { @@ -324,7 +343,8 @@ impl PaintTask where C: PaintListener + Send + 'static { }) } - fn initialize_layers(&mut self) { + fn initialize_layers(&mut self, + invalid_rects: &HashMap, DefaultState>) { let root_stacking_context = match self.root_stacking_context { None => return, Some(ref root_stacking_context) => root_stacking_context, @@ -334,69 +354,88 @@ impl PaintTask where C: PaintListener + Send + 'static { build(&mut properties, &**root_stacking_context, &ZERO_POINT, + invalid_rects, &Matrix4::identity(), &Matrix4::identity(), None); - self.compositor.initialize_layers_for_pipeline(self.id, properties, self.current_epoch.unwrap()); + self.compositor.initialize_layers_for_pipeline(self.id, + properties, + self.current_epoch.unwrap()); fn build(properties: &mut Vec, stacking_context: &StackingContext, page_position: &Point2D, + invalid_rects: &HashMap, DefaultState>, transform: &Matrix4, perspective: &Matrix4, parent_id: Option) { - let transform = transform.mul(&stacking_context.transform); let perspective = perspective.mul(&stacking_context.perspective); let (next_parent_id, page_position, transform, perspective) = match stacking_context.layer { - Some(ref paint_layer) => { - // Layers start at the top left of their overflow rect, as far as the info we - // give to the compositor is concerned. - let overflow_relative_page_position = *page_position + - stacking_context.bounds.origin + - stacking_context.overflow.origin; - let layer_position = - Rect::new(Point2D::new(overflow_relative_page_position.x.to_nearest_px() as - f32, - overflow_relative_page_position.y.to_nearest_px() as - f32), - Size2D::new(stacking_context.overflow.size.width.to_nearest_px() - as f32, - stacking_context.overflow.size.height.to_nearest_px() - as f32)); - - let establishes_3d_context = stacking_context.establishes_3d_context; - - properties.push(LayerProperties { - id: paint_layer.id, - parent_id: parent_id, - rect: layer_position, - background_color: paint_layer.background_color, - scroll_policy: paint_layer.scroll_policy, - transform: transform, - perspective: perspective, - establishes_3d_context: establishes_3d_context, - }); - - // When there is a new layer, the transforms and origin - // are handled by the compositor. - (Some(paint_layer.id), - Point2D::zero(), - Matrix4::identity(), - Matrix4::identity()) - } - None => { - (parent_id, - stacking_context.bounds.origin + *page_position, - transform, - perspective) - } - }; + Some(ref paint_layer) => { + // Layers start at the top left of their overflow rect, as far as the info + // we give to the compositor is concerned. + let overflow_relative_page_position = *page_position + + stacking_context.bounds.origin + + stacking_context.overflow.origin; + let layer_position = Rect::new( + Point2D::new(overflow_relative_page_position.x.to_nearest_px() as f32, + overflow_relative_page_position.y.to_nearest_px() as f32), + Size2D::new( + stacking_context.overflow.size.width.to_nearest_px() as f32, + stacking_context.overflow.size.height.to_nearest_px() as f32)); + + let invalid_rect = match invalid_rects.get(&paint_layer.id) { + None => Rect::new(Point2D::new(0.0, 0.0), layer_position.size), + Some(invalid_rect) => { + // FIXME(pcwalton): Round out? + Rect::new(Point2D::new( + invalid_rect.origin.x.to_nearest_px() as f32, + invalid_rect.origin.y.to_nearest_px() as f32), + Size2D::new(invalid_rect.size.width.to_nearest_px() as f32, + invalid_rect.size.height.to_nearest_px() as f32)) + } + }; + + let establishes_3d_context = stacking_context.establishes_3d_context; + + properties.push(LayerProperties { + id: paint_layer.id, + parent_id: parent_id, + rect: layer_position, + background_color: paint_layer.background_color, + scroll_policy: paint_layer.scroll_policy, + invalid_rect: invalid_rect, + transform: transform, + perspective: perspective, + establishes_3d_context: establishes_3d_context, + }); + + // When there is a new layer, the transforms and origin + // are handled by the compositor. + (Some(paint_layer.id), + Point2D::zero(), + Matrix4::identity(), + Matrix4::identity()) + } + None => { + (parent_id, + stacking_context.bounds.origin + *page_position, + transform, + perspective) + } + }; for kid in stacking_context.display_list.children.iter() { - build(properties, &**kid, &page_position, &transform, &perspective, next_parent_id) + build(properties, + &**kid, + &page_position, + invalid_rects, + &transform, + &perspective, + next_parent_id); } } } @@ -510,7 +549,11 @@ impl WorkerThread { loop { match self.receiver.recv().unwrap() { MsgToWorkerThread::Exit => break, - MsgToWorkerThread::PaintTile(thread_id, tile, stacking_context, scale, layer_kind) => { + MsgToWorkerThread::PaintTile(thread_id, + tile, + stacking_context, + scale, + layer_kind) => { let buffer = self.optimize_and_paint_tile(thread_id, tile, stacking_context, diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 9569947da139..5c163533faea 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -45,10 +45,9 @@ use std::sync::Arc; use std::sync::mpsc::channel; use std::f32; use style::computed_values::filter::Filter; -use style::computed_values::{background_attachment, background_clip, background_origin, - background_repeat, background_size}; -use style::computed_values::{border_style, image_rendering, overflow_x, position, - visibility, transform, transform_style}; +use style::computed_values::{background_attachment, background_clip, background_origin}; +use style::computed_values::{background_repeat, background_size, border_style, image_rendering}; +use style::computed_values::{overflow_x, position, transform, transform_style, visibility}; use style::properties::ComputedValues; use style::properties::style_structs::Border; use style::values::RGBA; diff --git a/components/msg/compositor_msg.rs b/components/msg/compositor_msg.rs index 3a1d098eb363..7a69deb81011 100644 --- a/components/msg/compositor_msg.rs +++ b/components/msg/compositor_msg.rs @@ -82,6 +82,8 @@ pub struct LayerProperties { pub background_color: Color, /// The scrolling policy of this layer. pub scroll_policy: ScrollPolicy, + /// The invalid rectangle for this layer. + pub invalid_rect: Rect, /// The transform for this layer pub transform: Matrix4, /// The perspective transform for this layer diff --git a/components/util/range.rs b/components/util/range.rs index d0143a30340e..02a91c75d689 100644 --- a/components/util/range.rs +++ b/components/util/range.rs @@ -124,7 +124,7 @@ macro_rules! int_range_index { } /// A range of indices -#[derive(Clone, RustcEncodable, Copy, Deserialize, Serialize)] +#[derive(Clone, RustcEncodable, PartialEq, Copy, Deserialize, Serialize)] pub struct Range { begin: I, length: I, diff --git a/tests/html/dlbi.html b/tests/html/dlbi.html new file mode 100644 index 000000000000..feb694b6961c --- /dev/null +++ b/tests/html/dlbi.html @@ -0,0 +1,22 @@ + + + + + + +

Receive a fortune from Zoltar:

+ +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean eget mauris mauris. Maecenas quis faucibus mauris. Donec placerat pretium volutpat. Suspendisse efficitur faucibus diam, cursus convallis dui fringilla ac. Pellentesque ut ex auctor, ornare leo in, placerat risus. Proin euismod ultrices ex at facilisis. Suspendisse congue et mi vitae feugiat. Proin rutrum in nisl quis semper. In imperdiet interdum lectus eu accumsan. Nullam vestibulum, libero at interdum malesuada, massa diam tristique arcu, sit amet venenatis urna massa eget eros. Suspendisse at tortor vitae sapien posuere egestas sed quis dui. Fusce aliquam leo laoreet imperdiet porta. Quisque porta ex nec scelerisque scelerisque. Vivamus tortor nibh, eleifend eu eros sed, pretium vulputate quam. Nunc accumsan diam porttitor augue sollicitudin dignissim eu in dui. Nunc aliquam euismod urna, in feugiat libero imperdiet et. Nullam sit amet elit nec arcu vulputate dictum. Mauris quam dolor, viverra dictum ante nec, vulputate iaculis turpis. Cras sollicitudin porta erat eget fermentum. Cras sit amet elit ultricies, commodo tellus nec, finibus neque. Suspendisse accumsan sem maximus, semper purus ornare, porta nibh. Maecenas et arcu vitae libero pulvinar ornare. Cras in pharetra tortor. Aliquam auctor mi vitae libero sagittis consequat. Morbi in nulla eget quam dignissim vulputate. Pellentesque maximus nisl tempor justo elementum, eu placerat mi pharetra. Vestibulum et felis felis. Vivamus vitae lobortis felis, ac porttitor ipsum.

+ + + diff --git a/tests/wpt/css-tests/css21_dev/html4/border-left-color-030.htm b/tests/wpt/css-tests/css21_dev/html4/border-left-color-030.htm index f26e06c47fd2..4019f7017ee5 100644 --- a/tests/wpt/css-tests/css21_dev/html4/border-left-color-030.htm +++ b/tests/wpt/css-tests/css21_dev/html4/border-left-color-030.htm @@ -24,4 +24,4 @@

Test passes if there is a filled black square.

- \ No newline at end of file +