/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use euclid::SideOffsets2D; use peek_poke::{ensure_red_zone, peek_from_slice, poke_extend_vec, strip_red_zone}; use peek_poke::{poke_inplace_slice, poke_into_vec, Poke}; #[cfg(feature = "deserialize")] use serde::de::Deserializer; #[cfg(feature = "serialize")] use serde::ser::Serializer; use serde::{Deserialize, Serialize}; use std::io::Write; use std::marker::PhantomData; use std::ops::Range; use std::mem; use std::collections::HashMap; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; // local imports usecrate::display_item as di; usecrate::{APZScrollGeneration, HasScrollLinkedEffect, PipelineId, PropertyBinding}; usecrate::gradient_builder::GradientBuilder; usecrate::color::ColorF; usecrate::font::{FontInstanceKey, GlyphInstance, GlyphOptions}; usecrate::image::{ColorDepth, ImageKey}; usecrate::units::*;
// We don't want to push a long text-run. If a text-run is too long, split it into several parts. // This needs to be set to (renderer::MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_TEXT_RUN) * 2 pubconst MAX_TEXT_RUN_LENGTH: usize = 2040;
// See ROOT_REFERENCE_FRAME_SPATIAL_ID and ROOT_SCROLL_NODE_SPATIAL_ID // TODO(mrobinson): It would be a good idea to eliminate the root scroll frame which is only // used by Servo. const FIRST_SPATIAL_NODE_INDEX: usize = 2;
// See ROOT_SCROLL_NODE_SPATIAL_ID const FIRST_CLIP_NODE_INDEX: usize = 1;
// We can safely ignore the preallocations failing, since we aren't // certain about how much memory we need, and this gives a chance for // the memory pressure events to run. if payload.items_data.try_reserve(capacity.items_size).is_err() { returnSelf::default();
} if payload.spatial_tree.try_reserve(capacity.spatial_tree_size).is_err() { returnSelf::default();
}
payload
}
/// Describes the memory layout of a display list. /// /// A display list consists of some number of display list items, followed by a number of display /// items. #[repr(C)] #[derive(Copy, Clone, Default, Deserialize, Serialize)] pubstruct BuiltDisplayListDescriptor { /// Gecko specific information about the display list.
gecko_display_list_type: GeckoDisplayListType, /// The first IPC time stamp: before any work has been done
builder_start_time: u64, /// The second IPC time stamp: after serialization
builder_finish_time: u64, /// The third IPC time stamp: just before sending
send_start_time: u64, /// The amount of clipping nodes created while building this display list.
total_clip_nodes: usize, /// The amount of spatial nodes created while building this display list.
total_spatial_nodes: usize,
}
/// A debug (human-readable) representation of a built display list that /// can be used for capture and replay. #[cfg(any(feature = "serialize", feature = "deserialize"))] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] struct DisplayListCapture {
display_items: Vec<di::DebugDisplayItem>,
spatial_tree_items: Vec<di::SpatialTreeItem>,
descriptor: BuiltDisplayListDescriptor,
}
#[cfg(feature = "serialize")] impl Serialize for BuiltDisplayList { fn serialize<S: Serializer>(
&self,
serializer: S
) -> Result<S::Ok, S::Error> { let display_items = BuiltDisplayList::create_debug_display_items(self.iter()); let spatial_tree_items = self.payload.create_debug_spatial_tree_items();
let dl = DisplayListCapture {
display_items,
spatial_tree_items,
descriptor: self.descriptor,
};
dl.serialize(serializer)
}
}
#[cfg(feature = "deserialize")] impl<'de> Deserialize<'de> for BuiltDisplayList { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
D: Deserializer<'de>,
{ usecrate::display_item::DisplayItem as Real; usecrate::display_item::DebugDisplayItem as Debug;
let capture = DisplayListCapture::deserialize(deserializer)?;
letmut spatial_tree = Vec::new(); for item in capture.spatial_tree_items {
poke_into_vec(&item, &mut spatial_tree);
}
ensure_red_zone::<di::SpatialTreeItem>(&mut spatial_tree);
Debug::PopStackingContext => Real::PopStackingContext,
Debug::PopReferenceFrame => Real::PopReferenceFrame,
Debug::PopAllShadows => Real::PopAllShadows,
Debug::DebugMarker(val) => Real::DebugMarker(val),
};
poke_into_vec(&item, &mut items_data); // the aux data is serialized after the item, hence the temporary
items_data.extend(temp.drain(..));
}
// Add `DisplayItem::max_size` zone of zeroes to the end of display list // so there is at least this amount available in the display list during // serialization.
ensure_red_zone::<di::DisplayItem>(&mut items_data);
pubstruct BuiltDisplayListIter<'a> {
data: &'a [u8],
cur_item: di::DisplayItem,
cur_stops: ItemRange<'a, di::GradientStop>,
cur_glyphs: ItemRange<'a, GlyphInstance>,
cur_filters: ItemRange<'a, di::FilterOp>,
cur_filter_data: Vec<TempFilterData<'a>>,
cur_clip_chain_items: ItemRange<'a, di::ClipId>,
cur_points: ItemRange<'a, LayoutPoint>,
peeking: Peek, /// Should just be initialized but never populated in release builds
debug_stats: DebugStats,
}
/// Internal info used for more detailed analysis of serialized display lists #[allow(dead_code)] struct DebugStats { /// Last address in the buffer we pointed to, for computing serialized sizes
last_addr: usize,
stats: HashMap<&'static str, ItemStats>,
}
/// Computes the number of bytes we've processed since we last called /// this method, so we can compute the serialized size of a display item. #[cfg(feature = "display_list_stats")] fn debug_num_bytes(&mutself, data: &[u8]) -> usize { let old_addr = self.last_addr; let new_addr = data.as_ptr() as usize; let delta = new_addr - old_addr; self.last_addr = new_addr;
delta
}
/// Logs stats for the last deserialized display item #[cfg(feature = "display_list_stats")] fn log_item(&mutself, data: &[u8], item: &di::DisplayItem) { let num_bytes = self.debug_num_bytes(data); self._update_entry(item.debug_name(), 1, num_bytes);
}
/// Logs the stats for the given serialized slice #[cfg(feature = "display_list_stats")] fn log_slice<T: Copy + Default + peek_poke::Peek>(
&mutself,
slice_name: &'static str,
range: &ItemRange<T>,
) { // Run this so log_item_stats is accurate, but ignore its result // because log_slice_stats may be called after multiple slices have been // processed, and the `range` has everything we need. self.last_addr = range.bytes.as_ptr() as usize + range.bytes.len();
/// Stats for an individual item #[derive(Copy, Clone, Debug, Default)] pubstruct ItemStats { /// How many instances of this kind of item we deserialized pub total_count: usize, /// How many bytes we processed for this kind of item pub num_bytes: usize,
}
// Some of these might just become ItemRanges impl<'a, 'b> DisplayItemRef<'a, 'b> { // Creates a new iterator where this element's iterator is, to hack around borrowck. pubfn sub_iter(&self) -> BuiltDisplayListIter<'a> { self.iter.sub_iter()
}
// Don't let these bleed into another item self.cur_stops = ItemRange::default(); self.cur_clip_chain_items = ItemRange::default(); self.cur_points = ItemRange::default(); self.cur_filters = ItemRange::default(); self.cur_filter_data.clear();
loop { self.next_raw()?; matchself.cur_item {
SetGradientStops |
SetFilterOps |
SetFilterData |
SetPoints => { // These are marker items for populating other display items, don't yield them. continue;
}
_ => { break;
}
}
}
Some(self.as_ref())
}
/// Gets the next display item, even if it's a dummy. Also doesn't handle peeking /// and may leave irrelevant ranges live (so a Clip may have GradientStops if /// for some reason you ask). pubfn next_raw<'b>(&'b mutself) -> Option<DisplayItemRef<'a, 'b>> { usecrate::DisplayItem::*;
// A "red zone" of DisplayItem::max_size() bytes has been added to the // end of the serialized display list. If this amount, or less, is // remaining then we've reached the end of the display list. ifself.data.len() <= di::DisplayItem::max_size() { return None;
}
/// Get the debug stats for what this iterator has deserialized. /// Should always be empty in release builds. pubfn debug_stats(&mutself) -> Vec<(&'static str, ItemStats)> { letmut result = self.debug_stats.stats.drain().collect::<Vec<_>>();
result.sort_by_key(|stats| stats.0);
result
}
/// Adds the debug stats from another to our own, assuming we are a sub-iter of the other /// (so we can ignore where they were in the traversal). pubfn merge_debug_stats_from(&mutself, other: &mutSelf) { for (key, other_entry) in other.debug_stats.stats.iter() { let entry = self.debug_stats.stats.entry(key).or_default();
/// Accumulated external scroll offset per spatial node, used to normalize /// item coordinates at push time so WebRender interns scroll-invariant /// positions. Scroll frames add their `external_scroll_offset`, sticky /// frames subtract their `previously_applied_offset`, reference frames /// reset to zero. The offset fields are still sent so WebRender can keep /// applying them at frame time (APZ reconciliation, sticky math).
spatial_offsets: HashMap<di::SpatialId, LayoutVector2D>, /// Single-entry cache for `spatial_offsets`. Items are typically emitted /// grouped by spatial node, so consecutive lookups hit this and skip /// hashing (mirrors the scene builder's `ScrollOffsetMapper`).
last_scroll_offset: Option<(di::SpatialId, LayoutVector2D)>, /// Reused buffer for normalized glyph positions, to avoid a per-text-run /// allocation when shifting glyphs by the external scroll offset.
glyph_scratch: Vec<GlyphInstance>,
}
/// Saves the current display list state, so it may be `restore()`'d. /// /// # Conditions: /// /// * Doesn't support popping clips that were pushed before the save. /// * Doesn't support nested saves. /// * Must call `clear_save()` if the restore becomes unnecessary. pubfn save(&mutself) {
assert!(self.save_state.is_none(), "DisplayListBuilder doesn't support nested saves");
/// Restores the state of the builder to when `save()` was last called. pubfn restore(&mutself) { let state = self.save_state.take().expect("No save to restore DisplayListBuilder from");
// Drop offsets recorded for spatial nodes defined after the save point; // those ids will be reused, so the single-entry cache could be stale. let next_spatial_index = state.next_spatial_index; self.spatial_offsets.retain(|id, _| id.0 < next_spatial_index); self.last_scroll_offset = None;
}
/// Discards the builder's save (indicating the attempted operation was successful). pubfn clear_save(&mutself) { self.save_state.take().expect("No save to clear in DisplayListBuilder");
}
/// Emits a debug representation of display items in the list, for debugging /// purposes. If the range's start parameter is specified, only display /// items starting at that index (inclusive) will be printed. If the range's /// end parameter is specified, only display items before that index /// (exclusive) will be printed. Calling this function with end <= start is /// allowed but is just a waste of CPU cycles. The function emits the /// debug representation of the selected display items, one per line, with /// the given indent, to the provided sink object. The return value is /// the total number of items in the display list, which allows the /// caller to subsequently invoke this function to only dump the newly-added /// items. pubfn emit_display_list<W>(
&mutself,
indent: usize,
range: Range<Option<usize>>, mut sink: W,
) -> usize where
W: Write
{ letmut temp = BuiltDisplayList::default();
ensure_red_zone::<di::DisplayItem>(&mutself.payload.items_data);
mem::swap(&mut temp.payload, &mutself.payload);
letmut index: usize = 0;
{ letmut iter = temp.iter(); whilelet Some(item) = iter.next_raw() { if index >= range.start.unwrap_or(0) && range.end.map_or(true, |e| index < e) {
writeln!(sink, "{}{:?}", " ".repeat(indent), item.item()).unwrap();
}
index += 1;
}
}
self.payload = temp.payload;
strip_red_zone::<di::DisplayItem>(&mutself.payload.items_data);
index
}
/// Print the display items in the list to stdout. pubfn dump_serialized_display_list(&mutself) { self.serialized_content_buffer = Some(String::new());
}
/// Returns the default section that DisplayListBuilder will write to, /// if no section is specified explicitly. fn default_section(&self) -> DisplayListSection {
DisplayListSection::Data
}
/// Add an item to the display list. /// /// NOTE: It is usually preferable to use the specialized methods to push /// display items. Pushing unexpected or invalid items here may /// result in WebRender panicking or behaving in unexpected ways. #[inline] pubfn push_item(&mutself, item: &di::DisplayItem) { self.push_item_to_section(item, self.default_section());
}
fn push_iter_impl<I>(data: &mut Vec<u8>, iter_source: I) where
I: IntoIterator,
I::IntoIter: ExactSizeIterator,
I::Item: Poke,
{ let iter = iter_source.into_iter(); let len = iter.len(); // Format: // payload_byte_size: usize, item_count: usize, [I; item_count]
// Track the the location of where to write byte size with offsets // instead of pointers because data may be moved in memory during // `serialize_iter_fast`. let byte_size_offset = data.len();
// We write a dummy value so there's room for later
poke_into_vec(&0usize, data);
poke_into_vec(&len, data); let count = poke_extend_vec(iter, data);
debug_assert_eq!(len, count, "iterator.len() returned two different values");
// Add red zone
ensure_red_zone::<I::Item>(data);
// Now write the actual byte_size let final_offset = data.len();
debug_assert!(final_offset >= (byte_size_offset + mem::size_of::<usize>()), "space was never allocated for this array's byte_size"); let byte_size = final_offset - byte_size_offset - mem::size_of::<usize>();
poke_inplace_slice(&byte_size, &mut data[byte_size_offset..]);
}
/// Push items from an iterator to the display list. /// /// NOTE: Pushing unexpected or invalid items to the display list /// may result in panic and confusion. pubfn push_iter<I>(&mutself, iter: I) where
I: IntoIterator,
I::IntoIter: ExactSizeIterator,
I::Item: Poke,
{
assert_eq!(self.state, BuildState::Build);
let buffer = self.buffer_from_section(self.default_section()); Self::push_iter_impl(buffer, iter);
}
// Take the scratch buffer out so we can hold it while also borrowing // `self` mutably for `push_item`/`push_iter`; put it back afterwards to // retain its capacity across text runs. A no-op (empty Vec swap) when // the offset is zero or the prototype is disabled. letmut scratch = mem::take(&mutself.glyph_scratch); for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) { self.push_item(&item); if offset != LayoutVector2D::zero() {
scratch.clear();
scratch.extend(split_glyphs.iter().map(|g| GlyphInstance {
index: g.index,
point: g.point + offset,
})); self.push_iter(&scratch);
} else { self.push_iter(split_glyphs);
}
} self.glyph_scratch = scratch;
}
/// NOTE: gradients must be pushed in the order they're created /// because create_gradient stores the stops in anticipation. pubfn create_gradient(
&mutself,
start_point: LayoutPoint,
end_point: LayoutPoint,
stops: Vec<di::GradientStop>,
extend_mode: di::ExtendMode,
) -> di::Gradient { letmut builder = GradientBuilder::with_stops(stops); let gradient = builder.gradient(start_point, end_point, extend_mode); self.push_stops(builder.stops());
gradient
}
/// NOTE: gradients must be pushed in the order they're created /// because create_gradient stores the stops in anticipation. pubfn create_radial_gradient(
&mutself,
center: LayoutPoint,
radius: LayoutSize,
stops: Vec<di::GradientStop>,
extend_mode: di::ExtendMode,
) -> di::RadialGradient { letmut builder = GradientBuilder::with_stops(stops); let gradient = builder.radial_gradient(center, radius, extend_mode); self.push_stops(builder.stops());
gradient
}
/// NOTE: gradients must be pushed in the order they're created /// because create_gradient stores the stops in anticipation. pubfn create_conic_gradient(
&mutself,
center: LayoutPoint,
angle: f32,
stops: Vec<di::GradientStop>,
extend_mode: di::ExtendMode,
) -> di::ConicGradient { letmut builder = GradientBuilder::with_stops(stops); let gradient = builder.conic_gradient(center, angle, extend_mode); self.push_stops(builder.stops());
gradient
}
/// Pushes a linear gradient to be displayed. /// /// The gradient itself is described in the /// `gradient` parameter. It is drawn on /// a "tile" with the dimensions from `tile_size`. /// These tiles are now repeated to the right and /// to the bottom infinitely. If `tile_spacing` /// is not zero spacers with the given dimensions /// are inserted between the tiles as seams. /// /// The origin of the tiles is given in `layout.rect.origin`. /// If the gradient should only be displayed once limit /// the `layout.rect.size` to a single tile. /// The gradient is only visible within the local clip. pubfn push_gradient(
&mutself,
common: &di::CommonItemProperties,
bounds: LayoutRect,
gradient: di::Gradient,
tile_size: LayoutSize,
tile_spacing: LayoutSize,
) { let (common, offset) = self.normalize_common(common); let item = di::DisplayItem::Gradient(di::GradientDisplayItem {
common,
bounds: bounds.translate(offset),
gradient,
tile_size,
tile_spacing,
});
self.push_item(&item);
}
/// Pushes a radial gradient to be displayed. /// /// See [`push_gradient`](#method.push_gradient) for explanation. pubfn push_radial_gradient(
&mutself,
common: &di::CommonItemProperties,
bounds: LayoutRect,
gradient: di::RadialGradient,
tile_size: LayoutSize,
tile_spacing: LayoutSize,
) { let (common, offset) = self.normalize_common(common); let item = di::DisplayItem::RadialGradient(di::RadialGradientDisplayItem {
common,
bounds: bounds.translate(offset),
gradient,
tile_size,
tile_spacing,
});
self.push_item(&item);
}
/// Pushes a conic gradient to be displayed. /// /// See [`push_gradient`](#method.push_gradient) for explanation. pubfn push_conic_gradient(
&mutself,
common: &di::CommonItemProperties,
bounds: LayoutRect,
gradient: di::ConicGradient,
tile_size: LayoutSize,
tile_spacing: LayoutSize,
) { let (common, offset) = self.normalize_common(common); let item = di::DisplayItem::ConicGradient(di::ConicGradientDisplayItem {
common,
bounds: bounds.translate(offset),
gradient,
tile_size,
tile_spacing,
});
self.push_item(&item);
}
pubfn push_reference_frame(
&mutself,
origin: LayoutPoint,
parent_spatial_id: di::SpatialId,
transform_style: di::TransformStyle,
transform: PropertyBinding<LayoutTransform>,
kind: di::ReferenceFrameKind,
) -> di::SpatialId { let parent_offset = self.accumulated_scroll_offset(parent_spatial_id); let id = self.generate_spatial_index();
let descriptor = di::SpatialTreeItem::ReferenceFrame(di::ReferenceFrameDescriptor {
parent_spatial_id,
origin: origin + parent_offset,
reference_frame: di::ReferenceFrame {
transform_style,
transform: di::ReferenceTransformBinding::Static {
binding: transform,
},
kind,
id,
},
}); self.push_spatial_tree_item(&descriptor); // External scroll offset does not propagate across reference frames. self.record_scroll_offset(id, LayoutVector2D::zero());
let item = di::DisplayItem::PushReferenceFrame(di::ReferenceFrameDisplayListItem {
}); self.push_item(&item);
id
}
pubfn push_computed_frame(
&mutself,
origin: LayoutPoint,
parent_spatial_id: di::SpatialId,
scale_from: Option<LayoutSize>,
vertical_flip: bool,
rotation: di::Rotation,
) -> di::SpatialId { let parent_offset = self.accumulated_scroll_offset(parent_spatial_id); let id = self.generate_spatial_index();
pubfn push_backdrop_filter(
&mutself,
common: &di::CommonItemProperties,
filters: &[di::FilterOp],
filter_datas: &[di::FilterData],
) { // Unlike a regular filter, a backdrop filter's picture is anchored to // the backdrop root (resolved from SpatialNodeIndex::UNKNOWN), not to // `common.spatial_id`, so the backdrop root does not re-apply this // node's external scroll offset at frame time. The SVGFE subregion must // therefore stay un-normalized; only the item geometry below is. self.push_filters(filters, filter_datas);
let (common, _offset) = self.normalize_common(common); let item = di::DisplayItem::BackdropFilter(di::BackdropFilterDisplayItem {
common,
}); self.push_item(&item);
}
/// As `push_filters`, but first normalizes SVGFE filter-graph subregions /// (the only absolutely-positioned filter geometry) by the accumulated /// external scroll offset for `spatial_id`, matching the normalization /// applied to the primitives the filter graph operates on. fn push_filters_normalized(
&mutself,
filters: &[di::FilterOp],
filter_datas: &[di::FilterData],
spatial_id: di::SpatialId,
) { let offset = self.accumulated_scroll_offset(spatial_id); if offset == LayoutVector2D::zero() { self.push_filters(filters, filter_datas); return;
}
/// Accumulated external scroll offset for `spatial_id` (zero for the /// implicit pipeline roots and any untracked node). A single-entry cache /// short-circuits the common case of consecutive items sharing a spatial /// node, avoiding a hash per item. fn accumulated_scroll_offset(&mutself, spatial_id: di::SpatialId) -> LayoutVector2D { iflet Some((cached_id, cached_offset)) = self.last_scroll_offset { if cached_id == spatial_id { return cached_offset;
}
} let offset = self.spatial_offsets
.get(&spatial_id)
.copied()
.unwrap_or_else(LayoutVector2D::zero); self.last_scroll_offset = Some((spatial_id, offset));
offset
}
/// Record the accumulated external scroll offset for a freshly-defined /// spatial node. fn record_scroll_offset(&mutself, spatial_id: di::SpatialId, offset: LayoutVector2D) { self.spatial_offsets.insert(spatial_id, offset);
}
/// Translate a rect from Gecko's pre-scrolled (painted) coordinates into /// the normalized, scroll-invariant space WebRender interns in, by adding /// the accumulated external scroll offset for `spatial_id`. fn normalize_rect(&mutself, rect: LayoutRect, spatial_id: di::SpatialId) -> LayoutRect {
rect.translate(self.accumulated_scroll_offset(spatial_id))
}
/// As `normalize_rect`, but for the common-properties chokepoint: returns a /// copy with `clip_rect` normalized, plus the offset to apply to the item's /// own geometry (bounds, glyphs, ...). fn normalize_common(
&mutself,
common: &di::CommonItemProperties,
) -> (di::CommonItemProperties, LayoutVector2D) { let offset = self.accumulated_scroll_offset(common.spatial_id); letmut common = *common;
common.clip_rect = common.clip_rect.translate(offset);
(common, offset)
}
// `content_rect`'s origin is discarded by the scene builder (only its // size is used), so it needs no normalization. let descriptor = di::SpatialTreeItem::ScrollFrame(di::ScrollFrameDescriptor {
content_rect,
frame_rect: self.normalize_rect(frame_rect, parent_space),
parent_space,
scroll_frame_id,
external_id,
external_scroll_offset,
scroll_offset_generation,
has_scroll_linked_effect,
});
let item = di::DisplayItem::ImageMaskClip(di::ImageMaskClipDisplayItem {
id,
spatial_id,
image_mask,
fill_rule,
});
// We only need to supply points if there are at least 3, which is the // minimum to specify a polygon. BuiltDisplayListIter.next ensures that points // are cleared between processing other display items, so we'll correctly get // zero points when no SetPoints item has been pushed. if points.len() >= 3 { self.push_item(&di::DisplayItem::SetPoints); if offset != LayoutVector2D::zero() { let shifted: Vec<LayoutPoint> = points.iter().map(|p| *p + offset).collect(); self.push_iter(&shifted);
} else { self.push_iter(points);
}
} self.push_item(&item);
id
}
pubfn define_clip_rect(
&mutself,
spatial_id: di::SpatialId,
clip_rect: LayoutRect,
) -> di::ClipId { let id = self.generate_clip_index();
let item = di::DisplayItem::RoundedRectClip(di::RoundedRectClipDisplayItem {
id,
spatial_id,
clip,
});
self.push_item(&item);
id
}
pubfn define_sticky_frame(
&mutself,
parent_spatial_id: di::SpatialId,
frame_rect: LayoutRect,
margins: SideOffsets2D<Option<f32>, LayoutPixel>,
vertical_offset_bounds: di::StickyOffsetBounds,
horizontal_offset_bounds: di::StickyOffsetBounds,
previously_applied_offset: LayoutVector2D, // TODO: The caller only ever passes an identity transform. // Could we pass just an (optional) animation id instead?
transform: Option<PropertyBinding<LayoutTransform>>
) -> di::SpatialId { // Fold the sticky frame's already-applied offset into the accumulated // offset so the frame rect (and all descendants) are normalized to the // item's natural, unstuck position. WebRender then computes the full // sticky offset at frame time and no longer needs the applied offset. let parent_offset = self.accumulated_scroll_offset(parent_spatial_id); let node_offset = parent_offset - previously_applied_offset; let id = self.generate_spatial_index();
pubfn end(&mutself) -> (PipelineId, BuiltDisplayList) {
assert_eq!(self.state, BuildState::Build);
assert!(self.save_state.is_none(), "Finalized DisplayListBuilder with a pending save");
iflet Some(content) = self.serialized_content_buffer.take() {
println!("-- WebRender display list for {:?} --\n{}", self.pipeline_id, content);
}
// Add `DisplayItem::max_size` zone of zeroes to the end of display list // so there is at least this amount available in the display list during // serialization.
ensure_red_zone::<di::DisplayItem>(&mutself.payload.items_data);
ensure_red_zone::<di::SpatialTreeItem>(&mutself.payload.spatial_tree);
// While the first display list after tab-switch can be large, the // following ones are always smaller thanks to interning. We attempt // to reserve the same capacity again, although it may fail. Memory // pressure events will cause us to release our buffers if we ask for // too much. See bug 1531819 for related OOM issues. let next_capacity = DisplayListCapacity {
items_size: self.payload.items_data.len(),
spatial_tree_size: self.payload.spatial_tree.len(),
}; let payload = mem::replace(
&mutself.payload,
DisplayListPayload::new(next_capacity),
); let end_time = zeitstempel::now();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.