From 5687f7d3d4c2dbead1ee4361b97ae61ed5f286b3 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Thu, 19 Jan 2017 13:18:09 -0500 Subject: [PATCH] Move all layer tree operations to a ScrollTree struct This will make it easier to introduce the idea of reference frames into WebRender. This should not change behavior. --- webrender/src/frame.rs | 441 +++-------------------------------- webrender/src/layer.rs | 5 +- webrender/src/lib.rs | 1 + webrender/src/scroll_tree.rs | 424 +++++++++++++++++++++++++++++++++ webrender/src/tiling.rs | 20 +- 5 files changed, 473 insertions(+), 418 deletions(-) create mode 100644 webrender/src/scroll_tree.rs diff --git a/webrender/src/frame.rs b/webrender/src/frame.rs index 2db9384895..39da0e2790 100644 --- a/webrender/src/frame.rs +++ b/webrender/src/frame.rs @@ -3,32 +3,23 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use app_units::Au; -use euclid::Point3D; use fnv::FnvHasher; -use geometry::ray_intersects_rect; use internal_types::{ANGLE_FLOAT_TO_FIXED, AxisDirection}; use internal_types::{CompositionOp}; use internal_types::{LowLevelFilterOp}; use internal_types::{RendererFrame}; -use layer::{Layer, ScrollingState}; +use layer::Layer; use resource_cache::ResourceCache; use scene::Scene; -use std::collections::{HashMap, HashSet}; +use scroll_tree::{ScrollTree, ScrollStates}; +use std::collections::HashMap; use std::hash::BuildHasherDefault; -use tiling::{AuxiliaryListsMap, FrameBuilder, FrameBuilderConfig, LayerMap, PrimitiveFlags}; -use webrender_traits::{AuxiliaryLists, PipelineId, Epoch, ScrollPolicy, ScrollLayerId}; -use webrender_traits::{ClipRegion, ColorF, DisplayItem, StackingContext, FilterOp, MixBlendMode}; -use webrender_traits::{ScrollEventPhase, ScrollLayerInfo, ScrollLocation, SpecificDisplayItem, ScrollLayerState}; -use webrender_traits::{LayerRect, LayerPoint, LayerSize}; -use webrender_traits::{ServoScrollRootId, ScrollLayerRect, as_scroll_parent_rect, ScrollLayerPixel}; -use webrender_traits::{WorldPoint, WorldPoint4D}; -use webrender_traits::{LayerToScrollTransform, ScrollToWorldTransform}; - -#[cfg(target_os = "macos")] -const CAN_OVERSCROLL: bool = true; - -#[cfg(not(target_os = "macos"))] -const CAN_OVERSCROLL: bool = false; +use tiling::{AuxiliaryListsMap, FrameBuilder, FrameBuilderConfig, PrimitiveFlags}; +use webrender_traits::{AuxiliaryLists, ClipRegion, ColorF, DisplayItem, Epoch, FilterOp}; +use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform, MixBlendMode}; +use webrender_traits::{PipelineId, ScrollEventPhase, ScrollLayerId, ScrollLayerState}; +use webrender_traits::{ScrollLocation, ScrollPolicy, ServoScrollRootId, SpecificDisplayItem}; +use webrender_traits::{StackingContext, WorldPoint}; #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] pub struct FrameId(pub u32); @@ -43,12 +34,9 @@ struct FlattenContext<'a> { // TODO: doc pub struct Frame { - pub layers: LayerMap, + pub scroll_tree: ScrollTree, pub pipeline_epoch_map: HashMap>, pub pipeline_auxiliary_lists: AuxiliaryListsMap, - pub root_scroll_layer_id: Option, - pending_scroll_offsets: HashMap<(PipelineId, ServoScrollRootId), LayerPoint>, - current_scroll_layer_id: Option, id: FrameId, debug: bool, frame_builder_config: FrameBuilderConfig, @@ -207,10 +195,7 @@ impl Frame { Frame { pipeline_epoch_map: HashMap::with_hasher(Default::default()), pipeline_auxiliary_lists: HashMap::with_hasher(Default::default()), - layers: HashMap::with_hasher(Default::default()), - root_scroll_layer_id: None, - pending_scroll_offsets: HashMap::new(), - current_scroll_layer_id: None, + scroll_tree: ScrollTree::new(), id: FrameId(0), debug: debug, frame_builder: None, @@ -218,82 +203,17 @@ impl Frame { } } - pub fn reset(&mut self) - -> HashMap> { + pub fn reset(&mut self) -> ScrollStates { self.pipeline_epoch_map.clear(); - // Free any render targets from last frame. - // TODO: This should really re-use existing targets here... - let mut old_layer_scrolling_states = HashMap::with_hasher(Default::default()); - for (layer_id, old_layer) in &mut self.layers.drain() { - old_layer_scrolling_states.insert(layer_id, old_layer.scrolling); - } - // Advance to the next frame. self.id.0 += 1; - old_layer_scrolling_states - } - - pub fn get_scroll_layer(&self, cursor: &WorldPoint, scroll_layer_id: ScrollLayerId) - -> Option { - self.layers.get(&scroll_layer_id).and_then(|layer| { - for child_layer_id in layer.children.iter().rev() { - if let Some(layer_id) = self.get_scroll_layer(cursor, *child_layer_id) { - return Some(layer_id); - } - } - - match scroll_layer_id.info { - ScrollLayerInfo::Fixed => { - None - } - ScrollLayerInfo::Scrollable(..) => { - let inv = layer.world_viewport_transform.inverse().unwrap(); - let z0 = -10000.0; - let z1 = 10000.0; - - let p0 = inv.transform_point4d(&WorldPoint4D::new(cursor.x, cursor.y, z0, 1.0)); - let p0 = Point3D::new(p0.x / p0.w, - p0.y / p0.w, - p0.z / p0.w); - let p1 = inv.transform_point4d(&WorldPoint4D::new(cursor.x, cursor.y, z1, 1.0)); - let p1 = Point3D::new(p1.x / p1.w, - p1.y / p1.w, - p1.z / p1.w); - - let is_unscrollable = layer.content_size.width <= layer.local_viewport_rect.size.width && - layer.content_size.height <= layer.local_viewport_rect.size.height; - if is_unscrollable { - None - } else { - let result = ray_intersects_rect(p0, p1, layer.local_viewport_rect.to_untyped()); - if result { - Some(scroll_layer_id) - } else { - None - } - } - } - } - }) + self.scroll_tree.drain() } pub fn get_scroll_layer_state(&self) -> Vec { - let mut result = vec![]; - for (scroll_layer_id, scroll_layer) in &self.layers { - match scroll_layer_id.info { - ScrollLayerInfo::Scrollable(_, servo_scroll_root_id) => { - result.push(ScrollLayerState { - pipeline_id: scroll_layer.pipeline_id, - scroll_root_id: servo_scroll_root_id, - scroll_offset: scroll_layer.scrolling.offset, - }) - } - ScrollLayerInfo::Fixed => {} - } - } - result + self.scroll_tree.get_scroll_layer_state() } /// Returns true if any layers actually changed position or false otherwise. @@ -302,30 +222,7 @@ impl Frame { pipeline_id: PipelineId, scroll_root_id: ServoScrollRootId) -> bool { - let origin = LayerPoint::new(origin.x.max(0.0), origin.y.max(0.0)); - - let mut scrolled_a_layer = false; - let mut found_layer = false; - for (layer_id, layer) in self.layers.iter_mut() { - if layer_id.pipeline_id != pipeline_id { - continue; - } - - match layer_id.info { - ScrollLayerInfo::Scrollable(_, id) if id != scroll_root_id => continue, - ScrollLayerInfo::Fixed => continue, - _ => {} - } - - found_layer = true; - scrolled_a_layer |= layer.set_scroll_origin(&origin); - } - - if !found_layer { - self.pending_scroll_offsets.insert((pipeline_id, scroll_root_id), origin); - } - - scrolled_a_layer + self.scroll_tree.scroll_layers(origin, pipeline_id, scroll_root_id) } /// Returns true if any layers actually changed position or false otherwise. @@ -334,172 +231,11 @@ impl Frame { cursor: WorldPoint, phase: ScrollEventPhase) -> bool { - let root_scroll_layer_id = match self.root_scroll_layer_id { - Some(root_scroll_layer_id) => root_scroll_layer_id, - None => return false, - }; - - let scroll_layer_id = match ( - phase, - self.get_scroll_layer(&cursor, root_scroll_layer_id), - self.current_scroll_layer_id) { - (ScrollEventPhase::Start, Some(scroll_layer_id), _) => { - self.current_scroll_layer_id = Some(scroll_layer_id); - scroll_layer_id - }, - (ScrollEventPhase::Start, None, _) => return false, - (_, _, Some(scroll_layer_id)) => scroll_layer_id, - (_, _, None) => return false, - }; - - let non_root_overscroll = if scroll_layer_id != root_scroll_layer_id { - // true if the current layer is overscrolling, - // and it is not the root scroll layer. - let child_layer = self.layers.get(&scroll_layer_id).unwrap(); - let overscroll_amount = child_layer.overscroll_amount(); - overscroll_amount.width != 0.0 || overscroll_amount.height != 0.0 - } else { - false - }; - - let switch_layer = match phase { - ScrollEventPhase::Start => { - // if this is a new gesture, we do not switch layer, - // however we do save the state of non_root_overscroll, - // for use in the subsequent Move phase. - let mut current_layer = self.layers.get_mut(&scroll_layer_id).unwrap(); - current_layer.scrolling.should_handoff_scroll = non_root_overscroll; - false - }, - ScrollEventPhase::Move(_) => { - // Switch layer if movement originated in a new gesture, - // from a non root layer in overscroll. - let current_layer = self.layers.get_mut(&scroll_layer_id).unwrap(); - current_layer.scrolling.should_handoff_scroll && non_root_overscroll - }, - ScrollEventPhase::End => { - // clean-up when gesture ends. - let mut current_layer = self.layers.get_mut(&scroll_layer_id).unwrap(); - current_layer.scrolling.should_handoff_scroll = false; - false - }, - }; - - let scroll_root_id = match (switch_layer, scroll_layer_id.info, root_scroll_layer_id.info) { - (true, _, ScrollLayerInfo::Scrollable(_, scroll_root_id)) | - (true, ScrollLayerInfo::Scrollable(_, scroll_root_id), ScrollLayerInfo::Fixed) | - (false, ScrollLayerInfo::Scrollable(_, scroll_root_id), _) => scroll_root_id, - (_, ScrollLayerInfo::Fixed, _) => unreachable!("Tried to scroll a fixed position layer."), - }; - - let mut scrolled_a_layer = false; - for (layer_id, layer) in self.layers.iter_mut() { - if layer_id.pipeline_id != scroll_layer_id.pipeline_id { - continue; - } - - match layer_id.info { - ScrollLayerInfo::Scrollable(_, id) if id != scroll_root_id => continue, - ScrollLayerInfo::Fixed => continue, - _ => {} - } - - if layer.scrolling.started_bouncing_back && phase == ScrollEventPhase::Move(false) { - continue; - } - - let mut delta = match scroll_location { - ScrollLocation::Delta(delta) => delta, - ScrollLocation::Start => { - if layer.scrolling.offset.y.round() >= 0.0 { - // Nothing to do on this layer. - continue; - } - - layer.scrolling.offset.y = 0.0; - scrolled_a_layer = true; - continue; - }, - ScrollLocation::End => { - let end_pos = layer.local_viewport_rect.size.height - - layer.content_size.height; - - if layer.scrolling.offset.y.round() <= end_pos { - // Nothing to do on this layer. - continue; - } - - layer.scrolling.offset.y = end_pos; - scrolled_a_layer = true; - continue; - } - }; - - let overscroll_amount = layer.overscroll_amount(); - let overscrolling = CAN_OVERSCROLL && (overscroll_amount.width != 0.0 || - overscroll_amount.height != 0.0); - if overscrolling { - if overscroll_amount.width != 0.0 { - delta.x /= overscroll_amount.width.abs() - } - if overscroll_amount.height != 0.0 { - delta.y /= overscroll_amount.height.abs() - } - } - - let is_unscrollable = - layer.content_size.width <= layer.local_viewport_rect.size.width && - layer.content_size.height <= layer.local_viewport_rect.size.height; - - let original_layer_scroll_offset = layer.scrolling.offset; - - if layer.content_size.width > layer.local_viewport_rect.size.width { - layer.scrolling.offset.x = layer.scrolling.offset.x + delta.x; - if is_unscrollable || !CAN_OVERSCROLL { - layer.scrolling.offset.x = layer.scrolling.offset.x.min(0.0); - layer.scrolling.offset.x = - layer.scrolling.offset.x.max(-layer.content_size.width + - layer.local_viewport_rect.size.width); - } - } - - if layer.content_size.height > layer.local_viewport_rect.size.height { - layer.scrolling.offset.y = layer.scrolling.offset.y + delta.y; - if is_unscrollable || !CAN_OVERSCROLL { - layer.scrolling.offset.y = layer.scrolling.offset.y.min(0.0); - layer.scrolling.offset.y = - layer.scrolling.offset.y.max(-layer.content_size.height + - layer.local_viewport_rect.size.height); - } - } - - if phase == ScrollEventPhase::Start || phase == ScrollEventPhase::Move(true) { - layer.scrolling.started_bouncing_back = false - } else if overscrolling && - ((delta.x < 1.0 && delta.y < 1.0) || phase == ScrollEventPhase::End) { - layer.scrolling.started_bouncing_back = true; - layer.scrolling.bouncing_back = true - } - - layer.scrolling.offset.x = layer.scrolling.offset.x.round(); - layer.scrolling.offset.y = layer.scrolling.offset.y.round(); - - if CAN_OVERSCROLL { - layer.stretch_overscroll_spring(); - } - - scrolled_a_layer = scrolled_a_layer || - layer.scrolling.offset != original_layer_scroll_offset || - layer.scrolling.started_bouncing_back; - } - - scrolled_a_layer + self.scroll_tree.scroll(scroll_location, cursor, phase,) } pub fn tick_scrolling_bounce_animations(&mut self) { - for (_, layer) in &mut self.layers { - layer.tick_scrolling_bounce_animation() - } + self.scroll_tree.tick_scrolling_bounce_animations(); } pub fn create(&mut self, @@ -521,7 +257,7 @@ impl Frame { None => return, }; - let old_layer_scrolling_states = self.reset(); + let old_scrolling_states = self.reset(); self.pipeline_auxiliary_lists = scene.pipeline_auxiliary_lists.clone(); self.pipeline_epoch_map.insert(root_pipeline_id, root_pipeline.epoch); @@ -535,18 +271,18 @@ impl Frame { }; let root_scroll_layer_id = ScrollLayerId::root(root_pipeline_id); - self.root_scroll_layer_id = Some(root_scroll_layer_id); + self.scroll_tree.root_scroll_layer_id = Some(root_scroll_layer_id); // Insert global position: fixed elements layer - debug_assert!(self.layers.is_empty()); + debug_assert!(self.scroll_tree.layers.is_empty()); let root_fixed_layer_id = ScrollLayerId::create_fixed(root_pipeline_id); let root_viewport = LayerRect::new(LayerPoint::zero(), root_pipeline.viewport_size); let layer = Layer::new(&root_viewport, root_clip.main.size, &LayerToScrollTransform::identity(), root_pipeline_id); - self.layers.insert(root_fixed_layer_id, layer.clone()); - self.layers.insert(root_scroll_layer_id, layer); + self.scroll_tree.layers.insert(root_fixed_layer_id, layer.clone()); + self.scroll_tree.layers.insert(root_scroll_layer_id, layer); let background_color = root_pipeline.background_color.and_then(|color| { if color.a > 0.0 { @@ -581,29 +317,7 @@ impl Frame { } self.frame_builder = Some(frame_builder); - - // TODO(gw): These are all independent - can be run through thread pool if it shows up - // in the profile! - for (scroll_layer_id, layer) in &mut self.layers { - let scrolling_state = match old_layer_scrolling_states.get(&scroll_layer_id) { - Some(old_scrolling_state) => *old_scrolling_state, - None => ScrollingState::new(), - }; - - layer.finalize(&scrolling_state); - - let scroll_root_id = match scroll_layer_id.info { - ScrollLayerInfo::Scrollable(_, scroll_root_id) => scroll_root_id, - _ => continue, - }; - - - let pipeline_id = scroll_layer_id.pipeline_id; - if let Some(pending_offset) = - self.pending_scroll_offsets.get_mut(&(pipeline_id, scroll_root_id)) { - layer.set_scroll_origin(pending_offset); - } - } + self.scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states); } fn flatten_scroll_layer<'a>(&mut self, @@ -623,17 +337,17 @@ impl Frame { return; } - debug_assert!(!self.layers.contains_key(&new_scroll_layer_id)); + debug_assert!(!self.scroll_tree.layers.contains_key(&new_scroll_layer_id)); let layer = Layer::new(&clip, *content_size, &layer_relative_transform, pipeline_id); debug_assert!(current_scroll_layer_id != new_scroll_layer_id); - self.layers + self.scroll_tree.layers .get_mut(¤t_scroll_layer_id) .unwrap() .add_child(new_scroll_layer_id); - self.layers.insert(new_scroll_layer_id, layer); + self.scroll_tree.layers.insert(new_scroll_layer_id, layer); current_scroll_layer_id = new_scroll_layer_id; let layer_rect = LayerRect::new(LayerPoint::zero(), @@ -746,11 +460,11 @@ impl Frame { if level == 0 && self.frame_builder_config.enable_scrollbars { let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0)); - context.builder.add_solid_rectangle(&scrollbar_rect, - &ClipRegion::simple(&scrollbar_rect), - &DEFAULT_SCROLLBAR_COLOR, - PrimitiveFlags::Scrollbar(self.root_scroll_layer_id.unwrap(), - 4.0)); + context.builder.add_solid_rectangle( + &scrollbar_rect, + &ClipRegion::simple(&scrollbar_rect), + &DEFAULT_SCROLLBAR_COLOR, + PrimitiveFlags::Scrollbar(self.scroll_tree.root_scroll_layer_id.unwrap(), 4.0)); } context.builder.pop_layer(); @@ -797,9 +511,9 @@ impl Frame { iframe_clip.main.size, &transform, pipeline_id); - self.layers.insert(iframe_fixed_layer_id, layer.clone()); - self.layers.insert(iframe_scroll_layer_id, layer); - self.layers.get_mut(¤t_scroll_layer_id).unwrap().add_child(iframe_scroll_layer_id); + self.scroll_tree.layers.insert(iframe_fixed_layer_id, layer.clone()); + self.scroll_tree.layers.insert(iframe_scroll_layer_id, layer); + self.scroll_tree.layers.get_mut(¤t_scroll_layer_id).unwrap().add_child(iframe_scroll_layer_id); let mut traversal = DisplayListTraversal::new_skipping_first(display_list); @@ -920,7 +634,7 @@ impl Frame { auxiliary_lists_map: &AuxiliaryListsMap, device_pixel_ratio: f32) -> RendererFrame { - self.update_layer_transforms(); + self.scroll_tree.update_all_layer_transforms(); let frame = self.build_frame(resource_cache, auxiliary_lists_map, device_pixel_ratio); @@ -928,72 +642,6 @@ impl Frame { frame } - fn update_layer_transform(&mut self, - layer_id: ScrollLayerId, - parent_world_transform: &ScrollToWorldTransform, - parent_viewport_rect: &ScrollLayerRect) { - // TODO(gw): This is an ugly borrow check workaround to clone these. - // Restructure this to avoid the clones! - let (layer_transform_for_children, viewport_rect, layer_children) = { - match self.layers.get_mut(&layer_id) { - Some(layer) => { - let inv_transform = layer.local_transform.inverse().unwrap(); - let parent_viewport_rect_in_local_space = inv_transform.transform_rect(parent_viewport_rect) - .translate(&-layer.scrolling.offset); - let local_viewport_rect = layer.local_viewport_rect - .translate(&-layer.scrolling.offset); - let viewport_rect = parent_viewport_rect_in_local_space.intersection(&local_viewport_rect) - .unwrap_or(LayerRect::zero()); - - layer.combined_local_viewport_rect = viewport_rect; - layer.world_viewport_transform = parent_world_transform.pre_mul(&layer.local_transform); - layer.world_content_transform = layer.world_viewport_transform - .pre_translated(layer.scrolling.offset.x, - layer.scrolling.offset.y, - 0.0); - - (layer.world_content_transform.with_source::(), - viewport_rect, - layer.children.clone()) - } - None => return, - } - }; - - for child_layer_id in layer_children { - self.update_layer_transform(child_layer_id, - &layer_transform_for_children, - &as_scroll_parent_rect(&viewport_rect)); - } - } - - fn update_layer_transforms(&mut self) { - if let Some(root_scroll_layer_id) = self.root_scroll_layer_id { - let root_viewport = self.layers[&root_scroll_layer_id].local_viewport_rect; - - self.update_layer_transform(root_scroll_layer_id, - &ScrollToWorldTransform::identity(), - &as_scroll_parent_rect(&root_viewport)); - - // Update any fixed layers - let mut fixed_layers = Vec::new(); - for (layer_id, _) in &self.layers { - match layer_id.info { - ScrollLayerInfo::Scrollable(..) => {} - ScrollLayerInfo::Fixed => { - fixed_layers.push(*layer_id); - } - } - } - - for layer_id in fixed_layers { - self.update_layer_transform(layer_id, - &ScrollToWorldTransform::identity(), - &as_scroll_parent_rect(&root_viewport)); - } - } - } - fn build_frame(&mut self, resource_cache: &mut ResourceCache, auxiliary_lists_map: &AuxiliaryListsMap, @@ -1002,26 +650,13 @@ impl Frame { let frame = frame_builder.as_mut().map(|builder| builder.build(resource_cache, self.id, - &self.layers, + &self.scroll_tree, auxiliary_lists_map, device_pixel_ratio) ); self.frame_builder = frame_builder; - let layers_bouncing_back = self.collect_layers_bouncing_back(); - RendererFrame::new(self.pipeline_epoch_map.clone(), - layers_bouncing_back, - frame) - } - - fn collect_layers_bouncing_back(&self) - -> HashSet> { - let mut layers_bouncing_back = HashSet::with_hasher(Default::default()); - for (scroll_layer_id, layer) in &self.layers { - if layer.scrolling.bouncing_back { - layers_bouncing_back.insert(*scroll_layer_id); - } - } - layers_bouncing_back + let layers_bouncing_back = self.scroll_tree.collect_layers_bouncing_back(); + RendererFrame::new(self.pipeline_epoch_map.clone(), layers_bouncing_back, frame) } } diff --git a/webrender/src/layer.rs b/webrender/src/layer.rs index 985420ef59..d603fd3b26 100644 --- a/webrender/src/layer.rs +++ b/webrender/src/layer.rs @@ -3,9 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use spring::{DAMPING, STIFFNESS, Spring}; -use webrender_traits::{PipelineId, ScrollLayerId}; -use webrender_traits::{LayerRect, LayerPoint, LayerSize}; -use webrender_traits::{LayerToScrollTransform, LayerToWorldTransform}; +use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform}; +use webrender_traits::{LayerToWorldTransform, PipelineId, ScrollLayerId}; /// Contains scrolling and transform information stacking contexts. #[derive(Clone)] diff --git a/webrender/src/lib.rs b/webrender/src/lib.rs index 17a9a9333f..a4d0f77090 100644 --- a/webrender/src/lib.rs +++ b/webrender/src/lib.rs @@ -67,6 +67,7 @@ mod record; mod render_backend; mod resource_cache; mod scene; +mod scroll_tree; mod spring; mod texture_cache; mod tiling; diff --git a/webrender/src/scroll_tree.rs b/webrender/src/scroll_tree.rs new file mode 100644 index 0000000000..dc502946fe --- /dev/null +++ b/webrender/src/scroll_tree.rs @@ -0,0 +1,424 @@ +/* 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/. */ + +use euclid::Point3D; +use fnv::FnvHasher; +use geometry::ray_intersects_rect; +use layer::{Layer, ScrollingState}; +use std::collections::{HashMap, HashSet}; +use std::hash::BuildHasherDefault; +use webrender_traits::{LayerPoint, LayerRect, PipelineId, ScrollEventPhase, ScrollLayerId}; +use webrender_traits::{ScrollLayerInfo, ScrollLayerPixel, ScrollLayerRect, ScrollLayerState}; +use webrender_traits::{ScrollLocation, ScrollToWorldTransform, ServoScrollRootId, WorldPoint}; +use webrender_traits::{WorldPoint4D, as_scroll_parent_rect}; + +#[cfg(target_os = "macos")] +const CAN_OVERSCROLL: bool = true; + +#[cfg(not(target_os = "macos"))] +const CAN_OVERSCROLL: bool = false; + +pub type ScrollStates = HashMap>; + +pub struct ScrollTree { + pub layers: HashMap>, + pub pending_scroll_offsets: HashMap<(PipelineId, ServoScrollRootId), LayerPoint>, + pub current_scroll_layer_id: Option, + pub root_scroll_layer_id: Option, +} + +impl ScrollTree { + pub fn new() -> ScrollTree { + ScrollTree { + layers: HashMap::with_hasher(Default::default()), + pending_scroll_offsets: HashMap::new(), + current_scroll_layer_id: None, + root_scroll_layer_id: None, + } + } + + pub fn collect_layers_bouncing_back(&self) + -> HashSet> { + let mut layers_bouncing_back = HashSet::with_hasher(Default::default()); + for (scroll_layer_id, layer) in self.layers.iter() { + if layer.scrolling.bouncing_back { + layers_bouncing_back.insert(*scroll_layer_id); + } + } + layers_bouncing_back + } + + pub fn get_scroll_layer(&self, + cursor: &WorldPoint, + scroll_layer_id: ScrollLayerId) + -> Option { + self.layers.get(&scroll_layer_id).and_then(|layer| { + for child_layer_id in layer.children.iter().rev() { + if let Some(layer_id) = self.get_scroll_layer(cursor, *child_layer_id) { + return Some(layer_id); + } + } + + match scroll_layer_id.info { + ScrollLayerInfo::Fixed => { + None + } + ScrollLayerInfo::Scrollable(..) => { + let inv = layer.world_viewport_transform.inverse().unwrap(); + let z0 = -10000.0; + let z1 = 10000.0; + + let p0 = inv.transform_point4d(&WorldPoint4D::new(cursor.x, cursor.y, z0, 1.0)); + let p0 = Point3D::new(p0.x / p0.w, + p0.y / p0.w, + p0.z / p0.w); + let p1 = inv.transform_point4d(&WorldPoint4D::new(cursor.x, cursor.y, z1, 1.0)); + let p1 = Point3D::new(p1.x / p1.w, + p1.y / p1.w, + p1.z / p1.w); + + let is_unscrollable = layer.content_size.width <= layer.local_viewport_rect.size.width && + layer.content_size.height <= layer.local_viewport_rect.size.height; + if is_unscrollable { + None + } else { + let result = ray_intersects_rect(p0, p1, layer.local_viewport_rect.to_untyped()); + if result { + Some(scroll_layer_id) + } else { + None + } + } + } + } + }) + } + + pub fn get_scroll_layer_state(&self) -> Vec { + let mut result = vec![]; + for (scroll_layer_id, scroll_layer) in self.layers.iter() { + match scroll_layer_id.info { + ScrollLayerInfo::Scrollable(_, servo_scroll_root_id) => { + result.push(ScrollLayerState { + pipeline_id: scroll_layer.pipeline_id, + scroll_root_id: servo_scroll_root_id, + scroll_offset: scroll_layer.scrolling.offset, + }) + } + ScrollLayerInfo::Fixed => {} + } + } + result + } + + pub fn drain(&mut self) -> ScrollStates { + let mut scroll_states = HashMap::with_hasher(Default::default()); + for (layer_id, old_layer) in &mut self.layers.drain() { + scroll_states.insert(layer_id, old_layer.scrolling); + } + scroll_states + } + + pub fn scroll_layers(&mut self, + origin: LayerPoint, + pipeline_id: PipelineId, + scroll_root_id: ServoScrollRootId) + -> bool { + let origin = LayerPoint::new(origin.x.max(0.0), origin.y.max(0.0)); + + let mut scrolled_a_layer = false; + let mut found_layer = false; + for (layer_id, layer) in self.layers.iter_mut() { + if layer_id.pipeline_id != pipeline_id { + continue; + } + + match layer_id.info { + ScrollLayerInfo::Scrollable(_, id) if id != scroll_root_id => continue, + ScrollLayerInfo::Fixed => continue, + _ => {} + } + + found_layer = true; + scrolled_a_layer |= layer.set_scroll_origin(&origin); + } + + if !found_layer { + self.pending_scroll_offsets.insert((pipeline_id, scroll_root_id), origin); + } + + scrolled_a_layer + } + + pub fn scroll(&mut self, + scroll_location: ScrollLocation, + cursor: WorldPoint, + phase: ScrollEventPhase) + -> bool { + let root_scroll_layer_id = match self.root_scroll_layer_id { + Some(root_scroll_layer_id) => root_scroll_layer_id, + None => return false, + }; + + let scroll_layer_id = match ( + phase, + self.get_scroll_layer(&cursor, root_scroll_layer_id), + self.current_scroll_layer_id) { + (ScrollEventPhase::Start, Some(scroll_layer_id), _) => { + self.current_scroll_layer_id = Some(scroll_layer_id); + scroll_layer_id + }, + (ScrollEventPhase::Start, None, _) => return false, + (_, _, Some(scroll_layer_id)) => scroll_layer_id, + (_, _, None) => return false, + }; + + let non_root_overscroll = if scroll_layer_id != root_scroll_layer_id { + // true if the current layer is overscrolling, + // and it is not the root scroll layer. + let child_layer = self.layers.get(&scroll_layer_id).unwrap(); + let overscroll_amount = child_layer.overscroll_amount(); + overscroll_amount.width != 0.0 || overscroll_amount.height != 0.0 + } else { + false + }; + + let switch_layer = match phase { + ScrollEventPhase::Start => { + // if this is a new gesture, we do not switch layer, + // however we do save the state of non_root_overscroll, + // for use in the subsequent Move phase. + let mut current_layer = self.layers.get_mut(&scroll_layer_id).unwrap(); + current_layer.scrolling.should_handoff_scroll = non_root_overscroll; + false + }, + ScrollEventPhase::Move(_) => { + // Switch layer if movement originated in a new gesture, + // from a non root layer in overscroll. + let current_layer = self.layers.get_mut(&scroll_layer_id).unwrap(); + current_layer.scrolling.should_handoff_scroll && non_root_overscroll + }, + ScrollEventPhase::End => { + // clean-up when gesture ends. + let mut current_layer = self.layers.get_mut(&scroll_layer_id).unwrap(); + current_layer.scrolling.should_handoff_scroll = false; + false + }, + }; + + let scroll_root_id = match (switch_layer, scroll_layer_id.info, root_scroll_layer_id.info) { + (true, _, ScrollLayerInfo::Scrollable(_, scroll_root_id)) | + (true, ScrollLayerInfo::Scrollable(_, scroll_root_id), ScrollLayerInfo::Fixed) | + (false, ScrollLayerInfo::Scrollable(_, scroll_root_id), _) => scroll_root_id, + (_, ScrollLayerInfo::Fixed, _) => unreachable!("Tried to scroll a fixed position layer."), + }; + + let mut scrolled_a_layer = false; + for (layer_id, layer) in self.layers.iter_mut() { + if layer_id.pipeline_id != scroll_layer_id.pipeline_id { + continue; + } + + match layer_id.info { + ScrollLayerInfo::Scrollable(_, id) if id != scroll_root_id => continue, + ScrollLayerInfo::Fixed => continue, + _ => {} + } + + if layer.scrolling.started_bouncing_back && phase == ScrollEventPhase::Move(false) { + continue; + } + + let mut delta = match scroll_location { + ScrollLocation::Delta(delta) => delta, + ScrollLocation::Start => { + if layer.scrolling.offset.y.round() >= 0.0 { + // Nothing to do on this layer. + continue; + } + + layer.scrolling.offset.y = 0.0; + scrolled_a_layer = true; + continue; + }, + ScrollLocation::End => { + let end_pos = layer.local_viewport_rect.size.height + - layer.content_size.height; + + if layer.scrolling.offset.y.round() <= end_pos { + // Nothing to do on this layer. + continue; + } + + layer.scrolling.offset.y = end_pos; + scrolled_a_layer = true; + continue; + } + }; + + let overscroll_amount = layer.overscroll_amount(); + let overscrolling = CAN_OVERSCROLL && (overscroll_amount.width != 0.0 || + overscroll_amount.height != 0.0); + if overscrolling { + if overscroll_amount.width != 0.0 { + delta.x /= overscroll_amount.width.abs() + } + if overscroll_amount.height != 0.0 { + delta.y /= overscroll_amount.height.abs() + } + } + + let is_unscrollable = + layer.content_size.width <= layer.local_viewport_rect.size.width && + layer.content_size.height <= layer.local_viewport_rect.size.height; + + let original_layer_scroll_offset = layer.scrolling.offset; + + if layer.content_size.width > layer.local_viewport_rect.size.width { + layer.scrolling.offset.x = layer.scrolling.offset.x + delta.x; + if is_unscrollable || !CAN_OVERSCROLL { + layer.scrolling.offset.x = layer.scrolling.offset.x.min(0.0); + layer.scrolling.offset.x = + layer.scrolling.offset.x.max(-layer.content_size.width + + layer.local_viewport_rect.size.width); + } + } + + if layer.content_size.height > layer.local_viewport_rect.size.height { + layer.scrolling.offset.y = layer.scrolling.offset.y + delta.y; + if is_unscrollable || !CAN_OVERSCROLL { + layer.scrolling.offset.y = layer.scrolling.offset.y.min(0.0); + layer.scrolling.offset.y = + layer.scrolling.offset.y.max(-layer.content_size.height + + layer.local_viewport_rect.size.height); + } + } + + if phase == ScrollEventPhase::Start || phase == ScrollEventPhase::Move(true) { + layer.scrolling.started_bouncing_back = false + } else if overscrolling && + ((delta.x < 1.0 && delta.y < 1.0) || phase == ScrollEventPhase::End) { + layer.scrolling.started_bouncing_back = true; + layer.scrolling.bouncing_back = true + } + + layer.scrolling.offset.x = layer.scrolling.offset.x.round(); + layer.scrolling.offset.y = layer.scrolling.offset.y.round(); + + if CAN_OVERSCROLL { + layer.stretch_overscroll_spring(); + } + + scrolled_a_layer = scrolled_a_layer || + layer.scrolling.offset != original_layer_scroll_offset || + layer.scrolling.started_bouncing_back; + } + + scrolled_a_layer + } + + pub fn update_all_layer_transforms(&mut self) { + let root_scroll_layer_id = self.root_scroll_layer_id; + self.update_layer_transforms(root_scroll_layer_id); + } + + fn update_layer_transform(&mut self, + layer_id: ScrollLayerId, + parent_world_transform: &ScrollToWorldTransform, + parent_viewport_rect: &ScrollLayerRect) { + // TODO(gw): This is an ugly borrow check workaround to clone these. + // Restructure this to avoid the clones! + let (layer_transform_for_children, viewport_rect, layer_children) = { + match self.layers.get_mut(&layer_id) { + Some(layer) => { + let inv_transform = layer.local_transform.inverse().unwrap(); + let parent_viewport_rect_in_local_space = inv_transform.transform_rect(parent_viewport_rect) + .translate(&-layer.scrolling.offset); + let local_viewport_rect = layer.local_viewport_rect + .translate(&-layer.scrolling.offset); + let viewport_rect = parent_viewport_rect_in_local_space.intersection(&local_viewport_rect) + .unwrap_or(LayerRect::zero()); + + layer.combined_local_viewport_rect = viewport_rect; + layer.world_viewport_transform = parent_world_transform.pre_mul(&layer.local_transform); + layer.world_content_transform = layer.world_viewport_transform + .pre_translated(layer.scrolling.offset.x, + layer.scrolling.offset.y, + 0.0); + + (layer.world_content_transform.with_source::(), + viewport_rect, + layer.children.clone()) + } + None => return, + } + }; + + for child_layer_id in layer_children { + self.update_layer_transform(child_layer_id, + &layer_transform_for_children, + &as_scroll_parent_rect(&viewport_rect)); + } + } + + pub fn update_layer_transforms(&mut self, root_scroll_layer_id: Option) { + if let Some(root_scroll_layer_id) = root_scroll_layer_id { + let root_viewport = self.layers[&root_scroll_layer_id].local_viewport_rect; + + self.update_layer_transform(root_scroll_layer_id, + &ScrollToWorldTransform::identity(), + &as_scroll_parent_rect(&root_viewport)); + + // Update any fixed layers + let mut fixed_layers = Vec::new(); + for (layer_id, _) in &self.layers { + match layer_id.info { + ScrollLayerInfo::Scrollable(..) => {} + ScrollLayerInfo::Fixed => { + fixed_layers.push(*layer_id); + } + } + } + + for layer_id in fixed_layers { + self.update_layer_transform(layer_id, + &ScrollToWorldTransform::identity(), + &as_scroll_parent_rect(&root_viewport)); + } + } + } + + pub fn tick_scrolling_bounce_animations(&mut self) { + for (_, layer) in &mut self.layers { + layer.tick_scrolling_bounce_animation() + } + } + + pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) { + // TODO(gw): These are all independent - can be run through thread pool if it shows up + // in the profile! + for (scroll_layer_id, layer) in &mut self.layers { + let scrolling_state = match old_states.get(&scroll_layer_id) { + Some(old_scrolling_state) => *old_scrolling_state, + None => ScrollingState::new(), + }; + + layer.finalize(&scrolling_state); + + let scroll_root_id = match scroll_layer_id.info { + ScrollLayerInfo::Scrollable(_, scroll_root_id) => scroll_root_id, + _ => continue, + }; + + + let pipeline_id = scroll_layer_id.pipeline_id; + if let Some(pending_offset) = + self.pending_scroll_offsets.get_mut(&(pipeline_id, scroll_root_id)) { + layer.set_scroll_origin(pending_offset); + } + } + + } +} + diff --git a/webrender/src/tiling.rs b/webrender/src/tiling.rs index efb77f711d..c569918b8b 100644 --- a/webrender/src/tiling.rs +++ b/webrender/src/tiling.rs @@ -9,7 +9,6 @@ use frame::FrameId; use gpu_store::GpuStoreAddress; use internal_types::{ANGLE_FLOAT_TO_FIXED, LowLevelFilterOp, CompositionOp}; use internal_types::{BatchTextures, CacheTextureId, SourceTexture}; -use layer::Layer; use mask_cache::{ClipSource, MaskCacheInfo}; use prim_store::{PrimitiveGeometry, RectanglePrimitive, PrimitiveContainer}; use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, BoxShadowPrimitiveGpu}; @@ -22,6 +21,7 @@ use prim_store::{PrimitiveStore, GpuBlock16, GpuBlock32, GpuBlock64, GpuBlock128 use profiler::FrameProfileCounters; use renderer::BlendMode; use resource_cache::ResourceCache; +use scroll_tree::ScrollTree; use std::cmp; use std::collections::{HashMap}; use std::{i32, f32}; @@ -48,9 +48,6 @@ const OPAQUE_TASK_INDEX: RenderTaskIndex = RenderTaskIndex(i32::MAX as usize); const FLOATS_PER_RENDER_TASK_INFO: usize = 12; -pub type LayerMap = HashMap>; pub type AuxiliaryListsMap = HashMap>; @@ -2479,7 +2476,7 @@ impl FrameBuilder { /// primitives in screen space. fn cull_layers(&mut self, screen_rect: &DeviceIntRect, - layer_map: &LayerMap, + scroll_tree: &ScrollTree, auxiliary_lists_map: &AuxiliaryListsMap, x_tile_count: i32, y_tile_count: i32, @@ -2503,7 +2500,7 @@ impl FrameBuilder { layer.xf_rect = None; layer.tile_range = None; - let scroll_layer = &layer_map[&layer.scroll_layer_id]; + let scroll_layer = &scroll_tree.layers[&layer.scroll_layer_id]; packed_layer.transform = scroll_layer.world_content_transform .with_source::() // the scroll layer is considered a parent of layer .pre_mul(&layer.local_transform); @@ -2821,13 +2818,12 @@ impl FrameBuilder { } } - fn update_scroll_bars(&mut self, - layer_map: &LayerMap) { + fn update_scroll_bars(&mut self, scroll_tree: &ScrollTree) { let distance_from_edge = 8.0; for scrollbar_prim in &self.scrollbar_prims { let mut geom = (*self.prim_store.gpu_geometry.get(GpuStoreAddress(scrollbar_prim.prim_index.0 as i32))).clone(); - let scroll_layer = &layer_map[&scrollbar_prim.scroll_layer_id]; + let scroll_layer = &scroll_tree.layers[&scrollbar_prim.scroll_layer_id]; let scrollable_distance = scroll_layer.content_size.height - scroll_layer.local_viewport_rect.size.height; @@ -2870,7 +2866,7 @@ impl FrameBuilder { pub fn build(&mut self, resource_cache: &mut ResourceCache, frame_id: FrameId, - layer_map: &LayerMap, + scroll_tree: &ScrollTree, auxiliary_lists_map: &AuxiliaryListsMap, device_pixel_ratio: f32) -> Frame { let mut profile_counters = FrameProfileCounters::new(); @@ -2903,10 +2899,10 @@ impl FrameBuilder { let (x_tile_count, y_tile_count, mut screen_tiles) = self.create_screen_tiles(device_pixel_ratio); - self.update_scroll_bars(layer_map); + self.update_scroll_bars(scroll_tree); self.cull_layers(&screen_rect, - layer_map, + scroll_tree, auxiliary_lists_map, x_tile_count, y_tile_count,