From 9cd019403f3ffb7c3790f067ca6fc2d4ecb42291 Mon Sep 17 00:00:00 2001 From: Rahul Menon Date: Thu, 21 Aug 2025 02:21:59 -0500 Subject: [PATCH] layout: Measure stacking context tree in layout thread memory report (#38762) Measures the memory usage of the stacking context tree in the memory report of the layout thread by adding `MallocSizeOf` to `StackingContextTree` and all the types required for that. Also requires adding `MallocSizeOf` to some webrender types. Testing: Manually looked at about:memory image Fixes: https://github.com/servo/servo/issues/38725 --------- Signed-off-by: Rahul Menon --- Cargo.lock | 15 ++++++++------- components/layout/display_list/clip.rs | 7 ++++--- .../layout/display_list/stacking_context.rs | 16 +++++++++++----- components/layout/layout_impl.rs | 6 ++++++ components/malloc_size_of/Cargo.toml | 1 + components/malloc_size_of/lib.rs | 15 +++++++++++++++ components/shared/compositing/display_list.rs | 16 ++++++++-------- 7 files changed, 53 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db511b64fa7..34adf5ea032 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6219,7 +6219,7 @@ dependencies = [ [[package]] name = "peek-poke" version = "0.3.0" -source = "git+https://github.com/servo/webrender?branch=0.67#15318d6627e91ec19fc0a44a7434b08673413140" +source = "git+https://github.com/servo/webrender?branch=0.67#428d64dc5c92f157be2b0fd85dc7a6506be77132" dependencies = [ "euclid", "peek-poke-derive", @@ -6228,7 +6228,7 @@ dependencies = [ [[package]] name = "peek-poke-derive" version = "0.3.0" -source = "git+https://github.com/servo/webrender?branch=0.67#15318d6627e91ec19fc0a44a7434b08673413140" +source = "git+https://github.com/servo/webrender?branch=0.67#428d64dc5c92f157be2b0fd85dc7a6506be77132" dependencies = [ "proc-macro2", "quote", @@ -7751,6 +7751,7 @@ dependencies = [ "url", "urlpattern", "uuid", + "webrender", "webrender_api", "wr_malloc_size_of", ] @@ -9687,7 +9688,7 @@ dependencies = [ [[package]] name = "webrender" version = "0.66.0" -source = "git+https://github.com/servo/webrender?branch=0.67#15318d6627e91ec19fc0a44a7434b08673413140" +source = "git+https://github.com/servo/webrender?branch=0.67#428d64dc5c92f157be2b0fd85dc7a6506be77132" dependencies = [ "allocator-api2", "bincode", @@ -9722,7 +9723,7 @@ dependencies = [ [[package]] name = "webrender_api" version = "0.66.0" -source = "git+https://github.com/servo/webrender?branch=0.67#15318d6627e91ec19fc0a44a7434b08673413140" +source = "git+https://github.com/servo/webrender?branch=0.67#428d64dc5c92f157be2b0fd85dc7a6506be77132" dependencies = [ "app_units", "bitflags 2.9.2", @@ -9743,7 +9744,7 @@ dependencies = [ [[package]] name = "webrender_build" version = "0.0.2" -source = "git+https://github.com/servo/webrender?branch=0.67#15318d6627e91ec19fc0a44a7434b08673413140" +source = "git+https://github.com/servo/webrender?branch=0.67#428d64dc5c92f157be2b0fd85dc7a6506be77132" dependencies = [ "bitflags 2.9.2", "lazy_static", @@ -10455,7 +10456,7 @@ dependencies = [ [[package]] name = "wr_glyph_rasterizer" version = "0.1.0" -source = "git+https://github.com/servo/webrender?branch=0.67#15318d6627e91ec19fc0a44a7434b08673413140" +source = "git+https://github.com/servo/webrender?branch=0.67#428d64dc5c92f157be2b0fd85dc7a6506be77132" dependencies = [ "core-foundation 0.9.4", "core-graphics", @@ -10480,7 +10481,7 @@ dependencies = [ [[package]] name = "wr_malloc_size_of" version = "0.2.0" -source = "git+https://github.com/servo/webrender?branch=0.67#15318d6627e91ec19fc0a44a7434b08673413140" +source = "git+https://github.com/servo/webrender?branch=0.67#428d64dc5c92f157be2b0fd85dc7a6506be77132" dependencies = [ "app_units", "euclid", diff --git a/components/layout/display_list/clip.rs b/components/layout/display_list/clip.rs index 606ed6204da..6ebd4a3824f 100644 --- a/components/layout/display_list/clip.rs +++ b/components/layout/display_list/clip.rs @@ -4,6 +4,7 @@ use app_units::Au; use base::id::ScrollTreeNodeId; +use malloc_size_of_derive::MallocSizeOf; use style::values::computed::basic_shape::{BasicShape, ClipPath}; use style::values::computed::length_percentage::NonNegativeLengthPercentage; use style::values::computed::position::Position; @@ -16,7 +17,7 @@ use super::{BuilderForBoxFragment, compute_margin_box_radius, normalize_radii}; /// An identifier for a clip used during StackingContextTree construction. This is a simple index in /// a [`ClipStore`]s vector of clips. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] pub(crate) struct ClipId(pub usize); impl ClipId { @@ -27,7 +28,7 @@ impl ClipId { /// All the information needed to create a clip on a WebRender display list. These are created at /// two times: during `StackingContextTree` creation and during WebRender display list construction. /// Only the former are stored in a [`ClipStore`]. -#[derive(Clone)] +#[derive(Clone, MallocSizeOf)] pub(crate) struct Clip { pub id: ClipId, pub radii: BorderRadius, @@ -39,7 +40,7 @@ pub(crate) struct Clip { /// A simple vector of [`Clip`] that is built during `StackingContextTree` construction. /// These are later turned into WebRender clips and clip chains during WebRender display /// list construction. -#[derive(Clone, Default)] +#[derive(Clone, Default, MallocSizeOf)] pub(crate) struct StackingContextTreeClipStore(pub Vec); impl StackingContextTreeClipStore { diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index 2eb0580f24a..d082c484c2b 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -18,6 +18,7 @@ use embedder_traits::ViewportDetails; use euclid::SideOffsets2D; use euclid::default::{Point2D, Rect, Size2D}; use log::warn; +use malloc_size_of_derive::MallocSizeOf; use servo_config::opts::DebugOptions; use style::Zero; use style::color::AbsoluteColor; @@ -92,7 +93,7 @@ impl ContainingBlock { pub(crate) type ContainingBlockInfo<'a> = ContainingBlockManager<'a, ContainingBlock>; -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, Ord, MallocSizeOf, PartialEq, PartialOrd)] pub(crate) enum StackingContextSection { OwnBackgroundsAndBorders, DescendantBackgroundsAndBorders, @@ -100,6 +101,7 @@ pub(crate) enum StackingContextSection { Outline, } +#[derive(MallocSizeOf)] pub(crate) struct ScrollFrameHitTestItem { /// The [`ScrollTreeNodeId`] of the spatial node that contains this hit test item. pub scroll_node_id: ScrollTreeNodeId, @@ -116,6 +118,7 @@ pub(crate) struct ScrollFrameHitTestItem { pub external_scroll_id: ExternalScrollId, } +#[derive(MallocSizeOf)] pub(crate) struct StackingContextTree { /// The root stacking context of this [`StackingContextTree`]. pub root_stacking_context: StackingContext, @@ -282,7 +285,7 @@ impl StackingContextTree { } /// The text decorations for a Fragment, collecting during [`StackingContextTree`] construction. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, MallocSizeOf)] pub(crate) struct FragmentTextDecoration { pub line: TextDecorationLine, pub color: AbsoluteColor, @@ -293,6 +296,7 @@ pub(crate) struct FragmentTextDecoration { /// /// This is generally part of a fragment, like its borders or foreground, but it /// can also be a stacking container that needs to be painted in fragment order. +#[derive(MallocSizeOf)] pub(crate) enum StackingContextContent { /// A fragment that does not generate a stacking context or stacking container. Fragment { @@ -303,6 +307,7 @@ pub(crate) enum StackingContextContent { containing_block: PhysicalRect, fragment: Fragment, is_collapsed_table_borders: bool, + #[conditional_malloc_size_of] text_decorations: Arc>, }, @@ -354,7 +359,7 @@ impl StackingContextContent { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] pub(crate) enum StackingContextType { RealStackingContext, PositionedStackingContainer, @@ -367,6 +372,7 @@ pub(crate) enum StackingContextType { /// /// We use the term “real stacking context” in situations that call for a /// stacking context but not a stacking container. +#[derive(MallocSizeOf)] pub struct StackingContext { /// The spatial id of this fragment. This is used to properly handle /// things like preserve-3d. @@ -416,14 +422,14 @@ pub struct StackingContext { } /// Refers to one of the child contents or stacking contexts of a [StackingContext]. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, MallocSizeOf)] pub struct DebugPrintItem { field: DebugPrintField, index: usize, } /// Refers to one of the vecs of a [StackingContext]. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, MallocSizeOf)] pub enum DebugPrintField { Contents, RealStackingContextsAndPositionedStackingContainers, diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index 869f34c6269..ded3558a245 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -459,6 +459,12 @@ impl Layout for LayoutThread { .unwrap_or_default(), }); + reports.push(Report { + path: path![formatted_url, "layout-thread", "stacking-context-tree"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: self.stacking_context_tree.size_of(ops), + }); + reports.push(self.image_cache.memory_report(formatted_url, ops)); } diff --git a/components/malloc_size_of/Cargo.toml b/components/malloc_size_of/Cargo.toml index 6e5e45a4e35..b23c6fe2057 100644 --- a/components/malloc_size_of/Cargo.toml +++ b/components/malloc_size_of/Cargo.toml @@ -39,5 +39,6 @@ unicode-script = { workspace = true } url = { workspace = true } urlpattern = { workspace = true } uuid = { workspace = true } +webrender = { workspace = true } webrender_api = { workspace = true } wr_malloc_size_of = { workspace = true } diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index 7897a85dc9b..2ad54e623b3 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -643,6 +643,12 @@ impl MallocSizeOf for euclid::Point2D { } } +impl MallocSizeOf for euclid::Box2D { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.min.size_of(ops) + self.max.size_of(ops) + } +} + impl MallocSizeOf for euclid::Rect { fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { self.origin.size_of(ops) + self.size.size_of(ops) @@ -826,7 +832,9 @@ malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BorderRadius); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BorderStyle); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BoxShadowClipMode); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ColorF); +malloc_size_of_is_webrender_malloc_size_of!(webrender_api::Epoch); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ExtendMode); +malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ExternalScrollId); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontKey); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontInstanceKey); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::GlyphInstance); @@ -836,8 +844,14 @@ malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ImageRendering); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::LineStyle); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::MixBlendMode); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::NormalBorder); +malloc_size_of_is_webrender_malloc_size_of!(webrender_api::PipelineId); +malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ReferenceFrameKind); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::RepeatMode); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontVariation); +malloc_size_of_is_webrender_malloc_size_of!(webrender_api::SpatialId); +malloc_size_of_is_webrender_malloc_size_of!(webrender_api::StickyOffsetBounds); +malloc_size_of_is_webrender_malloc_size_of!(webrender_api::TransformStyle); +malloc_size_of_is_webrender_malloc_size_of!(webrender::FastTransform); macro_rules! malloc_size_of_is_stylo_malloc_size_of( ($($ty:ty),+) => ( @@ -892,6 +906,7 @@ malloc_size_of_is_stylo_malloc_size_of!(style::attr::AttrIdentifier); malloc_size_of_is_stylo_malloc_size_of!(style::attr::AttrValue); malloc_size_of_is_stylo_malloc_size_of!(style::color::AbsoluteColor); malloc_size_of_is_stylo_malloc_size_of!(style::computed_values::font_variant_caps::T); +malloc_size_of_is_stylo_malloc_size_of!(style::computed_values::text_decoration_style::T); malloc_size_of_is_stylo_malloc_size_of!(style::dom::OpaqueNode); malloc_size_of_is_stylo_malloc_size_of!(style::invalidation::element::restyle_hints::RestyleHint); malloc_size_of_is_stylo_malloc_size_of!(style::logical_geometry::WritingMode); diff --git a/components/shared/compositing/display_list.rs b/components/shared/compositing/display_list.rs index 604ef5b3bf4..979bbdf84cb 100644 --- a/components/shared/compositing/display_list.rs +++ b/components/shared/compositing/display_list.rs @@ -56,14 +56,14 @@ pub struct AxesScrollSensitivity { pub y: ScrollType, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] pub enum SpatialTreeNodeInfo { ReferenceFrame(ReferenceFrameNodeInfo), Scroll(ScrollableNodeInfo), Sticky(StickyNodeInfo), } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] pub struct StickyNodeInfo { pub frame_rect: LayoutRect, pub margins: SideOffsets2D, LayoutPixel>, @@ -160,7 +160,7 @@ impl StickyNodeInfo { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] pub struct ReferenceFrameNodeInfo { pub origin: LayoutPoint, /// Origin of this frame relative to the document for bounding box queries. @@ -172,7 +172,7 @@ pub struct ReferenceFrameNodeInfo { /// Data stored for nodes in the [ScrollTree] that actually scroll, /// as opposed to reference frames and sticky nodes which do not. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] pub struct ScrollableNodeInfo { /// The external scroll id of this node, used to track /// it between successive re-layouts. @@ -278,7 +278,7 @@ impl ScrollableNodeInfo { /// Potential ideas for improvement: /// - Test optimizing simple translations to avoid having to do full matrix /// multiplication when transforms are not involved. -#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, Serialize)] pub struct ScrollTreeNodeTransformationCache { node_to_root_transform: FastLayoutTransform, root_to_node_transform: Option, @@ -290,7 +290,7 @@ struct AncestorStickyInfo { nearest_scrolling_ancestor_viewport: LayoutRect, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] /// A node in a tree of scroll nodes. This may either be a scrollable /// node which responds to scroll events or a non-scrollable one. pub struct ScrollTreeNode { @@ -421,7 +421,7 @@ impl ScrollTreeNode { /// A tree of spatial nodes, which mirrors the spatial nodes in the WebRender /// display list, except these are used to scrolling in the compositor so that /// new offsets can be sent to WebRender. -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Debug, Default, Deserialize, MallocSizeOf, Serialize)] pub struct ScrollTree { /// A list of compositor-side scroll nodes that describe the tree /// of WebRender spatial nodes, used by the compositor to scroll the @@ -778,7 +778,7 @@ impl ScrollTree { /// A data structure which stores compositor-side information about /// display lists sent to the compositor. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, MallocSizeOf, Serialize)] pub struct CompositorDisplayListInfo { /// The WebRender [PipelineId] of this display list. pub pipeline_id: PipelineId,