Refactor Layout 2020 layout for a compositor-side scroll tree

This change refactors how layout is done in Layout 2020 in preparation
for a compositor-side scroll tree:

1. Now the SpatialId and ClipId of each fragment is stored separately.
   This will allow storing a scroll node id instead of only the handle
   to the WebRender spatial node.
2. Separate out stacking context tree construction and display list
   building. This change will make it possible to eventually build the
   stacking context tree without the full display list if we find that
   necessary. For instance, this might be useful to cache containing
   block boundaries.
3. Add a `DisplayList` struct that stores both the WebRender display
   list builder and the compositor info. This exposes the API to the
   layout thread for display list building.

In addition, this change adds a lot of missing documentation.  This
should not change behavior.
This commit is contained in:
Martin Robinson 2023-05-15 10:35:45 +02:00
parent c5d31c3ab6
commit b60e105526
5 changed files with 326 additions and 178 deletions

View file

@ -10,6 +10,7 @@ use crate::fragments::{BoxFragment, Fragment, TextFragment};
use crate::geom::{PhysicalPoint, PhysicalRect};
use crate::replaced::IntrinsicSizes;
use crate::style_ext::ComputedValuesExt;
use crate::FragmentTree;
use embedder_traits::Cursor;
use euclid::{Point2D, SideOffsets2D, Size2D};
use fnv::FnvHashMap;
@ -32,7 +33,9 @@ use webrender_api::{self as wr, units};
mod background;
mod conversions;
mod gradient;
pub mod stacking_context;
mod stacking_context;
pub use stacking_context::*;
#[derive(Clone, Copy)]
pub struct WebRenderImageInfo {
@ -45,38 +48,99 @@ pub struct WebRenderImageInfo {
type ItemTag = (u64, u16);
type HitInfo = Option<ItemTag>;
pub struct DisplayListBuilder<'a> {
/// The current SpatialId and ClipId information for this `DisplayListBuilder`.
current_space_and_clip: wr::SpaceAndClipInfo,
element_for_canvas_background: OpaqueNode,
pub context: &'a LayoutContext<'a>,
/// Where the information that's used to build display lists is stored. This
/// includes both a [wr::DisplayListBuilder] for building up WebRender-specific
/// display list information and a [CompositorDisplayListInfo] used to store
/// information used by the compositor, such as a compositor-side scroll tree.
pub struct DisplayList {
/// The [wr::DisplayListBuilder] used to collect display list items.
pub wr: wr::DisplayListBuilder,
pub compositor_info: CompositorDisplayListInfo,
pub iframe_sizes: FnvHashMap<BrowsingContextId, Size2D<f32, CSSPixel>>,
/// Contentful paint, for the purpose of
/// https://w3c.github.io/paint-timing/#first-contentful-paint
/// (i.e. the display list contains items of type text,
/// image, non-white canvas or SVG). Used by metrics.
pub is_contentful: bool,
/// The information about the WebRender display list that the compositor
/// consumes. This curerntly contains the out-of-band hit testing information
/// data structure that the compositor uses to map hit tests to information
/// about the item hit.
pub compositor_info: CompositorDisplayListInfo,
}
impl<'a> DisplayListBuilder<'a> {
impl DisplayList {
/// Create a new [DisplayList] given the dimensions of the layout and the WebRender
/// pipeline id.
///
/// TODO(mrobinson): `_viewport_size` will eventually be used in the creation
/// of the compositor-side scroll tree.
pub fn new(
_viewport_size: units::LayoutSize,
content_size: units::LayoutSize,
pipeline_id: wr::PipelineId,
context: &'a LayoutContext,
fragment_tree: &crate::FragmentTree,
) -> Self {
Self {
current_space_and_clip: wr::SpaceAndClipInfo::root_scroll(pipeline_id),
wr: wr::DisplayListBuilder::new(pipeline_id, content_size),
compositor_info: CompositorDisplayListInfo::default(),
}
}
}
pub(crate) struct DisplayListBuilder<'a> {
/// The current [wr::SpatialId] for this [DisplayListBuilder]. This allows
/// only passing the builder instead passing the containing
/// [stacking_context::StackingContextFragment] as an argument to display
/// list building functions.
current_spatial_id: wr::SpatialId,
/// The current [wr::ClipId] for this [DisplayListBuilder]. This allows
/// only passing the builder instead passing the containing
/// [stacking_context::StackingContextFragment] as an argument to display
/// list building functions.
current_clip_id: wr::ClipId,
/// The [OpaqueNode] handle to the node used to paint the page background
/// if the background was a canvas.
element_for_canvas_background: OpaqueNode,
/// A [LayoutContext] used to get information about the device pixel ratio
/// and get handles to WebRender images.
pub context: &'a LayoutContext<'a>,
/// The [DisplayList] used to collect display list items and metadata.
pub display_list: &'a mut DisplayList,
/// A recording of the sizes of iframes encountered when building this
/// display list. This information is forwarded to the layout thread for the
/// iframe so that its layout knows how large the initial containing block /
/// viewport is.
iframe_sizes: FnvHashMap<BrowsingContextId, Size2D<f32, CSSPixel>>,
/// Contentful paint i.e. whether the display list contains items of type
/// text, image, non-white canvas or SVG). Used by metrics.
/// See https://w3c.github.io/paint-timing/#first-contentful-paint.
is_contentful: bool,
}
impl DisplayList {
pub fn build<'a>(
&mut self,
context: &'a LayoutContext,
fragment_tree: &FragmentTree,
root_stacking_context: &StackingContext,
) -> (FnvHashMap<BrowsingContextId, Size2D<f32, CSSPixel>>, bool) {
let mut builder = DisplayListBuilder {
current_spatial_id: wr::SpatialId::root_scroll_node(self.wr.pipeline_id),
current_clip_id: wr::ClipId::root(self.wr.pipeline_id),
element_for_canvas_background: fragment_tree.canvas_background.from_element,
is_contentful: false,
context,
wr: wr::DisplayListBuilder::new(pipeline_id, fragment_tree.scrollable_overflow()),
compositor_info: CompositorDisplayListInfo::default(),
display_list: self,
iframe_sizes: FnvHashMap::default(),
}
};
fragment_tree.build_display_list(&mut builder, root_stacking_context);
(builder.iframe_sizes, builder.is_contentful)
}
}
impl<'a> DisplayListBuilder<'a> {
fn wr(&mut self) -> &mut wr::DisplayListBuilder {
&mut self.display_list.wr
}
fn common_properties(
@ -89,8 +153,8 @@ impl<'a> DisplayListBuilder<'a> {
// for fragments that paint their entire border rectangle.
wr::CommonItemProperties {
clip_rect,
spatial_id: self.current_space_and_clip.spatial_id,
clip_id: self.current_space_and_clip.clip_id,
spatial_id: self.current_spatial_id,
clip_id: self.current_clip_id,
hit_info: None,
flags: style.get_webrender_primitive_flags(),
}
@ -109,7 +173,7 @@ impl<'a> DisplayListBuilder<'a> {
return None;
}
let hit_test_index = self.compositor_info.add_hit_test_info(
let hit_test_index = self.display_list.compositor_info.add_hit_test_info(
tag?.node.0 as u64,
Some(cursor(inherited_ui.cursor.keyword, auto_cursor)),
);
@ -143,7 +207,7 @@ impl Fragment {
.translate(containing_block.origin.to_vector());
let common = builder.common_properties(rect.to_webrender(), &i.style);
builder.wr.push_image(
builder.wr().push_image(
&common,
rect.to_webrender(),
image_rendering(i.style.get_inherited_box().image_rendering),
@ -169,7 +233,7 @@ impl Fragment {
);
let common = builder.common_properties(rect.to_webrender(), &iframe.style);
builder.wr.push_iframe(
builder.wr().push_iframe(
rect.to_webrender(),
common.clip_rect,
&wr::SpaceAndClipInfo {
@ -248,7 +312,7 @@ impl Fragment {
}
// Text.
builder.wr.push_text(
builder.wr().push_text(
&common,
rect.to_webrender(),
&glyphs,
@ -287,7 +351,7 @@ impl Fragment {
if text_decoration_style == ComputedTextDecorationStyle::MozNone {
return;
}
builder.wr.push_line(
builder.display_list.wr.push_line(
&builder.common_properties(rect, &fragment.parent_style),
&rect,
wavy_line_thickness,
@ -448,7 +512,7 @@ impl<'a> BuilderForBoxFragment<'a> {
if let Some(clip_id) = self.border_edge_clip(builder) {
common.clip_id = clip_id
}
builder.wr.push_hit_test(&common)
builder.wr().push_hit_test(&common)
}
}
@ -473,7 +537,7 @@ impl<'a> BuilderForBoxFragment<'a> {
let layer_index = b.background_image.0.len() - 1;
let (bounds, common) = background::painting_area(self, &source, builder, layer_index);
builder
.wr
.wr()
.push_rect(&common, *bounds, rgba(background_color))
}
@ -550,7 +614,7 @@ impl<'a> BuilderForBoxFragment<'a> {
{
let image_rendering = image_rendering(style.clone_image_rendering());
if layer.repeat {
builder.wr.push_repeating_image(
builder.wr().push_repeating_image(
&layer.common,
layer.bounds,
layer.tile_size,
@ -561,7 +625,7 @@ impl<'a> BuilderForBoxFragment<'a> {
wr::ColorF::WHITE,
)
} else {
builder.wr.push_image(
builder.wr().push_image(
&layer.common,
layer.bounds,
image_rendering,
@ -620,7 +684,7 @@ impl<'a> BuilderForBoxFragment<'a> {
do_aa: true,
});
builder
.wr
.wr()
.push_border(&common, self.border_rect, widths, details)
}
@ -655,7 +719,7 @@ impl<'a> BuilderForBoxFragment<'a> {
do_aa: true,
});
builder
.wr
.wr()
.push_border(&common, outline_rect, widths, details)
}
}
@ -801,8 +865,12 @@ fn clip_for_radii(
if radii.is_zero() {
None
} else {
Some(builder.wr.define_clip_rounded_rect(
&builder.current_space_and_clip,
let parent_space_and_clip = wr::SpaceAndClipInfo {
spatial_id: builder.current_spatial_id,
clip_id: builder.current_clip_id,
};
Some(builder.wr().define_clip_rounded_rect(
&parent_space_and_clip,
wr::ComplexClipRegion {
rect,
radii,