From f5d899e11ba81b564c337b80b1f7839356c7659d Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Fri, 4 May 2018 14:30:22 +0200 Subject: [PATCH] Scroll via HitTester This change uses the HitTester to do scrolling, which allows us to remove some redundant code. It also will help us to remove bounds for reference frames, which will be useful when we separate the concept of reference frames and stacking contexts. --- webrender/src/clip_scroll_node.rs | 25 ------- webrender/src/clip_scroll_tree.rs | 53 +++++--------- webrender/src/geometry.rs | 113 ------------------------------ webrender/src/hit_test.rs | 33 +++++++++ webrender/src/lib.rs | 1 - webrender/src/render_backend.rs | 31 +++++--- webrender/src/util.rs | 15 +--- webrender_api/src/api.rs | 6 +- 8 files changed, 79 insertions(+), 198 deletions(-) delete mode 100644 webrender/src/geometry.rs diff --git a/webrender/src/clip_scroll_node.rs b/webrender/src/clip_scroll_node.rs index 4052a056ff..5d1064d0a4 100644 --- a/webrender/src/clip_scroll_node.rs +++ b/webrender/src/clip_scroll_node.rs @@ -5,12 +5,10 @@ use api::{DevicePixelScale, ExternalScrollId, LayoutPixel, LayoutPoint, LayoutRect, LayoutSize}; use api::{LayoutVector2D, LayoutTransform, PipelineId, PropertyBinding}; use api::{ScrollClamping, ScrollLocation, ScrollSensitivity, StickyOffsetBounds}; -use api::WorldPoint; use clip::{ClipChain, ClipChainNode, ClipSourcesHandle, ClipStore, ClipWorkItem}; use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId}; use clip_scroll_tree::TransformUpdateState; use euclid::SideOffsets2D; -use geometry::ray_intersects_rect; use gpu_cache::GpuCache; use gpu_types::{ClipScrollNodeIndex as GPUClipScrollNodeIndex, ClipScrollNodeData}; use resource_cache::ResourceCache; @@ -712,29 +710,6 @@ impl ClipScrollNode { scrolling.offset != original_layer_scroll_offset } - pub fn ray_intersects_node(&self, cursor: &WorldPoint) -> bool { - let inv = match self.world_viewport_transform.inverse() { - Some(inv) => inv, - None => return false, - }; - - let z0 = -10000.0; - let z1 = 10000.0; - - let p0 = inv.transform_point3d(&cursor.extend(z0)); - let p1 = inv.transform_point3d(&cursor.extend(z1)); - - if self.scrollable_size() == LayoutSize::zero() { - return false; - } - - ray_intersects_rect( - p0.to_untyped(), - p1.to_untyped(), - self.local_viewport_rect.to_untyped(), - ) - } - pub fn scroll_offset(&self) -> LayoutVector2D { match self.node_type { NodeType::ScrollFrame(ref scrolling) => scrolling.offset, diff --git a/webrender/src/clip_scroll_tree.rs b/webrender/src/clip_scroll_tree.rs index ae5a9ac544..067cc8fb8e 100644 --- a/webrender/src/clip_scroll_tree.rs +++ b/webrender/src/clip_scroll_tree.rs @@ -133,36 +133,6 @@ impl ClipScrollTree { TOPMOST_SCROLL_NODE_INDEX } - fn find_scrolling_node_at_point_in_node( - &self, - cursor: &WorldPoint, - index: ClipScrollNodeIndex, - ) -> Option { - let node = &self.nodes[index.0]; - for child_index in node.children.iter().rev() { - let found_index = self.find_scrolling_node_at_point_in_node(cursor, *child_index); - if found_index.is_some() { - return found_index; - } - } - - match node.node_type { - NodeType::ScrollFrame(state) if state.sensitive_to_input_events() => {} - _ => return None, - } - - if node.ray_intersects_node(cursor) { - Some(index) - } else { - None - } - } - - pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ClipScrollNodeIndex { - self.find_scrolling_node_at_point_in_node(cursor, self.root_reference_frame_index()) - .unwrap_or(self.topmost_scroll_node_index()) - } - pub fn get_scroll_node_state(&self) -> Vec { let mut result = vec![]; for node in &self.nodes { @@ -214,16 +184,31 @@ impl ClipScrollTree { false } - pub fn scroll( + fn find_nearest_scrolling_ancestor( + &self, + index: Option + ) -> ClipScrollNodeIndex { + let index = match index { + Some(index) => index, + None => return self.topmost_scroll_node_index(), + }; + + let node = &self.nodes[index.0]; + match node.node_type { + NodeType::ScrollFrame(state) if state.sensitive_to_input_events() => index, + _ => self.find_nearest_scrolling_ancestor(node.parent) + } + } + + pub fn scroll_nearest_scrolling_ancestor( &mut self, scroll_location: ScrollLocation, - cursor: WorldPoint, + node_index: Option, ) -> bool { if self.nodes.is_empty() { return false; } - - let node_index = self.find_scrolling_node_at_point(&cursor); + let node_index = self.find_nearest_scrolling_ancestor(node_index); self.nodes[node_index.0].scroll(scroll_location) } diff --git a/webrender/src/geometry.rs b/webrender/src/geometry.rs deleted file mode 100644 index b8ca5b343f..0000000000 --- a/webrender/src/geometry.rs +++ /dev/null @@ -1,113 +0,0 @@ -/* 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, Rect}; - -/* - A naive port of "An Efficient and Robust Ray–Box Intersection Algorithm" - from https://www.cs.utah.edu/~awilliam/box/box.pdf - - This should be cleaned up and extracted into more useful types! - */ - -// Assumes rect is in the z=0 plane! -pub fn ray_intersects_rect( - ray_origin: Point3D, - ray_end: Point3D, - rect: Rect, -) -> bool { - let mut dir = ray_end - ray_origin; - let len = ((dir.x * dir.x) + (dir.y * dir.y) + (dir.z * dir.z)).sqrt(); - dir.x = dir.x / len; - dir.y = dir.y / len; - dir.z = dir.z / len; - let inv_direction = Point3D::new(1.0 / dir.x, 1.0 / dir.y, 1.0 / dir.z); - - let sign = [ - if inv_direction.x < 0.0 { 1 } else { 0 }, - if inv_direction.y < 0.0 { 1 } else { 0 }, - if inv_direction.z < 0.0 { 1 } else { 0 }, - ]; - - let parameters = [rect.origin.to_3d(), rect.bottom_right().to_3d()]; - - let mut tmin = (parameters[sign[0]].x - ray_origin.x) * inv_direction.x; - let mut tmax = (parameters[1 - sign[0]].x - ray_origin.x) * inv_direction.x; - let tymin = (parameters[sign[1]].y - ray_origin.y) * inv_direction.y; - let tymax = (parameters[1 - sign[1]].y - ray_origin.y) * inv_direction.y; - if (tmin > tymax) || (tymin > tmax) { - return false; - } - if tymin > tmin { - tmin = tymin; - } - if tymax < tmax { - tmax = tymax; - } - let tzmin = (parameters[sign[2]].z - ray_origin.z) * inv_direction.z; - let tzmax = (parameters[1 - sign[2]].z - ray_origin.z) * inv_direction.z; - if (tmin > tzmax) || (tzmin > tmax) { - return false; - } - - // Don't care about where on the ray it hits... - true - - /* - if tzmin > tmin { - tmin = tzmin; - } - if tzmax < tmax { - tmax = tzmax; - } - - let t0 = 0.0; - let t1 = len; - - (tmin < t1) && (tmax > t0) - */ -} - -/* -pub fn circle_contains_rect(circle_center: &Point2D, - radius: f32, - rect: &Rect) -> bool { - let dx = (circle_center.x - rect.origin.x).max( - rect.origin.x + rect.size.width - circle_center.x - ); - let dy = (circle_center.y - rect.origin.y).max( - rect.origin.y + rect.size.height - circle_center.y - ); - radius * radius >= dx * dx + dy * dy -} - -pub fn rect_intersects_circle(circle_center: &Point2D, - radius: f32, - rect: &Rect) -> bool { - let circle_distance_x = (circle_center.x - (rect.origin.x + rect.size.width * 0.5)).abs(); - let circle_distance_y = (circle_center.y - (rect.origin.y + rect.size.height * 0.5)).abs(); - - if circle_distance_x > rect.size.width * 0.5 + radius { - return false - } - if circle_distance_y > rect.size.height * 0.5 + radius { - return false - } - - if circle_distance_x <= rect.size.width * 0.5 { - return true; - } - if circle_distance_y <= rect.size.height * 0.5 { - return true; - } - - let corner_distance_sq = - (circle_distance_x - rect.size.width * 0.5) * - (circle_distance_x - rect.size.width * 0.5) + - (circle_distance_y - rect.size.height * 0.5) * - (circle_distance_y - rect.size.height * 0.5); - - corner_distance_sq <= radius * radius -} -*/ diff --git a/webrender/src/hit_test.rs b/webrender/src/hit_test.rs index f469849f7a..c5928ea67d 100644 --- a/webrender/src/hit_test.rs +++ b/webrender/src/hit_test.rs @@ -223,6 +223,39 @@ impl HitTester { true } + pub fn find_node_under_point(&self, mut test: HitTest) -> Option { + let point = test.get_absolute_point(self); + + for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() { + let scroll_node_id = clip_and_scroll.scroll_node_id; + let scroll_node = &self.nodes[scroll_node_id.0]; + let transform = scroll_node.world_content_transform; + let point_in_layer = match transform.inverse() { + Some(inverted) => inverted.transform_point2d(&point), + None => continue, + }; + + let mut clipped_in = false; + for item in items.iter().rev() { + if !item.rect.contains(&point_in_layer) || + !item.clip_rect.contains(&point_in_layer) { + continue; + } + + let clip_chain_index = clip_and_scroll.clip_chain_index; + clipped_in |= + self.is_point_clipped_in_for_clip_chain(point, clip_chain_index, &mut test); + if !clipped_in { + break; + } + + return Some(scroll_node_id); + } + } + + None + } + pub fn hit_test(&self, mut test: HitTest) -> HitTestResult { let point = test.get_absolute_point(self); diff --git a/webrender/src/lib.rs b/webrender/src/lib.rs index 482dd2cea0..6413f8688b 100644 --- a/webrender/src/lib.rs +++ b/webrender/src/lib.rs @@ -76,7 +76,6 @@ mod frame_builder; mod freelist; #[cfg(any(target_os = "macos", target_os = "windows"))] mod gamma_lut; -mod geometry; mod glyph_cache; mod glyph_rasterizer; mod gpu_cache; diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index c94d87cdee..3c00f1e0ee 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -6,15 +6,15 @@ use api::{ApiMsg, BuiltDisplayList, ClearCache, DebugCommand}; #[cfg(feature = "debugger")] use api::{BuiltDisplayListIter, SpecificDisplayItem}; use api::{DeviceIntPoint, DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize}; -use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestResult}; +use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestFlags, HitTestResult}; use api::{IdNamespace, LayoutPoint, PipelineId, RenderNotifier, SceneMsg, ScrollClamping}; -use api::{ScrollLocation, ScrollNodeState, TransactionMsg, WorldPoint}; +use api::{ScrollLocation, ScrollNodeState, TransactionMsg}; use api::channel::{MsgReceiver, Payload}; #[cfg(feature = "capture")] use api::CaptureBits; #[cfg(feature = "replay")] use api::CapturedDocument; -use clip_scroll_tree::ClipScrollTree; +use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree}; #[cfg(feature = "debugger")] use debug_server; use display_list_flattener::DisplayListFlattener; @@ -315,12 +315,12 @@ impl Document { } /// Returns true if any nodes actually changed position or false otherwise. - pub fn scroll( + pub fn scroll_nearest_scrolling_ancestor( &mut self, scroll_location: ScrollLocation, - cursor: WorldPoint, + scroll_node_index: Option, ) -> bool { - self.clip_scroll_tree.scroll(scroll_location, cursor) + self.clip_scroll_tree.scroll_nearest_scrolling_ancestor(scroll_location, scroll_node_index) } /// Returns true if the node actually changed position or false otherwise. @@ -620,9 +620,24 @@ impl RenderBackend { FrameMsg::Scroll(delta, cursor) => { profile_scope!("Scroll"); - let should_render = doc.scroll(delta, cursor) - && doc.render_on_scroll == Some(true); + let mut should_render = true; + let node_index = match doc.hit_tester { + Some(ref hit_tester) => { + // Ideally we would call doc.scroll_nearest_scrolling_ancestor here, but + // we need have to avoid a double-borrow. + let test = HitTest::new(None, cursor, HitTestFlags::empty()); + hit_tester.find_node_under_point(test) + } + None => { + should_render = false; + None + } + }; + let should_render = + should_render && + doc.scroll_nearest_scrolling_ancestor(delta, node_index) && + doc.render_on_scroll == Some(true); DocumentOps { scroll: true, render: should_render, diff --git a/webrender/src/util.rs b/webrender/src/util.rs index b54b2fa685..282c4b9ed2 100644 --- a/webrender/src/util.rs +++ b/webrender/src/util.rs @@ -5,8 +5,8 @@ use api::{BorderRadius, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale}; use api::{DevicePoint, DeviceRect, DeviceSize, LayoutPixel, LayoutPoint, LayoutRect, LayoutSize}; use api::{WorldPixel, WorldRect}; -use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedPoint3D, TypedRect, TypedSize2D}; -use euclid::{TypedTransform2D, TypedTransform3D, TypedVector2D}; +use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedRect, TypedSize2D, TypedTransform2D}; +use euclid::{TypedTransform3D, TypedVector2D}; use num_traits::Zero; use std::{i32, f32}; @@ -456,15 +456,6 @@ impl FastTransform { } } - #[inline(always)] - pub fn transform_point3d(&self, point: &TypedPoint3D) -> TypedPoint3D { - match *self { - FastTransform::Offset(offset) => - TypedPoint3D::new(point.x + offset.x, point.y + offset.y, point.z), - FastTransform::Transform { ref transform, .. } => transform.transform_point3d(point), - } - } - #[inline(always)] pub fn transform_rect(&self, rect: &TypedRect) -> TypedRect { match *self { @@ -557,4 +548,4 @@ impl From> for FastTransform { pub type LayoutFastTransform = FastTransform; pub type LayoutToWorldFastTransform = FastTransform; -pub type WorldToLayoutFastTransform = FastTransform; \ No newline at end of file +pub type WorldToLayoutFastTransform = FastTransform; diff --git a/webrender_api/src/api.rs b/webrender_api/src/api.rs index 68829437a4..7738ba7db3 100644 --- a/webrender_api/src/api.rs +++ b/webrender_api/src/api.rs @@ -280,11 +280,7 @@ impl Transaction { /// /// WebRender looks for the layer closest to the user /// which has `ScrollPolicy::Scrollable` set. - pub fn scroll( - &mut self, - scroll_location: ScrollLocation, - cursor: WorldPoint, - ) { + pub fn scroll(&mut self, scroll_location: ScrollLocation, cursor: WorldPoint) { self.frame_ops.push(FrameMsg::Scroll(scroll_location, cursor)); }