layout: Use FastTransform for hit testing (#38554)

`FastTransform` provides faster matrix operations when the involved
transforms are just translations.

Testing: Not needed (no change in behavior)

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Oriol Brufau 2025-08-11 09:23:17 -07:00 committed by GitHub
parent 0d40547bec
commit 4ff1e8dbd9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 58 additions and 71 deletions

View file

@ -7,16 +7,17 @@ use std::collections::HashMap;
use app_units::Au;
use base::id::ScrollTreeNodeId;
use embedder_traits::Cursor;
use euclid::{Box2D, Point2D, Point3D, Vector2D};
use euclid::{Box2D, Vector2D};
use kurbo::{Ellipse, Shape};
use layout_api::{ElementsFromPointFlags, ElementsFromPointResult};
use servo_geometry::FastLayoutTransform;
use style::computed_values::backface_visibility::T as BackfaceVisibility;
use style::computed_values::pointer_events::T as PointerEvents;
use style::computed_values::visibility::T as Visibility;
use style::properties::ComputedValues;
use style::values::computed::ui::CursorKind;
use webrender_api::BorderRadius;
use webrender_api::units::{LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, RectExt};
use webrender_api::units::{LayoutPoint, LayoutRect, LayoutSize, RectExt};
use crate::display_list::clip::{Clip, ClipId};
use crate::display_list::stacking_context::StackingContextSection;
@ -33,7 +34,7 @@ pub(crate) struct HitTest<'a> {
point_to_test: LayoutPoint,
/// A cached version of [`Self::point_to_test`] projected to a spatial node, to avoid
/// doing a lot of matrix math over and over.
projected_point_to_test: Option<(ScrollTreeNodeId, LayoutPoint, LayoutTransform)>,
projected_point_to_test: Option<(ScrollTreeNodeId, LayoutPoint, FastLayoutTransform)>,
/// The stacking context tree against which to perform the hit test.
stacking_context_tree: &'a StackingContextTree,
/// The resulting [`HitTestResultItems`] for this hit test.
@ -89,7 +90,7 @@ impl<'a> HitTest<'a> {
fn location_in_spatial_node(
&mut self,
scroll_tree_node_id: ScrollTreeNodeId,
) -> Option<(LayoutPoint, LayoutTransform)> {
) -> Option<(LayoutPoint, FastLayoutTransform)> {
match self.projected_point_to_test {
Some((cached_scroll_tree_node_id, projected_point, transform))
if cached_scroll_tree_node_id == scroll_tree_node_id =>
@ -105,21 +106,7 @@ impl<'a> HitTest<'a> {
.scroll_tree
.cumulative_root_to_node_transform(&scroll_tree_node_id)?;
// This comes from WebRender at `webrender/utils.rs`.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1765204#c3.
//
// We are going from world coordinate space to spatial node coordinate space.
// Normally, that transformation happens in the opposite direction, but for hit
// testing everything is reversed. The result of the display transformation comes
// with a z-coordinate that we do not have access to here.
//
// We must solve for a value of z here that transforms to 0 (the value of z for our
// point).
let point = self.point_to_test;
let z =
-(point.x * transform.m13 + point.y * transform.m23 + transform.m43) / transform.m33;
let projected_point = transform.transform_point3d(Point3D::new(point.x, point.y, z))?;
let projected_point = Point2D::new(projected_point.x, projected_point.y);
let projected_point = transform.project_point2d(self.point_to_test)?;
self.projected_point_to_test = Some((scroll_tree_node_id, projected_point, transform));
Some((projected_point, transform))

View file

@ -272,7 +272,7 @@ impl DisplayListBuilder<'_> {
info.origin,
*parent_spatial_node_id,
info.transform_style,
PropertyBinding::Value(info.transform),
PropertyBinding::Value(*info.transform.to_transform()),
info.kind,
spatial_tree_item_key,
);

View file

@ -234,7 +234,7 @@ impl StackingContextTree {
origin,
frame_origin_for_query,
transform_style,
transform,
transform: transform.into(),
kind,
}),
)

View file

@ -14,7 +14,7 @@ use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafe
use layout_api::{LayoutElementType, LayoutNodeType, OffsetParentResponse};
use script::layout_dom::ServoLayoutNode;
use servo_arc::Arc as ServoArc;
use servo_geometry::{au_rect_to_f32_rect, f32_rect_to_au_rect};
use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
use servo_url::ServoUrl;
use style::computed_values::display::T as Display;
use style::computed_values::position::T as Position;
@ -39,7 +39,6 @@ use style::values::specified::GenericGridTemplateComponent;
use style::values::specified::box_::DisplayInside;
use style::values::specified::text::TextTransformCase;
use style_traits::{ParsingMode, ToCss};
use webrender_api::units::LayoutTransform;
use crate::ArcRefCell;
use crate::display_list::StackingContextTree;
@ -55,7 +54,7 @@ use crate::taffy::SpecificTaffyGridInfo;
fn root_transform_for_layout_node(
scroll_tree: &ScrollTree,
node: ServoLayoutNode<'_>,
) -> Option<LayoutTransform> {
) -> Option<FastLayoutTransform> {
let fragments = node.fragments_for_pseudo(None);
let box_fragment = fragments
.first()
@ -90,7 +89,7 @@ pub(crate) fn process_content_box_request(
return Some(rect_union);
};
Some(transform_au_rectangle(rect_union, transform))
transform_au_rectangle(rect_union, transform)
}
pub(crate) fn process_content_boxes_request(
@ -110,7 +109,7 @@ pub(crate) fn process_content_boxes_request(
};
content_boxes
.map(|rect| transform_au_rectangle(rect, transform))
.filter_map(|rect| transform_au_rectangle(rect, transform))
.collect()
}
@ -1150,9 +1149,17 @@ where
Some(computed_values.clone_font())
}
fn transform_au_rectangle(rect_to_transform: Rect<Au>, transform: LayoutTransform) -> Rect<Au> {
transform
.outer_transformed_rect(&au_rect_to_f32_rect(rect_to_transform).cast_unit())
fn transform_au_rectangle(
rect_to_transform: Rect<Au>,
transform: FastLayoutTransform,
) -> Option<Rect<Au>> {
let rect_to_transform = &au_rect_to_f32_rect(rect_to_transform).cast_unit();
let outer_transformed_rect = match transform {
FastLayoutTransform::Offset(offset) => Some(rect_to_transform.translate(offset)),
FastLayoutTransform::Transform { transform, .. } => {
transform.outer_transformed_rect(rect_to_transform)
},
};
outer_transformed_rect
.map(|transformed_rect| f32_rect_to_au_rect(transformed_rect.to_untyped()))
.unwrap_or(rect_to_transform)
}