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
<img width="636" height="241" alt="image"
src="https://github.com/user-attachments/assets/6bf9d65a-0bf0-4a99-99b5-ddedba3269c1"
/>

Fixes: https://github.com/servo/servo/issues/38725

---------

Signed-off-by: Rahul Menon <menonrahul02@gmail.com>
This commit is contained in:
Rahul Menon 2025-08-21 02:21:59 -05:00 committed by GitHub
parent 634c1897cf
commit 9cd019403f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 53 additions and 23 deletions

15
Cargo.lock generated
View file

@ -6219,7 +6219,7 @@ dependencies = [
[[package]] [[package]]
name = "peek-poke" name = "peek-poke"
version = "0.3.0" 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 = [ dependencies = [
"euclid", "euclid",
"peek-poke-derive", "peek-poke-derive",
@ -6228,7 +6228,7 @@ dependencies = [
[[package]] [[package]]
name = "peek-poke-derive" name = "peek-poke-derive"
version = "0.3.0" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -7751,6 +7751,7 @@ dependencies = [
"url", "url",
"urlpattern", "urlpattern",
"uuid", "uuid",
"webrender",
"webrender_api", "webrender_api",
"wr_malloc_size_of", "wr_malloc_size_of",
] ]
@ -9687,7 +9688,7 @@ dependencies = [
[[package]] [[package]]
name = "webrender" name = "webrender"
version = "0.66.0" 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 = [ dependencies = [
"allocator-api2", "allocator-api2",
"bincode", "bincode",
@ -9722,7 +9723,7 @@ dependencies = [
[[package]] [[package]]
name = "webrender_api" name = "webrender_api"
version = "0.66.0" 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 = [ dependencies = [
"app_units", "app_units",
"bitflags 2.9.2", "bitflags 2.9.2",
@ -9743,7 +9744,7 @@ dependencies = [
[[package]] [[package]]
name = "webrender_build" name = "webrender_build"
version = "0.0.2" 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 = [ dependencies = [
"bitflags 2.9.2", "bitflags 2.9.2",
"lazy_static", "lazy_static",
@ -10455,7 +10456,7 @@ dependencies = [
[[package]] [[package]]
name = "wr_glyph_rasterizer" name = "wr_glyph_rasterizer"
version = "0.1.0" 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 = [ dependencies = [
"core-foundation 0.9.4", "core-foundation 0.9.4",
"core-graphics", "core-graphics",
@ -10480,7 +10481,7 @@ dependencies = [
[[package]] [[package]]
name = "wr_malloc_size_of" name = "wr_malloc_size_of"
version = "0.2.0" 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 = [ dependencies = [
"app_units", "app_units",
"euclid", "euclid",

View file

@ -4,6 +4,7 @@
use app_units::Au; use app_units::Au;
use base::id::ScrollTreeNodeId; use base::id::ScrollTreeNodeId;
use malloc_size_of_derive::MallocSizeOf;
use style::values::computed::basic_shape::{BasicShape, ClipPath}; use style::values::computed::basic_shape::{BasicShape, ClipPath};
use style::values::computed::length_percentage::NonNegativeLengthPercentage; use style::values::computed::length_percentage::NonNegativeLengthPercentage;
use style::values::computed::position::Position; 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 /// An identifier for a clip used during StackingContextTree construction. This is a simple index in
/// a [`ClipStore`]s vector of clips. /// 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); pub(crate) struct ClipId(pub usize);
impl ClipId { 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 /// 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. /// two times: during `StackingContextTree` creation and during WebRender display list construction.
/// Only the former are stored in a [`ClipStore`]. /// Only the former are stored in a [`ClipStore`].
#[derive(Clone)] #[derive(Clone, MallocSizeOf)]
pub(crate) struct Clip { pub(crate) struct Clip {
pub id: ClipId, pub id: ClipId,
pub radii: BorderRadius, pub radii: BorderRadius,
@ -39,7 +40,7 @@ pub(crate) struct Clip {
/// A simple vector of [`Clip`] that is built during `StackingContextTree` construction. /// A simple vector of [`Clip`] that is built during `StackingContextTree` construction.
/// These are later turned into WebRender clips and clip chains during WebRender display /// These are later turned into WebRender clips and clip chains during WebRender display
/// list construction. /// list construction.
#[derive(Clone, Default)] #[derive(Clone, Default, MallocSizeOf)]
pub(crate) struct StackingContextTreeClipStore(pub Vec<Clip>); pub(crate) struct StackingContextTreeClipStore(pub Vec<Clip>);
impl StackingContextTreeClipStore { impl StackingContextTreeClipStore {

View file

@ -18,6 +18,7 @@ use embedder_traits::ViewportDetails;
use euclid::SideOffsets2D; use euclid::SideOffsets2D;
use euclid::default::{Point2D, Rect, Size2D}; use euclid::default::{Point2D, Rect, Size2D};
use log::warn; use log::warn;
use malloc_size_of_derive::MallocSizeOf;
use servo_config::opts::DebugOptions; use servo_config::opts::DebugOptions;
use style::Zero; use style::Zero;
use style::color::AbsoluteColor; use style::color::AbsoluteColor;
@ -92,7 +93,7 @@ impl ContainingBlock {
pub(crate) type ContainingBlockInfo<'a> = ContainingBlockManager<'a, 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 { pub(crate) enum StackingContextSection {
OwnBackgroundsAndBorders, OwnBackgroundsAndBorders,
DescendantBackgroundsAndBorders, DescendantBackgroundsAndBorders,
@ -100,6 +101,7 @@ pub(crate) enum StackingContextSection {
Outline, Outline,
} }
#[derive(MallocSizeOf)]
pub(crate) struct ScrollFrameHitTestItem { pub(crate) struct ScrollFrameHitTestItem {
/// The [`ScrollTreeNodeId`] of the spatial node that contains this hit test item. /// The [`ScrollTreeNodeId`] of the spatial node that contains this hit test item.
pub scroll_node_id: ScrollTreeNodeId, pub scroll_node_id: ScrollTreeNodeId,
@ -116,6 +118,7 @@ pub(crate) struct ScrollFrameHitTestItem {
pub external_scroll_id: ExternalScrollId, pub external_scroll_id: ExternalScrollId,
} }
#[derive(MallocSizeOf)]
pub(crate) struct StackingContextTree { pub(crate) struct StackingContextTree {
/// The root stacking context of this [`StackingContextTree`]. /// The root stacking context of this [`StackingContextTree`].
pub root_stacking_context: StackingContext, pub root_stacking_context: StackingContext,
@ -282,7 +285,7 @@ impl StackingContextTree {
} }
/// The text decorations for a Fragment, collecting during [`StackingContextTree`] construction. /// The text decorations for a Fragment, collecting during [`StackingContextTree`] construction.
#[derive(Clone, Debug)] #[derive(Clone, Debug, MallocSizeOf)]
pub(crate) struct FragmentTextDecoration { pub(crate) struct FragmentTextDecoration {
pub line: TextDecorationLine, pub line: TextDecorationLine,
pub color: AbsoluteColor, 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 /// 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. /// can also be a stacking container that needs to be painted in fragment order.
#[derive(MallocSizeOf)]
pub(crate) enum StackingContextContent { pub(crate) enum StackingContextContent {
/// A fragment that does not generate a stacking context or stacking container. /// A fragment that does not generate a stacking context or stacking container.
Fragment { Fragment {
@ -303,6 +307,7 @@ pub(crate) enum StackingContextContent {
containing_block: PhysicalRect<Au>, containing_block: PhysicalRect<Au>,
fragment: Fragment, fragment: Fragment,
is_collapsed_table_borders: bool, is_collapsed_table_borders: bool,
#[conditional_malloc_size_of]
text_decorations: Arc<Vec<FragmentTextDecoration>>, text_decorations: Arc<Vec<FragmentTextDecoration>>,
}, },
@ -354,7 +359,7 @@ impl StackingContextContent {
} }
} }
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
pub(crate) enum StackingContextType { pub(crate) enum StackingContextType {
RealStackingContext, RealStackingContext,
PositionedStackingContainer, PositionedStackingContainer,
@ -367,6 +372,7 @@ pub(crate) enum StackingContextType {
/// ///
/// We use the term “real stacking context” in situations that call for a /// We use the term “real stacking context” in situations that call for a
/// stacking context but not a stacking container. /// stacking context but not a stacking container.
#[derive(MallocSizeOf)]
pub struct StackingContext { pub struct StackingContext {
/// The spatial id of this fragment. This is used to properly handle /// The spatial id of this fragment. This is used to properly handle
/// things like preserve-3d. /// things like preserve-3d.
@ -416,14 +422,14 @@ pub struct StackingContext {
} }
/// Refers to one of the child contents or stacking contexts of a [StackingContext]. /// Refers to one of the child contents or stacking contexts of a [StackingContext].
#[derive(Clone, Copy)] #[derive(Clone, Copy, MallocSizeOf)]
pub struct DebugPrintItem { pub struct DebugPrintItem {
field: DebugPrintField, field: DebugPrintField,
index: usize, index: usize,
} }
/// Refers to one of the vecs of a [StackingContext]. /// Refers to one of the vecs of a [StackingContext].
#[derive(Clone, Copy)] #[derive(Clone, Copy, MallocSizeOf)]
pub enum DebugPrintField { pub enum DebugPrintField {
Contents, Contents,
RealStackingContextsAndPositionedStackingContainers, RealStackingContextsAndPositionedStackingContainers,

View file

@ -459,6 +459,12 @@ impl Layout for LayoutThread {
.unwrap_or_default(), .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)); reports.push(self.image_cache.memory_report(formatted_url, ops));
} }

View file

@ -39,5 +39,6 @@ unicode-script = { workspace = true }
url = { workspace = true } url = { workspace = true }
urlpattern = { workspace = true } urlpattern = { workspace = true }
uuid = { workspace = true } uuid = { workspace = true }
webrender = { workspace = true }
webrender_api = { workspace = true } webrender_api = { workspace = true }
wr_malloc_size_of = { workspace = true } wr_malloc_size_of = { workspace = true }

View file

@ -643,6 +643,12 @@ impl<T: MallocSizeOf, U> MallocSizeOf for euclid::Point2D<T, U> {
} }
} }
impl<T: MallocSizeOf, U> MallocSizeOf for euclid::Box2D<T, U> {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self.min.size_of(ops) + self.max.size_of(ops)
}
}
impl<T: MallocSizeOf, U> MallocSizeOf for euclid::Rect<T, U> { impl<T: MallocSizeOf, U> MallocSizeOf for euclid::Rect<T, U> {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self.origin.size_of(ops) + self.size.size_of(ops) 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::BorderStyle);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BoxShadowClipMode); 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::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::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::FontKey);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontInstanceKey); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontInstanceKey);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::GlyphInstance); 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::LineStyle);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::MixBlendMode); 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::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::RepeatMode);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontVariation); 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<webrender_api::units::LayoutPixel,webrender_api::units::LayoutPixel>);
macro_rules! malloc_size_of_is_stylo_malloc_size_of( macro_rules! malloc_size_of_is_stylo_malloc_size_of(
($($ty:ty),+) => ( ($($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::attr::AttrValue);
malloc_size_of_is_stylo_malloc_size_of!(style::color::AbsoluteColor); 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::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::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::invalidation::element::restyle_hints::RestyleHint);
malloc_size_of_is_stylo_malloc_size_of!(style::logical_geometry::WritingMode); malloc_size_of_is_stylo_malloc_size_of!(style::logical_geometry::WritingMode);

View file

@ -56,14 +56,14 @@ pub struct AxesScrollSensitivity {
pub y: ScrollType, pub y: ScrollType,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
pub enum SpatialTreeNodeInfo { pub enum SpatialTreeNodeInfo {
ReferenceFrame(ReferenceFrameNodeInfo), ReferenceFrame(ReferenceFrameNodeInfo),
Scroll(ScrollableNodeInfo), Scroll(ScrollableNodeInfo),
Sticky(StickyNodeInfo), Sticky(StickyNodeInfo),
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct StickyNodeInfo { pub struct StickyNodeInfo {
pub frame_rect: LayoutRect, pub frame_rect: LayoutRect,
pub margins: SideOffsets2D<Option<f32>, LayoutPixel>, pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
@ -160,7 +160,7 @@ impl StickyNodeInfo {
} }
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct ReferenceFrameNodeInfo { pub struct ReferenceFrameNodeInfo {
pub origin: LayoutPoint, pub origin: LayoutPoint,
/// Origin of this frame relative to the document for bounding box queries. /// 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, /// Data stored for nodes in the [ScrollTree] that actually scroll,
/// as opposed to reference frames and sticky nodes which do not. /// as opposed to reference frames and sticky nodes which do not.
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct ScrollableNodeInfo { pub struct ScrollableNodeInfo {
/// The external scroll id of this node, used to track /// The external scroll id of this node, used to track
/// it between successive re-layouts. /// it between successive re-layouts.
@ -278,7 +278,7 @@ impl ScrollableNodeInfo {
/// Potential ideas for improvement: /// Potential ideas for improvement:
/// - Test optimizing simple translations to avoid having to do full matrix /// - Test optimizing simple translations to avoid having to do full matrix
/// multiplication when transforms are not involved. /// multiplication when transforms are not involved.
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, Serialize)]
pub struct ScrollTreeNodeTransformationCache { pub struct ScrollTreeNodeTransformationCache {
node_to_root_transform: FastLayoutTransform, node_to_root_transform: FastLayoutTransform,
root_to_node_transform: Option<FastLayoutTransform>, root_to_node_transform: Option<FastLayoutTransform>,
@ -290,7 +290,7 @@ struct AncestorStickyInfo {
nearest_scrolling_ancestor_viewport: LayoutRect, 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 /// 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. /// node which responds to scroll events or a non-scrollable one.
pub struct ScrollTreeNode { pub struct ScrollTreeNode {
@ -421,7 +421,7 @@ impl ScrollTreeNode {
/// A tree of spatial nodes, which mirrors the spatial nodes in the WebRender /// 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 /// display list, except these are used to scrolling in the compositor so that
/// new offsets can be sent to WebRender. /// new offsets can be sent to WebRender.
#[derive(Debug, Default, Deserialize, Serialize)] #[derive(Debug, Default, Deserialize, MallocSizeOf, Serialize)]
pub struct ScrollTree { pub struct ScrollTree {
/// A list of compositor-side scroll nodes that describe the tree /// A list of compositor-side scroll nodes that describe the tree
/// of WebRender spatial nodes, used by the compositor to scroll the /// 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 /// A data structure which stores compositor-side information about
/// display lists sent to the compositor. /// display lists sent to the compositor.
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct CompositorDisplayListInfo { pub struct CompositorDisplayListInfo {
/// The WebRender [PipelineId] of this display list. /// The WebRender [PipelineId] of this display list.
pub pipeline_id: PipelineId, pub pipeline_id: PipelineId,