scripts/WEBRENDER_CLIPPING_ANALYSIS.md
Diese Analyse untersucht wie Clipping und Scroll Frames in WebRender zusammenarbeiten, um das Problem zu diagnostizieren, warum Scroll-Container-Inhalte nicht korrekt geclippt werden.
Die SpatialTree verwaltet eine Hierarchie von räumlichen Knoten (SpatialNode), die Transformationen und Koordinatensysteme definieren.
Drei Typen von SpatialNodes:
pub enum SpatialNodeType {
/// Sticky Positionierung (CSS position: sticky)
StickyFrame(StickyFrameInfo),
/// Scroll Frame - Transformiert Inhalt, aber clippt NICHT automatisch!
ScrollFrame(ScrollFrameInfo),
/// Referenz Frame - etabliert ein neues Koordinatensystem
ReferenceFrame(ReferenceFrameInfo),
}
KRITISCH: Ein ScrollFrame ist NUR eine Transformation - er definiert, wie Inhalt scrollt (Offset-Transformation), führt aber KEIN Clipping durch!
ClipNode: Ein einzelner Clip (Rechteck, abgerundetes Rechteck, Image-Maske, Box-Shadow).
ClipChain: Eine verkettete Liste von ClipNodes, die auf Primitives angewendet werden. ClipChains können einen Parent haben und bilden so eine Hierarchie.
struct ClipChain {
parent: Option<usize>, // Index des Parent-ClipChain
clips: Vec<ClipDataHandle>, // Die Clips in dieser Chain
}
Der ClipTree wird während des Scene-Buildings aufgebaut und während des Frame-Buildings verwendet:
pub struct ClipTree {
nodes: Vec<ClipTreeNode>,
leaves: Vec<ClipTreeLeaf>,
clip_root_stack: Vec<ClipNodeId>,
}
pub struct ClipTreeLeaf {
pub node_id: ClipNodeId,
pub local_clip_rect: LayoutRect, // Lokaler Clip-Rect vom Primitive
}
Jedes Display Item hat CommonItemProperties:
pub struct CommonItemProperties {
/// Bounding box für das Primitive
pub clip_rect: LayoutRect,
/// Clip Chain für zusätzliches Clipping
pub clip_chain_id: ClipChainId,
/// Das Koordinatensystem (Spatial Node)
pub spatial_id: SpatialId,
/// Flags (z.B. backface-visibility)
pub flags: PrimitiveFlags,
}
Der Clip wird durch ZWEI Mechanismen angewendet:
clip_rect: Ein lokales Rechteck-Clip, das direkt im Vertex-Shader angewendet wird (schneller Pfad)clip_chain_id: Referenz auf eine ClipChain für komplexe Clips (benötigt möglicherweise Clip-Masken)Display Item kommt an
↓
process_common_properties() wird aufgerufen
↓
get_clip_node() holt ClipNodeId für clip_chain_id
↓
Beim Frame Building: set_active_clips() sammelt alle relevanten Clips
↓
build_clip_chain_instance() erstellt optimierte Clip-Instanz
↓
Clips werden zu local_clip_rect zusammengeführt oder als Masken gerendert
pub fn define_scroll_frame(
&mut self,
parent_space: SpatialId,
external_id: ExternalScrollId,
content_rect: LayoutRect, // Größe des scrollbaren Inhalts
frame_rect: LayoutRect, // Sichtbarer Viewport (Clip-Bereich)
external_scroll_offset: LayoutVector2D,
...
) -> SpatialId
Was es erstellt:
SpatialNodeIndex in der Spatial Treeviewport_rect (frame_rect) und scrollable_sizeWebRender-Design-Philosophie:
SpatialId definiert das Koordinatensystem (wo etwas ist)ClipChainId definiert das Clipping (was sichtbar ist)Ein ScrollFrame alleine führt KEIN Clipping durch!
Für einen korrekt clippenden Scroll-Container sind DREI Schritte notwendig:
// 1. ScrollFrame definieren (für Scroll-Transformation)
let scroll_spatial_id = builder.define_scroll_frame(
parent_space,
external_scroll_id,
content_rect, // Gesamtgröße des scrollbaren Inhalts
frame_rect, // Sichtbarer Bereich = Clip-Bereich
...
);
// 2. Clip-Rect definieren (für Clipping!)
// WICHTIG: Clip muss im PARENT-Space definiert werden, nicht im Scroll-Space!
let scroll_clip_id = builder.define_clip_rect(
parent_space, // <-- NICHT scroll_spatial_id!
frame_rect, // Der sichtbare Bereich
);
// 3. ClipChain erstellen
let scroll_clip_chain = builder.define_clip_chain(
parent_clip_chain, // Parent Chain (oder None)
[scroll_clip_id], // Der Clip
);
// 4. Content mit korrektem spatial_id UND clip_chain_id pushen
let info = CommonItemProperties {
clip_rect: ...,
clip_chain_id: scroll_clip_chain, // Clip Chain für Clipping
spatial_id: scroll_spatial_id, // Scroll Space für Transformation
..
};
builder.push_rect(&info, bounds, color);
Der Clip-Rect repräsentiert den stationären Viewport - er bewegt sich nicht, wenn gescrollt wird. Der Inhalt bewegt sich (im scroll_spatial_id-Space), aber der Clip bleibt stehen.
Falsch:
// FALSCH! Clip scrollt mit dem Inhalt mit
let clip = builder.define_clip_rect(scroll_spatial_id, frame_rect);
Richtig:
// RICHTIG! Clip bleibt stationär im Parent-Space
let clip = builder.define_clip_rect(parent_space, frame_rect);
Alle Rechtecke in WebRender sind relativ zu ihrem Parent-Space:
frame_rect in define_scroll_frame: Relativ zum Parent-Spacecontent_rect in define_scroll_frame: Relativ zum Parent-Space (Origin ist normalerweise gleich wie frame_rect.origin)clip_rect in define_clip_rect: Relativ zum angegebenen spatial_idNachdem ein ScrollFrame definiert wurde:
DisplayListItem::PushScrollFrame { clip_bounds, content_size, scroll_id } => {
// ... frame_rect und content_rect Setup ...
let parent_space = *spatial_stack.last().unwrap();
// ScrollFrame definieren
let scroll_spatial_id = builder.define_scroll_frame(
parent_space,
external_scroll_id,
content_rect,
frame_rect,
...
);
spatial_stack.push(scroll_spatial_id);
// Clip definieren - KORREKT im Parent-Space
let scroll_clip_id = builder.define_clip_rect(parent_space, frame_rect);
// ClipChain erstellen
let scroll_clip_chain = builder.define_clip_chain(parent_clip, [scroll_clip_id]);
clip_stack.push(scroll_clip_chain);
}
Dieser Code sieht korrekt aus! Der Clip wird im parent_space definiert.
clip_rect in CommonItemProperties stimmt nicht:
clip_rect in CommonItemProperties sollte auch begrenzt seinInhalt wird vor dem ScrollFrame gepusht:
content_rect ist falsch:
content_rect.origin sollte gleich frame_rect.origin seincontent_rect.size sollte die tatsächliche Inhaltsgröße sein (kann größer als frame_rect.size sein)Der Clip-Stack wird nicht korrekt verwendet:
clip_stack.last() tatsächlich den ScrollFrame-Clip enthältFür jedes Rect/Text/etc. Item:
log_debug!("Item spatial_id={:?}, clip_chain_id={:?}, clip_stack={:?}",
spatial_stack.last(),
clip_stack.last(),
clip_stack
);
// In der Render-Konfiguration
debug_flags: DebugFlags::PRIMITIVE_DBG
| DebugFlags::CLIP_DBG
| DebugFlags::SPATIAL_DBG
Jedes CommonItemProperties hat einen clip_rect. Dieser sollte:
// FALSCH - Clip scrollt mit
let clip = builder.define_clip_rect(scroll_spatial_id, frame_rect);
// RICHTIG - Clip bleibt stationär
let clip = builder.define_clip_rect(parent_space, frame_rect);
// FALSCH - Clip wird nie verwendet
let clip = builder.define_clip_rect(...);
let chain = builder.define_clip_chain(...);
// Vergessen: clip_stack.push(chain);
// FALSCH - Content beginnt bei (0,0) statt bei frame_rect.origin
let content_rect = LayoutRect::from_origin_and_size(
LayoutPoint::zero(), // <-- FALSCH!
content_size,
);
// RICHTIG - Content Origin = Frame Origin
let content_rect = LayoutRect::from_origin_and_size(
frame_rect.origin, // <-- RICHTIG!
content_size,
);
Achtung: Der clip_rect in CommonItemProperties ist NICHT der Viewport-Clip!
// Dies ist korrekt - clip_rect ist die Bounds des Primitives selbst
let info = CommonItemProperties {
clip_rect: primitive_bounds, // Bounds des Elements
clip_chain_id: scroll_clip_chain, // Hier passiert das eigentliche Clipping!
...
};
Der clip_rect beschreibt die lokalen Bounds des Primitives (z.B. für Gradients, die logisch unendlich sind). Das Clipping durch den Scroll-Frame geschieht über die clip_chain_id.
// FALSCH - Neue ClipChain hat keinen Parent
let clip_chain = builder.define_clip_chain(None, [clip_id]);
// RICHTIG - ClipChain erbt vom Parent
let parent_clip = if current_clip == WrClipChainId::INVALID {
None
} else {
Some(current_clip)
};
let clip_chain = builder.define_clip_chain(parent_clip, [clip_id]);
Der häufigste Fehler: Clip ist im falschen Koordinatensystem definiert.
Beispiel-Szenario:
// Die Clip-Rect Koordinaten müssen absolut (im Parent-Space) sein
let clip_rect = LayoutRect::from_origin_and_size(
LayoutPoint::new(100.0, 200.0), // Position des ScrollFrame
LayoutSize::new(300.0, 400.0), // Größe des sichtbaren Bereichs
);
// Clip im Parent-Space definieren
let clip_id = builder.define_clip_rect(parent_spatial_id, clip_rect);
Falls ein Debug-Server läuft, können folgende Kommandos helfen:
# Clip-Informationen abrufen
curl -X POST http://localhost:8765/ -d '{"op": "get_logs"}' | grep -i clip
# Spatial Tree Informationen
curl -X POST http://localhost:8765/ -d '{"op": "get_logs"}' | grep -i spatial
clip_chain_id verwendendefine_clip_rect im Parent-Space aufgerufen?define_clip_chain aufgerufen und das Ergebnis gepusht?clip_chain_id?content_rect.origin == frame_rect.origin?