mirror of
https://github.com/servo/servo.git
synced 2025-09-27 23:30:08 +01:00
layout: Split stacking context and display list construction (#37047)
Previously, after a layout was finished (or skipped in the case of repaint-only layout), both the stacking context tree and display list were built. In the case of repaint-only layout, we should be able to skip the reconstruction of the stacking context tree and only do display list building. This change does that, also generally cleaning and up and clarifying the data structure used during this phase of layout. This opens up the possibility of a new kind of incremental layout that does both repaint and a rebuild of the stacking context tree. On the blaster.html test case[^1], this reduces tightly-measured layout time from ~45-50 milliseconds to ~25-30 milliseconds on my M3. [^1]: https://gist.github.com/mrobinson/44ec87d028c0198917a7715a06dd98a0 Testing: There are currently no performance tests for layout. :( This should not modify the results of WPT tests. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
27c8a899ea
commit
d8294fa423
13 changed files with 967 additions and 789 deletions
|
@ -77,7 +77,7 @@ use webrender_api::units::{DevicePixel, DevicePoint, LayoutPixel, LayoutPoint, L
|
|||
use webrender_api::{ExternalScrollId, HitTestFlags};
|
||||
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::{DisplayList, WebRenderImageInfo};
|
||||
use crate::display_list::{DisplayListBuilder, StackingContextTree, WebRenderImageInfo};
|
||||
use crate::query::{
|
||||
get_the_text_steps, process_client_rect_request, process_content_box_request,
|
||||
process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query,
|
||||
|
@ -144,6 +144,9 @@ pub struct LayoutThread {
|
|||
/// The fragment tree.
|
||||
fragment_tree: RefCell<Option<Arc<FragmentTree>>>,
|
||||
|
||||
/// The [`StackingContextTree`] cached from previous layouts.
|
||||
stacking_context_tree: RefCell<Option<StackingContextTree>>,
|
||||
|
||||
/// A counter for epoch messages
|
||||
epoch: Cell<Epoch>,
|
||||
|
||||
|
@ -521,6 +524,7 @@ impl LayoutThread {
|
|||
first_reflow: Cell::new(true),
|
||||
box_tree: Default::default(),
|
||||
fragment_tree: Default::default(),
|
||||
stacking_context_tree: Default::default(),
|
||||
// Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR
|
||||
epoch: Cell::new(Epoch(1)),
|
||||
viewport_size: Size2D::new(
|
||||
|
@ -649,15 +653,17 @@ impl LayoutThread {
|
|||
highlighted_dom_node: reflow_request.highlighted_dom_node,
|
||||
};
|
||||
|
||||
self.restyle_and_build_trees(
|
||||
let did_reflow = self.restyle_and_build_trees(
|
||||
&reflow_request,
|
||||
root_element,
|
||||
rayon_pool,
|
||||
&mut layout_context,
|
||||
);
|
||||
self.build_display_list(&reflow_request, &mut layout_context);
|
||||
self.first_reflow.set(false);
|
||||
|
||||
self.build_stacking_context_tree(&reflow_request, did_reflow);
|
||||
self.build_display_list(&reflow_request, &mut layout_context);
|
||||
|
||||
self.first_reflow.set(false);
|
||||
if let ReflowGoal::UpdateScrollNode(scroll_state) = reflow_request.reflow_goal {
|
||||
self.update_scroll_node_state(&scroll_state);
|
||||
}
|
||||
|
@ -666,6 +672,7 @@ impl LayoutThread {
|
|||
let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock());
|
||||
let node_to_image_animation_map =
|
||||
std::mem::take(&mut *layout_context.node_image_animation_map.write());
|
||||
|
||||
Some(ReflowResult {
|
||||
pending_images,
|
||||
iframe_sizes,
|
||||
|
@ -742,7 +749,7 @@ impl LayoutThread {
|
|||
root_element: ServoLayoutElement<'_>,
|
||||
rayon_pool: Option<&ThreadPool>,
|
||||
layout_context: &mut LayoutContext<'_>,
|
||||
) {
|
||||
) -> bool {
|
||||
let dirty_root = unsafe {
|
||||
ServoLayoutNode::new(&reflow_request.dirty_root.unwrap())
|
||||
.as_element()
|
||||
|
@ -758,7 +765,7 @@ impl LayoutThread {
|
|||
|
||||
if !token.should_traverse() {
|
||||
layout_context.style_context.stylist.rule_tree().maybe_gc();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let dirty_root: ServoLayoutNode =
|
||||
|
@ -768,7 +775,7 @@ impl LayoutThread {
|
|||
let damage = compute_damage_and_repair_style(layout_context.shared_context(), root_node);
|
||||
if damage == RestyleDamage::REPAINT {
|
||||
layout_context.style_context.stylist.rule_tree().maybe_gc();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut box_tree = self.box_tree.borrow_mut();
|
||||
|
@ -803,8 +810,15 @@ impl LayoutThread {
|
|||
run_layout()
|
||||
});
|
||||
|
||||
if self.debug.dump_flow_tree {
|
||||
fragment_tree.print();
|
||||
}
|
||||
*self.fragment_tree.borrow_mut() = Some(fragment_tree);
|
||||
|
||||
// The FragmentTree has been updated, so any existing StackingContext tree that layout
|
||||
// had is now out of date and should be rebuilt.
|
||||
*self.stacking_context_tree.borrow_mut() = None;
|
||||
|
||||
if self.debug.dump_style_tree {
|
||||
println!(
|
||||
"{:?}",
|
||||
|
@ -822,6 +836,39 @@ impl LayoutThread {
|
|||
|
||||
// GC the rule tree if some heuristics are met.
|
||||
layout_context.style_context.stylist.rule_tree().maybe_gc();
|
||||
true
|
||||
}
|
||||
|
||||
fn build_stacking_context_tree(&self, reflow_request: &ReflowRequest, did_reflow: bool) {
|
||||
if !reflow_request.reflow_goal.needs_display_list() &&
|
||||
!reflow_request.reflow_goal.needs_display()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Some(fragment_tree) = &*self.fragment_tree.borrow() else {
|
||||
return;
|
||||
};
|
||||
if !did_reflow && self.stacking_context_tree.borrow().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let viewport_size = LayoutSize::from_untyped(Size2D::new(
|
||||
self.viewport_size.width.to_f32_px(),
|
||||
self.viewport_size.height.to_f32_px(),
|
||||
));
|
||||
|
||||
// Build the StackingContextTree. This turns the `FragmentTree` into a
|
||||
// tree of fragments in CSS painting order and also creates all
|
||||
// applicable spatial and clip nodes.
|
||||
*self.stacking_context_tree.borrow_mut() = Some(StackingContextTree::new(
|
||||
fragment_tree,
|
||||
viewport_size,
|
||||
fragment_tree.scrollable_overflow(),
|
||||
self.id.into(),
|
||||
fragment_tree.viewport_scroll_sensitivity,
|
||||
self.first_reflow.get(),
|
||||
&self.debug,
|
||||
));
|
||||
}
|
||||
|
||||
fn build_display_list(
|
||||
|
@ -829,68 +876,40 @@ impl LayoutThread {
|
|||
reflow_request: &ReflowRequest,
|
||||
layout_context: &mut LayoutContext<'_>,
|
||||
) {
|
||||
if !reflow_request.reflow_goal.needs_display() {
|
||||
return;
|
||||
}
|
||||
let Some(fragment_tree) = &*self.fragment_tree.borrow() else {
|
||||
return;
|
||||
};
|
||||
if !reflow_request.reflow_goal.needs_display_list() {
|
||||
|
||||
let mut stacking_context_tree = self.stacking_context_tree.borrow_mut();
|
||||
let Some(stacking_context_tree) = stacking_context_tree.as_mut() else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut epoch = self.epoch.get();
|
||||
epoch.next();
|
||||
self.epoch.set(epoch);
|
||||
stacking_context_tree.compositor_info.epoch = epoch.into();
|
||||
|
||||
let viewport_size = LayoutSize::from_untyped(Size2D::new(
|
||||
self.viewport_size.width.to_f32_px(),
|
||||
self.viewport_size.height.to_f32_px(),
|
||||
));
|
||||
let mut display_list = DisplayList::new(
|
||||
viewport_size,
|
||||
fragment_tree.scrollable_overflow(),
|
||||
self.id.into(),
|
||||
epoch.into(),
|
||||
fragment_tree.viewport_scroll_sensitivity,
|
||||
self.first_reflow.get(),
|
||||
let built_display_list = DisplayListBuilder::build(
|
||||
layout_context,
|
||||
stacking_context_tree,
|
||||
fragment_tree,
|
||||
&self.debug,
|
||||
);
|
||||
self.compositor_api.send_display_list(
|
||||
self.webview_id,
|
||||
&stacking_context_tree.compositor_info,
|
||||
built_display_list,
|
||||
);
|
||||
display_list.wr.begin();
|
||||
|
||||
// `dump_serialized_display_list` doesn't actually print anything. It sets up
|
||||
// the display list for printing the serialized version when `finalize()` is called.
|
||||
// We need to call this before adding any display items so that they are printed
|
||||
// during `finalize()`.
|
||||
if self.debug.dump_display_list {
|
||||
display_list.wr.dump_serialized_display_list();
|
||||
}
|
||||
|
||||
// Build the root stacking context. This turns the `FragmentTree` into a
|
||||
// tree of fragments in CSS painting order and also creates all
|
||||
// applicable spatial and clip nodes.
|
||||
let root_stacking_context =
|
||||
display_list.build_stacking_context_tree(fragment_tree, &self.debug);
|
||||
|
||||
// Build the rest of the display list which inclues all of the WebRender primitives.
|
||||
display_list.build(layout_context, fragment_tree, &root_stacking_context);
|
||||
|
||||
if self.debug.dump_flow_tree {
|
||||
fragment_tree.print();
|
||||
}
|
||||
if self.debug.dump_stacking_context_tree {
|
||||
root_stacking_context.debug_print();
|
||||
}
|
||||
|
||||
if reflow_request.reflow_goal.needs_display() {
|
||||
self.compositor_api.send_display_list(
|
||||
self.webview_id,
|
||||
display_list.compositor_info,
|
||||
display_list.wr.end().1,
|
||||
);
|
||||
|
||||
let (keys, instance_keys) = self
|
||||
.font_context
|
||||
.collect_unused_webrender_resources(false /* all */);
|
||||
self.compositor_api
|
||||
.remove_unused_font_resources(keys, instance_keys)
|
||||
}
|
||||
let (keys, instance_keys) = self
|
||||
.font_context
|
||||
.collect_unused_webrender_resources(false /* all */);
|
||||
self.compositor_api
|
||||
.remove_unused_font_resources(keys, instance_keys)
|
||||
}
|
||||
|
||||
fn update_scroll_node_state(&self, state: &ScrollState) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue